Index: frontend/test/unit/wizard_form_test.rb
===================================================================
--- frontend/test/unit/wizard_form_test.rb	(revision 0)
+++ frontend/test/unit/wizard_form_test.rb	(revision 0)
@@ -0,0 +1,8 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WizardFormTest < ActiveSupport::TestCase
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
Index: frontend/test/unit/wizard_state_test.rb
===================================================================
--- frontend/test/unit/wizard_state_test.rb	(revision 0)
+++ frontend/test/unit/wizard_state_test.rb	(revision 0)
@@ -0,0 +1,8 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WizardStateTest < ActiveSupport::TestCase
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
Index: frontend/test/functional/wizard_controller_test.rb
===================================================================
--- frontend/test/functional/wizard_controller_test.rb	(revision 0)
+++ frontend/test/functional/wizard_controller_test.rb	(revision 0)
@@ -0,0 +1,8 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WizardControllerTest < ActionController::TestCase
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
Index: frontend/test/fixtures/wizard_forms.yml
===================================================================
--- frontend/test/fixtures/wizard_forms.yml	(revision 0)
+++ frontend/test/fixtures/wizard_forms.yml	(revision 0)
@@ -0,0 +1,7 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+
+# one:
+#   column: value
+#
+# two:
+#   column: value
Index: frontend/app/helpers/wizard_helper.rb
===================================================================
--- frontend/app/helpers/wizard_helper.rb	(revision 0)
+++ frontend/app/helpers/wizard_helper.rb	(revision 0)
@@ -0,0 +1,2 @@
+module WizardHelper
+end
Index: frontend/app/models/wizard_form.rb
===================================================================
--- frontend/app/models/wizard_form.rb	(revision 0)
+++ frontend/app/models/wizard_form.rb	(revision 0)
@@ -0,0 +1,31 @@
+# wizard input form with entries
+class WizardForm
+
+  attr_reader(:label, :legend, :entries)
+  attr_accessor(:last)
+
+  def initialize(label, legend="")
+    @label = label
+    @legend = legend
+    @entries = []
+  end
+
+  class Entry
+    attr_reader(:name, :type, :label, :legend, :value)
+    def initialize(name, type, label, legend, value)
+      @name = name
+      @type = type
+      @label = label
+      @legend = legend
+      @value = value
+    end
+  end
+
+  def add_entry(name, type, label, legend="", value="")
+    e = Entry.new(name, type, label, legend, value)
+    @entries << e
+  end
+
+end
+
+# vim:et:ts=2:sw=2
Index: frontend/app/models/wizard_state.rb
===================================================================
--- frontend/app/models/wizard_state.rb	(revision 0)
+++ frontend/app/models/wizard_state.rb	(revision 0)
@@ -0,0 +1,62 @@
+class WizardState
+
+  attr_reader(:dirty)
+
+  def initialize(text = "")
+    @data = {}
+    @guess = {}
+    @dirty = false
+    xml = REXML::Document.new(text)
+    xml.elements.each("wizard/data") do |element|
+      @data[element.attributes["name"]] = element.text
+    end
+    xml.elements.each("wizard/guess") do |element|
+      @guess[element.attributes["name"]] = element.text
+    end
+  end
+
+  def store(name, value)
+    if @data[name] != value
+      @data[name] = value
+      @dirty = true
+    end
+  end
+
+  def store_guess(name, value)
+    if @guess[name] != value
+      @guess[name] = value
+      @dirty = true
+    end
+  end
+
+  def get(name)
+    return @data[name] || @guess[name]
+  end
+
+  def get_data(name)
+    return @data[name]
+  end
+
+  def serialize
+    xml = REXML::Document.new
+    xml.add_element(REXML::Element.new("wizard"))
+    @data.each do |name, value|
+      e = REXML::Element.new("data")
+      e.attributes["name"] = name
+      e.text = value
+      xml.root.add_element(e)
+    end
+    @guess.each do |name, value|
+      e = REXML::Element.new("guess")
+      e.attributes["name"] = name
+      e.text = value
+      xml.root.add_element(e)
+    end
+    res = ""
+    xml.write(res)
+    return res
+  end
+
+end
+
+# vim:et:ts=2:sw=2
Index: frontend/app/controllers/wizard_controller.rb
===================================================================
--- frontend/app/controllers/wizard_controller.rb	(revision 0)
+++ frontend/app/controllers/wizard_controller.rb	(revision 0)
@@ -0,0 +1,135 @@
+class WizardController < ApplicationController
+
+  # GET/POST /source/<project>/<package>/_wizard
+  def package_wizard
+    prj_name = params[:project]
+    pkg_name = params[:package]
+    pkg = DbPackage.find_by_project_and_name(prj_name, pkg_name)
+    unless pkg
+      render_error :status => 404, :errorcode => "unknown_package",
+        :message => "unknown package '#{pkg_name}' in project '#{prj_name}'"
+      return
+    end
+    if not @http_user.can_modify_package?(pkg)
+      render_error :status => 403, :errorcode => "change_package_no_permission",
+        :message => "no permission to change package"
+      return
+    end
+
+    logger.debug("package_wizard, #{params.inspect}")
+
+    wizard_xml = "/source/#{prj_name}/#{pkg_name}/wizard.xml"
+    begin
+      @wizard_state = WizardState.new(backend_get(wizard_xml))
+    rescue ActiveXML::Transport::NotFoundError
+      @wizard_state = WizardState.new("")
+    end
+    @wizard_state.store("name", pkg_name)
+    wizard_step_tarball
+    if @wizard_state.dirty
+      backend_put(wizard_xml, @wizard_state.serialize)
+    end
+  end
+
+  private
+
+  @@wizard_entries = {
+    # name => [type, description, legend]
+    "tarball"     => ["file", "Source tarball to upload", ""],
+    "version"     => ["text", "Version of the package", "Note that the version must not contain dashes (-)"],
+    "summary"     => ["text", "Short summary of the package", ""],
+    "description" => ["longtext", "Describe your package", ""],
+    "license"     => ["text", "License of the package", ""],
+    "group"       => ["text", "Package group", "See http://en.opensuse.org/SUSE_Package_Conventions/RPM_Groups"],
+    # autogenerated
+    "name"        => [],
+    "date"        => [],
+    "email"       => [],
+  }
+
+  def wizard_add_entry(name)
+    @wizard_form.add_entry(name,
+                           @@wizard_entries[name][0],
+                           @@wizard_entries[name][1],
+                           @@wizard_entries[name][2],
+                           @wizard_state.get(name))
+  end
+
+  def wizard_step_tarball
+    if params[:tarball] && ! params[:tarball].empty?
+      filename = params[:tarball]
+      # heuristics
+      @wizard_state.store("tarball", filename)
+      if filename =~ /^#{params[:package]}-(.*)\.tar\.(gz|bz2)$/i
+        @wizard_state.store_guess("version", $1)
+      end
+      @wizard_state.store_guess("license", "GPL v2 or later")
+      # TODO: unpack the tarball somehow and try to guess as much as possible...
+    end
+    if @wizard_state.get_data("tarball")
+      wizard_step_meta
+      return
+    end
+    @wizard_form = WizardForm.new("Step 1/2", "What do you want to package?")
+    @wizard_form.add_entry("tarball", "file", "Source tarball to upload")
+    render :template => "wizard", :status => 200;
+  end
+
+  def wizard_step_meta
+    have_all = true
+    ["version", "summary", "description", "license", "group"].each do |entry|
+      if params[entry] && ! params[entry].empty?
+        @wizard_state.store(entry, params[entry])
+      end
+      if ! @wizard_state.get_data(entry)
+        have_all = false
+      end
+    end
+    if have_all
+      wizard_step_finish
+      return
+    end
+    @wizard_form = WizardForm.new("Step 2/2", "Please describe your package")
+    wizard_add_entry("summary")
+    wizard_add_entry("description")
+    wizard_add_entry("version")
+    wizard_add_entry("license")
+    wizard_add_entry("group")
+    render :template => "wizard", :status => 200
+  end
+
+  def wizard_step_finish
+    if @wizard_state.get_data("created_spec") == "true"
+      wizard_step_done
+      return
+    end
+    package = Package.find(params[:package], :project => params[:project])
+    # FIXME: is there a cleaner way to do it?
+    package.data.elements["title"].text = @wizard_state.get_data("summary")
+    package.data.elements["description"].text = @wizard_state.get_data("description")
+    package.save
+    specfile = "#{params[:package]}.spec"
+    template = File.read("#{RAILS_ROOT}/files/wizardtemplate.spec")
+    @wizard_state.store("date", Date.today.strftime("%a %b %d %Y"))
+    @wizard_state.store("email", @http_user.email)
+    @@wizard_entries.each_key do |entry|
+      template.gsub!(/@#{entry}@/, @wizard_state.get_data(entry) || "UNSET")
+    end
+    backend_put("/source/#{params[:project]}/#{params[:package]}/#{specfile}", template)
+    @wizard_state.store("created_spec", "true")
+    @wizard_form = WizardForm.new("Finished",
+                                  "I created #{specfile} for you")
+    @wizard_form.last = true
+    render :template => "wizard", :status => 200
+  end
+
+  def wizard_step_done
+    @wizard_form = WizardForm.new("Nothing to do",
+    "There is nothing I can do for you now. In the future, I will be able to help you updating your package or fixing build errors")
+    @wizard_form.last = true
+    render :template => "wizard", :status => 200
+  end
+
+end
+
+# vim:et:ts=2:sw=2
Index: frontend/app/views/wizard.rxml
===================================================================
--- frontend/app/views/wizard.rxml	(revision 0)
+++ frontend/app/views/wizard.rxml	(revision 0)
@@ -0,0 +1,14 @@
+xml.instruct!
+xml.wizard("last" => @wizard_form.last ? "true" : "false") do
+  xml.label(@wizard_form.label)
+  xml.legend(@wizard_form.legend)
+  @wizard_form.entries.each do |entry|
+    xml.entry("name" => entry.name, "type" => entry.type) do
+      xml.label(entry.label)
+      xml.legend(entry.legend)
+      xml.value(entry.value)
+    end
+  end
+end
+
+# vim:et:ts=2:sw=2
Index: frontend/files/wizardtemplate.spec
===================================================================
--- frontend/files/wizardtemplate.spec	(revision 0)
+++ frontend/files/wizardtemplate.spec	(revision 0)
@@ -0,0 +1,34 @@
+Name:           @name@
+Version:        @version@
+Release:        1
+License:        GPL
+Source:         @tarball@
+Group:          @group@
+Summary:        @summary@
+BuildRoot:      %{_tmppath}/%{name}-%{version}-build
+
+%description
+@description@
+
+%prep
+%setup
+
+%build
+%configure
+make
+
+%install
+make DESTDIR=%buildroot install
+
+echo '%%defattr(-,root,root)' >filelist
+find %buildroot -type f -printf '/%%P*\n' >>filelist
+
+%clean
+rm -rf %buildroot
+
+%files -f filelist
+%defattr(-,root,root)
+
+%changelog
+* @date@ @email@
+- packaged @name@ version @version@ using the buildservice spec file wizard
Index: frontend/config/routes.rb
===================================================================
--- frontend/config/routes.rb	(revision 4060)
+++ frontend/config/routes.rb	(working copy)
@@ -48,6 +48,8 @@
     :action => 'package_meta'
   map.connect 'source/:project/:package/_tags', :controller => 'tag',
     :action => 'package_tags'
+  map.connect 'source/:project/:package/_wizard', :controller => 'wizard',
+    :action => 'package_wizard'
   map.connect 'source/:project/:package/:file', :controller => "source",
     :action => 'file'
   map.connect 'source/:project/_pattern', :controller => 'source',
Index: frontend/config/environments/development_base.rb
===================================================================
Index: common/lib/activexml/transport.rb
===================================================================
--- common/lib/activexml/transport.rb	(revision 4060)
+++ common/lib/activexml/transport.rb	(working copy)
@@ -443,6 +443,7 @@
               #new substitution rules:
               #when param is not there, don't put anything in url
               #when param is array, put multiple params in url
+              #when param is a hash, put key=value params in url
               #any other case, stringify param and put it in url
               next if not params.has_key? $1.to_sym or params[$1.to_sym].nil?
               sub_val = params[$1.to_sym]
@@ -450,6 +451,10 @@
                 sub_val.each do |val|
                   new_pairs << $1 + "=" + CGI.escape(val)
                 end
+              elsif sub_val.kind_of? Hash
+                sub_val.each_key do |key|
+                  new_pairs << CGI.escape(key) + "=" + CGI.escape(sub_val[key])
+                end
               else
                 new_pairs << $1 + "=" + CGI.escape(sub_val.to_s)
               end
Index: webclient/app/models/wizard.rb
===================================================================
--- webclient/app/models/wizard.rb	(revision 0)
+++ webclient/app/models/wizard.rb	(revision 0)
@@ -0,0 +1,3 @@
+class Wizard < ActiveXML::Base
+  handles_xml_element 'wizard'
+end
Index: webclient/app/controllers/package_controller.rb
===================================================================
--- webclient/app/controllers/package_controller.rb	(revision 4060)
+++ webclient/app/controllers/package_controller.rb	(working copy)
@@ -157,6 +157,54 @@
     @project = Project.find( params[:project] )
   end
 
+  def wizard_new
+    @project = Project.find( params[:project] )
+    if params[:name]
+      if !valid_package_name? params[:name]
+        flash[:error] = "Invalid package name: '#{params[:name]}'"
+        redirect_to :action => 'wizard_new', :project => params[:project]
+      else
+        @package = Package.new( :name => params[:name], :project => @project )
+        if @package.save
+          redirect_to :action => 'wizard', :project => params[:project], :package => params[:name]
+        else
+          flash[:note] = "Failed to save package '#{@package}'"
+          redirect_to :controller => 'project', :action => 'show', :project => params[:project]
+        end
+      end
+    end
+  end
+
+  def wizard
+    @project = Project.find( params[:project] )
+    @package = Package.find( params[:package], :project => params[:project] )
+    files = params[:wizard_files]
+    fnames = {}
+    if files
+      logger.debug "files: #{files.inspect}"
+      files.each_key do |key|
+        file = files[key]
+        next if ! file.respond_to?(:original_filename)
+        fname = file.original_filename
+        fnames[key] = fname
+        # TODO: reuse code from PackageController#save_file and add_file.rhtml
+        # to also support fetching remote urls
+        @package.save_file :file => file, :filename => fname
+      end
+    end
+    other = params[:wizard]
+    if other
+      response = other.merge(fnames)
+    elsif ! fnames.empty?
+      response = fnames
+    else
+      response = nil
+    end
+    @wizard = Wizard.find(:project => params[:project],
+                          :package => params[:package],
+                          :response => response)
+  end
+
   def edit
     @project = Project.find( params[:project] )
     @package = Package.find( params[:package], :project => params[:project] )
@@ -801,3 +849,5 @@
 
 
 end
+
+# vim:et:ts=2:sw=2
Index: webclient/app/views/package/wizard_new.rhtml
===================================================================
--- webclient/app/views/package/wizard_new.rhtml	(revision 0)
+++ webclient/app/views/package/wizard_new.rhtml	(revision 0)
@@ -0,0 +1,16 @@
+<% @pagetitle = "New Package" %>
+<%
+@crumb_list = [
+  link_to( 'Projects', :controller => 'project', :action => :list_public),
+  link_to( @project, :controller => 'project', :action => :show, :project => @project ),
+  'Add Package'
+]
+-%>
+<h2>New Package in project <%= @project.name %></h2>
+
+<% form_tag :action => "wizard_new" do %>
+<b>Name:</b><br/>
+<%= text_field_tag 'name', '', :size => 80 %><br/>
+<p><%= submit_tag 'Next' %></p>
+<%= hidden_field_tag 'project', @project.name %>
+<% end %>
Index: webclient/app/views/package/new.rhtml
===================================================================
--- webclient/app/views/package/new.rhtml	(revision 4060)
+++ webclient/app/views/package/new.rhtml	(working copy)
@@ -6,6 +6,7 @@
   'Add Package'
 ]
 -%>
+<p><%= link_to('Create package using spec file wizard', :controller => 'package', :action => 'wizard_new', :project => @project) %> (experimental)</p>
 <h2>New Package in project <%= @project.name %></h2>
 
 <% form_tag :action => "save_new" do %>
Index: webclient/app/views/package/wizard.rhtml
===================================================================
--- webclient/app/views/package/wizard.rhtml	(revision 0)
+++ webclient/app/views/package/wizard.rhtml	(revision 0)
@@ -0,0 +1,37 @@
+<% @pagetitle = "New Package" %>
+
+<h2><%= @wizard.label %></h2>
+<p><%= @wizard.legend %></p>
+<%
+  if @wizard.last == "true"
+    action = "show"
+    submit = "Finish"
+  else
+    action = "wizard"
+    submit = "Next"
+  end
+%>
+<% form_tag({:action => action}, {:multipart => true}) do %>
+  <% @wizard.each_entry do |entry| %>
+    <p>
+    <b><label for="wizard[<%= entry.name %>]"><%= entry.label %></label></b><br/>
+    <%= typ3 = entry.data.attributes["type"] # entry.type would call Oject#type
+        case typ3
+        when "text":
+          text_field_tag("wizard[#{entry.name}]", entry.value)
+        when "longtext":
+          text_area_tag("wizard[#{entry.name}]", entry.value)
+        when "file":
+          file_field_tag("wizard_files[#{entry.name}]")
+        else
+          raise RuntimeError.new("WizardError: unknown entry type #{typ3}")
+        end
+    %>
+    <br/>
+    <i><%= entry.legend %></i>
+    </p>
+  <% end %>
+  <%= submit_tag(submit) %>
+  <%= hidden_field_tag 'project', @project.name %>
+  <%= hidden_field_tag 'package', @package.name %>
+<% end %>
Index: webclient/config/environment.rb
===================================================================
--- webclient/config/environment.rb	(revision 4060)
+++ webclient/config/environment.rb	(working copy)
@@ -102,6 +102,8 @@
 
     map.connect :architecture, "rest:///architecture"
 
+    map.connect :wizard, "rest:///source/:project/:package/_wizard?:response"
+
     ##DEPRECATED
     map.connect :platform, "rest:///platform/:project/:name",
         :all => "rest:///platform/"
Index: webclient/config/environments/development_base.rb
===================================================================
