sysexits 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,502 +0,0 @@
1
- # encoding: utf-8
2
-
3
- #####################################################################
4
- ### G L O B A L H E L P E R F U N C T I O N S
5
- #####################################################################
6
-
7
-
8
- require 'pathname'
9
- require 'uri'
10
-
11
- begin
12
- require 'readline'
13
- include Readline
14
- rescue LoadError
15
- # Fall back to a plain prompt
16
- def readline( text )
17
- $stderr.print( text.chomp )
18
- return $stdin.gets
19
- end
20
- end
21
-
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,
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
- }
45
-
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 }
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
77
-
78
-
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
83
-
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
103
- end
104
- end
105
-
106
-
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(' ')]
143
- end
144
- end
145
- end
146
- end
147
-
148
-
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'
154
-
155
- targetpath = Pathname.new( targetfile )
156
-
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
174
-
175
- end
176
-
177
- return targetpath
178
- ensure
179
- $stdout.sync = oldsync
180
- end
181
-
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
190
-
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
208
-
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 = ''
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
223
-
224
- return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
225
- end
226
-
227
-
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
234
-
235
-
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
240
-
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?
260
-
261
- return response
262
- end
263
-
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 ]
298
- end
299
-
300
- results = []
301
- result = nil
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?
311
-
312
- return results.flatten
313
- end
314
-
315
-
316
- ### Turn echo and masking of input on/off.
317
- def noecho( masked=false )
318
- require 'termios'
319
-
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
336
- end
337
-
338
-
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
346
- end
347
-
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
366
-
367
- return false
368
- end
369
- alias :prompt_for_confirmation :ask_for_confirmation
370
-
371
-
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
386
- end
387
-
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?
403
- end
404
-
405
- result = output.find { |line| regexp.match(line) }
406
- return $1 || result
407
- end
408
-
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
419
-
420
-
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
425
- end
426
-
427
-
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}"
435
- end
436
-
437
-
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
442
-
443
-
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
-
502
-