Initial commit master
authorDuncan Mac-Vicar P <dmacvicar@suse.de>
Fri, 4 Jan 2008 21:59:37 +0000 (22:59 +0100)
committerDuncan Mac-Vicar P <dmacvicar@suse.de>
Fri, 4 Jan 2008 21:59:37 +0000 (22:59 +0100)
LICENSE [new file with mode: 0644]
README [new file with mode: 0644]
Rakefile [new file with mode: 0644]
bin/rpmer.rb [new file with mode: 0644]
lib/rpmer.rb [new file with mode: 0644]
lib/rpmer/cmds/import.rb [new file with mode: 0644]
lib/rpmer/rpmbuild.rb [new file with mode: 0644]
lib/rpmer/rpmspec.rb [new file with mode: 0644]
lib/rpmer/rubygemtranslator.rb [new file with mode: 0644]
lib/rpmer/util.rb [new file with mode: 0644]
spec/rpmer/rpmspec_spec.rb [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..f08f4ca
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+http://www.ruby-lang.org/en/LICENSE.txt
\ No newline at end of file
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..9e51a30
--- /dev/null
+++ b/README
@@ -0,0 +1,17 @@
+
+Tool to generate spec files for the openSUSE distro.
+
+Q: only openSUSE?
+A: for now I use those rpm conventions.
+
+Q: Why?
+A: Because I don't like to type the same thing 1000 times?
+
+Q: Is it good to generate .spec files automatically?
+A: Probably not. You can always review them. No excuse to avoid generating
+   the repetitive parts
+
+Q: It generates... from?
+A: Right now gem files, based on gem2rpm code from Marek Gilbert
+   In the future: cpan, kde tarballs, java jars, etc.
+
diff --git a/Rakefile b/Rakefile
new file mode 100644 (file)
index 0000000..d40f6a6
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,35 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+
+if `ruby -Ilib ./bin/rpmer --version` =~ /\S+$/
+  CURRENT_VERSION = $&
+else
+  CURRENT_VERSION = "0.0.0"
+end
+
+PKG_FILES = FileList[
+  'bin/**/*',
+  'lib/**/*',
+  'LICENSE',
+  'README'
+  #SPEC_FILE
+]
+
+spec = Gem::Specification.new do |s|
+  s.platform = Gem::Platform::RUBY
+  s.summary = "rpm packaging tools"
+  s.name = 'rpmer'
+  s.version = '0.1'
+  s.requirements << 'cmdparse'
+  s.require_path = 'lib'
+  s.autorequire = 'rake'
+  s.files = PKG_FILES.to_a
+  s.description = <<EOF
+rpmer is a tool to assist the creation of rpm spec files.
+EOF
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+  pkg.need_zip = true
+  pkg.need_tar = true
+end
\ No newline at end of file
diff --git a/bin/rpmer.rb b/bin/rpmer.rb
new file mode 100644 (file)
index 0000000..b3d2a5a
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env ruby
+#
+# rpmer: generates spec files
+#
+# --input x, -i x:
+#    Uses x as input. x is another package of supported types
+#
+# --name [name]:
+#    greet user by name, if name not supplied default is John
+#
+# command: The directory in which to issue the greeting.
+
+# make easy to run from the project dir
+$: << "#{Dir.pwd}/lib"
+$:.push(File.expand_path(File.dirname(__FILE__) + "/../lib"))
+
+require 'rubygems'
+require 'logger'
+require 'cmdparse'
+require 'pp'
+require 'rpmer/cmds/import'
+
+def setup_command_line(log, args)
+  cmd = CmdParse::CommandParser.new( true, true )
+  cmd.program_name = "rpmer"
+  cmd.program_version = [0, 1, 0]
+  cmd.options = CmdParse::OptionParserWrapper.new do |opt|
+    opt.separator "Global options:"
+    opt.on("--verbose", "Be verbose when outputting info") do | t |
+      log.level = Logger::DEBUG
+    end
+  end
+   
+  # check
+  check_cmd = CmdParse::Command.new( 'check', false )
+  check_cmd.short_desc = "Check metadata from repository"
+  cmd.add_command( check_cmd )
+  check_cmd.set_execution_block do |args|
+    log.info "ooo check"
+  end
+  
+  # create
+  create_cmd = ImportCmd.new(log)
+
+  cmd.add_command( create_cmd )
+  
+  cmd.add_command( CmdParse::HelpCommand.new )
+  cmd.add_command( CmdParse::VersionCommand.new )
+
+  cmd.parse(args)
+  
+end
+
+def main(args)
+  
+  log = Logger.new(STDOUT)
+  log.datetime_format = "%Y-%m-%d %H:%M:%S"
+  log.level = Logger::INFO
+  
+  begin
+    setup_command_line(log, args)
+#   rescue SystemExit => se
+#     log.info se.message
+#   rescue RuntimeError => e
+#     log.error e.message
+#   rescue Exception => e
+#     log.error e.message
+  end
+
+end
+
+if __FILE__ == $0
+  main(ARGV)
+end
+
+
+
diff --git a/lib/rpmer.rb b/lib/rpmer.rb
new file mode 100644 (file)
index 0000000..bd5b0a9
--- /dev/null
@@ -0,0 +1,3 @@
+require 'rpmer/rpmbuild'
+require 'rpmer/rpmspec'
+require 'rpmer/rubygemtranslator'
\ No newline at end of file
diff --git a/lib/rpmer/cmds/import.rb b/lib/rpmer/cmds/import.rb
new file mode 100644 (file)
index 0000000..bd585d4
--- /dev/null
@@ -0,0 +1,159 @@
+
+require 'fileutils'
+require 'optparse'
+require 'ostruct'
+require 'rpmer/rpmbuild'
+require 'rpmer/rpmspec'
+require 'rpmer/rubygemtranslator'
+
+# gem2rpm supports bootstrapping rubygems itself into an rpm.  When doing
+# this, Gem classes won't be available.
+$gems_present = true
+begin
+  require 'rubygems'
+  require 'rubygems/package'
+
+  # XXX Brutal hack that's required to interrogate requirements in a
+  # gem.  This allows us to get the requirements in their raw form, rather
+  # than re-parsing their textual form.
+  class Gem::Version::Requirement
+    attr_reader :requirements
+  end
+
+rescue LoadError
+  $gems_present = false
+end
+
+#Development/Languages/Ruby
+
+# generate from a gem
+class ImportGemCmd < CmdParse::Command
+  def initialize( logger )
+    @log = logger
+    super('gem', false)
+    #puts options
+
+    gemopts = OpenStruct.new()
+    gemopts.arch = nil
+    gemopts.group = "Development/Languages/Ruby"
+    gemopts.license = "REVIEW"
+    gemopts.output_dir = nil
+    gemopts.release = '1'
+    gemopts.rpm_build_opts = RpmBuildOption::BINARIES
+    gemopts.show_gem_spec = false
+    gemopts.show_rpm_spec = false
+    gemopts.verbose = false
+    gemopts.gems_prefix = 'rubygem'
+
+    self.short_desc = "generate a spec file from ruby gems"
+    self.options = CmdParse::OptionParserWrapper.new do |op|
+      op.on('-G', '--show-gem', "show the gem spec file on stdout") do
+        gemopts.show_gem_spec = true
+      end
+    
+      op.on('-R', '--show-rpm', "show the RPM spec file on stdout") do
+        gemopts.show_rpm_spec = true
+      end
+
+      @output_dir = Dir.pwd
+      op.on('-O', '--output-dir [DIR]', "output directory") do |d|
+        @output_dir = d
+      end
+
+      @output_dir = Dir.pwd
+      op.on('-p', '--gems-prefix value', "rpm gems prefix (default: rubygem-foo.rpm") do |d|
+        gemopts.gems_prefix = d
+      end
+    end
+
+    @gemopts = gemopts
+  end
+
+  def execute (args)
+    #@log.info "create repo rpm-md #{args} #{super_command.input}"
+
+    if args.empty?
+      @log.error "no input gem specified." and exit
+    end
+
+    @input = args[0]
+
+    if @output_dir.nil?
+      @log.error "Please specify a output directory" and exit
+    end
+
+    if not ( File.exists?(@output_dir) and File.directory?(@output_dir) )
+      @log.error "specified output directory #{@output_dir} is not valid"
+      exit
+    end
+
+    src_file = @input
+    rpm = nil
+    build = RpmBuild.new()
+    case File.basename(src_file)
+    when /\.gem$/
+      if !$gems_present
+        @log.error("Cannot process #{src_file} until rubygems is itself installed")
+        exit(1)
+      end
+  
+      trans = RubyGemTranslator.from_gem(src_file)
+      trans.translate_gem(@gemopts)
+      trans.add_external_requirements(@gemopts)
+      rpm = trans.rpm
+  
+      if @gemopts.show_gem_spec
+        @log.info("##### gem spec contents: #####")
+        $stderr.puts trans.gem.to_ruby
+        @log.info("##### end gem spec contents #####")
+      end
+      
+    when /^rubygems-[0-9\.]*\.tgz$/
+      rpm = build_rubygems_spec(build, src_file, $options)
+  
+    else
+      @log.error("Don't know how to make an rpm from #{src_file}")
+      exit(1)
+    end
+  
+    # Create the spec file
+    @log.info("# creating #{rpm.spec_filename}")
+    File.open(rpm.spec_filename, 'w') do |fd|
+      rpm_out = RpmSpecWriter.new(fd)
+      rpm.write(rpm_out)
+    end
+  
+    if @show_rpm_spec
+      @log.info("##### rpm spec contents: #####")
+      File.open(rpm.spec_filename, 'r') do |fd|
+        fd.each_line do |line|
+          $stderr.puts(line)
+        end
+      end
+      ptrace("##### end rpm spec contents #####")
+    end
+    @log.info "done. wrote #{rpm.spec_filename}"
+
+  end
+end
+
+class ImportCmd < CmdParse::Command
+  attr_accessor :input
+  def initialize(logger)
+    @log = logger
+    super('import', true)
+    @input = Array.new
+    self.short_desc = "Generates .spec files"
+    self.options = CmdParse::OptionParserWrapper.new do |opt|
+      #opt.on( :REQUIRED, '-i', '--input x,y,z', Array, 'input object' ) do | inputopt |
+      #  @input = inputopt
+      #end
+      opt
+    end
+    self.add_command(ImportGemCmd.new(@log))
+  end
+
+  def execute(args)
+    #@log.info "create #{args}"
+  end
+end
\ No newline at end of file
diff --git a/lib/rpmer/rpmbuild.rb b/lib/rpmer/rpmbuild.rb
new file mode 100644 (file)
index 0000000..0d3b2fd
--- /dev/null
@@ -0,0 +1,103 @@
+# This file can be distributed under the same terms as ruby itself.  
+# Author: Marek Gilbert <gil (at) fooplanet (dot) com>
+
+# A simple wrapper around rpmbuild's --showrc command that allows rpm to
+# tell gem2rpm where to install things.
+class RpmRc
+  def initialize()
+    IO.popen('rpmbuild --showrc', 'r') do |fd|
+      read_rc(fd)
+    end
+  end
+
+  # Read the rc file and define all the macros in it.
+  def read_rc(fd)
+    @macros = {}
+    fd.each_line() do |line|
+      if line =~ /^-14:\s+(\w+)\s+(\S+)/
+        @macros[$1] = $2
+      end
+    end
+  end
+
+  # Lookup a macro by name.  Use only the name part and leave out the
+  # leading '%{' and trailing '}'.  If any value exists for the macro, it is
+  # evaluated such that the result will be macro-free.
+  def lookup(name)
+    val = @macros[name]
+    return evaluate(val)
+  end
+
+  # Recursively examine the string and substitute any macros with their
+  # values.
+  def evaluate(value)
+    return nil if value.nil?
+#     value = value.gsub(/%\(([^\)]+)\)/) do |x|
+#       puts $1
+#      `#{$1}`
+#     end
+    return value.gsub(/%\{([^}]+)\}/) { |x| lookup($1) }
+  end
+end
+
+class RpmBuildOption
+  attr_reader :name, :bits, :flags
+
+  def initialize(bits, name, flags)
+    @bits = bits
+    @name = name
+    @flags = flags
+  end
+
+  def includes(other)
+    0 != @bits & other.bits
+  end
+
+  BINARIES = RpmBuildOption.new(1, 'binary', '-bb')
+  SOURCES = RpmBuildOption.new(2, 'sources', '-bs')
+  ALL = RpmBuildOption.new(1 + 2, 'all', '-ba')
+end
+
+# Wrapper around the rpmbuild command.
+class RpmBuild
+  attr_reader :rc
+
+  def initialize()
+    @rc = RpmRc.new()
+  end
+
+  def build_binary(rpm)
+    SysUtils.run("rpmbuild #{$options.rpm_build_opts.flags} " +
+      "#{rpm.spec_filename}")
+  end
+
+  def build_dir
+    @rc.lookup('_builddir')
+  end
+
+  def rpm_dir
+    @rc.lookup('_rpmdir')
+  end
+
+  def src_rpm_dir
+    @rc.lookup('_srcrpmdir')
+  end
+
+  def sources_dir
+    @rc.lookup('_sourcedir')
+  end
+
+  def spec_dir
+    @rc.lookup('_specdir')
+  end
+
+  def rpm_filename(rpm)
+    return File.join(rpm_dir, rpm.build_arch, 
+      "#{rpm.name}-#{rpm.version}-#{rpm.release}.#{rpm.build_arch}.rpm")
+  end
+
+  def srpm_filename(rpm)
+    return File.join(src_rpm_dir,
+      "#{rpm.name}-#{rpm.version}-#{rpm.release}.src.rpm")
+  end
+end
diff --git a/lib/rpmer/rpmspec.rb b/lib/rpmer/rpmspec.rb
new file mode 100644 (file)
index 0000000..b73b55f
--- /dev/null
@@ -0,0 +1,197 @@
+# This file can be distributed under the same terms as ruby itself.  
+# Author: Marek Gilbert <gil (at) fooplanet (dot) com>
+
+# RpmSpecWriter has formatting primitives that simplify generating RPM spec
+# files.
+class RpmSpecWriter
+  def initialize(fd)
+    @fd = fd
+  end
+
+  # Write out the contents of +line+ and a newline, just like
+  # +Kernel::puts+.
+  def puts(line = '')
+    @fd.puts(line)
+  end
+
+  # Write out an RPM header.  If the header is a multi-valued header then
+  # the value is joined with ", ".  If the value is nil, nothing is written.
+  def header(name, value)
+    return if value.nil?
+    if value.kind_of?(Array)
+      return if value.length == 0
+
+      value = value.join(', ')
+    end
+    puts("#{name}: #{value}")
+  end
+
+  # Declare a section and yield.
+  def section(name, rest = nil)
+    out = '%' + name
+    if !rest.nil?
+      out += ' ' + rest
+    end
+
+    puts()
+    puts(out)
+    yield
+  end
+end
+
+# A simple abstraction for a %name section in an rpm file.  Every section
+# has a %-sign, a name, possibly some text following the %name (e.g.
+# %files -f %{name}.files), and a block of lines that follow.
+class RpmSection
+  # The name of the section
+  attr_accessor :name
+
+  # The rest of the line following the section declaration, if any.
+  attr_accessor :rest
+
+  # The block of lines that follow the section declaration after a newline.
+  attr_accessor :text
+
+  def initialize(name, rest = nil)
+    @name = name
+    @rest = rest
+    @text = ''
+  end
+
+  # Add a line or lines to +text+.
+  def <<(line)
+    @text << "\n" if @text.length > 0
+    @text << line
+  end
+end
+
+# A generic interface to an RPM specification, consisiting of headers,
+# sections, and a little bit of glue to connect to RpmSpecWriter.
+class RpmSpec
+  @@headers = []
+  @@sections = []
+
+  # RPM header names aren't suitable method names because Ruby wants at
+  # least the initial character of the method to be lower case.  Generate
+  # good ruby names from the RPM names by converting from CamelCase to
+  # camel_case.
+  def self.sym_name(name)
+    case name
+    when 'URL'
+      return name.downcase
+
+    else
+      sym = name.dup
+      sym.sub!(/^[A-Z]/) { |x| x.downcase }
+      sym.gsub!(/[A-Z]/) { |x| '_' + x.downcase }
+      return sym
+    end
+  end
+
+
+  # Declares an RPM header.  +default_value+ can be lambda in which case a
+  # new value is taken from the result of calling the lambda with no
+  # arguments.
+  def self.rpm_header(name, default_value = nil)
+    get = sym_name(name)
+    set = get + '='
+
+    @@headers.push([name, get.intern, set.intern, default_value])
+    attr_accessor(get.intern)
+  end
+
+  # Declares an RPM header whose value consists of a list of things.
+  def self.rpm_list_header(name)
+    default_value = lambda do
+      Array.new()
+    end
+    rpm_header(name, default_value)
+  end
+
+  # Declares an RPM section.
+  def self.rpm_section(name)
+    get = sym_name(name)
+    set = get + '='
+    @@sections.push([name, get.intern, set.intern])
+    attr_accessor(get.intern)
+  end
+
+  attr_accessor :spec_filename
+
+  rpm_header('Name')
+  rpm_header('Version')
+  rpm_header('Release')
+  rpm_header('Summary')
+  rpm_header('License')
+  rpm_header('Group')
+  rpm_header('URL')
+
+  #rpm_header('AutoReqProv')
+  rpm_list_header('Conflicts')
+  rpm_list_header('Provides')
+  rpm_list_header('Requires')
+  rpm_list_header('Obsoletes')
+
+  rpm_list_header('Prereq')
+
+  rpm_header('BuildArch')
+  rpm_header('BuildRoot')
+  rpm_list_header('BuildConflicts')
+  rpm_list_header('BuildRequires')
+
+  # The +sources+ array generates the Source0, Source1, etc headers.
+  attr_reader :sources
+
+  # The +patches+ array generates the Patch0, Patch1, etc headers.
+  attr_reader :patches
+
+  rpm_section('description')
+  rpm_section('prep')
+  rpm_section('build')
+  rpm_section('install')
+  rpm_section('clean')
+  rpm_section('files')
+  rpm_section('changelog')
+
+  # Create a new RpmSpec that is intended to be written to the file
+  # at +filename+.
+  def initialize(filename)
+    @spec_filename = filename
+
+    @@headers.each do |name, get, set, default_value|
+      if default_value.kind_of?(Proc)
+        default_value = default_value.call()
+      end
+      self.send(set, default_value)
+    end
+
+    @sources = []
+    @patches = []
+
+    @@sections.each do |name, get, set|
+      self.send(set, RpmSection.new(name))
+    end
+  end
+
+  # Write the RpmSpec out to the +io+ which is already open for write.
+  def write(io)
+    @@headers.each do |name, get, set, |
+      io.header(name, self.send(get))
+    end
+
+    @sources.each_with_index do |src, i|
+      io.header('Source' + i.to_s, src)
+    end
+
+    @patches.each_with_index do |src, i|
+      io.header('Source' + i.to_s, src)
+    end
+
+    @@sections.each do |name, get, set|
+      sec = self.send(get)
+      io.section(sec.name, sec.rest) do
+        io.puts(sec.text)
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rpmer/rubygemtranslator.rb b/lib/rpmer/rubygemtranslator.rb
new file mode 100644 (file)
index 0000000..b8d2b2c
--- /dev/null
@@ -0,0 +1,164 @@
+# This file can be distributed under the same terms as ruby itself.  
+# Author: Marek Gilbert <gil (at) fooplanet (dot) com>
+
+require 'rpmer/util'
+
+# Does the actual work of mapping GEM metadata to RPM metadata and
+# generating RPM spec contents that will result in a successful build of the
+# RPM.
+class RubyGemTranslator
+  attr_accessor :gem
+  attr_accessor :rpm
+
+  def initialize(gem_spec)
+    @gem = gem_spec
+    #@rpm = RpmSpec.new(File.join(build.spec_dir, gem.name + '.spec'))
+    @rpm = RpmSpec.new(gem.name + '.spec')
+  end
+
+  # Factory method that creates an RpmTranslator from a +build+ and a GEM
+  # filename.
+  def self.from_gem(filename)
+    gem_spec = nil
+    File.open(filename, 'r') do |fd|
+      Gem::Package::open_from_io(fd) do |g|
+        gem_spec = g.metadata
+        gem_spec.loaded_from = filename
+      end
+    end
+    rt = RubyGemTranslator.new(gem_spec)
+    return rt
+  end
+
+  def add_external_requirements(options)
+    # rubygems itself doesn't require rubygems, but everything else
+    # does.
+    @rpm.requires << "rubygems"
+
+    # most packages require ri and rdoc to properly install
+    @rpm.build_requires << 'ruby'
+
+    # FIXME add hook for multiple distros
+    #@rpm.build_requires << 'rubygems'
+    @rpm.build_requires << 'rubygems_with_buildroot_patch'
+
+    if @gem.has_rdoc?
+      @rpm.build_requires << 'ruby-rdoc'  # or maybe /usr/bin/rdoc?
+      @rpm.build_requires << 'ruby-ri'    # or maybe /usr/bin/ri?
+    end
+  end
+
+  def add_requirement(name, op, ver)
+    case op
+    when '~>'
+      @rpm.requires << "#{name} >= #{ver}"
+      @rpm.requires << "#{name} < #{ver.bump}"
+    else
+      @rpm.requires << "#{name} #{op} #{ver}"
+    end
+  end
+
+  def add_build_requirement(name, op, ver)
+    case op
+    when '~>'
+      @rpm.build_requires << "#{name} >= #{ver}"
+      @rpm.build_requires << "#{name} < #{ver.bump}"
+    else
+      @rpm.build_requires << "#{name} #{op} #{ver}"
+    end
+  end
+
+  # Translate the gem to an RPM using the given options.
+  def translate_gem(options)
+    name_ver = @gem.name + '-' + @gem.version.to_s
+    gem_basename = File.basename(@gem.loaded_from)
+
+    @rpm.sources << gem_basename
+
+    @rpm.name = "rubygem-#{@gem.name}"
+    @rpm.version = @gem.version.to_s
+    @rpm.release = options.release
+    @rpm.summary = @gem.summary
+    @rpm.license = options.license
+    @rpm.group = options.group
+    @rpm.url = @gem.homepage
+
+    if !options.arch.nil?
+      @rpm.build_arch = options.arch
+    else
+      case @gem.platform
+      when Gem::Platform::RUBY
+        @rpm.build_arch = 'noarch'
+
+      when /(i.86)-linux/
+        @rpm.build_arch = 'i386'
+
+      else
+        raise "failed to convert unknown gem platform '#{@gem.platform}' to equivalent rpm BuildArch."
+      end
+    end
+
+    @rpm.build_root = '%{_tmppath}/%{name}-%{version}-%{release}-root'
+
+    # Populate dependencies.  Note that GEM can express multiple
+    # requirements on a given package and its syntax for that is
+    # incompatible with RPM.  Iterate over each version requirement
+    # so as to build up the list correctly.
+    @gem.dependencies.each do |dep|
+      dep.version_requirements.requirements.each do |x|
+        if options.gems_prefix.empty?
+          add_requirement( dep.name, *x)
+          add_build_requirement( dep.name, *x)
+        else
+          add_requirement( "rubygem-#{dep.name}", *x)
+          add_build_requirement( "rubygem-#{dep.name}", *x)
+        end
+        
+      end
+    end
+
+    @rpm.description << wrap_text(@gem.description, 76)
+
+    @rpm.prep << <<EOF
+EOF
+
+    @rpm.install << <<EOF
+rm -rf $RPM_BUILD_ROOT
+gem install --build-root=%{buildroot} %{S:0}
+gem_build_cleanup %{buildroot}%{_libdir}/ruby/gems/%{rb_ver}/gems/#{@gem.name}-%{version}/
+EOF
+
+    ## When running as non-root, gem wants to install into install-dir/bin.
+    # We need that to end up in prefix/bin.
+    #@gem.executables.each do |bin|
+    #  @rpm.install << "mv $gem_path/#{@gem.bindir}/#{bin} $usr_bin/#{bin}"
+    #end
+
+    @rpm.clean << <<EOF
+%{__rm} -rf %{buildroot}
+EOF
+
+    @rpm.files << '%defattr(-,root,root)'
+
+    @gem.executables.each do |bin|
+      @rpm.files << File.join('%{_bindir}/', bin)
+    end
+
+    # FIXME move this out
+    #gems_path = '%{_libdir}/ruby/gems/%{rb_ver}/'
+    @rpm.files << File.join(Gem.path, 'cache', gem_basename)
+    @rpm.files << File.join(Gem.path, 'gems', name_ver)
+    @rpm.files << File.join(Gem.path, 'specifications', name_ver + '.gemspec')
+
+    if @gem.has_rdoc?
+      @rpm.files << '%doc ' + File.join(Gem.path, 'doc', name_ver)
+    end
+
+    date = @gem.date.strftime('%a %b %d %Y')
+    @rpm.changelog << <<EOF
+* #{date} #{@gem.authors[0]} <#{gem.email}> - #{gem.version}-#{options.release}
+- #{gem.name} release #{gem.version}.
+EOF
+  end
+end
+
diff --git a/lib/rpmer/util.rb b/lib/rpmer/util.rb
new file mode 100644 (file)
index 0000000..3b13f58
--- /dev/null
@@ -0,0 +1,9 @@
+# wrap_text takes +text+ and wraps it such that no line exeeds +col+
+# characters.
+def wrap_text(text, col = 80)
+  if text
+    text.gsub(/(.{1,#{col}})(\s+|$)\n?/, "\\1\n")
+  else
+    ''
+  end
+end
\ No newline at end of file
diff --git a/spec/rpmer/rpmspec_spec.rb b/spec/rpmer/rpmspec_spec.rb
new file mode 100644 (file)
index 0000000..c32481e
--- /dev/null
@@ -0,0 +1,14 @@
+$:.push(File.expand_path(File.dirname(__FILE__) + "../../lib"))
+require 'rpmer'
+
+describe RPMSpec do
+  before(:each) do
+    @bowling = RPMSPec.new
+  end
+
+  
+  it "should score 0 for gutter game" do
+    20.times { @bowling.hit(0) }
+    @bowling.score.should == 0
+  end
+end