linguistics 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1,22 +1,24 @@
1
- #!rake
1
+ #!rake -*- ruby -*-
2
2
  #
3
3
  # Linguistics rakefile
4
4
  #
5
5
  # Based on various other Rakefiles, especially one by Ben Bleything
6
6
  #
7
- # Copyright (c) 2007-2009 The FaerieMUD Consortium
7
+ # Copyright (c) 2007-2011 The FaerieMUD Consortium
8
8
  #
9
9
  # Authors:
10
10
  # * Michael Granger <ged@FaerieMUD.org>
11
11
  #
12
12
 
13
13
  BEGIN {
14
+ require 'rbconfig'
14
15
  require 'pathname'
15
16
  basedir = Pathname.new( __FILE__ ).dirname
16
17
 
17
18
  libdir = basedir + "lib"
18
- extdir = basedir + "ext"
19
+ extdir = libdir + Config::CONFIG['sitearch']
19
20
 
21
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
20
22
  $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
21
23
  $LOAD_PATH.unshift( extdir.to_s ) unless $LOAD_PATH.include?( extdir.to_s )
22
24
  }
@@ -32,6 +34,15 @@ rescue LoadError
32
34
  end
33
35
  end
34
36
 
37
+ begin
38
+ require 'rubygems'
39
+ rescue LoadError
40
+ module Gem
41
+ class Specification; end
42
+ end
43
+ end
44
+
45
+ require 'pathname'
35
46
  require 'rbconfig'
36
47
  require 'rake'
37
48
  require 'rake/testtask'
@@ -65,10 +76,10 @@ if VERSION_FILE.exist? && buildrev = ENV['CC_BUILD_LABEL']
65
76
  PKG_VERSION = VERSION_FILE.read[ /VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, 1 ] + '.' + buildrev
66
77
  elsif VERSION_FILE.exist?
67
78
  PKG_VERSION = VERSION_FILE.read[ /VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, 1 ]
68
- else
69
- PKG_VERSION = '0.0.0'
70
79
  end
71
80
 
81
+ PKG_VERSION = '0.0.0' unless defined?( PKG_VERSION ) && !PKG_VERSION.nil?
82
+
72
83
  PKG_FILE_NAME = "#{PKG_NAME.downcase}-#{PKG_VERSION}"
73
84
  GEM_FILE_NAME = "#{PKG_FILE_NAME}.gem"
74
85
 
@@ -82,7 +93,7 @@ EXTCONF = EXTDIR + 'extconf.rb'
82
93
 
83
94
  ARTIFACTS_DIR = Pathname.new( CC_BUILD_ARTIFACTS )
84
95
 
85
- TEXT_FILES = Rake::FileList.new( %w[Rakefile ChangeLog README LICENSE] )
96
+ TEXT_FILES = Rake::FileList.new( %w[Rakefile ChangeLog README* LICENSE] )
86
97
  BIN_FILES = Rake::FileList.new( "#{BINDIR}/*" )
87
98
  LIB_FILES = Rake::FileList.new( "#{LIBDIR}/**/*.rb" )
88
99
  EXT_FILES = Rake::FileList.new( "#{EXTDIR}/**/*.{c,h,rb}" )
@@ -121,6 +132,10 @@ RELEASE_FILES = TEXT_FILES +
121
132
 
122
133
  RELEASE_FILES << LOCAL_RAKEFILE.to_s if LOCAL_RAKEFILE.exist?
123
134
 
135
+ RELEASE_ANNOUNCE_ADDRESSES = [
136
+ "Ruby-Talk List <ruby-talk@tuby-lang.org>",
137
+ ]
138
+
124
139
  COVERAGE_MINIMUM = ENV['COVERAGE_MINIMUM'] ? Float( ENV['COVERAGE_MINIMUM'] ) : 85.0
125
140
  RCOV_EXCLUDES = 'spec,tests,/Library/Ruby,/var/lib,/usr/local/lib'
126
141
  RCOV_OPTS = [
@@ -140,7 +155,7 @@ if !RAKE_TASKDIR.exist?
140
155
 
141
156
  if ans =~ /^y/i
142
157
  $stderr.puts "Okay, fetching #{RAKE_TASKLIBS_URL} into #{RAKE_TASKDIR}..."
143
- system 'hg', 'clone', RAKE_TASKLIBS_URL, RAKE_TASKDIR
158
+ system 'hg', 'clone', RAKE_TASKLIBS_URL, "./#{RAKE_TASKDIR}"
144
159
  if ! $?.success?
145
160
  fail "Damn. That didn't work. Giving up; maybe try manually fetching?"
146
161
  end
@@ -153,30 +168,40 @@ if !RAKE_TASKDIR.exist?
153
168
  end
154
169
 
155
170
  require RAKE_TASKDIR + 'helpers.rb'
171
+ include RakefileHelpers
156
172
 
157
- # Define some constants that depend on the 'svn' tasklib
173
+ # Set the build ID if the mercurial executable is available
158
174
  if hg = which( 'hg' )
159
- id = IO.read('|-') or exec hg.to_s, 'id', '-n'
160
- PKG_BUILD = id.chomp[ /^[[:xdigit:]]+/ ]
175
+ id = `#{hg} id -n`.chomp
176
+ PKG_BUILD = (id.chomp[ /^[[:xdigit:]]+/ ] || '1')
161
177
  else
162
- PKG_BUILD = 0
178
+ PKG_BUILD = '0'
163
179
  end
164
180
  SNAPSHOT_PKG_NAME = "#{PKG_FILE_NAME}.#{PKG_BUILD}"
165
181
  SNAPSHOT_GEM_NAME = "#{SNAPSHOT_PKG_NAME}.gem"
166
182
 
167
183
  # Documentation constants
168
- RDOCDIR = DOCSDIR + 'api'
184
+ API_DOCSDIR = DOCSDIR + 'api'
185
+ README_FILE = TEXT_FILES.find {|path| path =~ /^README/ } || 'README'
169
186
  RDOC_OPTIONS = [
170
- '-w', '4',
171
- '-HN',
172
- '-i', '.',
173
- '-m', 'README',
174
- '-t', PKG_NAME,
175
- '-W', 'http://deveiate.org/projects/Linguistics/browser/'
187
+ '--tab-width=4',
188
+ '--show-hash',
189
+ '--include', BASEDIR.to_s,
190
+ "--main=#{README_FILE}",
191
+ "--title=#{PKG_NAME}",
192
+ ]
193
+ YARD_OPTIONS = [
194
+ '--use-cache',
195
+ '--protected',
196
+ '-r', README_FILE,
197
+ '--exclude', 'extconf\\.rb',
198
+ '--files', 'ChangeLog,LICENSE',
199
+ '--output-dir', API_DOCSDIR.to_s,
200
+ '--title', "#{PKG_NAME} #{PKG_VERSION}",
176
201
  ]
177
202
 
178
203
  # Release constants
179
- SMTP_HOST = 'mail.faeriemud.org'
204
+ SMTP_HOST = "mail.faeriemud.org"
180
205
  SMTP_PORT = 465 # SMTP + SSL
181
206
 
182
207
  # Project constants
@@ -186,9 +211,7 @@ PROJECT_DOCDIR = "#{PROJECT_PUBDIR}/#{PKG_NAME}"
186
211
  PROJECT_SCPPUBURL = "#{PROJECT_HOST}:#{PROJECT_PUBDIR}"
187
212
  PROJECT_SCPDOCURL = "#{PROJECT_HOST}:#{PROJECT_DOCDIR}"
188
213
 
189
- # Rubyforge stuff
190
- RUBYFORGE_GROUP = 'deveiate'
191
- RUBYFORGE_PROJECT = 'linguistics'
214
+ GEM_PUBHOST = 'rubygems.org'
192
215
 
193
216
  # Gem dependencies: gemname => version
194
217
  DEPENDENCIES = {
@@ -196,17 +219,14 @@ DEPENDENCIES = {
196
219
 
197
220
  # Developer Gem dependencies: gemname => version
198
221
  DEVELOPMENT_DEPENDENCIES = {
199
- 'rake' => '>= 0.8.7',
200
- 'rcodetools' => '>= 0.7.0.0',
201
- 'rcov' => '>= 0.8.1.2.0',
202
- 'rdoc' => '>= 2.4.3',
203
- 'RedCloth' => '>= 4.0.3',
204
- 'rspec' => '>= 1.2.6',
205
- 'rubyforge' => '>= 0',
206
- 'termios' => '>= 0',
207
- 'text-format' => '>= 1.0.0',
208
- 'tmail' => '>= 1.2.3.1',
209
- 'diff-lcs' => '>= 1.1.2',
222
+ 'rake' => '~> 0.8.7',
223
+ 'rcodetools' => '~> 0.7.0.0',
224
+ 'rcov' => '~> 0.8.1.2.0',
225
+ 'RedCloth' => '~> 4.2.3',
226
+ 'rspec' => '~> 1.2.6',
227
+ 'ruby-termios' => '~> 0.9.6',
228
+ 'text-format' => '~> 1.0.0',
229
+ 'tmail' => '~> 1.2.3.1',
210
230
  'wordnet' => '>=0.0.5',
211
231
  'linkparser' => '>=1.0.3',
212
232
  }
@@ -227,16 +247,14 @@ GEMSPEC = Gem::Specification.new do |gem|
227
247
  "contains various English-language utilities.",
228
248
  ].join( "\n" )
229
249
 
230
- gem.authors = "Michael Granger"
250
+ gem.authors = ["Michael Granger"]
231
251
  gem.email = ["ged@FaerieMUD.org"]
232
252
  gem.homepage = 'http://deveiate.org/projects/Linguistics/'
233
-
234
- # Apparently this isn't actually the 'project'?
235
- gem.rubyforge_project = RUBYFORGE_GROUP
253
+ gem.licenses = ["BSD"]
236
254
 
237
255
  gem.has_rdoc = true
238
256
  gem.rdoc_options = RDOC_OPTIONS
239
- gem.extra_rdoc_files = %w[ChangeLog README LICENSE]
257
+ gem.extra_rdoc_files = TEXT_FILES - [ 'Rakefile' ]
240
258
 
241
259
  gem.bindir = BINDIR.relative_path_from(BASEDIR).to_s
242
260
  gem.executables = BIN_FILES.select {|pn| File.executable?(pn) }.
@@ -250,6 +268,13 @@ GEMSPEC = Gem::Specification.new do |gem|
250
268
  gem.files = RELEASE_FILES
251
269
  gem.test_files = SPEC_FILES
252
270
 
271
+ # signing key and certificate chain
272
+ gem.signing_key = '/Volumes/Keys/ged-private_gem_key.pem'
273
+ gem.cert_chain = [File.expand_path('~/.gem/ged-public_gem_cert.pem')]
274
+
275
+
276
+ gem.required_ruby_version = '>= 1.8.7'
277
+
253
278
  DEPENDENCIES.each do |name, version|
254
279
  version = '>= 0' if version.length.zero?
255
280
  gem.add_runtime_dependency( name, version )
@@ -290,14 +315,14 @@ import LOCAL_RAKEFILE if LOCAL_RAKEFILE.exist?
290
315
  #####################################################################
291
316
 
292
317
  ### Default task
293
- task :default => [:clean, :local, :spec, :rdoc, :package]
318
+ task :default => [:clean, :local, :spec, :apidocs, :package]
294
319
 
295
320
  ### Task the local Rakefile can append to -- no-op by default
296
321
  task :local
297
322
 
298
323
  ### Task: clean
299
- CLEAN.include 'coverage'
300
- CLOBBER.include 'artifacts', 'coverage.info', PKGDIR
324
+ CLEAN.include 'coverage', '**/*.orig', '**/*.rej'
325
+ CLOBBER.include 'artifacts', 'coverage.info', 'ChangeLog', PKGDIR
301
326
 
302
327
  ### Task: changelog
303
328
  file 'ChangeLog' do |task|
data/lib/linguistics.rb CHANGED
@@ -26,7 +26,7 @@ module Linguistics
26
26
  ### Class constants
27
27
 
28
28
  # Release version
29
- VERSION = '1.0.8'
29
+ VERSION = '1.0.9'
30
30
 
31
31
  # Language module implementors should do something like:
32
32
  # Linguistics::DefaultLanguages.push( :ja ) # or whatever
@@ -1366,7 +1366,7 @@ module Linguistics::EN
1366
1366
 
1367
1367
  ### Transform the given +number+ into an ordinate word.
1368
1368
  def ordinate( number )
1369
- numwords( number ).ordinal
1369
+ return Linguistics::EN.ordinal( Linguistics::EN.numwords(number) )
1370
1370
  end
1371
1371
 
1372
1372
 
@@ -74,7 +74,7 @@ require 'linguistics/en'
74
74
  #
75
75
  # == Version
76
76
  #
77
- # $Id$
77
+ # $Id: wordnet.rb,v 2640c845eb5c 2009/11/17 16:59:25 ged $
78
78
  #
79
79
  module Linguistics::EN
80
80
 
@@ -0,0 +1,123 @@
1
+ #
2
+ # Documentation Rake tasks
3
+ #
4
+
5
+ require 'rake/clean'
6
+
7
+
8
+ # Append docs/lib to the load path if it exists for documentation
9
+ # helpers.
10
+ DOCSLIB = DOCSDIR + 'lib'
11
+ $LOAD_PATH.unshift( DOCSLIB.to_s ) if DOCSLIB.exist?
12
+
13
+ # Make relative string paths of all the stuff we need to generate docs for
14
+ DOCFILES = Rake::FileList[ LIB_FILES + EXT_FILES + GEMSPEC.extra_rdoc_files ]
15
+
16
+ # Documentation coverage constants
17
+ COVERAGE_DIR = BASEDIR + 'coverage'
18
+ COVERAGE_REPORT = COVERAGE_DIR + 'documentation.txt'
19
+
20
+
21
+ # Prefer YARD, fallback to RDoc
22
+ begin
23
+ require 'yard'
24
+ require 'yard/rake/yardoc_task'
25
+
26
+ # Undo the lazy-assed monkeypatch yard/globals.rb installs and
27
+ # re-install them as mixins as they should have been from the
28
+ # start
29
+ # <metamonkeypatch>
30
+ class Object
31
+ remove_method :log
32
+ remove_method :P
33
+ end
34
+
35
+ module YardGlobals
36
+ def P(namespace, name = nil)
37
+ namespace, name = nil, namespace if name.nil?
38
+ YARD::Registry.resolve(namespace, name, false, true)
39
+ end
40
+
41
+ def log
42
+ YARD::Logger.instance
43
+ end
44
+ end
45
+
46
+ class YARD::CLI::Base; include YardGlobals; end
47
+ class YARD::CLI::Command; include YardGlobals; end
48
+ class YARD::Parser::SourceParser; extend YardGlobals; include YardGlobals; end
49
+ class YARD::Parser::CParser; include YardGlobals; end
50
+ class YARD::CodeObjects::Base; include YardGlobals; end
51
+ class YARD::Handlers::Base; include YardGlobals; end
52
+ class YARD::Handlers::Processor; include YardGlobals; end
53
+ class YARD::Serializers::Base; include YardGlobals; end
54
+ class YARD::RegistryStore; include YardGlobals; end
55
+ class YARD::Docstring; include YardGlobals; end
56
+ module YARD::Templates::Helpers::ModuleHelper; include YardGlobals; end
57
+ module YARD::Templates::Helpers::HtmlHelper; include YardGlobals; end
58
+
59
+ if vvec(RUBY_VERSION) >= vvec("1.9.1")
60
+ # Monkeypatched to allow more than two '#' characters at the beginning
61
+ # of the comment line.
62
+ # Patched from yard-0.5.8
63
+ require 'yard/parser/ruby/ruby_parser'
64
+ class YARD::Parser::Ruby::RipperParser < Ripper
65
+ def on_comment(comment)
66
+ visit_ns_token(:comment, comment)
67
+ case comment
68
+ when /\A# @group\s+(.+)\s*\Z/
69
+ @groups.unshift [lineno, $1]
70
+ return
71
+ when /\A# @endgroup\s*\Z/
72
+ @groups.unshift [lineno, nil]
73
+ return
74
+ end
75
+
76
+ comment = comment.gsub(/^\#+\s{0,1}/, '').chomp
77
+ append_comment = @comments[lineno - 1]
78
+
79
+ if append_comment && @comments_last_column == column
80
+ @comments.delete(lineno - 1)
81
+ comment = append_comment + "\n" + comment
82
+ end
83
+
84
+ @comments[lineno] = comment
85
+ @comments_last_column = column
86
+ end
87
+ end # class YARD::Parser::Ruby::RipperParser
88
+ end
89
+
90
+ # </metamonkeypatch>
91
+
92
+ YARD_OPTIONS = [] unless defined?( YARD_OPTIONS )
93
+
94
+ yardoctask = YARD::Rake::YardocTask.new( :apidocs ) do |task|
95
+ task.files = DOCFILES
96
+ task.options = YARD_OPTIONS
97
+ task.options << '--debug' << '--verbose' if $trace
98
+ end
99
+ yardoctask.before = lambda {
100
+ trace "Calling yardoc like:",
101
+ " yardoc %s" % [ quotelist(yardoctask.options + yardoctask.files).join(' ') ]
102
+ }
103
+
104
+ YARDOC_CACHE = BASEDIR + '.yardoc'
105
+ CLOBBER.include( YARDOC_CACHE.to_s )
106
+
107
+ rescue LoadError
108
+ require 'rdoc/task'
109
+
110
+ desc "Build API documentation in #{API_DOCSDIR}"
111
+ RDoc::Task.new( :apidocs ) do |task|
112
+ task.main = "README"
113
+ task.rdoc_files.include( DOCFILES )
114
+ task.rdoc_dir = API_DOCSDIR.to_s
115
+ task.options = RDOC_OPTIONS
116
+ end
117
+ end
118
+
119
+ # Need the DOCFILES to exist to build the API docs
120
+ task :apidocs => DOCFILES
121
+
122
+ CLEAN.include( API_DOCSDIR.to_s )
123
+
data/rake/helpers.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+
2
3
  #####################################################################
3
4
  ### G L O B A L H E L P E R F U N C T I O N S
4
5
  #####################################################################
@@ -6,7 +7,6 @@
6
7
 
7
8
  require 'pathname'
8
9
  require 'uri'
9
- require 'open3'
10
10
 
11
11
  begin
12
12
  require 'readline'
@@ -19,416 +19,484 @@ rescue LoadError
19
19
  end
20
20
  end
21
21
 
22
- # Set some ANSI escape code constants (Shamelessly stolen from Perl's
23
- # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
24
- ANSI_ATTRIBUTES = {
25
- 'clear' => 0,
26
- 'reset' => 0,
27
- 'bold' => 1,
28
- 'dark' => 2,
29
- 'underline' => 4,
30
- 'underscore' => 4,
31
- 'blink' => 5,
32
- 'reverse' => 7,
33
- 'concealed' => 8,
34
-
35
- 'black' => 30, 'on_black' => 40,
36
- 'red' => 31, 'on_red' => 41,
37
- 'green' => 32, 'on_green' => 42,
38
- 'yellow' => 33, 'on_yellow' => 43,
39
- 'blue' => 34, 'on_blue' => 44,
40
- 'magenta' => 35, 'on_magenta' => 45,
41
- 'cyan' => 36, 'on_cyan' => 46,
42
- 'white' => 37, 'on_white' => 47
43
- }
44
-
45
-
46
- MULTILINE_PROMPT = <<-'EOF'
47
- Enter one or more values for '%s'.
48
- A blank line finishes input.
49
- EOF
50
-
51
-
52
- CLEAR_TO_EOL = "\e[K"
53
- CLEAR_CURRENT_LINE = "\e[2K"
54
-
55
-
56
- ### Output a logging message
57
- def log( *msg )
58
- output = colorize( msg.flatten.join(' '), 'cyan' )
59
- $stderr.puts( output )
60
- end
22
+ module RakefileHelpers
23
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
24
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
25
+ ANSI_ATTRIBUTES = {
26
+ 'clear' => 0,
27
+ 'reset' => 0,
28
+ 'bold' => 1,
29
+ 'dark' => 2,
30
+ 'underline' => 4,
31
+ 'underscore' => 4,
32
+ 'blink' => 5,
33
+ 'reverse' => 7,
34
+ 'concealed' => 8,
61
35
 
36
+ 'black' => 30, 'on_black' => 40,
37
+ 'red' => 31, 'on_red' => 41,
38
+ 'green' => 32, 'on_green' => 42,
39
+ 'yellow' => 33, 'on_yellow' => 43,
40
+ 'blue' => 34, 'on_blue' => 44,
41
+ 'magenta' => 35, 'on_magenta' => 45,
42
+ 'cyan' => 36, 'on_cyan' => 46,
43
+ 'white' => 37, 'on_white' => 47
44
+ }
62
45
 
63
- ### Output a logging message if tracing is on
64
- def trace( *msg )
65
- return unless $trace
66
- output = colorize( msg.flatten.join(' '), 'yellow' )
67
- $stderr.puts( output )
68
- end
69
46
 
47
+ MULTILINE_PROMPT = <<-'EOF'
48
+ Enter one or more values for '%s'.
49
+ A blank line finishes input.
50
+ EOF
70
51
 
71
- ### Return the specified args as a string, quoting any that have a space.
72
- def quotelist( *args )
73
- return args.flatten.collect {|part| part =~ /\s/ ? part.inspect : part}
74
- end
75
52
 
53
+ CLEAR_TO_EOL = "\e[K"
54
+ CLEAR_CURRENT_LINE = "\e[2K"
55
+
56
+
57
+ TAR_OPTS = { :compression => :gzip }
58
+
59
+
60
+ ###############
61
+ module_function
62
+ ###############
63
+
64
+ ### Output a logging message
65
+ def log( *msg )
66
+ output = colorize( msg.flatten.join(' '), 'cyan' )
67
+ $stderr.puts( output )
68
+ end
69
+
70
+
71
+ ### Output a logging message if tracing is on
72
+ def trace( *msg )
73
+ return unless $trace
74
+ output = colorize( msg.flatten.join(' '), 'yellow' )
75
+ $stderr.puts( output )
76
+ end
76
77
 
77
- ### Run the specified command +cmd+ with system(), failing if the execution
78
- ### fails.
79
- def run( *cmd )
80
- cmd.flatten!
81
78
 
82
- if cmd.length > 1
83
- trace( quotelist(*cmd) )
84
- else
85
- trace( cmd )
79
+ ### Return the specified args as a string, quoting any that have a space.
80
+ def quotelist( *args )
81
+ return args.flatten.collect {|part| part =~ /\s/ ? part.inspect : part}
86
82
  end
87
83
 
88
- if $dryrun
89
- $stderr.puts "(dry run mode)"
90
- else
91
- system( *cmd )
92
- unless $?.success?
93
- fail "Command failed: [%s]" % [cmd.join(' ')]
84
+
85
+ ### Run the specified command +cmd+ with system(), failing if the execution
86
+ ### fails.
87
+ def run( *cmd )
88
+ cmd.flatten!
89
+
90
+ if cmd.length > 1
91
+ trace( quotelist(*cmd) )
92
+ else
93
+ trace( cmd )
94
+ end
95
+
96
+ if $dryrun
97
+ $stderr.puts "(dry run mode)"
98
+ else
99
+ system( *cmd )
100
+ unless $?.success?
101
+ fail "Command failed: [%s]" % [cmd.join(' ')]
102
+ end
94
103
  end
95
104
  end
96
- end
97
105
 
98
106
 
99
- ### Run the given +cmd+ with the specified +args+ without interpolation by the shell and
100
- ### return anything written to its STDOUT.
101
- def read_command_output( cmd, *args )
102
- trace "Reading output from: %s" % [ cmd, quotelist(cmd, *args) ]
103
- output = IO.read( '|-' ) or exec cmd, *args
104
- return output
105
- end
107
+ ### Run the given +cmd+ with the specified +args+ without interpolation by the shell and
108
+ ### return anything written to its STDOUT.
109
+ def read_command_output( cmd, *args )
110
+ trace "Reading output from: %s" % [ cmd, quotelist(cmd, *args) ]
111
+ output = IO.read( '|-' ) or exec cmd, *args
112
+ return output
113
+ end
106
114
 
107
115
 
108
- ### Run a subordinate Rake process with the same options and the specified +targets+.
109
- def rake( *targets )
110
- opts = ARGV.select {|arg| arg[0,1] == '-' }
111
- args = opts + targets.map {|t| t.to_s }
112
- run 'rake', '-N', *args
113
- end
116
+ ### Run a subordinate Rake process with the same options and the specified +targets+.
117
+ def rake( *targets )
118
+ opts = ARGV.select {|arg| arg[0,1] == '-' }
119
+ args = opts + targets.map {|t| t.to_s }
120
+ run 'rake', '-N', *args
121
+ end
114
122
 
115
123
 
116
- ### Open a pipe to a process running the given +cmd+ and call the given block with it.
117
- def pipeto( *cmd )
118
- $DEBUG = true
124
+ ### Open a pipe to a process running the given +cmd+ and call the given block with it.
125
+ def pipeto( *cmd )
126
+ $DEBUG = true
119
127
 
120
- cmd.flatten!
121
- log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
122
- if $dryrun
123
- $stderr.puts "(dry run mode)"
124
- else
125
- open( '|-', 'w+' ) do |io|
128
+ cmd.flatten!
129
+ log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
130
+ if $dryrun
131
+ $stderr.puts "(dry run mode)"
132
+ else
133
+ open( '|-', 'w+' ) do |io|
126
134
 
127
- # Parent
128
- if io
129
- yield( io )
135
+ # Parent
136
+ if io
137
+ yield( io )
130
138
 
131
- # Child
132
- else
133
- exec( *cmd )
134
- fail "Command failed: [%s]" % [cmd.join(' ')]
139
+ # Child
140
+ else
141
+ exec( *cmd )
142
+ fail "Command failed: [%s]" % [cmd.join(' ')]
143
+ end
135
144
  end
136
145
  end
137
146
  end
138
- end
139
147
 
140
148
 
141
- ### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
142
- def download( sourceuri, targetfile=nil )
143
- oldsync = $stdout.sync
144
- $stdout.sync = true
145
- require 'open-uri'
149
+ ### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
150
+ def download( sourceuri, targetfile=nil )
151
+ oldsync = $stdout.sync
152
+ $stdout.sync = true
153
+ require 'open-uri'
146
154
 
147
- targetpath = Pathname.new( targetfile )
155
+ targetpath = Pathname.new( targetfile )
148
156
 
149
- log "Downloading %s to %s" % [sourceuri, targetfile]
150
- trace " connecting..."
151
- ifh = open( sourceuri ) do |ifh|
152
- trace " connected..."
153
- targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
154
- log "Downloading..."
155
- buf = ''
157
+ log "Downloading %s to %s" % [sourceuri, targetfile]
158
+ trace " connecting..."
159
+ ifh = open( sourceuri ) do |ifh|
160
+ trace " connected..."
161
+ targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
162
+ log "Downloading..."
163
+ buf = ''
156
164
 
157
- while ifh.read( 16384, buf )
158
- until buf.empty?
159
- bytes = ofh.write( buf )
160
- buf.slice!( 0, bytes )
165
+ while ifh.read( 16384, buf )
166
+ until buf.empty?
167
+ bytes = ofh.write( buf )
168
+ buf.slice!( 0, bytes )
169
+ end
161
170
  end
171
+
172
+ log "Done."
162
173
  end
163
174
 
164
- log "Done."
165
175
  end
166
176
 
177
+ return targetpath
178
+ ensure
179
+ $stdout.sync = oldsync
167
180
  end
168
181
 
169
- return targetpath
170
- ensure
171
- $stdout.sync = oldsync
172
- end
173
182
 
183
+ ### Return the fully-qualified path to the specified +program+ in the PATH.
184
+ def which( program )
185
+ return nil unless ENV['PATH']
186
+ ENV['PATH'].split(/:/).
187
+ collect {|dir| Pathname.new(dir) + program }.
188
+ find {|path| path.exist? && path.executable? }
189
+ end
174
190
 
175
- ### Return the fully-qualified path to the specified +program+ in the PATH.
176
- def which( program )
177
- ENV['PATH'].split(/:/).
178
- collect {|dir| Pathname.new(dir) + program }.
179
- find {|path| path.exist? && path.executable? }
180
- end
181
191
 
192
+ ### Create a string that contains the ANSI codes specified and return it
193
+ def ansi_code( *attributes )
194
+ attributes.flatten!
195
+ attributes.collect! {|at| at.to_s }
196
+ # $stderr.puts "Returning ansicode for TERM = %p: %p" %
197
+ # [ ENV['TERM'], attributes ]
198
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
199
+ attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
182
200
 
183
- ### Create a string that contains the ANSI codes specified and return it
184
- def ansi_code( *attributes )
185
- attributes.flatten!
186
- attributes.collect! {|at| at.to_s }
187
- # $stderr.puts "Returning ansicode for TERM = %p: %p" %
188
- # [ ENV['TERM'], attributes ]
189
- return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
190
- attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
191
-
192
- # $stderr.puts " attr is: %p" % [attributes]
193
- if attributes.empty?
194
- return ''
195
- else
196
- return "\e[%sm" % attributes
201
+ # $stderr.puts " attr is: %p" % [attributes]
202
+ if attributes.empty?
203
+ return ''
204
+ else
205
+ return "\e[%sm" % attributes
206
+ end
197
207
  end
198
- end
199
208
 
200
209
 
201
- ### Colorize the given +string+ with the specified +attributes+ and return it, handling
202
- ### line-endings, color reset, etc.
203
- def colorize( *args )
204
- string = ''
210
+ ### Colorize the given +string+ with the specified +attributes+ and return it, handling
211
+ ### line-endings, color reset, etc.
212
+ def colorize( *args )
213
+ string = ''
214
+
215
+ if block_given?
216
+ string = yield
217
+ else
218
+ string = args.shift
219
+ end
220
+
221
+ ending = string[/(\s)$/] || ''
222
+ string = string.rstrip
205
223
 
206
- if block_given?
207
- string = yield
208
- else
209
- string = args.shift
224
+ return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
210
225
  end
211
226
 
212
- ending = string[/(\s)$/] || ''
213
- string = string.rstrip
214
227
 
215
- return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
216
- end
228
+ ### Output the specified <tt>msg</tt> as an ANSI-colored error message
229
+ ### (white on red).
230
+ def error_message( msg, details='' )
231
+ $stderr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
232
+ end
233
+ alias :error :error_message
217
234
 
218
235
 
219
- ### Output the specified <tt>msg</tt> as an ANSI-colored error message
220
- ### (white on red).
221
- def error_message( msg, details='' )
222
- $stderr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
223
- end
224
- alias :error :error_message
236
+ ### Highlight and embed a prompt control character in the given +string+ and return it.
237
+ def make_prompt_string( string )
238
+ return CLEAR_CURRENT_LINE + colorize( 'bold', 'green' ) { string + ' ' }
239
+ end
225
240
 
226
241
 
227
- ### Highlight and embed a prompt control character in the given +string+ and return it.
228
- def make_prompt_string( string )
229
- return CLEAR_CURRENT_LINE + colorize( 'bold', 'green' ) { string + ' ' }
230
- end
242
+ ### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
243
+ ### return the user's input with leading and trailing spaces removed. If a
244
+ ### test is provided, the prompt will repeat until the test returns true.
245
+ ### An optional failure message can also be passed in.
246
+ def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
247
+ prompt_string.chomp!
248
+ prompt_string << ":" unless /\W$/.match( prompt_string )
249
+ response = nil
250
+
251
+ begin
252
+ prompt = make_prompt_string( prompt_string )
253
+ response = readline( prompt ) || ''
254
+ response.strip!
255
+ if block_given? && ! yield( response )
256
+ error_message( failure_msg + "\n\n" )
257
+ response = nil
258
+ end
259
+ end while response.nil?
231
260
 
261
+ return response
262
+ end
232
263
 
233
- ### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
234
- ### return the user's input with leading and trailing spaces removed. If a
235
- ### test is provided, the prompt will repeat until the test returns true.
236
- ### An optional failure message can also be passed in.
237
- def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
238
- prompt_string.chomp!
239
- prompt_string << ":" unless /\W$/.match( prompt_string )
240
- response = nil
241
-
242
- begin
243
- prompt = make_prompt_string( prompt_string )
244
- response = readline( prompt ) || ''
245
- response.strip!
246
- if block_given? && ! yield( response )
247
- error_message( failure_msg + "\n\n" )
248
- response = nil
249
- end
250
- end while response.nil?
251
264
 
252
- return response
253
- end
265
+ ### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
266
+ ### substituting the given <tt>default</tt> if the user doesn't input
267
+ ### anything. If a test is provided, the prompt will repeat until the test
268
+ ### returns true. An optional failure message can also be passed in.
269
+ def prompt_with_default( prompt_string, default, failure_msg="Try again." )
270
+ response = nil
254
271
 
272
+ begin
273
+ default ||= '~'
274
+ response = prompt( "%s [%s]" % [ prompt_string, default ] )
275
+ response = default.to_s if !response.nil? && response.empty?
255
276
 
256
- ### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
257
- ### substituting the given <tt>default</tt> if the user doesn't input
258
- ### anything. If a test is provided, the prompt will repeat until the test
259
- ### returns true. An optional failure message can also be passed in.
260
- def prompt_with_default( prompt_string, default, failure_msg="Try again." )
261
- response = nil
277
+ trace "Validating response %p" % [ response ]
262
278
 
263
- begin
264
- default ||= '~'
265
- response = prompt( "%s [%s]" % [ prompt_string, default ] )
266
- response = default.to_s if !response.nil? && response.empty?
279
+ # the block is a validator. We need to make sure that the user didn't
280
+ # enter '~', because if they did, it's nil and we should move on. If
281
+ # they didn't, then call the block.
282
+ if block_given? && response != '~' && ! yield( response )
283
+ error_message( failure_msg + "\n\n" )
284
+ response = nil
285
+ end
286
+ end while response.nil?
267
287
 
268
- trace "Validating response %p" % [ response ]
288
+ return nil if response == '~'
289
+ return response
290
+ end
269
291
 
270
- # the block is a validator. We need to make sure that the user didn't
271
- # enter '~', because if they did, it's nil and we should move on. If
272
- # they didn't, then call the block.
273
- if block_given? && response != '~' && ! yield( response )
274
- error_message( failure_msg + "\n\n" )
275
- response = nil
292
+
293
+ ### Prompt for an array of values
294
+ def prompt_for_multiple_values( label, default=nil )
295
+ $stderr.puts( MULTILINE_PROMPT % [label] )
296
+ if default
297
+ $stderr.puts "Enter a single blank line to keep the default:\n %p" % [ default ]
276
298
  end
277
- end while response.nil?
278
299
 
279
- return nil if response == '~'
280
- return response
281
- end
300
+ results = []
301
+ result = nil
282
302
 
303
+ begin
304
+ result = readline( make_prompt_string("> ") )
305
+ if result.nil? || result.empty?
306
+ results << default if default && results.empty?
307
+ else
308
+ results << result
309
+ end
310
+ end until result.nil? || result.empty?
283
311
 
284
- ### Prompt for an array of values
285
- def prompt_for_multiple_values( label, default=nil )
286
- $stderr.puts( MULTILINE_PROMPT % [label] )
287
- if default
288
- $stderr.puts "Enter a single blank line to keep the default:\n %p" % [ default ]
312
+ return results.flatten
289
313
  end
290
314
 
291
- results = []
292
- result = nil
293
315
 
294
- begin
295
- result = readline( make_prompt_string("> ") )
296
- if result.nil? || result.empty?
297
- results << default if default && results.empty?
298
- else
299
- results << result
300
- end
301
- end until result.nil? || result.empty?
316
+ ### Turn echo and masking of input on/off.
317
+ def noecho( masked=false )
318
+ require 'termios'
302
319
 
303
- return results.flatten
304
- end
320
+ rval = nil
321
+ term = Termios.getattr( $stdin )
305
322
 
323
+ begin
324
+ newt = term.dup
325
+ newt.c_lflag &= ~Termios::ECHO
326
+ newt.c_lflag &= ~Termios::ICANON if masked
306
327
 
307
- ### Turn echo and masking of input on/off.
308
- def noecho( masked=false )
309
- require 'termios'
328
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
310
329
 
311
- rval = nil
312
- term = Termios.getattr( $stdin )
330
+ rval = yield
331
+ ensure
332
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
333
+ end
313
334
 
314
- begin
315
- newt = term.dup
316
- newt.c_lflag &= ~Termios::ECHO
317
- newt.c_lflag &= ~Termios::ICANON if masked
335
+ return rval
336
+ end
318
337
 
319
- Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
320
338
 
321
- rval = yield
322
- ensure
323
- Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
339
+ ### Prompt the user for her password, turning off echo if the 'termios' module is
340
+ ### available.
341
+ def prompt_for_password( prompt="Password: " )
342
+ return noecho( true ) do
343
+ $stderr.print( prompt )
344
+ ($stdin.gets || '').chomp
345
+ end
324
346
  end
325
347
 
326
- return rval
327
- end
328
348
 
349
+ ### Display a description of a potentially-dangerous task, and prompt
350
+ ### for confirmation. If the user answers with anything that begins
351
+ ### with 'y', yield to the block. If +abort_on_decline+ is +true+,
352
+ ### any non-'y' answer will fail with an error message.
353
+ def ask_for_confirmation( description, abort_on_decline=true )
354
+ puts description
355
+
356
+ answer = prompt_with_default( "Continue?", 'n' ) do |input|
357
+ input =~ /^[yn]/i
358
+ end
359
+
360
+ if answer =~ /^y/i
361
+ return yield
362
+ elsif abort_on_decline
363
+ error "Aborted."
364
+ fail
365
+ end
329
366
 
330
- ### Prompt the user for her password, turning off echo if the 'termios' module is
331
- ### available.
332
- def prompt_for_password( prompt="Password: " )
333
- return noecho( true ) do
334
- $stderr.print( prompt )
335
- ($stdin.gets || '').chomp
367
+ return false
336
368
  end
337
- end
369
+ alias :prompt_for_confirmation :ask_for_confirmation
338
370
 
339
371
 
340
- ### Display a description of a potentially-dangerous task, and prompt
341
- ### for confirmation. If the user answers with anything that begins
342
- ### with 'y', yield to the block. If +abort_on_decline+ is +true+,
343
- ### any non-'y' answer will fail with an error message.
344
- def ask_for_confirmation( description, abort_on_decline=true )
345
- puts description
372
+ ### Search line-by-line in the specified +file+ for the given +regexp+, returning the
373
+ ### first match, or nil if no match was found. If the +regexp+ has any capture groups,
374
+ ### those will be returned in an Array, else the whole matching line is returned.
375
+ def find_pattern_in_file( regexp, file )
376
+ rval = nil
346
377
 
347
- answer = prompt_with_default( "Continue?", 'n' ) do |input|
348
- input =~ /^[yn]/i
349
- end
378
+ File.open( file, 'r' ).each do |line|
379
+ if (( match = regexp.match(line) ))
380
+ rval = match.captures.empty? ? match[0] : match.captures
381
+ break
382
+ end
383
+ end
350
384
 
351
- if answer =~ /^y/i
352
- return yield
353
- elsif abort_on_decline
354
- error "Aborted."
355
- fail
385
+ return rval
356
386
  end
357
387
 
358
- return false
359
- end
360
- alias :prompt_for_confirmation :ask_for_confirmation
361
388
 
389
+ ### Search line-by-line in the output of the specified +cmd+ for the given +regexp+,
390
+ ### returning the first match, or nil if no match was found. If the +regexp+ has any
391
+ ### capture groups, those will be returned in an Array, else the whole matching line
392
+ ### is returned.
393
+ def find_pattern_in_pipe( regexp, *cmd )
394
+ require 'open3'
395
+ output = []
362
396
 
363
- ### Search line-by-line in the specified +file+ for the given +regexp+, returning the
364
- ### first match, or nil if no match was found. If the +regexp+ has any capture groups,
365
- ### those will be returned in an Array, else the whole matching line is returned.
366
- def find_pattern_in_file( regexp, file )
367
- rval = nil
397
+ log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
398
+ Open3.popen3( *cmd ) do |stdin, stdout, stderr|
399
+ stdin.close
368
400
 
369
- File.open( file, 'r' ).each do |line|
370
- if (( match = regexp.match(line) ))
371
- rval = match.captures.empty? ? match[0] : match.captures
372
- break
401
+ output << stdout.gets until stdout.eof?
402
+ output << stderr.gets until stderr.eof?
373
403
  end
374
- end
375
404
 
376
- return rval
377
- end
405
+ result = output.find { |line| regexp.match(line) }
406
+ return $1 || result
407
+ end
378
408
 
379
409
 
380
- ### Search line-by-line in the output of the specified +cmd+ for the given +regexp+,
381
- ### returning the first match, or nil if no match was found. If the +regexp+ has any
382
- ### capture groups, those will be returned in an Array, else the whole matching line
383
- ### is returned.
384
- def find_pattern_in_pipe( regexp, *cmd )
385
- output = []
410
+ ### Invoke the user's editor on the given +filename+ and return the exit code
411
+ ### from doing so.
412
+ def edit( filename )
413
+ editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
414
+ system editor, filename
415
+ unless $?.success? || editor =~ /vim/i
416
+ fail "Editor exited uncleanly."
417
+ end
418
+ end
386
419
 
387
- log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
388
- Open3.popen3( *cmd ) do |stdin, stdout, stderr|
389
- stdin.close
390
420
 
391
- output << stdout.gets until stdout.eof?
392
- output << stderr.gets until stderr.eof?
421
+ ### Extract all the non Rake-target arguments from ARGV and return them.
422
+ def get_target_args
423
+ args = ARGV.reject {|arg| arg =~ /^-/ || Rake::Task.task_defined?(arg) }
424
+ return args
393
425
  end
394
426
 
395
- result = output.find { |line| regexp.match(line) }
396
- return $1 || result
397
- end
398
427
 
428
+ ### Log a subdirectory change, execute a block, and exit the subdirectory
429
+ def in_subdirectory( subdir )
430
+ block = Proc.new
399
431
 
400
- ### Invoke the user's editor on the given +filename+ and return the exit code
401
- ### from doing so.
402
- def edit( filename )
403
- editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
404
- system editor, filename
405
- unless $?.success? || editor =~ /vim/i
406
- fail "Editor exited uncleanly."
432
+ log "Entering #{subdir}"
433
+ Dir.chdir( subdir, &block )
434
+ log "Leaving #{subdir}"
407
435
  end
408
- end
409
436
 
410
437
 
411
- ### Extract all the non Rake-target arguments from ARGV and return them.
412
- def get_target_args
413
- args = ARGV.reject {|arg| arg =~ /^-/ || Rake::Task.task_defined?(arg) }
414
- return args
415
- end
438
+ ### Make an easily-comparable version vector out of +ver+ and return it.
439
+ def vvec( ver )
440
+ return ver.split('.').collect {|char| char.to_i }.pack('N*')
441
+ end
416
442
 
417
443
 
418
- ### Log a subdirectory change, execute a block, and exit the subdirectory
419
- def in_subdirectory( subdir )
420
- block = Proc.new
444
+ ### Archive::Tar::Reader#extract (as of 0.9.0) is broken w.r.t.
445
+ ### permissions, so we have to do this ourselves.
446
+ def untar( tarfile, targetdir )
447
+ require 'archive/tar'
448
+ targetdir = Pathname( targetdir )
449
+ raise "No such directory: #{targetdir}" unless targetdir.directory?
421
450
 
422
- log "Entering #{subdir}"
423
- Dir.chdir( subdir, &block )
424
- log "Leaving #{subdir}"
425
- end
451
+ reader = Archive::Tar::Reader.new( tarfile.to_s, TAR_OPTS )
426
452
 
453
+ mkdir_p( targetdir )
454
+ reader.each( true ) do |header, body|
455
+ path = targetdir + header[:path]
456
+ # trace "Header is: %p" % [ header ]
427
457
 
428
- ### Make an easily-comparable version vector out of +ver+ and return it.
429
- def vvec( ver )
430
- return ver.split('.').collect {|char| char.to_i }.pack('N*')
431
- end
458
+ case header[:type]
459
+ when :file
460
+ trace " #{path}"
461
+ path.open( File::WRONLY|File::EXCL|File::CREAT|File::TRUNC, header[:mode] ) do |fio|
462
+ bytesize = header[:size]
463
+ fio.write( body[0,bytesize] )
464
+ end
465
+
466
+ when :directory
467
+ trace " #{path}"
468
+ path.mkpath
469
+
470
+ when :link
471
+ linktarget = targetdir + header[:dest]
472
+ trace " #{path} => #{linktarget}"
473
+ path.make_link( linktarget.to_s )
474
+
475
+ when :symlink
476
+ linktarget = targetdir + header[:dest]
477
+ trace " #{path} -> #{linktarget}"
478
+ path.make_symlink( linktarget )
479
+ end
480
+ end
481
+
482
+ end
483
+
484
+
485
+ ### Extract the contents of the specified +zipfile+ into the given +targetdir+.
486
+ def unzip( zipfile, targetdir, *files )
487
+ require 'zip/zip'
488
+ targetdir = Pathname( targetdir )
489
+ raise "No such directory: #{targetdir}" unless targetdir.directory?
490
+
491
+ Zip::ZipFile.foreach( zipfile ) do |entry|
492
+ # trace " entry is: %p" % [ entry ]
493
+ next unless files.empty? || files.include?( entry.name )
494
+ target_path = targetdir + entry.name
495
+ # trace " would extract to: %s" % [ target_path ]
496
+ entry.extract( target_path ) { true }
497
+ end
498
+ end
432
499
 
500
+ end # module Rakefile::Helpers
433
501
 
434
502