Class: Reap::Project

Inherits:
Object
  • Object
show all
Includes:
Utilities
Defined in:
lib/reap/project.rb,
lib/reap/metadata.rb,
lib/reap/settings.rb,
lib/reap/project/gem.rb,
lib/reap/project/log.rb,
lib/reap/project/scm.rb,
lib/reap/project/svn.rb,
lib/reap/project/html.rb,
lib/reap/project/make.rb,
lib/reap/project/rdoc.rb,
lib/reap/project/site.rb,
lib/reap/project/spec.rb,
lib/reap/project/test.rb,
lib/reap/project/check.rb,
lib/reap/project/clean.rb,
lib/reap/project/stats.rb,
lib/reap/project/package.rb,
lib/reap/project/publish.rb,
lib/reap/project/release.rb,
lib/reap/project/version.rb,
lib/reap/project/announce.rb,
lib/reap/project/scaffold.rb,
lib/reap/project/rubyforge.rb

Overview

Project

The Project class is the main class of Reap. It provides the tools for working with a project. The CLI Application class delegates to this class, for instance.

Defined Under Namespace

Classes: Metadata, Settings

Constant Summary collapse

MAKE_COMMAND =

The Make tool routes to extension Makefile(s). Presently, it is designed to support only extconf.rb design.

TODO: win32 cross-compile ?

ENV['make'] || (RUBY_PLATFORM =~ /(win|w)32$/ ? 'nmake' : 'make')

Instance Attribute Summary

Attributes included from Utilities

#dryrun, #force, #trace, #verbose

Instance Method Summary collapse

Methods included from Utilities

#ask, #bin?, #cd, #command_paths, #dir!, #dir?, directory!, directory?, #email, exist!, exist?, #exists!, #exists?, #file!, #file?, #fileutils, #glob, #multiglob, #multiglob_r, #out_of_date?, #password, path!, path?, #read, #rm_r, #safe?, #sh, #stage, #stage_manifest, #status, #tar_bzip, #tgz, #write, #zip, #ziputils

Constructor Details

#initialize(options = nil) ⇒ Project

New Project.



41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/reap/project.rb', line 41

def initialize(options=nil)
  @options        = options || {}
  begin
    @location       = locate
    raise LoadError, "no .reap configuration file" unless @location

    @metadata = Metadata.read(location)
    @settings = Settings.read(location, @metadata)
  rescue LoadError => e
    abort e.message.capitalize + '.'
  end
end

Instance Method Details

#announce(options = nil) ⇒ Object

Make a release announcement. Generates and can email release announcements. The announcement if built from the README file unless another file is specified.

This will subsititue the first line mathing /please see notes/i for the notelog. And /please see change/i for the changelog.

The following settings apply:

title        Project title.
subtitle     Brief one-line description.
version      Project version.
description  Long description of project.
homepage     Project homepage web address.
slogan       Motto for you project.
memo         File that contains announcement message.
template     Announcement template file, rather then README.
mail_to      Email address(es) to send announcemnt.

If mail_to is set then these also apply:

from         Message FROM address [email].
subject      Subject of email message ([ANN] title verison).
server       Email server to route message.
port         Email server's port.
domain       Email server's domain name.
account      Email account name [email].
login        Login type: plain, cram_md5 or login.
secure       Uses TLS security, true or false?

A template file can be specified that uses “$setting” as substitutes for poject information.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/reap/project/announce.rb', line 38

def announce(options=nil)
  options = configure_options(options, 'announce', 'mail')

  message = announce_message(options)

  options = options.to_ostruct

  mail_to     = options.mail_to
  mail_from   = options.mail_from
  subject     = options.subject         # Subject line (default is "ANN: project version").
  server      = options.server          # Email server
  port        = options.port            # Emails server port (default is usually correct).
       = options.         # Email account name (defaults to mail_from).
  domain      = options.domain          # User domain (not sure why SMTP requires this?)
         = options.           # Login type (plain, login)
  secure      = options.secure          # Use TLS/SSL true or false?
  password    = options.password || ENV['EMAIL_PASSWORD']

  title       = options.title    || .title
  version     = options.versoin  || .version

  # defaults
  subject ||= "%s, v%s release"
   ||= mail_from

  subject = subject % [title, version]

  if dryrun?
    puts "email '#{subject}'"
    puts "\n#{message}\n\n" if verbose?
  else
    puts "\n#{message}\n\n"
    if mail_to
      ans = ask("Would you like to email this announcement?", "yN")
      case ans.downcase
      when 'y', 'yes'
        email(message,
          :to       => mail_to,
          :from     => mail_from,
          :subject  => subject,
          :server   => server,
          :port     => port,
          :domain   => domain,
          :account  => ,
          :login    => ,
          :secure   => secure,
          :password => password
        )
      end
    end
  end
end

#announce_message(options = {}) ⇒ Object

Make a release announcement. Generates and can email a release announcements. These are nicely formated message and can email the message to the specified address(es).

The following settings apply:

template     Announcement file/template.
cutoff       Max number of lines of changelog to show.

A template file can be specified that uses “$setting” as substitutes for poject information.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/reap/project/announce.rb', line 103

def announce_message(options={})
  config  = settings['announce'] || {}
  options = config.merge(options).to_ostruct

  cutoff   = options.cutoff    || 30
  template = options.template  || "{ANNOUNCE}{,.txt}"

  #config['mail_to'] = nil if keys['mail_to'].empty?

  # Build message

  # template
  template = Dir.glob(options.template.to_s, File::FNM_CASEFOLD).first

  if template
    readme = File.read(template)
    readme = unfold_paragraphs(readme)
  else
    readme = ''
    readme << "= #{.title} v#{.version}\n\n"
    readme << "    #{.homepage}\n\n"
    readme << "#{.description}\n\n"
    readme << "Please see the NOTES file.\n\n"
    readme << "== CHANGES\n\n"
    readme << "Please see the CHANGES file.\n"
  end

  # changelog
  file   = Dir.glob('change{s,log}{,.txt}', File::FNM_CASEFOLD)[0]
  changelog = file ? File.read(file).strip : ''
  #changelog = unfold_paragraphs(changelog)
  changelog = changelog.split("\n")[0..cutoff].join("\n")

  # noteslog
  file = Dir.glob('note{s,log}{,.txt}', File::FNM_CASEFOLD)[0]
  notelog  = file ? File.read(file).strip : ''
  notelog  = unfold_paragraphs(notelog)

  # Strip tiny version zero.
  #if keys['version'] =~ /[.].*?[.]/
  #  keys['version'] = keys['version'].chomp('.0')
  #end

  # Make announcement message
  message = readme.dup

  #message.gsub!('$readme$', readme || '')
  message.sub!(/^\s*please\ see(\ the)?\ notes(.*?)$/i, "\n" + notelog) if notelog
  message.sub!(/^\s*please\ see(\ the)?\ change(.*?)$/i, "\n" + changelog) if changelog

  template = message.dup

  template.scan(/\$(\w+?)\$/m) do |key|
    #key   = key.strip
    name  = $1.strip #key[1..-1]
    if .respond_to?(name.downcase)
      value = .send(name.downcase)
      message.gsub!("$#{name}$", value.to_s.strip)
    else
      puts "Warning: Unknown project field -- #{name}."
    end
  end

  message.gsub!(/(^|[ ])[$].*?(?=[ ]|$)/,'') # remove unused vars
  message.gsub!(/\n\s*\n\s*\n/m,"\n\n")      # remove any triple blank lines
  message.rstrip!

  return message
end

#chdir_to_project(&block) ⇒ Object

Change directory to project’s root location, and execute block if given. If a block is provided, the current directory will revert back to what it was prior to this call, otherwise it will remain changed.



117
118
119
120
121
122
123
# File 'lib/reap/project.rb', line 117

def chdir_to_project(&block)
  if block
    Dir.chdir(location, &block)
  else
    Dir.chdir(location)
  end
end

#check_load(options = nil) ⇒ Object

Load each script independently to ensure there are no require dependency issues.

WARNING! You should only run this on scripts that have no toplevel side-effects!!!

This takes one option :scripts which is a glob or list of globs of the scripts to check. By default this is all scripts in the libpath(s).

FIXME: This isn’t routing output to dev/null as expected.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/reap/project/check.rb', line 63

def check_load(options=nil)
  options = configure_options(options, 'check-load', 'check')

  make if compiles?

  libpath = options['libpath'] || .libpath
  exclude = options['exclude']

  libpath = list_option(libpath)
  exclude = list_option(exclude)

  files = multiglob_r(*libpath) - multiglob_r(exclude)
  files   = files.select{ |f| File.extname(f) == '.rb' }
  max   = files.collect{ |f| f.size }.max
  list  = []

  files.each do |s|
    next unless File.file?(s)
    #if not system "ruby -c -Ibin:lib:test #{s} &> /dev/null" then
    cmd = "ruby -I#{libpath.join(':')} #{s} > /dev/null 2>&1"
    puts cmd if debug?
    if r = system(cmd)
      puts "%-#{max}s  [PASS]" % [s]
    else
      puts "%-#{max}s  [FAIL]" % [s]
      list << s #:load
    end
  end

  puts "  #{list.size} Load Failures"

  if verbose?
    unless list.empty?
      puts "\n-- Load Failures --\n"
      list.each do |f|
        print "* "
        system "ruby -I#{libpath} #{f} 2>&1"
        #puts
      end
      puts
    end
  end
end

#check_syntax(options = nil) ⇒ Object

Verify syntax of ruby scripts.

This takes one option :scripts which is a glob or list of globs of the scripts to check. By default this is all scripts in the libpath(s).



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/reap/project/check.rb', line 10

def check_syntax(options=nil)
  options = configure_options(options, 'check-syntax', 'check')

  libpath = options['loadpath'] || .loadpath
  exclude = options['exclude']

  #libpath = libpath.split(/[:;]/) unless Array===libpath
  libpath = list_option(libpath)
  exclude = list_option(exclude)

  files   = multiglob_r(*libpath) - multiglob_r(exclude)
  files   = files.select{ |f| File.extname(f) == '.rb' }
  max     = files.collect{ |f| f.size }.max
  list    = []

  files.each do |s|
    next unless File.file?(s)
    #if not system "ruby -c -Ibin:lib:test #{s} &> /dev/null" then
    r = system "ruby -c -I#{libpath} #{s} > /dev/null 2>&1"
    if r
      puts("%-#{max}s  [PASS]" % [s]) #if verbose?
    else
      puts("%-#{max}s  [FAIL]" % [s]) #if verbose?
      list << s #:syntax
    end
  end

  puts "  #{list.size} Syntax Errors"

  if verbose?
    unless list.empty?
      puts "\n-- Syntax Errors --\n"
      list.each do |f|
        print "* "
        system "ruby -c -I#{libpath} #{f} 2>&1"
        #puts
      end
      puts
    end
  end
end

#clean(options = nil) ⇒ Object

Clean scrap products. All directory paths and or file globs listed under the clean configuration entry, can be removed via this method. By default all files ending with “~” or .back are removed. To specifcy an alternate provide a list of files and/or glibs under remove: sub-entry. You can also provide an exclude: sub-entry to isolate files not to be removed. For example, on might do:

clean:
  remove [ '**/*~', '**/*.bak', '.config' ]

Clean is run as a prerequiste to #release via #prepare.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/reap/project/clean.rb', line 18

def clean(options=nil)
  options = configure_options(options, 'clean')

  remove  = options['remove']
  exclude = options['exclude']

  remove  = list_option(remove)
  exclude = list_option(exclude)

  files   = multiglob_r(*remove) - multiglob_r(exclude)

  make_clean if compiles?

  return if files.empty?

  puts files.join("\n")

  if verbose?
    ans = ask("Remove files?", "yN").downcase
    return unless ans == 'y' or ans == 'yes'
  end

  files.each do |f|
    rm(f) if File.exist?(f)
  end
end

#clobber(options = nil) ⇒ Object

Run all clobber methods. This method literally looks for all other methods starting with the phrase “clobber_” and calls them, then runs #clean as well.



49
50
51
52
53
54
55
# File 'lib/reap/project/clean.rb', line 49

def clobber(options=nil)
  clean
  clobber_methods = methods.select{ |m| m.to_s =~ /^clobber_/ }
  clobber_methods.each do |m|
    send(m)
  end
end

#clobber_packages(options = nil) ⇒ Object

Remove packages.



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/reap/project/package.rb', line 9

def clobber_packages(options=nil)
  options = configure_options(options, 'package')

  store = 'pkg'

  packages = glob(File.join(store, '*'))

  packages.each do |path|
    rm_r(path)
    puts "Removed #{path}" unless dryrun?
  end
end

#clobber_rdoc(options = nil) ⇒ Object

Remove rdocs products.



78
79
80
81
82
83
84
85
86
87
# File 'lib/reap/project/rdoc.rb', line 78

def clobber_rdoc(options=nil)
  options = configure_options(options, 'doc-rdoc', 'rdoc')

  output = options['output']

  if File.directory?(output)
    rm_r(output)
    puts "Removed #{output}" unless dryrun?
  end
end

#clobber_ridoc(options = nil) ⇒ Object

Remove ri products.



158
159
160
161
162
163
164
165
166
167
# File 'lib/reap/project/rdoc.rb', line 158

def clobber_ridoc(options=nil)
  options = configure_options(options, 'doc-ri', 'ri')

  output = options['output']

  if File.directory?(output)
    rm_r(output)
    puts "Removed #{output}" unless dryrun?
  end
end

#compiles?Boolean

Check to see if this project has extensions that need to be compiled.

Returns:

  • (Boolean)


17
18
19
# File 'lib/reap/project/make.rb', line 17

def compiles?
  !extensions.empty?
end

#debug=(x) ⇒ Object



85
# File 'lib/reap/project.rb', line 85

def debug=(x)   ; options['debug']   = x ; end

#debug?Boolean

Returns:

  • (Boolean)


79
# File 'lib/reap/project.rb', line 79

def debug?      ; options['debug']   ; end

#display_locationObject

Display the project’s root location.



107
108
109
# File 'lib/reap/project.rb', line 107

def display_location
  puts "[#{location}]"
end

#document(options) ⇒ Object

Generate documentation. At this time it simply means generating rdocs.



8
9
10
# File 'lib/reap/project/rdoc.rb', line 8

def document(options)
  rdoc(options)
end

#dryrun=(x) ⇒ Object Also known as: noharm=



81
# File 'lib/reap/project.rb', line 81

def dryrun=(x)  ; options['dryrun']  = x ; end

#dryrun?Boolean Also known as: noharm?

alias_method :init_options, :options # TODO: Improve me! (see stamp.rb)

Returns:

  • (Boolean)


75
# File 'lib/reap/project.rb', line 75

def dryrun?     ; options['dryrun']  ; end

#extensionsObject

Extension directories. Often this will simply be ‘ext’. but sometimes more then one extension is needed and are kept in separate directories. This works by looking for ext/*/.c files, where ever they are is considered an extension directory.



26
27
28
# File 'lib/reap/project/make.rb', line 26

def extensions
  @extensions ||= Dir['ext/**/*.c'].collect{ |file| File.dirname(file) }.uniq
end

#force=(x) ⇒ Object



83
# File 'lib/reap/project.rb', line 83

def force=(x)   ; options['force']   = x ; end

#force?Boolean

Returns:

  • (Boolean)


77
# File 'lib/reap/project.rb', line 77

def force?      ; options['force']   ; end

#gem_clobber(options = nil) ⇒ Object

Remove gem package products.



7
8
9
10
11
12
13
14
15
# File 'lib/reap/project/gem.rb', line 7

def gem_clobber(options=nil)
  store = "pkg"

  packages = glob(File.join(store, '*.gem'))

  packages.each do |path|
    File.directory?(path) ? rm_r(path) : rm(path)
  end
end

#gem_install(options = nil) ⇒ Object

Install gem package, creating the package if not already created.

TODO: Endure that we even need a gem package using #out_of_date?



81
82
83
84
# File 'lib/reap/project/gem.rb', line 81

def gem_install(options=nil)
  file = gem_package(options)
  sh "gem install #{file}"
end

#gem_package(options = nil) ⇒ Object

Create a Gem package.

TODO: Should this use staging too, like zip/tgz?



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/reap/project/gem.rb', line 21

def gem_package(options=nil)
  begin
    require 'rubygems/specification'
    Gem::manage_gems
  rescue LoadError
    #raise LoadError, "RubyGems is not installed?"
  end

  options = configure_options(options, 'package-gem', 'package')

  prepare(options)

  package = .clone
  package.update(options)

  abort "No name" unless package.name
  abort "No version" unless package.version

  store = 'pkg' #package.package_directory
  fname = package.stage_name
  files = package.filelist

  stage = File.join(store, fname)
  pfile = stage + ".gem"

  unless out_of_date?(pfile, *files) or force?
    report_package_already_built(pfile)
    return
  end 

  rm_r(stage) if File.exist?(stage)  # remove old stage
  stage(stage, files)                # make new stage
  package_manifest(stage)            # generate manifest

  if dryrun?
    status "gem build #{stage}"
  else
    file = nil
    cd(stage) do
      #status "vi #{metadata.name}.gemspec"
      builder = ::Gem::Builder.new(gemspec(package))
      status "gem build #{stage}"
      unless dryrun?
        file = builder.build
        file = File.expand_path(file)
      end
    end
    # transfer gem package to package store
    mkdir_p(store)
    destination = File.join(store, File.basename(file))
    mv(file, store) unless File.expand_path(file) == File.expand_path(destination)
  end

  return destination
end

#gem_uninstallObject

Uninstall gem package.

TODO: Sepcify version?



90
91
92
93
94
# File 'lib/reap/project/gem.rb', line 90

def gem_uninstall
  i = .package_name.rindex('-')
  name, version = .package_name[0...i], .package_name[i+1..-1]
  sh "gem uninstall #{name} -v #{version}"
end

#htmlObject

Create web index.html from README. (Not yet used)



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/reap/project/html.rb', line 7

def html
  status_title "Creating HTML documents"

  require 'rdoc/markup/simple_markup'
  require 'rdoc/markup/simple_markup/to_html'

  options = configure_options(options, 'html')

  output = options['output']
  files  = options['files']
  style  = options['css']

  output ||= 'doc'
  files  ||= '[A-Z]*'
  style  ||= Dir.glob('*.css').first

  files = Dir.glob(files)

  s = SM::SimpleMarkup.new
  h = SM::ToHtml.new

  files.each do |file|
    unless File.exist?(file)
      puts "Warning: file does not exist -- #{file}"
    end
  end

  mkdir_p(output) unless dryrun?

  files.each do |file|
    name = file.downcase.chomp('.txt')
    if /^readme/ =~ name
      name = "index"
    end
    path = File.join(output, name + '.html')

    next unless out_of_date?(path, file)

    title = "#{package.title} #{name.upcase}"

    input  = File.read(file)
    output = s.convert(input, h)  # FIX

    text = ''
    text << %{<html>}
    text << %{<head>}
    text << %{  <title>#{title}<title>}
    text << %{  <link rel="stylesheet" TYPE="text/css" HREF="#{style}">} if style
    text << %{</head>}
    text << %{<body>}
    text << output
    text << %{</body>}
    text << %{</html>}

    write(path, text)

    puts "Created #{path}"
  end
end

#introspect(options) ⇒ Object

Query infromation about reap settings and/or the current project.

NOTE: This would dhave been naed #inspect but for the built-in method.



136
137
138
139
140
141
142
143
144
145
# File 'lib/reap/project.rb', line 136

def introspect(options)
  args = options['arguments']
  if args
    args.each do |field|
      puts .send(field)
    end
  else
    y self
  end
end

#invoke(command, *args) ⇒ Object

Invoke a tool.



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/reap/project.rb', line 92

def invoke(command, *args)
  #display_location
  meth = method(command)
  chdir_to_project do
    case meth.arity
    when 0
      meth.call
    else
      meth.call(*args)
    end
  end
end

#locationObject

Location of project.



56
# File 'lib/reap/project.rb', line 56

def location ; @location ; end

#log(*args) ⇒ Object

Update all logs.



9
10
11
12
# File 'lib/reap/project/log.rb', line 9

def log(*args)
  log_changes(*args)
  log_notes(*args)
end

#log_changes(options = nil) ⇒ Object

Generate ChangeLog. This routes to the source control manager library.



17
18
19
20
# File 'lib/reap/project/log.rb', line 17

def log_changes(options=nil)
  options = configure_options(options, 'log-changes', 'log')
  scm_log(options)
end

#log_notes(options = {}) ⇒ Object

Collect embedded notes.

This task scans source code for developer notes and writes to well organized files. This tool can lookup and list TODO, FIXME and other types of labeled comments from source code.

files    Glob(s) of files to search.
labels   Labels to search for. Defaults to [ 'TODO', 'FIXME' ].
output   Output directory. Defaults to log/.

TODO: Remove format field, and ultimately use XML as primary format?



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/reap/project/log.rb', line 36

def log_notes(options={})
  options = configure_options(options, 'log-notes', 'log')

  loadpath = options['loadpath'] || .loadpath
  labels   = options['labels']   || ['TODO', 'FIXME', 'OPTIMIZE']
  output   = options['output']   || 'log'

  loadpath = list_option(loadpath)

  labels = labels.split(',') if String === labels
  labels = [labels].flatten.compact

  output.chomp!('/')

  records, counts = log_notes_extract(labels, loadpath)
  notes = log_notes_format(labels, records, (format=nil))

  if records.empty?
    puts "No #{labels.join(', ')} notes."
  else
    log_notes_save(output, notes, labels)
    puts counts.collect{|l,n| "#{n} #{l}s"}.join(', ')
    puts "Notes saved in #{output} folder."
  end
end

#makeObject

Compile extensions.



33
34
35
36
# File 'lib/reap/project/make.rb', line 33

def make
  make_config
  make_target
end

#make_cleanObject

Remove enouhg compile products for a clean compile.



47
48
49
# File 'lib/reap/project/make.rb', line 47

def make_clean
  make_target 'clean'
end

#make_configObject

Create Makefile(s).



65
66
67
68
69
70
71
72
# File 'lib/reap/project/make.rb', line 65

def make_config
  extensions.each do |directory|
    next if File.exist?(File.join(directory, 'Makefile'))
    cd(directory) do
      sh "ruby extconf.rb"
    end
  end  
end

#make_distcleanObject Also known as: clobber_make

Remove all compile products.



53
54
55
56
57
58
59
# File 'lib/reap/project/make.rb', line 53

def make_distclean
  make_target 'distclean'
  extensions.each do |directory|
    makefile = File.join(directory, 'Makefile')
    rm(makefile) if File.exist?(makefile)
  end
end

#make_staticObject

Compile static.



40
41
42
43
# File 'lib/reap/project/make.rb', line 40

def make_static
  make_config
  make_target 'static'
end

#manifest_fileObject

Project manifest file.



149
150
151
# File 'lib/reap/project.rb', line 149

def manifest_file 
  Dir.glob('Manifest{,.txt}', File::FNM_CASEFOLD).first
end

#metadataObject

Project metadata.



60
# File 'lib/reap/project.rb', line 60

def  ; @metadata  ; end

#optionsObject

Common options.



72
# File 'lib/reap/project.rb', line 72

def options  ; @options  ; end

#package(options = nil) ⇒ Object

General pack command.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/reap/project/package.rb', line 38

def package(options=nil)
  packopts = configure_options(options, 'package')

  formats = packopts['formats'] || ['zip']
  formats = [formats].flatten

  prepare(options)

  puts unless dryrun?
  formats.each do |format|
    send("package_#{format}", options)
    puts unless dryrun?
  end
end

#package_docs(options = nil) ⇒ Object

Create off-line documentation package.

FIXME: package_docs is not yet ready for use.



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/reap/project/package.rb', line 145

def package_docs(options=nil)
  options = configure_options(options, 'package-doc')

  dir  = options['dir']     || 'doc'
  name = options['name']    || .name
  ver  = options['version'] || .version

  cd(dir) do
    zip "-czhvf #{name}-docs-#{ver}.zip *"
  end
end

#package_gem(options = nil) ⇒ Object

Routes to $gem_package.



55
56
57
# File 'lib/reap/project/package.rb', line 55

def package_gem(options=nil)
  gem_package(options)
end

#package_tgz(options = nil) ⇒ Object

Create a Tar’d Gzip package.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/reap/project/package.rb', line 61

def package_tgz(options=nil)
  options = configure_options(options, 'package-tgz', 'package')

  prepare(options)

  package = .clone
  package.update(options)

  abort "No name" unless package.name
  abort "No version" unless package.version

  store = 'pkg' #package.package_directory
  fname = package.stage_name
  files = package.filelist

  stage = File.join(store, fname)
  pfile = stage + ".tgz"

  unless out_of_date?(pfile, *files) or force?
    report_package_already_built(pfile)
    return
  end

  rm_r(stage) if File.exist?(stage)  # remove old stage
  stage(stage, files)                # make new stage
  package_manifest(stage)            # generate stage manifest

  if dryrun?
    status "tar -cxf #{fname}.tgz"
  else
    file = nil
    cd(store) do
      file = tgz(fname)  # FIXME: Prefer .tgz as extensions.
    end
    transfer(file, store)
    report_package_built(file)
  end
end

#package_zip(options = nil) ⇒ Object

Create Zip package.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/reap/project/package.rb', line 102

def package_zip(options=nil)
  options = configure_options(options, 'package-zip', 'package')

  prepare(options)

  package = .clone
  package.update(options)

  abort "No name" unless package.name
  abort "No version" unless package.version

  store = 'pkg' #package.package_directory
  fname = package.stage_name
  files = package.filelist

  stage = File.join(store, fname)
  pfile = stage + ".zip"

  unless out_of_date?(pfile, *files) or force?
    report_package_already_built(pfile)
    return
  end 

  rm_r(stage) if File.exist?(stage)  # remove old stage
  stage(stage, files)                # make new stage
  package_manifest(stage)            # generate manifest

  if dryrun?
    status "zip -r #{fname}.zip ."
  else
    file = nil
    cd(store) do
      file = zip(fname)
    end
    transfer(file, store)
    report_package_built(file)
  end
end

#prepare(options) ⇒ Object

Prepare for packaging (clean, distclean, stamp).

TODO: When we add support for binary packages distclean

should not be done for them.


27
28
29
30
31
32
33
34
# File 'lib/reap/project/package.rb', line 27

def prepare(options)
  @prepared ||= (
    clean
    make_distclean if compiles?
    stamp(options)
    true
  )
end

#publish(options = nil) ⇒ Object

Publish website to rubyforge.

This task publishes the source dir (deafult ‘doc’) to a rubyforge website.

Uses RSync to upload files to webserver.

TODO: Add FTP/SFTP support.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/reap/project/publish.rb', line 14

def publish(options=nil)
  options = configure_options(options, 'publish', 'rubyforge')

  project  = options['project']  || .project
  webdir   = options['webdir']
  source   = options['source']
  username = options['username'] || ENV['RUBYFORGE_USERNAME']
  clear    = options['clear']
  protect  = options['protect']
  exclude  = options['exclude']

  source   ||= "doc"
  username ||= ENV['RUBYFORGE_USERNAME']

  if clear
    protect   = protect().to_a
    exclude   = exclude().to_a
  else
    protect   = %w{usage statcvs statsvn robot.txt wiki} + [protect].flatten
    exclude   = %w{.svn} + [exclude].flatten
  end

  abort "No project name." unless project
  abort "No username." unless username

  if webdir and webdir != '.'
    destination = File.join(project, webdir)
  else
    destination = project
  end

  dir = source.chomp('/') + '/'
  url = "#{username}@rubyforge.org:/var/www/gforge-projects/#{destination}"

  op = ['-rLvz', '--delete-after']  # maybe -p ?

  # Using commandline filter options didn't seem
  # to work, so I opted for creating an .rsync_filter file for
  # all cases.

  unless protect.empty? && exclude.empty?
    rsync_file = File.join(source,'.rsync-filter')
    unless file?(rsync_file)
      File.open(rsync_file, 'w') do |f|
        exclude.each{|e| f << "- #{e}\n"}
        protect.each{|e| f << "P #{e}\n"}
      end
    end
    op << "--filter='dir-merge #{rsync_file}'"
  end

  args = op + [dir, url]

  sh "rsync #{args.to_params}"
end

#rdoc(options = nil) ⇒ Object

Generate rdocs.

Generate Rdoc documentation. Settings are the same as the rdoc command’s option, with two exceptions: inline for inline-source and output for op.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/reap/project/rdoc.rb', line 19

def rdoc(options=nil)
  options = configure_options(options, 'doc-rdoc', 'rdoc', 'doc')
  #options = DEFAULT['rdoc'].merge(options)

  options['title'] ||= .title

  targets = options.delete('targets') || {'' => options}
  output  = options['output']
  adfile  = options['adfile']

  adfile = [adfile].flatten.find do |f|
    File.exist?(f)
  end

  targets.each do |subdir, target|
    target = options.merge(target)

    target_solo = target['solo']
    target_main = Dir.glob(target['main'].to_s, File::FNM_CASEFOLD).first

    #target_main   = File.expand_path(target_main) if target_main
    #target_output = File.expand_path(File.join(output, subdir))
    target_output = File.join(output, subdir)

    cmdopts = {}
    #cmdopts['op']            = target_output
    cmdopts['main']          = target_main if target_main
    cmdopts['template']      = target['template']
    #cmdopts['merge']         = target['merge']
    cmdopts['inline-source'] = target['inline']
    cmdopts['exclude']       = list_option(target['exclude'])

    files = list_option(target['include'])
    files = files.collect{ |g| Dir[g] }.flatten  # Need this little rig to remove unwanted toplevel files.
    files = files - ['Rakefile', 'Rakefile.rb']  # b/c rdoc's exlcude options doesn't work well.
    files = files - [manifest_file].compact

    #folder = target['chdir'] || '.'

    #puts "cd #{folder}" if dryrun?  # TODO: Shouldn't chdir do this automatically?
    #chdir(folder) do
      if target_solo
        input_files = files.collect{ |i| multiglob_r(i) }.flatten.reject{ |f| File.directory?(f) }
        input_files.each do |input_file|
          out = File.join(target_output, File.basename(input_file).chomp(File.extname(input_file)))
          rdoc_target(out, input_file, cmdopts)
          rdoc_insert_ads(out, adfile)
        end
      else
        input_files = files.collect{ |i| dir?(i) ? File.join(i,'**','*') : i }
        rdoc_target(target_output, input_files, cmdopts)
        rdoc_insert_ads(target_output, adfile)
      end
    #end
  end
end

#release(options = {}) ⇒ Object

Release packages (to rubyforge). This generates the packages, and then distributes them to the file server.



11
12
13
14
# File 'lib/reap/project/release.rb', line 11

def release(options={})
  package(options)
  rubyforge_release(options)
end

#ridoc(options = nil) ⇒ Object

generate local ri docs

Generate RI documentation. This utilizes rdoc to produce the appropriate files.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/reap/project/rdoc.rb', line 130

def ridoc(options=nil)
  options = configure_options(options, 'doc-ri', 'ri')
  options = DEFAULT['ri'].merge(options)

  cmdopts = {}
  cmdopts['op']            = options['output']
  cmdopts['exclude']       = options['exclude']

  output = options['output']
  files  = options['include'] # || ['lib', '[A-Z]*']

  input = files.collect do |i|
    dir?(i) ? File.join(i,'**','*') : i
  end

  if out_of_date?(output, *input) or force?
    rm_r(output) if exist?(output) and safe?(output)  # remove old ridocs

    #input = input.collect{ |i| glob(i) }.flatten
    vector = [input, cmdopts]
    sh "rdoc --ri #{vector.to_console}"
  else
    puts "RI Docs are current."
  end
end

#rollout(options = {}) ⇒ Object

A complete rollout. This will prepare (clean, stamp and package), then document, publish and release, tag and announce. It will do under direction. You can use the –force option to bypass this and have evey action taken automatically.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/reap/project/release.rb', line 21

def rollout(options={})
  if force?
    doc, pub, ann, tag = true, true, true, true
  else
    doc = ask("Generate doumentation?", "Yn").downcase =~ /^(|y|yes)$/i
    pub = ask("Publish website?      ", "Yn") =~ /^(|y|yes)$/i
    tag = ask("Tag current version?  ", "Yn") =~ /^(|y|yes)$/i
    ann = ask("Announce release?     ", "Yn") =~ /^(|y|yes)$/i
    puts
  end

  document(options) if doc
  publish(options)  if pub

  #package(options)
  release(options)
  scm_tag(options)  if tag
  announce(options) if ann
end

#rubyforge(options = nil) ⇒ Object

Returns Rubyforge system object.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/reap/project/rubyforge.rb', line 9

def rubyforge(options = nil)
  options = configure_options(options, 'rubyforge')

  rubyforge_options = {}
  rubyforge_options[:dryrun]   = dryrun?
  rubyforge_options[:trace]    = trace?
  rubyforge_options[:project]  = options['project']  || .project
  rubyforge_options[:username] = options['username'] || ENV['RUBYFORGE_USERNAME']
  rubyforge_options[:group_id] = options['group']
  rubyforge_options[:password] = options['password'] || ENV['RUBYFORGE_PASSWORD']

  unless rubyforge_options[:password]
    abort "Please provide --password=xxxxxxx."
  end

  Rubyforge.new(rubyforge_options)
end

#rubyforge_release(options) ⇒ Object

Release packages to rubyforge.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/reap/project/rubyforge.rb', line 29

def rubyforge_release(options)
  release_options = configure_options(options, 'release')

  store   = 'pkg'
  version = .version

  release_options['version'] = version
  release_options['store']   = store

  changelog = release_options['changelog'] #|| DEFAULT['release']['changelog'] || DEFAULT['rubyforge']['changelog']
  notelog   = release_options['notelog']   #|| DEFAULT['release']['notelog']   || DEFAULT['rubyforge']['notelog']

  changelog = Dir.glob(changelog.to_s, File::FNM_CASEFOLD).first
  notelog   = Dir.glob(notelog.to_s, File::FNM_CASEFOLD).first

  release_options['changelog'] = changelog if File.exist?(changelog)
  release_options['notelog']   = notelog   if File.exist?(notelog)

  files   = release_options['files'] || []

  if files.empty?
    files = Dir[File.join(store, '*')].select do |file|
      /#{version}[.]/ =~ file
    end
    release_options['files'] = files
    #files = Dir.glob(File.join(store,"#{name}-#{version}*"))
  end

  # Not going to do dryrun in Rubyforge class b/c it still requires logging in.
  if dryrun?
    files.each do |file|
      puts "release #{file}"
    end
  else
    host = rubyforge(options)
    host.release(release_options)
  end
end

#scaffold(options) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/reap/project/scaffold.rb', line 7

def scaffold(options)
  requests = options['arguments']

  if requests
    requests.each do |f|
      case f
      #when /^(meta\/)?project(info)?(.yaml|.yml)?$/i
      #  scaffold_projectfile(f)
      when /^rake(file)?$/i
        scaffold_rakefile(f)
      when /^setup[.]rb$/
        scaffold_setup_rb(f)
      when /^(task|script)s?\//i
        scaffold_task(f)
      when /^(task|script)(s)?$/
        scaffold_tasks(f)
      else
        raise "Unknown scaffolding."
      end
    end
  else
    scaffold_skeleton(options=nil)
  end
end

#scaffold_rakefile(fname) ⇒ Object

Add tasks in Rakefile form to project.



44
45
46
47
# File 'lib/reap/project/scaffold.rb', line 44

def scaffold_rakefile(fname)
  from = File.join(data_dir, 'buildset', 'rake', 'Rakefile')
  cp(from, fname) unless File.exist?(fname)
end

#scaffold_setup_rb(fname) ⇒ Object



51
52
53
54
# File 'lib/reap/project/scaffold.rb', line 51

def scaffold_setup_rb(fname)
  from = File.join(data_dir, 'buidset', 'rake', 'setup.rb')
  cp(from, fname) unless File.exist?(fname)
end

#scaffold_skeleton(options = nil) ⇒ Object

Create a project skeleton.

TODO: Improve scaffolding. Make more intelligent.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/reap/project/scaffold.rb', line 74

def scaffold_skeleton(options=nil)
  options = (options || {}).rekey(:to_s)

  files = glob('**/*') - glob('meta/**/*') - ['.reap', 'meta']

  unless files.empty?
    ans = ask("Directory isn't empty. Are you sure you want to add scaffolding?", 'yN')
    case ans.downcase
    when 'y', 'yes'
    else
      abort "Scaffolding aborted."
    end
  end

  #      if options['svn']
  #        if glob('**/*').empty?
  #          mkdir_p('trunk')
  #          mkdir_p('branches')
  #          mkdir_p('tags')
  #          chdir('trunk')
  #        else
  #          abort "Can't create a svn repo unless directory is empty."
  #        end
  #      end

  paths = nil
  dir   = File.join(data_dir, 'base')
  chdir(dir){ paths = Dir['**/*'] }

  dirs  = paths.select{ |f| File.directory?(File.join(dir, f)) }
  files = (paths - dirs).reject{ |f| /[.]svn/ =~ f }

  dirs.each do |dname|
    if File.exist?(dname) and !File.directory?(dname)
      abort "Directory to be created clashes with a prexistent file -- #{dname}"
    end
  end

  dirs.each do |dname|
    mkdir_p(dname) unless File.exist?(dname)
  end

  files.each do |fname|
    next if File.exist?(fname)
    file = File.join(dir, fname)
    if File.extname(file) == '.erb'
      erb = ERB.new(File.read(file))
      txt = erb.result(.get_binding)
      File.open(fname.chomp('.erb'), 'w'){ |f| f << txt }
    else
      cp(file, fname)
    end
  end

  # A little extra love.

  dir = File.join('lib',.name)
  mkdir_p(dir) unless File.exist?(dir)
end

#scaffold_task(fname) ⇒ Object

Add a user tasks to the project.



58
59
60
61
# File 'lib/reap/project/scaffold.rb', line 58

def scaffold_task(fname)
  from = File.join(data_dir, 'buildset', 'tasks', 'task', File.basename(fname))
  cp(from, fname) unless File.exist?(fname)
end

#scaffold_tasks(fname) ⇒ Object

Add all user tasks to the project.



65
66
67
68
# File 'lib/reap/project/scaffold.rb', line 65

def scaffold_tasks(fname)
  dir = File.join(data_dir, 'buildset', 'tasks', 'task')
  cp_r(dir, fname)
end

#scm_branch(*args) ⇒ Object

Branch current version of project. This method routes to the appropriate method for the project’s source control manager.



55
56
57
58
59
60
61
# File 'lib/reap/project/scm.rb', line 55

def scm_branch(*args)
  if File.directory?('.svn')
    svn_branch(*args)
  else
    abort "Only Subversion is currently supported."
  end
end

#scm_log(options = {}) ⇒ Object

Generate ChangeLog. This method routes to the appropriate method for the project’s source control manager.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/reap/project/scm.rb', line 13

def scm_log(options={})
  create_txtlog = (options['txtlog'] != false)
  create_xmllog = (options['xmllog'] != false)

  xmlout = options['xmloutput'] || 'site/log'  # TODO: How to set site/?

  if create_txtlog
    txtlog = apply_naming_policy('changelog', 'txt')
    txtlog = File.join('log', txtlog)
  end

  if create_xmllog
    xmllog = apply_naming_policy('changelog', 'xml')
    xmllog = File.join(xmlout, xmllog)
  end

  #txtlog = File.join('lib', txtlog) unless txtlog.include?('/')
  #xmllog = File.join(xmldir, xmllog) unless xmllog.include?('/')

  if File.directory?('.svn')
    svn_log('txtlog' => txtlog, 'xmllog' => xmllog)
  else
    abort "Only Subversion is currently supported."
  end
end

#scm_tag(*args) ⇒ Object

Tag current versoin of project. This method routes to the appropriate method for the project’s source control manager.



43
44
45
46
47
48
49
# File 'lib/reap/project/scm.rb', line 43

def scm_tag(*args)
  if File.directory?('.svn')
    svn_tag(*args)
  else
    abort "Only Subversion is currently supported."
  end
end

#settingsObject Also known as: configuration

Configuration data.



64
# File 'lib/reap/project.rb', line 64

def settings ; @settings ; end

#site_installObject

Install via project’s install/setup script.

TODO: Remove special reap options from command line.



9
10
11
12
13
14
15
16
# File 'lib/reap/project/site.rb', line 9

def site_install
  script = glob("setup.rb,install.rb,task/setup,task/install").first
  if script
    sh "#{script} #{ARGV.join(' ')}"
  else
    abort "Project needs an install/setup script."
  end
end

#site_uninstallObject

TODO: Create uninstall task.



20
21
22
# File 'lib/reap/project/site.rb', line 20

def site_uninstall
  abort "Not yet implemented."
end

#spec(options = nil) ⇒ Object

Run all specs with basic output.

Options:

specs     File glob(s) of spec files. Defaults to ['spec/**/*_spec.rb', 'spec/**/spec_*.rb'].
loadpath  Paths to add $LOAD_PATH. Defaults to ['lib'].
live      Ignore loadpath, use installed libraries instead. Default is false.
require   Lib(s) to require before excuting specifications.
warning   Whether to show warnings or not. Default is false.
command   Spec command to use. Defaults to 'spec'.
format    Format of RSpec output.
rubyopt   Additional options to pass to the ruby command.
specopt   Additional commandline options for spec command.

– RCOV suppot?

ruby [ruby_opts] -Ilib -S rcov [rcov_opts] bin/spec -- examples [spec_opts]

++



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/reap/project/spec.rb', line 29

def spec(options=nil)
  options = configure_options(options, 'spec')

  #specs   = options['specs']    || DEFAULT['spec']['specs']
  #reqs    = options['require']  || DEFAULT['spec']['require']
  #warning = options['warning']  || DEFAULT['spec']['false']
  #command = options['command']  || DEFAULT['spec']['command']

  specs    = options['specs']
  warning  = options['warning']
  command  = options['command']  || 'spec'
  loadpath = options['loadpath'] || .loadpath
  format   = options['format']
  rubyopt  = options['rubyopt']
  specopt  = options['specopt']
  live     = options['live']

  specs    = list_options(specs) 
  loadpath = list_options(loadpath)
  requires = list_options(requires)

  files = multiglob(*specs)

  if files.empty?
    puts "No specifications."
  else
    #RakeFileUtils.verbose(verbose) do
      # ruby [ruby_opts] -Ilib bin/spec examples [spec_opts]
      cmd = "ruby"
      cmd << " -w" if warning
      cmd << %[ -I"#{loadpath.join(':')}"] unless loadpath.empty?
      cmd << %[ -r"#{requires.join(':')}"] unless requires.empty?
      cmd << rubyopt #.join(" ")
      cmd << " "
      #rb_opts << "-S rcov" if rcov
      #cmd << rcov_option_list
      #cmd << %[ -o "#{rcov_dir}" ] if rcov
      cmd << command
      cmd << " "
      #cmd << "-- " if rcov
      cmd << files.join(' ')
      cmd << " "
      cmd << specopt #.join(' ')
      cmd << " --format #{format}" if format

      puts cmd if verbose?
      unless system(cmd)
        STDERR.puts failure_message if failure_message
        raise("Command #{cmd} failed") if fail_on_error
      end
    #end
  end
end

#spec_doc(options = nil) ⇒ Object

Run all specs with text output



85
86
87
88
89
# File 'lib/reap/project/spec.rb', line 85

def spec_doc(options=nil)
  options ||= {}
  options['format'] = 'specdoc'
  spec(options)
end

#stamp(options = {}) ⇒ Object

Update VERSION stamp file.

This file is either called VERSION, or meta/version (case-insensitive and with optional .txt extension).

The format of the files is:

x.y.z status (date)

For exmaple:

1.2.4 alpha (2008-10-10)

On the command line:

--major    will bump the major number
--minor    will bump the minor number
--tiny     will bump the tiny  number
--teeny    will bump the teeny number

One can alternately specify the entire version:

--version=x.y.z

As well as status:

--status=(alpha, beta, rc1, rc2, ...)

TODO: Should we also update a lib/version.rb file? TODO: Considerding createing a standard status marker (a=alpha, b=beta, r=release candidate)

So a version would read, eg. 1.2.4a, or with status number, eg. 1.2.4r1).


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/reap/project/version.rb', line 37

def stamp(options={})
  version = options['version']
  status  = options['status']

  bumps = [ options['major'], options['minor'], options['tiny'], options['teeny'] ]
  bump  = bumps.any?{|x|x}

  abort "Specify bumps or version, not both." if bump and version

  #options = configure_options(options, 'stamp')

  version = version || .version || '0.0.0'
  status  = status  || .status  || '0.0.0'

  if bump
    points = []
    version.to_s.split(/[.]/).each_with_index do |x, i|
      points[i] = (bumps[i] ? x.to_i + 1 : x).to_s
    end
    version = points.join('.').sub(/[.0]$/,'')
  else
    abort "Invalid version -- #{version}" unless String===version && /^[0-9]/ =~ version
  end

  meta = File.directory?('meta')

  file = glob('{,meta/}version{,.txt}', File::FNM_CASEFOLD).first
  file = (meta ? 'meta/version' : 'VERSION') unless file

  text = "#{version} #{status} (#{Time.now.strftime('%Y-%m-%d')})"

  if File.exist?(file)
    old_text = File.read(file).strip
    old_version, old_status, old_date = *old_text.split(/\s/)
    if old_version == "#{version}" && old_status == status
      puts old_text
      return
    end
  end

  if dryrun?
    puts "echo '#{text}' > #{file}"
  else
    write(file, text)
    puts text
    puts "#{file} updated."

    .version  = version
    .status   = status
    .released = Time.now
  end

  # TODO: Stamp .roll if roll file exists.
  # should we read current .roll file and use as defaults?
  if File.exist?('.roll')
    str = []
    str << "name    = #{.name}"
    str << "version = #{.version}"
    str << "status  = #{.status}"
    str << "date    = #{.date}"
    str << "default = #{.default}"
    str << "libpath = #{.libpath}"
    # File.open('.roll','w'){ |f| f << str.join("\n") }
  end
end

#stats(options = nil) ⇒ Object

Simple code count analysis.

Scan source code counting files, lines of code and comments and presents a report of it’s findings.

loadpath   Path to include in analysis. The default
           is the project's loadpath.

exclude    File globs to exclude from analysis. Default
           is 'ext' b/c this does not yet support C analysis.

TODO: Add C support for ext/.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/reap/project/stats.rb', line 22

def stats(options=nil)
  options = configure_options(options, 'stats')

  loadpath = options['loadpath'] || .loadpath
  exclude  = options['exclude']  || ['ext']

  loadpath = list_option(loadpath)
  exclude  = list_option(exclude)

  files = multiglob_r(*loadpath) - multiglob_r(*exclude)

  #() #.inject([]){ |memo, find| memo.concat(glob(find)); memo }
  #Dir.multiglob_with_default(DEFAULT_STATS_FILES)

  fc, l, c, r, t, s = *line_count(*files)

  fct, lt, ct, rt, tt, st = *([0]*6)
  if File.directory?('test')
    fct, lt, ct, rt, tt, st = *line_count('test/**/*')
    t = lt if lt > 0
  end

  rat = lambda do |d,n|
    if d > n and n != 0
      "%.1f" % [ d.to_f / n ]
    elsif n > d and d != 0
      "-" #"%.1f:1" % [ n.to_f / d ]
    elsif d == 0 or n == 0
      "-"
    else
      "1.0"
    end
  end

  per = lambda do |n,d|
    if d != 0
      (((n.to_f / d)*100).to_i).to_s + "%"
    else
      "-"
    end
  end

  max = l.to_s.size + 4

  puts
  #puts "FILES:"
  #puts "  source: #{fc}"
  #puts "  test  : #{fct}"
  #puts "  total : #{fc+fct}"
  #puts
  #puts "LINES:"
  #puts "  code  : %#{max}s   %4s" % [ c.to_s, per[c,l] ]
  #puts "  docs  : %#{max}s   %4s" % [ r.to_s, per[r,l] ]
  #puts "  space : %#{max}s   %4s" % [ s.to_s, per[s,l] ]
  #puts "  test  : %#{max}s   %4s" % [ t.to_s, per[t,l] ]
  #puts "  total : %#{max}s   %4s" % [ l.to_s, per[l,l] ]
  #puts
  #puts "Ratio to 1 :"
  #puts "  code to test : #{rat[c,t]} #{per[c,t]}"

  head = ["Total", "Code", "-%-", "Docs", "-%-", "Blank", "-%-", "Files"]
  prod = [l.to_s, c.to_s, per[c,l], r.to_s, per[r,l], s.to_s, per[s,l], fc]
  test = [lt.to_s, ct.to_s, per[ct,l], rt.to_s, per[rt,l], st.to_s, per[st,l], fct]
  totl = [(l+lt), (c+ct), per[c+ct,l+lt], (r+rt), per[r+rt,l+lt], (s+st), per[s+st,l+lt], (fc+fct)]

  puts "TYPE    %#{max}s %#{max}s %4s %#{max}s %4s %#{max}s %4s %#{max}s" % head
  puts "Source  %#{max}s %#{max}s %4s %#{max}s %4s %#{max}s %4s %#{max}s" % prod
  puts "Test    %#{max}s %#{max}s %4s %#{max}s %4s %#{max}s %4s %#{max}s" % test
  puts "Total   %#{max}s %#{max}s %4s %#{max}s %4s %#{max}s %4s %#{max}s" % totl
  puts
  puts "RATIO     Code    Docs    Blank   Test   Total"
  puts "Code   %7s %7s %7s %7s %7s" % [ rat[c,c], rat[c,r], rat[c,s], rat[c,t], rat[c,l] ]
  puts "Docs   %7s %7s %7s %7s %7s" % [ rat[r,c], rat[r,r], rat[r,s], rat[r,t], rat[r,l] ]
  puts "Blank  %7s %7s %7s %7s %7s" % [ rat[s,c], rat[s,r], rat[s,s], rat[s,t], rat[s,l] ]
  puts "Test   %7s %7s %7s %7s %7s" % [ rat[t,c], rat[t,r], rat[t,s], rat[t,t], rat[t,l] ]
  puts "Total  %7s %7s %7s %7s %7s" % [ rat[l,c], rat[l,r], rat[l,s], rat[l,t], rat[l,l] ]
  puts
end

#svn_branch(options = nil) ⇒ Object

Branch current version.

message       Optional commit message. This is intended for commandline
              usage. (Use -m for shorthand).

TODO: How should metadata.repository come into play here?



14
15
16
17
# File 'lib/reap/project/svn.rb', line 14

def svn_branch(options=nil)
  options = configure_options(options, 'scm-branch', 'scm')
  svn_system.branch(options)
end

#svn_log(options = nil) ⇒ Object

Create changelog.

change     File path to store rdoc formated changelog. Default is 'log/changelog.txt'.
xmlchange  File path to store XML formated changelog. Default is 'doc/log/changelog.xml'.

Set either to false to supress creation.



38
39
40
41
42
43
44
# File 'lib/reap/project/svn.rb', line 38

def svn_log(options=nil)
  txtlog = options['txtlog']
  xmllog = options['xmllog']

  svn_system.log(txtlog)
  svn_system.log_xml(xmllog) if xmllog
end

#svn_tag(options = nil) ⇒ Object

Tag current version.

message       Optional commit message. This is intended for commandline
              usage. (Use -m for shorthand).

TODO: How should metadata.repository come into play here?



26
27
28
29
# File 'lib/reap/project/svn.rb', line 26

def svn_tag(options=nil)
  options = configure_options(options, 'scm-tag', 'scm')
  svn_system.tag(options)
end

#test_cross(options = nil) ⇒ Object

Run cross comparison testing.

This tool runs unit tests in pairs to make sure there is cross library compatibility. Each pari is run in a separate interpretor to prevent script clash. This makes for a more robust test facility and prevents potential conflicts between test scripts.

tests     Test files (eg. test/tc_**/*.rb) [test/**/*]
loadpath  Directories to include in load path.
require   List of files to require prior to running tests.
live      Deactive use of local libs and test against install.


198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/reap/project/test.rb', line 198

def test_cross(options=nil)
  options = test_configuration(options)

  tests    = options['tests']
  loadpath = options['loadpath']
  requires = options['requires']
  live     = options['live']
  exclude  = options['exclude']
  log      = options['log'] != false

  files = multiglob_r(*tests) - multiglob_r(exclude)

  return puts("No tests.") if files.empty?

  files = files.select{ |f| File.extname(f) == '.rb' and File.file?(f) }
  width = files.collect{ |f| f.size }.max
  pairs = files.inject([]){ |m, f| files.collect{ |g| m << [f,g] }; m }

  make if compiles?

  cmd   = %[ruby -I#{loadpath.join(':')} -e"load('./%s'); load('%s')"]
  dis   = "%-#{width}s %-#{width}s"

  testruns = pairs.collect do |pair|
    { 'file'    => pair,
      'command' => cmd % pair,
      'display' => dis % pair
    }
  end

  report = test_loop_runner(testruns)

  puts report

  if log && !dryrun?
    logfile = File.join('log', apply_naming_policy('testlog', 'txt'))
    File.open(logfile, 'a') do |f| 
      f << "= Cross Test @ #{Time.now}\n"
      f << report
      f << "\n"
    end
  end
end

#test_load(options = nil) ⇒ Object

Load each test independently to ensure there are no require dependency issues. This is actually a bit redundant as test-solo will also cover these results. So we may deprecate this in the future. This does not generate a test log entry.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/reap/project/test.rb', line 93

def test_load(options=nil)
  options = test_configuration(options)

  tests    = options['tests']
  loadpath = options['loadpath']
  requires = options['requires']
  live     = options['live']
  exclude  = options['exclude']

  files = multiglob_r(*tests) - multiglob_r(*exclude)

  return puts("No tests.") if files.empty?

  max   = files.collect{ |f| f.size }.max
  list  = []

  files.each do |f|
    next unless File.file?(f)
    if r = system("ruby -I#{loadpath.join(':')} #{f} > /dev/null 2>&1")
      puts "%-#{max}s  [PASS]" % [f]  #if verbose?
    else
      puts "%-#{max}s  [FAIL]" % [f]  #if verbose?
      list << f
    end
  end

  puts "  #{list.size} Load Failures"

  if verbose?
    unless list.empty?
      puts "\n-- Load Failures --\n"
      list.each do |f|
        print "* "
        system "ruby -I#{loadpath} #{f} 2>&1"
        #puts
      end
      puts
    end
  end
end

#test_solo(options = nil) ⇒ Object

Run unit-tests. Each test is run in a separate interpretor to prevent script clash. This makes for a more robust test facility and prevents potential conflicts between test scripts.

tests     Test files (eg. test/tc_**/*.rb) [test/**/*]
loadpath  Directories to include in load path [lib].
require   List of files to require prior to running tests.
live      Deactive use of local libs and test against install.


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/reap/project/test.rb', line 143

def test_solo(options=nil)
  options = test_configuration(options)

  tests    = options['tests']
  loadpath = options['loadpath']
  requires = options['requires']
  live     = options['live']
  exclude  = options['exclude']
  log      = options['log'] != false

  files = multiglob_r(*tests) - multiglob_r(*exclude)

  return puts("No tests.") if files.empty?

  files = files.select{ |f| File.extname(f) == '.rb' and File.file?(f) }
  width = files.collect{ |f| f.size }.max

  make if compiles?

  cmd   = %[ruby -I#{loadpath.join(':')} %s]
  dis   = "%-#{width}s"

  testruns = files.collect do |file|
    { 'files'   => file,
      'command' => cmd % file,
      'display' => dis % file
    }
  end

  report = test_loop_runner(testruns)

  puts report

  if log && !dryrun?
    logfile = File.join('log', apply_naming_policy('testlog', 'txt'))
    File.open(logfile, 'a') do |f|
      f << "= Solo Test @ #{Time.now}\n"
      f << report
      f << "\n"
    end
  end
end

#test_unit(options = {}) ⇒ Object

Run unit tests. Unlike test-solo and test-cross this loads all tests and runs them together in a single process.

Note that this shells out to the testrb program.

TODO: Generate a test log entry?



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/reap/project/test.rb', line 40

def test_unit(options={})
  options = test_configuration(options)

  tests    = options['tests']
  loadpath = options['loadpath']
  requires = options['requires']
  live     = options['live']
  exclude  = options['exclude']
  #log      = options['log'] != false
  #logfile  = File.join('log', apply_naming_policy('test', 'log'))

  # what about arguments for selecting specific tests?
  tests = options['arguments'] if options['arguments']

  #unless live
  #  loadpath.each do |lp|
  #    $LOAD_PATH.unshift(File.expand_path(lp))
  #  end
  #end

  if File.exist?('test/suite.rb')
    files = 'test/suite.rb'
  else
    files = multiglob_r(*tests)
  end

  if files.empty?
    $stderr.puts "No tests."
    return
  end

  filelist = files.select{|file| !File.directory?(file) }.join(' ')

  if live
    command  = %[testrb #{filelist} 2>&1]
  else
    command  = %[testrb -I#{loadpath.join(':')} #{filelist} 2>&1]
  end

  sh command

  #if log && !dryrun?
  #  command = %[testrb -I#{loadpath} #{filelist} > #{logfile} 2>&1]  # /dev/null 2>&1
  #  system command
  #  puts "Updated #{logfile}"
  #end
end

#trace=(x) ⇒ Object



82
# File 'lib/reap/project.rb', line 82

def trace=(x)   ; options['trace']   = x ; end

#trace?Boolean

Returns:

  • (Boolean)


76
# File 'lib/reap/project.rb', line 76

def trace?      ; options['trace']   ; end

#unfold_paragraphs(string) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/reap/project/announce.rb', line 173

def unfold_paragraphs(string)
  blank = false
  text  = ''
  string.split(/\n/).each do |line|
    if /\S/ !~ line
      text << "\n\n"
      blank = true
    else
      if /^(\s+|[*])/ =~ line 
        text << (line.rstrip + "\n")
      else
        text << (line.rstrip + " ")
      end
      blank = false
    end
  end
  return text
end

#verbose=(x) ⇒ Object



84
# File 'lib/reap/project.rb', line 84

def verbose=(x) ; options['verbose'] = x ; end

#verbose?Boolean

Returns:

  • (Boolean)


78
# File 'lib/reap/project.rb', line 78

def verbose?    ; options['verbose'] ; end