commander-fastlane 4.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +37 -0
  5. data/.rubocop_todo.yml +77 -0
  6. data/.travis.yml +12 -0
  7. data/DEVELOPMENT +15 -0
  8. data/Gemfile +3 -0
  9. data/History.rdoc +428 -0
  10. data/LICENSE +22 -0
  11. data/Manifest +38 -0
  12. data/README.md +460 -0
  13. data/Rakefile +13 -0
  14. data/bin/commander +104 -0
  15. data/commander.gemspec +31 -0
  16. data/lib/commander.rb +35 -0
  17. data/lib/commander/blank.rb +7 -0
  18. data/lib/commander/command.rb +210 -0
  19. data/lib/commander/configure.rb +14 -0
  20. data/lib/commander/core_ext.rb +2 -0
  21. data/lib/commander/core_ext/array.rb +24 -0
  22. data/lib/commander/core_ext/object.rb +8 -0
  23. data/lib/commander/delegates.rb +25 -0
  24. data/lib/commander/help_formatters.rb +49 -0
  25. data/lib/commander/help_formatters/base.rb +24 -0
  26. data/lib/commander/help_formatters/terminal.rb +19 -0
  27. data/lib/commander/help_formatters/terminal/command_help.erb +35 -0
  28. data/lib/commander/help_formatters/terminal/help.erb +44 -0
  29. data/lib/commander/help_formatters/terminal_compact.rb +11 -0
  30. data/lib/commander/help_formatters/terminal_compact/command_help.erb +27 -0
  31. data/lib/commander/help_formatters/terminal_compact/help.erb +35 -0
  32. data/lib/commander/import.rb +5 -0
  33. data/lib/commander/methods.rb +11 -0
  34. data/lib/commander/platform.rb +7 -0
  35. data/lib/commander/runner.rb +484 -0
  36. data/lib/commander/user_interaction.rb +528 -0
  37. data/lib/commander/version.rb +3 -0
  38. data/spec/command_spec.rb +157 -0
  39. data/spec/configure_spec.rb +37 -0
  40. data/spec/core_ext/array_spec.rb +18 -0
  41. data/spec/core_ext/object_spec.rb +19 -0
  42. data/spec/help_formatters/terminal_compact_spec.rb +195 -0
  43. data/spec/help_formatters/terminal_spec.rb +190 -0
  44. data/spec/methods_spec.rb +20 -0
  45. data/spec/runner_spec.rb +646 -0
  46. data/spec/spec_helper.rb +78 -0
  47. data/spec/ui_spec.rb +30 -0
  48. metadata +175 -0
@@ -0,0 +1,528 @@
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].freeze
328
+ # All special cases in HighLine::Question#convert, except those that implement #parse
329
+ (
330
+ [Float, Integer, String, Symbol, Regexp, Array, File, Pathname] +
331
+ # All Classes that respond to #parse
332
+ # Ignore constants that trigger deprecation warnings
333
+ (Object.constants - DEPRECATED_CONSTANTS).map do |const|
334
+ Object.const_get(const)
335
+ end.select do |const|
336
+ const.class == Class && const.respond_to?(:parse)
337
+ end
338
+ ).each do |klass|
339
+ define_method "ask_for_#{klass.to_s.downcase}" do |prompt|
340
+ $terminal.ask(prompt, klass)
341
+ end
342
+ end
343
+ end
344
+
345
+ ##
346
+ # Substitute _hash_'s keys with their associated values in _str_.
347
+
348
+ def replace_tokens(str, hash) #:nodoc:
349
+ hash.inject(str) do |string, (key, value)|
350
+ string.gsub ":#{key}", value.to_s
351
+ end
352
+ end
353
+
354
+ ##
355
+ # = Progress Bar
356
+ #
357
+ # Terminal progress bar utility. In its most basic form
358
+ # requires that the developer specifies when the bar should
359
+ # be incremented. Note that a hash of tokens may be passed to
360
+ # #increment, (or returned when using Object#progress).
361
+ #
362
+ # uris = %w(
363
+ # http://vision-media.ca
364
+ # http://yahoo.com
365
+ # http://google.com
366
+ # )
367
+ #
368
+ # bar = Commander::UI::ProgressBar.new uris.length, options
369
+ # threads = []
370
+ # uris.each do |uri|
371
+ # threads << Thread.new do
372
+ # begin
373
+ # res = open uri
374
+ # bar.increment :uri => uri
375
+ # rescue Exception => e
376
+ # bar.increment :uri => "#{uri} failed"
377
+ # end
378
+ # end
379
+ # end
380
+ # threads.each { |t| t.join }
381
+ #
382
+ # The Object method #progress is also available:
383
+ #
384
+ # progress uris, :width => 10 do |uri|
385
+ # res = open uri
386
+ # { :uri => uri } # Can now use :uri within :format option
387
+ # end
388
+ #
389
+
390
+ class ProgressBar
391
+ ##
392
+ # Creates a new progress bar.
393
+ #
394
+ # === Options
395
+ #
396
+ # :title Title, defaults to "Progress"
397
+ # :width Width of :progress_bar
398
+ # :progress_str Progress string, defaults to "="
399
+ # :incomplete_str Incomplete bar string, defaults to '.'
400
+ # :format Defaults to ":title |:progress_bar| :percent_complete% complete "
401
+ # :tokens Additional tokens replaced within the format string
402
+ # :complete_message Defaults to "Process complete"
403
+ #
404
+ # === Tokens
405
+ #
406
+ # :title
407
+ # :percent_complete
408
+ # :progress_bar
409
+ # :step
410
+ # :steps_remaining
411
+ # :total_steps
412
+ # :time_elapsed
413
+ # :time_remaining
414
+ #
415
+
416
+ def initialize(total, options = {})
417
+ @total_steps, @step, @start_time = total, 0, Time.now
418
+ @title = options.fetch :title, 'Progress'
419
+ @width = options.fetch :width, 25
420
+ @progress_str = options.fetch :progress_str, '='
421
+ @incomplete_str = options.fetch :incomplete_str, '.'
422
+ @complete_message = options.fetch :complete_message, 'Process complete'
423
+ @format = options.fetch :format, ':title |:progress_bar| :percent_complete% complete '
424
+ @tokens = options.fetch :tokens, {}
425
+ end
426
+
427
+ ##
428
+ # Completion percentage.
429
+
430
+ def percent_complete
431
+ if @total_steps.zero?
432
+ 100
433
+ else
434
+ @step * 100 / @total_steps
435
+ end
436
+ end
437
+
438
+ ##
439
+ # Time that has elapsed since the operation started.
440
+
441
+ def time_elapsed
442
+ Time.now - @start_time
443
+ end
444
+
445
+ ##
446
+ # Estimated time remaining.
447
+
448
+ def time_remaining
449
+ (time_elapsed / @step) * steps_remaining
450
+ end
451
+
452
+ ##
453
+ # Number of steps left.
454
+
455
+ def steps_remaining
456
+ @total_steps - @step
457
+ end
458
+
459
+ ##
460
+ # Formatted progress bar.
461
+
462
+ def progress_bar
463
+ (@progress_str * (@width * percent_complete / 100)).ljust @width, @incomplete_str
464
+ end
465
+
466
+ ##
467
+ # Generates tokens for this step.
468
+
469
+ def generate_tokens
470
+ {
471
+ title: @title,
472
+ percent_complete: percent_complete,
473
+ progress_bar: progress_bar,
474
+ step: @step,
475
+ steps_remaining: steps_remaining,
476
+ total_steps: @total_steps,
477
+ time_elapsed: format('%0.2fs', time_elapsed),
478
+ time_remaining: @step > 0 ? format('%0.2fs', time_remaining) : '',
479
+ }.merge! @tokens
480
+ end
481
+
482
+ ##
483
+ # Output the progress bar.
484
+
485
+ def show
486
+ return if finished?
487
+ erase_line
488
+ if completed?
489
+ $terminal.say UI.replace_tokens(@complete_message, generate_tokens) if @complete_message.is_a? String
490
+ else
491
+ $terminal.say UI.replace_tokens(@format, generate_tokens) << ' '
492
+ end
493
+ end
494
+
495
+ ##
496
+ # Whether or not the operation is complete, and we have finished.
497
+
498
+ def finished?
499
+ @step == @total_steps + 1
500
+ end
501
+
502
+ ##
503
+ # Whether or not the operation has completed.
504
+
505
+ def completed?
506
+ @step == @total_steps
507
+ end
508
+
509
+ ##
510
+ # Increment progress. Optionally pass _tokens_ which
511
+ # can be displayed in the output format.
512
+
513
+ def increment(tokens = {})
514
+ @step += 1
515
+ @tokens.merge! tokens if tokens.is_a? Hash
516
+ show
517
+ end
518
+
519
+ ##
520
+ # Erase previous terminal line.
521
+
522
+ def erase_line
523
+ # highline does not expose the output stream
524
+ $terminal.instance_variable_get('@output').print "\r\e[K"
525
+ end
526
+ end
527
+ end
528
+ end