commander-openflighthpc 1.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +44 -0
  5. data/.rubocop_todo.yml +77 -0
  6. data/.travis.yml +14 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE +51 -0
  10. data/Manifest +38 -0
  11. data/README.md +492 -0
  12. data/Rakefile +13 -0
  13. data/bin/commander +104 -0
  14. data/commander-openflighthpc.gemspec +32 -0
  15. data/lib/commander.rb +36 -0
  16. data/lib/commander/blank.rb +7 -0
  17. data/lib/commander/command.rb +263 -0
  18. data/lib/commander/configure.rb +14 -0
  19. data/lib/commander/core_ext.rb +2 -0
  20. data/lib/commander/core_ext/array.rb +24 -0
  21. data/lib/commander/core_ext/object.rb +8 -0
  22. data/lib/commander/delegates.rb +27 -0
  23. data/lib/commander/help_formatters.rb +52 -0
  24. data/lib/commander/help_formatters/base.rb +24 -0
  25. data/lib/commander/help_formatters/terminal.rb +24 -0
  26. data/lib/commander/help_formatters/terminal/command_help.erb +35 -0
  27. data/lib/commander/help_formatters/terminal/help.erb +36 -0
  28. data/lib/commander/help_formatters/terminal/subcommand_help.erb +23 -0
  29. data/lib/commander/help_formatters/terminal_compact.rb +11 -0
  30. data/lib/commander/help_formatters/terminal_compact/command_help.erb +26 -0
  31. data/lib/commander/help_formatters/terminal_compact/help.erb +29 -0
  32. data/lib/commander/help_formatters/terminal_compact/subcommand_help.erb +15 -0
  33. data/lib/commander/import.rb +5 -0
  34. data/lib/commander/methods.rb +11 -0
  35. data/lib/commander/patches/decimal-integer.rb +17 -0
  36. data/lib/commander/patches/help_formatter_binding.rb +15 -0
  37. data/lib/commander/patches/implicit-short-tags.rb +75 -0
  38. data/lib/commander/patches/option_defaults.rb +23 -0
  39. data/lib/commander/patches/validate_inputs.rb +76 -0
  40. data/lib/commander/platform.rb +7 -0
  41. data/lib/commander/runner.rb +493 -0
  42. data/lib/commander/user_interaction.rb +551 -0
  43. data/lib/commander/version.rb +3 -0
  44. data/spec/command_spec.rb +157 -0
  45. data/spec/configure_spec.rb +37 -0
  46. data/spec/core_ext/array_spec.rb +18 -0
  47. data/spec/core_ext/object_spec.rb +19 -0
  48. data/spec/help_formatters/terminal_compact_spec.rb +69 -0
  49. data/spec/help_formatters/terminal_spec.rb +67 -0
  50. data/spec/methods_spec.rb +61 -0
  51. data/spec/patches/validate_inputs_spec.rb +84 -0
  52. data/spec/runner_spec.rb +672 -0
  53. data/spec/spec_helper.rb +79 -0
  54. data/spec/ui_spec.rb +30 -0
  55. metadata +183 -0
@@ -0,0 +1,551 @@
1
+ require 'tempfile'
2
+ require 'shellwords'
3
+
4
+ module Commander
5
+ ##
6
+ # = User Interaction
7
+ #
8
+ # Commander's user interaction module mixes in common
9
+ # methods which extend HighLine's functionality such
10
+ # as a #password method rather than calling #ask directly.
11
+
12
+ module UI
13
+ module_function
14
+
15
+ #--
16
+ # Auto include growl when available.
17
+ #++
18
+
19
+ begin
20
+ require 'growl'
21
+ rescue LoadError
22
+ # Do nothing
23
+ else
24
+ include Growl
25
+ end
26
+
27
+ ##
28
+ # Ask the user for a password. Specify a custom
29
+ # _message_ other than 'Password: ' or override the
30
+ # default _mask_ of '*'.
31
+
32
+ def password(message = 'Password: ', mask = '*')
33
+ pass = ask(message) { |q| q.echo = mask }
34
+ pass = password message, mask if pass.nil? || pass.empty?
35
+ pass
36
+ end
37
+
38
+ ##
39
+ # Choose from a set array of _choices_.
40
+
41
+ def choose(message = nil, *choices, &block)
42
+ say message if message
43
+ super(*choices, &block)
44
+ end
45
+
46
+ ##
47
+ # 'Log' an _action_ to the terminal. This is typically used
48
+ # for verbose output regarding actions performed. For example:
49
+ #
50
+ # create path/to/file.rb
51
+ # remove path/to/old_file.rb
52
+ # remove path/to/old_file2.rb
53
+ #
54
+
55
+ def log(action, *args)
56
+ say format('%15s %s', action, args.join(' '))
57
+ end
58
+
59
+ ##
60
+ # 'Say' something using the OK color (green).
61
+ #
62
+ # === Examples
63
+ # say_ok 'Everything is fine'
64
+ # say_ok 'It is ok', 'This is ok too'
65
+ #
66
+
67
+ def say_ok(*args)
68
+ args.each do |arg|
69
+ say $terminal.color(arg, :green)
70
+ end
71
+ end
72
+
73
+ ##
74
+ # 'Say' something using the WARNING color (yellow).
75
+ #
76
+ # === Examples
77
+ # say_warning 'This is a warning'
78
+ # say_warning 'Be careful', 'Think about it'
79
+ #
80
+
81
+ def say_warning(*args)
82
+ args.each do |arg|
83
+ say $terminal.color(arg, :yellow)
84
+ end
85
+ end
86
+
87
+ ##
88
+ # 'Say' something using the ERROR color (red).
89
+ #
90
+ # === Examples
91
+ # say_error 'Everything is not fine'
92
+ # say_error 'It is not ok', 'This is not ok too'
93
+ #
94
+
95
+ def say_error(*args)
96
+ args.each do |arg|
97
+ say $terminal.color(arg, :red)
98
+ end
99
+ end
100
+
101
+ ##
102
+ # 'Say' something using the specified color
103
+ #
104
+ # === Examples
105
+ # color 'I am blue', :blue
106
+ # color 'I am bold', :bold
107
+ # color 'White on Red', :white, :on_red
108
+ #
109
+ # === Notes
110
+ # You may use:
111
+ # * color: black blue cyan green magenta red white yellow
112
+ # * style: blink bold clear underline
113
+ # * highligh: on_<color>
114
+
115
+ def color(*args)
116
+ say $terminal.color(*args)
117
+ end
118
+
119
+ ##
120
+ # Speak _message_ using _voice_ at a speaking rate of _rate_
121
+ #
122
+ # Voice defaults to 'Alex', which is one of the better voices.
123
+ # Speaking rate defaults to 175 words per minute
124
+ #
125
+ # === Examples
126
+ #
127
+ # speak 'What is your favorite food? '
128
+ # food = ask 'favorite food?: '
129
+ # speak "Wow, I like #{food} too. We have so much in common."
130
+ # speak "I like #{food} as well!", "Victoria", 190
131
+ #
132
+ # === Notes
133
+ #
134
+ # * MacOS only
135
+ #
136
+
137
+ def speak(message, voice = :Alex, rate = 175)
138
+ Thread.new { applescript "say #{message.inspect} using #{voice.to_s.inspect} speaking rate #{rate}" }
139
+ end
140
+
141
+ ##
142
+ # Converse with speech recognition.
143
+ #
144
+ # Currently a "poorman's" DSL to utilize applescript and
145
+ # the MacOS speech recognition server.
146
+ #
147
+ # === Examples
148
+ #
149
+ # case converse 'What is the best food?', :cookies => 'Cookies', :unknown => 'Nothing'
150
+ # when :cookies
151
+ # speak 'o.m.g. you are awesome!'
152
+ # else
153
+ # case converse 'That is lame, shall I convince you cookies are the best?', :yes => 'Ok', :no => 'No', :maybe => 'Maybe another time'
154
+ # when :yes
155
+ # speak 'Well you see, cookies are just fantastic.'
156
+ # else
157
+ # speak 'Ok then, bye.'
158
+ # end
159
+ # end
160
+ #
161
+ # === Notes
162
+ #
163
+ # * MacOS only
164
+ #
165
+
166
+ def converse(prompt, responses = {})
167
+ i, commands = 0, responses.map { |_key, value| value.inspect }.join(',')
168
+ statement = responses.inject '' do |inner_statement, (key, value)|
169
+ inner_statement <<
170
+ (
171
+ (i += 1) == 1 ?
172
+ %(if response is "#{value}" then\n) :
173
+ %(else if response is "#{value}" then\n)
174
+ ) <<
175
+ %(do shell script "echo '#{key}'"\n)
176
+ end
177
+ applescript(
178
+ %(
179
+ tell application "SpeechRecognitionServer"
180
+ set response to listen for {#{commands}} with prompt "#{prompt}"
181
+ #{statement}
182
+ end if
183
+ end tell
184
+ )
185
+ ).strip.to_sym
186
+ end
187
+
188
+ ##
189
+ # Execute apple _script_.
190
+
191
+ def applescript(script)
192
+ `osascript -e "#{ script.gsub('"', '\"') }"`
193
+ end
194
+
195
+ ##
196
+ # Normalize IO streams, allowing for redirection of
197
+ # +input+ and/or +output+, for example:
198
+ #
199
+ # $ foo # => read from terminal I/O
200
+ # $ foo in # => read from 'in' file, output to terminal output stream
201
+ # $ foo in out # => read from 'in' file, output to 'out' file
202
+ # $ foo < in > out # => equivalent to above (essentially)
203
+ #
204
+ # Optionally a +block+ may be supplied, in which case
205
+ # IO will be reset once the block has executed.
206
+ #
207
+ # === Examples
208
+ #
209
+ # command :foo do |c|
210
+ # c.syntax = 'foo [input] [output]'
211
+ # c.when_called do |args, options|
212
+ # # or io(args.shift, args.shift)
213
+ # io *args
214
+ # str = $stdin.gets
215
+ # puts 'input was: ' + str.inspect
216
+ # end
217
+ # end
218
+ #
219
+
220
+ def io(input = nil, output = nil, &block)
221
+ $stdin = File.new(input) if input
222
+ $stdout = File.new(output, 'r+') if output
223
+ return unless block
224
+ yield
225
+ reset_io
226
+ end
227
+
228
+ ##
229
+ # Reset IO to initial constant streams.
230
+
231
+ def reset_io
232
+ $stdin, $stdout = STDIN, STDOUT
233
+ end
234
+
235
+ ##
236
+ # Find an editor available in path. Optionally supply the _preferred_
237
+ # editor. Returns the name as a string, nil if none is available.
238
+
239
+ def available_editor(preferred = nil)
240
+ [preferred, ENV['EDITOR'], 'mate -w', 'vim', 'vi', 'emacs', 'nano', 'pico']
241
+ .compact
242
+ .find { |name| system("hash #{name.split.first} 2>&-") }
243
+ end
244
+
245
+ ##
246
+ # Prompt an editor for input. Optionally supply initial
247
+ # _input_ which is written to the editor.
248
+ #
249
+ # _preferred_editor_ can be hinted.
250
+ #
251
+ # === Examples
252
+ #
253
+ # ask_editor # => prompts EDITOR with no input
254
+ # ask_editor('foo') # => prompts EDITOR with default text of 'foo'
255
+ # ask_editor('foo', 'mate -w') # => prompts TextMate with default text of 'foo'
256
+ #
257
+
258
+ def ask_editor(input = nil, preferred_editor = nil)
259
+ editor = available_editor preferred_editor
260
+ program = Commander::Runner.instance.program(:name).downcase rescue 'commander'
261
+ tmpfile = Tempfile.new program
262
+ begin
263
+ tmpfile.write input if input
264
+ tmpfile.close
265
+ system("#{editor} #{tmpfile.path.shellescape}") ? IO.read(tmpfile.path) : nil
266
+ ensure
267
+ tmpfile.unlink
268
+ end
269
+ end
270
+
271
+ ##
272
+ # Enable paging of output after called.
273
+
274
+ def enable_paging
275
+ return unless $stdout.tty?
276
+ return unless Process.respond_to? :fork
277
+ read, write = IO.pipe
278
+
279
+ # Kernel.fork is not supported on all platforms and configurations.
280
+ # As of Ruby 1.9, `Process.respond_to? :fork` should return false on
281
+ # configurations that don't support it, but versions before 1.9 don't
282
+ # seem to do this reliably and instead raise a NotImplementedError
283
+ # (which is rescued below).
284
+
285
+ if Kernel.fork
286
+ $stdin.reopen read
287
+ write.close
288
+ read.close
289
+ Kernel.select [$stdin]
290
+ ENV['LESS'] = 'FSRX' unless ENV.key? 'LESS'
291
+ pager = ENV['PAGER'] || 'less'
292
+ exec pager rescue exec '/bin/sh', '-c', pager
293
+ else
294
+ # subprocess
295
+ $stdout.reopen write
296
+ $stderr.reopen write if $stderr.tty?
297
+ write.close
298
+ read.close
299
+ end
300
+ rescue NotImplementedError
301
+ ensure
302
+ write.close if write && !write.closed?
303
+ read.close if read && !read.closed?
304
+ end
305
+
306
+ ##
307
+ # Output progress while iterating _arr_.
308
+ #
309
+ # === Examples
310
+ #
311
+ # uris = %w( http://vision-media.ca http://google.com )
312
+ # progress uris, :format => "Remaining: :time_remaining" do |uri|
313
+ # res = open uri
314
+ # end
315
+ #
316
+
317
+ def progress(arr, options = {})
318
+ bar = ProgressBar.new arr.length, options
319
+ bar.show
320
+ arr.each { |v| bar.increment yield(v) }
321
+ end
322
+
323
+ ##
324
+ # Implements ask_for_CLASS methods.
325
+
326
+ module AskForClass
327
+ DEPRECATED_CONSTANTS = [:Config, :TimeoutError, :MissingSourceFile, :NIL, :TRUE, :FALSE, :Fixnum, :Bignum, :Data].freeze
328
+
329
+ # define methods for common classes
330
+ [Float, Integer, String, Symbol, Regexp, Array, File, Pathname].each do |klass|
331
+ define_method "ask_for_#{klass.to_s.downcase}" do |prompt|
332
+ $terminal.ask(prompt, klass)
333
+ end
334
+ end
335
+
336
+ def method_missing(method_name, *arguments, &block)
337
+ if method_name.to_s =~ /^ask_for_(.*)/
338
+ if arguments.count != 1
339
+ fail ArgumentError, "wrong number of arguments (given #{arguments.count}, expected 1)"
340
+ end
341
+ prompt = arguments.first
342
+ requested_class = Regexp.last_match[1]
343
+
344
+ # All Classes that respond to #parse
345
+ # Ignore constants that trigger deprecation warnings
346
+ available_classes = (Object.constants - DEPRECATED_CONSTANTS).map do |const|
347
+ Object.const_get(const)
348
+ end.select do |const|
349
+ const.class == Class && const.respond_to?(:parse)
350
+ end
351
+
352
+ klass = available_classes.find { |k| k.to_s.downcase == requested_class }
353
+ if klass
354
+ $terminal.ask(prompt, klass)
355
+ else
356
+ super
357
+ end
358
+ else
359
+ super
360
+ end
361
+ end
362
+
363
+ def respond_to_missing?(method_name, include_private = false)
364
+ method_name.to_s.start_with?('ask_for_') || super
365
+ end
366
+ end
367
+
368
+ ##
369
+ # Substitute _hash_'s keys with their associated values in _str_.
370
+
371
+ def replace_tokens(str, hash) #:nodoc:
372
+ hash.inject(str) do |string, (key, value)|
373
+ string.gsub ":#{key}", value.to_s
374
+ end
375
+ end
376
+
377
+ ##
378
+ # = Progress Bar
379
+ #
380
+ # Terminal progress bar utility. In its most basic form
381
+ # requires that the developer specifies when the bar should
382
+ # be incremented. Note that a hash of tokens may be passed to
383
+ # #increment, (or returned when using Object#progress).
384
+ #
385
+ # uris = %w(
386
+ # http://vision-media.ca
387
+ # http://yahoo.com
388
+ # http://google.com
389
+ # )
390
+ #
391
+ # bar = Commander::UI::ProgressBar.new uris.length, options
392
+ # threads = []
393
+ # uris.each do |uri|
394
+ # threads << Thread.new do
395
+ # begin
396
+ # res = open uri
397
+ # bar.increment :uri => uri
398
+ # rescue Exception => e
399
+ # bar.increment :uri => "#{uri} failed"
400
+ # end
401
+ # end
402
+ # end
403
+ # threads.each { |t| t.join }
404
+ #
405
+ # The Object method #progress is also available:
406
+ #
407
+ # progress uris, :width => 10 do |uri|
408
+ # res = open uri
409
+ # { :uri => uri } # Can now use :uri within :format option
410
+ # end
411
+ #
412
+
413
+ class ProgressBar
414
+ ##
415
+ # Creates a new progress bar.
416
+ #
417
+ # === Options
418
+ #
419
+ # :title Title, defaults to "Progress"
420
+ # :width Width of :progress_bar
421
+ # :progress_str Progress string, defaults to "="
422
+ # :incomplete_str Incomplete bar string, defaults to '.'
423
+ # :format Defaults to ":title |:progress_bar| :percent_complete% complete "
424
+ # :tokens Additional tokens replaced within the format string
425
+ # :complete_message Defaults to "Process complete"
426
+ #
427
+ # === Tokens
428
+ #
429
+ # :title
430
+ # :percent_complete
431
+ # :progress_bar
432
+ # :step
433
+ # :steps_remaining
434
+ # :total_steps
435
+ # :time_elapsed
436
+ # :time_remaining
437
+ #
438
+
439
+ def initialize(total, options = {})
440
+ @total_steps, @step, @start_time = total, 0, Time.now
441
+ @title = options.fetch :title, 'Progress'
442
+ @width = options.fetch :width, 25
443
+ @progress_str = options.fetch :progress_str, '='
444
+ @incomplete_str = options.fetch :incomplete_str, '.'
445
+ @complete_message = options.fetch :complete_message, 'Process complete'
446
+ @format = options.fetch :format, ':title |:progress_bar| :percent_complete% complete '
447
+ @tokens = options.fetch :tokens, {}
448
+ end
449
+
450
+ ##
451
+ # Completion percentage.
452
+
453
+ def percent_complete
454
+ if @total_steps.zero?
455
+ 100
456
+ else
457
+ @step * 100 / @total_steps
458
+ end
459
+ end
460
+
461
+ ##
462
+ # Time that has elapsed since the operation started.
463
+
464
+ def time_elapsed
465
+ Time.now - @start_time
466
+ end
467
+
468
+ ##
469
+ # Estimated time remaining.
470
+
471
+ def time_remaining
472
+ (time_elapsed / @step) * steps_remaining
473
+ end
474
+
475
+ ##
476
+ # Number of steps left.
477
+
478
+ def steps_remaining
479
+ @total_steps - @step
480
+ end
481
+
482
+ ##
483
+ # Formatted progress bar.
484
+
485
+ def progress_bar
486
+ (@progress_str * (@width * percent_complete / 100)).ljust @width, @incomplete_str
487
+ end
488
+
489
+ ##
490
+ # Generates tokens for this step.
491
+
492
+ def generate_tokens
493
+ {
494
+ title: @title,
495
+ percent_complete: percent_complete,
496
+ progress_bar: progress_bar,
497
+ step: @step,
498
+ steps_remaining: steps_remaining,
499
+ total_steps: @total_steps,
500
+ time_elapsed: format('%0.2fs', time_elapsed),
501
+ time_remaining: @step > 0 ? format('%0.2fs', time_remaining) : '',
502
+ }.merge! @tokens
503
+ end
504
+
505
+ ##
506
+ # Output the progress bar.
507
+
508
+ def show
509
+ return if finished?
510
+ erase_line
511
+ if completed?
512
+ $terminal.say UI.replace_tokens(@complete_message, generate_tokens) if @complete_message.is_a? String
513
+ else
514
+ $terminal.say UI.replace_tokens(@format, generate_tokens) << ' '
515
+ end
516
+ end
517
+
518
+ ##
519
+ # Whether or not the operation is complete, and we have finished.
520
+
521
+ def finished?
522
+ @step == @total_steps + 1
523
+ end
524
+
525
+ ##
526
+ # Whether or not the operation has completed.
527
+
528
+ def completed?
529
+ @step == @total_steps
530
+ end
531
+
532
+ ##
533
+ # Increment progress. Optionally pass _tokens_ which
534
+ # can be displayed in the output format.
535
+
536
+ def increment(tokens = {})
537
+ @step += 1
538
+ @tokens.merge! tokens if tokens.is_a? Hash
539
+ show
540
+ end
541
+
542
+ ##
543
+ # Erase previous terminal line.
544
+
545
+ def erase_line
546
+ # highline does not expose the output stream
547
+ $terminal.instance_variable_get('@output').print "\r\e[K"
548
+ end
549
+ end
550
+ end
551
+ end