linkparser 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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