linkparser 1.0.4 → 1.1.0

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.
@@ -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,394 +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
46
+
47
+ MULTILINE_PROMPT = <<-'EOF'
48
+ Enter one or more values for '%s'.
49
+ A blank line finishes input.
50
+ EOF
51
+
52
+
53
+ CLEAR_TO_EOL = "\e[K"
54
+ CLEAR_CURRENT_LINE = "\e[2K"
55
+
56
+
57
+ TAR_OPTS = { :compression => :gzip }
69
58
 
70
59
 
71
- ### Run the specified command +cmd+ with system(), failing if the execution
72
- ### fails.
73
- def run( *cmd )
74
- cmd.flatten!
60
+ ###############
61
+ module_function
62
+ ###############
75
63
 
76
- if cmd.length > 1
77
- trace( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
78
- else
79
- trace( cmd )
64
+ ### Output a logging message
65
+ def log( *msg )
66
+ output = colorize( msg.flatten.join(' '), 'cyan' )
67
+ $stderr.puts( output )
80
68
  end
81
-
82
- if $dryrun
83
- $stderr.puts "(dry run mode)"
84
- else
85
- system( *cmd )
86
- unless $?.success?
87
- fail "Command failed: [%s]" % [cmd.join(' ')]
88
- 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 )
89
76
  end
90
- end
91
77
 
92
78
 
93
- ### Run a subordinate Rake process with the same options and the specified +targets+.
94
- def rake( *targets )
95
- opts = ARGV.select {|arg| arg[0,1] == '-' }
96
- args = opts + targets.map {|t| t.to_s }
97
- run 'rake', '-N', *args
98
- end
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}
82
+ end
99
83
 
100
84
 
101
- ### Open a pipe to a process running the given +cmd+ and call the given block with it.
102
- def pipeto( *cmd )
103
- $DEBUG = true
85
+ ### Run the specified command +cmd+ with system(), failing if the execution
86
+ ### fails.
87
+ def run( *cmd )
88
+ cmd.flatten!
104
89
 
105
- cmd.flatten!
106
- log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
107
- if $dryrun
108
- $stderr.puts "(dry run mode)"
109
- else
110
- open( '|-', 'w+' ) do |io|
111
-
112
- # Parent
113
- if io
114
- yield( io )
90
+ if cmd.length > 1
91
+ trace( quotelist(*cmd) )
92
+ else
93
+ trace( cmd )
94
+ end
115
95
 
116
- # Child
117
- else
118
- exec( *cmd )
96
+ if $dryrun
97
+ $stderr.puts "(dry run mode)"
98
+ else
99
+ system( *cmd )
100
+ unless $?.success?
119
101
  fail "Command failed: [%s]" % [cmd.join(' ')]
120
102
  end
121
103
  end
122
104
  end
123
- end
124
105
 
125
106
 
126
- ### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
127
- def download( sourceuri, targetfile=nil )
128
- oldsync = $stdout.sync
129
- $stdout.sync = true
130
- require 'open-uri'
131
-
132
- targetpath = Pathname.new( targetfile )
133
-
134
- log "Downloading %s to %s" % [sourceuri, targetfile]
135
- trace " connecting..."
136
- ifh = open( sourceuri ) do |ifh|
137
- trace " connected..."
138
- targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
139
- log "Downloading..."
140
- buf = ''
141
-
142
- while ifh.read( 16384, buf )
143
- until buf.empty?
144
- bytes = ofh.write( buf )
145
- buf.slice!( 0, bytes )
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
114
+
115
+
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
122
+
123
+
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
127
+
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|
134
+
135
+ # Parent
136
+ if io
137
+ yield( io )
138
+
139
+ # Child
140
+ else
141
+ exec( *cmd )
142
+ fail "Command failed: [%s]" % [cmd.join(' ')]
146
143
  end
147
144
  end
148
-
149
- log "Done."
150
145
  end
151
-
152
146
  end
153
-
154
- return targetpath
155
- ensure
156
- $stdout.sync = oldsync
157
- end
158
147
 
159
148
 
160
- ### Return the fully-qualified path to the specified +program+ in the PATH.
161
- def which( program )
162
- ENV['PATH'].split(/:/).
163
- collect {|dir| Pathname.new(dir) + program }.
164
- find {|path| path.exist? && path.executable? }
165
- end
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'
166
154
 
155
+ targetpath = Pathname.new( targetfile )
167
156
 
168
- ### Create a string that contains the ANSI codes specified and return it
169
- def ansi_code( *attributes )
170
- attributes.flatten!
171
- attributes.collect! {|at| at.to_s }
172
- # $stderr.puts "Returning ansicode for TERM = %p: %p" %
173
- # [ ENV['TERM'], attributes ]
174
- return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
175
- attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
176
-
177
- # $stderr.puts " attr is: %p" % [attributes]
178
- if attributes.empty?
179
- return ''
180
- else
181
- return "\e[%sm" % attributes
182
- end
183
- end
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 = ''
164
+
165
+ while ifh.read( 16384, buf )
166
+ until buf.empty?
167
+ bytes = ofh.write( buf )
168
+ buf.slice!( 0, bytes )
169
+ end
170
+ end
171
+
172
+ log "Done."
173
+ end
184
174
 
175
+ end
185
176
 
186
- ### Colorize the given +string+ with the specified +attributes+ and return it, handling
187
- ### line-endings, color reset, etc.
188
- def colorize( *args )
189
- string = ''
190
-
191
- if block_given?
192
- string = yield
193
- else
194
- string = args.shift
177
+ return targetpath
178
+ ensure
179
+ $stdout.sync = oldsync
195
180
  end
196
-
197
- ending = string[/(\s)$/] || ''
198
- string = string.rstrip
199
181
 
200
- return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
201
- end
202
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
203
190
 
204
- ### Output the specified <tt>msg</tt> as an ANSI-colored error message
205
- ### (white on red).
206
- def error_message( msg, details='' )
207
- $stderr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
208
- end
209
- alias :error :error_message
210
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(';')
200
+
201
+ # $stderr.puts " attr is: %p" % [attributes]
202
+ if attributes.empty?
203
+ return ''
204
+ else
205
+ return "\e[%sm" % attributes
206
+ end
207
+ end
211
208
 
212
- ### Highlight and embed a prompt control character in the given +string+ and return it.
213
- def make_prompt_string( string )
214
- return CLEAR_CURRENT_LINE + colorize( 'bold', 'green' ) { string + ' ' }
215
- end
216
209
 
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 = ''
217
214
 
218
- ### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
219
- ### return the user's input with leading and trailing spaces removed. If a
220
- ### test is provided, the prompt will repeat until the test returns true.
221
- ### An optional failure message can also be passed in.
222
- def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
223
- prompt_string.chomp!
224
- prompt_string << ":" unless /\W$/.match( prompt_string )
225
- response = nil
226
-
227
- begin
228
- prompt = make_prompt_string( prompt_string )
229
- response = readline( prompt ) || ''
230
- response.strip!
231
- if block_given? && ! yield( response )
232
- error_message( failure_msg + "\n\n" )
233
- response = nil
215
+ if block_given?
216
+ string = yield
217
+ else
218
+ string = args.shift
234
219
  end
235
- end while response.nil?
236
220
 
237
- return response
238
- end
221
+ ending = string[/(\s)$/] || ''
222
+ string = string.rstrip
239
223
 
224
+ return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
225
+ end
240
226
 
241
- ### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
242
- ### substituting the given <tt>default</tt> if the user doesn't input
243
- ### anything. If a test is provided, the prompt will repeat until the test
244
- ### returns true. An optional failure message can also be passed in.
245
- def prompt_with_default( prompt_string, default, failure_msg="Try again." )
246
- response = nil
247
227
 
248
- begin
249
- default ||= '~'
250
- response = prompt( "%s [%s]" % [ prompt_string, default ] )
251
- response = default.to_s if !response.nil? && response.empty?
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
252
234
 
253
- trace "Validating response %p" % [ response ]
254
235
 
255
- # the block is a validator. We need to make sure that the user didn't
256
- # enter '~', because if they did, it's nil and we should move on. If
257
- # they didn't, then call the block.
258
- if block_given? && response != '~' && ! yield( response )
259
- error_message( failure_msg + "\n\n" )
260
- response = nil
261
- end
262
- end while response.nil?
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
263
240
 
264
- return nil if response == '~'
265
- return response
266
- end
267
241
 
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?
268
260
 
269
- ### Prompt for an array of values
270
- def prompt_for_multiple_values( label, default=nil )
271
- $stderr.puts( MULTILINE_PROMPT % [label] )
272
- if default
273
- $stderr.puts "Enter a single blank line to keep the default:\n %p" % [ default ]
261
+ return response
274
262
  end
275
263
 
276
- results = []
277
- result = nil
278
-
279
- begin
280
- result = readline( make_prompt_string("> ") )
281
- if result.nil? || result.empty?
282
- results << default if default && results.empty?
283
- else
284
- results << result
264
+
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
271
+
272
+ begin
273
+ default ||= '~'
274
+ response = prompt( "%s [%s]" % [ prompt_string, default ] )
275
+ response = default.to_s if !response.nil? && response.empty?
276
+
277
+ trace "Validating response %p" % [ response ]
278
+
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?
287
+
288
+ return nil if response == '~'
289
+ return response
290
+ end
291
+
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 ]
285
298
  end
286
- end until result.nil? || result.empty?
287
-
288
- return results.flatten
289
- end
290
299
 
300
+ results = []
301
+ result = nil
291
302
 
292
- ### Turn echo and masking of input on/off.
293
- def noecho( masked=false )
294
- require 'termios'
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?
295
311
 
296
- rval = nil
297
- term = Termios.getattr( $stdin )
312
+ return results.flatten
313
+ end
298
314
 
299
- begin
300
- newt = term.dup
301
- newt.c_lflag &= ~Termios::ECHO
302
- newt.c_lflag &= ~Termios::ICANON if masked
303
315
 
304
- Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
316
+ ### Turn echo and masking of input on/off.
317
+ def noecho( masked=false )
318
+ require 'termios'
305
319
 
306
- rval = yield
307
- ensure
308
- Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
320
+ rval = nil
321
+ term = Termios.getattr( $stdin )
322
+
323
+ begin
324
+ newt = term.dup
325
+ newt.c_lflag &= ~Termios::ECHO
326
+ newt.c_lflag &= ~Termios::ICANON if masked
327
+
328
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
329
+
330
+ rval = yield
331
+ ensure
332
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
333
+ end
334
+
335
+ return rval
309
336
  end
310
-
311
- return rval
312
- end
313
337
 
314
338
 
315
- ### Prompt the user for her password, turning off echo if the 'termios' module is
316
- ### available.
317
- def prompt_for_password( prompt="Password: " )
318
- return noecho( true ) do
319
- $stderr.print( prompt )
320
- ($stdin.gets || '').chomp
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
321
346
  end
322
- end
323
347
 
324
348
 
325
- ### Display a description of a potentially-dangerous task, and prompt
326
- ### for confirmation. If the user answers with anything that begins
327
- ### with 'y', yield to the block. If +abort_on_decline+ is +true+,
328
- ### any non-'y' answer will fail with an error message.
329
- def ask_for_confirmation( description, abort_on_decline=true )
330
- puts description
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
331
366
 
332
- answer = prompt_with_default( "Continue?", 'n' ) do |input|
333
- input =~ /^[yn]/i
367
+ return false
334
368
  end
369
+ alias :prompt_for_confirmation :ask_for_confirmation
370
+
335
371
 
336
- if answer =~ /^y/i
337
- return yield
338
- elsif abort_on_decline
339
- error "Aborted."
340
- fail
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
377
+
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
384
+
385
+ return rval
341
386
  end
342
387
 
343
- return false
344
- end
345
- alias :prompt_for_confirmation :ask_for_confirmation
346
-
347
-
348
- ### Search line-by-line in the specified +file+ for the given +regexp+, returning the
349
- ### first match, or nil if no match was found. If the +regexp+ has any capture groups,
350
- ### those will be returned in an Array, else the whole matching line is returned.
351
- def find_pattern_in_file( regexp, file )
352
- rval = nil
353
-
354
- File.open( file, 'r' ).each do |line|
355
- if (( match = regexp.match(line) ))
356
- rval = match.captures.empty? ? match[0] : match.captures
357
- break
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 = []
396
+
397
+ log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
398
+ Open3.popen3( *cmd ) do |stdin, stdout, stderr|
399
+ stdin.close
400
+
401
+ output << stdout.gets until stdout.eof?
402
+ output << stderr.gets until stderr.eof?
358
403
  end
404
+
405
+ result = output.find { |line| regexp.match(line) }
406
+ return $1 || result
359
407
  end
360
408
 
361
- return rval
362
- end
363
409
 
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
364
419
 
365
- ### Search line-by-line in the output of the specified +cmd+ for the given +regexp+,
366
- ### returning the first match, or nil if no match was found. If the +regexp+ has any
367
- ### capture groups, those will be returned in an Array, else the whole matching line
368
- ### is returned.
369
- def find_pattern_in_pipe( regexp, *cmd )
370
- output = []
371
-
372
- log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
373
- Open3.popen3( *cmd ) do |stdin, stdout, stderr|
374
- stdin.close
375
420
 
376
- output << stdout.gets until stdout.eof?
377
- 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
378
425
  end
379
-
380
- result = output.find { |line| regexp.match(line) }
381
- return $1 || result
382
- end
383
426
 
384
427
 
385
- ### Invoke the user's editor on the given +filename+ and return the exit code
386
- ### from doing so.
387
- def edit( filename )
388
- editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
389
- system editor, filename
390
- unless $?.success?
391
- fail "Editor exited uncleanly."
428
+ ### Log a subdirectory change, execute a block, and exit the subdirectory
429
+ def in_subdirectory( subdir )
430
+ block = Proc.new
431
+
432
+ log "Entering #{subdir}"
433
+ Dir.chdir( subdir, &block )
434
+ log "Leaving #{subdir}"
392
435
  end
393
- end
394
436
 
395
437
 
396
- ### Extract all the non Rake-target arguments from ARGV and return them.
397
- def get_target_args
398
- args = ARGV.reject {|arg| arg =~ /^-/ || Rake::Task.task_defined?(arg) }
399
- return args
400
- 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
401
442
 
402
443
 
403
- ### Log a subdirectory change, execute a block, and exit the subdirectory
404
- def in_subdirectory( subdir )
405
- block = Proc.new
406
-
407
- log "Entering #{subdir}"
408
- Dir.chdir( subdir, &block )
409
- log "Leaving #{subdir}"
410
- end
411
-
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?
450
+
451
+ reader = Archive::Tar::Reader.new( tarfile.to_s, TAR_OPTS )
452
+
453
+ mkdir_p( targetdir )
454
+ reader.each( true ) do |header, body|
455
+ path = targetdir + header[:path]
456
+ # trace "Header is: %p" % [ header ]
457
+
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
499
+
500
+ end # module Rakefile::Helpers
501
+
412
502