commander-fastlane 4.4.3

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.
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