commander 4.4.6 → 4.6.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -6
  3. data/.rubocop_todo.yml +4 -4
  4. data/.travis.yml +7 -8
  5. data/Gemfile +2 -0
  6. data/History.rdoc +25 -0
  7. data/README.md +6 -6
  8. data/Rakefile +3 -1
  9. data/bin/commander +2 -1
  10. data/commander.gemspec +13 -10
  11. data/lib/commander.rb +2 -0
  12. data/lib/commander/blank.rb +2 -0
  13. data/lib/commander/command.rb +10 -5
  14. data/lib/commander/configure.rb +2 -0
  15. data/lib/commander/core_ext.rb +2 -0
  16. data/lib/commander/core_ext/array.rb +3 -1
  17. data/lib/commander/core_ext/object.rb +2 -0
  18. data/lib/commander/delegates.rb +3 -1
  19. data/lib/commander/help_formatters.rb +3 -1
  20. data/lib/commander/help_formatters/base.rb +2 -0
  21. data/lib/commander/help_formatters/terminal.rb +7 -1
  22. data/lib/commander/help_formatters/terminal/command_help.erb +6 -6
  23. data/lib/commander/help_formatters/terminal/help.erb +7 -7
  24. data/lib/commander/help_formatters/terminal_compact.rb +7 -1
  25. data/lib/commander/import.rb +2 -0
  26. data/lib/commander/methods.rb +4 -2
  27. data/lib/commander/platform.rb +2 -0
  28. data/lib/commander/runner.rb +44 -36
  29. data/lib/commander/user_interaction.rb +27 -21
  30. data/lib/commander/version.rb +3 -1
  31. data/spec/command_spec.rb +29 -0
  32. data/spec/configure_spec.rb +2 -0
  33. data/spec/core_ext/array_spec.rb +3 -1
  34. data/spec/core_ext/object_spec.rb +2 -0
  35. data/spec/help_formatters/terminal_compact_spec.rb +2 -0
  36. data/spec/help_formatters/terminal_spec.rb +2 -0
  37. data/spec/methods_spec.rb +5 -3
  38. data/spec/runner_spec.rb +123 -8
  39. data/spec/spec_helper.rb +15 -4
  40. data/spec/ui_spec.rb +4 -2
  41. metadata +34 -30
@@ -1,34 +1,34 @@
1
- <%= $terminal.color "NAME", :bold %>:
1
+ <%= HighLine.default_instance.color "NAME", :bold %>:
2
2
 
3
3
  <%= program :name %>
4
4
 
5
- <%= $terminal.color "DESCRIPTION", :bold %>:
5
+ <%= HighLine.default_instance.color "DESCRIPTION", :bold %>:
6
6
 
7
7
  <%= Commander::HelpFormatter.indent 4, program(:description) %>
8
8
 
9
- <%= $terminal.color "COMMANDS", :bold %>:
9
+ <%= HighLine.default_instance.color "COMMANDS", :bold %>:
10
10
  <% for name, command in @commands.sort -%>
11
11
  <% unless alias? name %>
12
12
  <%= "%-#{max_command_length}s %s" % [command.name, command.summary || command.description] -%>
13
13
  <% end -%>
14
14
  <% end %>
15
15
  <% unless @aliases.empty? %>
16
- <%= $terminal.color "ALIASES", :bold %>:
16
+ <%= HighLine.default_instance.color "ALIASES", :bold %>:
17
17
  <% for alias_name, args in @aliases.sort %>
18
18
  <%= "%-#{max_aliases_length}s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] -%>
19
19
  <% end %>
20
20
  <% end %>
21
21
  <% unless @options.empty? -%>
22
- <%= $terminal.color "GLOBAL OPTIONS", :bold %>:
22
+ <%= HighLine.default_instance.color "GLOBAL OPTIONS", :bold %>:
23
23
  <% for option in @options -%>
24
24
 
25
- <%= option[:switches].join ', ' %>
25
+ <%= option[:switches].join ', ' %>
26
26
  <%= option[:description] %>
27
27
  <% end -%>
28
28
  <% end -%>
29
29
  <% if program :help -%>
30
30
  <% for title, body in program(:help) %>
31
- <%= $terminal.color title.to_s.upcase, :bold %>:
31
+ <%= HighLine.default_instance.color title.to_s.upcase, :bold %>:
32
32
 
33
33
  <%= body %>
34
34
  <% end -%>
@@ -1,10 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  module Commander
4
6
  module HelpFormatter
5
7
  class TerminalCompact < Terminal
6
8
  def template(name)
7
- ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal_compact', "#{name}.erb")), nil, '-')
9
+ if RUBY_VERSION < '2.6'
10
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal_compact', "#{name}.erb")), nil, '-')
11
+ else
12
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal_compact', "#{name}.erb")), trim_mode: '-')
13
+ end
8
14
  end
9
15
  end
10
16
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'commander'
2
4
 
3
5
  include Commander::Methods
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Commander
2
4
  module Methods
3
5
  include Commander::UI
4
6
  include Commander::UI::AskForClass
5
7
  include Commander::Delegates
6
8
 
7
- if $stdin.tty? && (cols = $terminal.output_cols) >= 40
8
- $terminal.wrap_at = cols - 5
9
+ if $stdin.tty? && (cols = HighLine.default_instance.output_cols) >= 40
10
+ HighLine.default_instance.wrap_at = cols - 5
9
11
  end
10
12
  end
11
13
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Commander
2
4
  module Platform
3
5
  def self.jruby?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
 
3
5
  module Commander
@@ -7,22 +9,10 @@ module Commander
7
9
  #++
8
10
 
9
11
  class CommandError < StandardError; end
10
- class InvalidCommandError < CommandError; end
11
-
12
- ##
13
- # Array of commands.
14
-
15
- attr_reader :commands
16
12
 
17
- ##
18
- # Global options.
19
-
20
- attr_reader :options
21
-
22
- ##
23
- # Hash of help formatter aliases.
13
+ class InvalidCommandError < CommandError; end
24
14
 
25
- attr_reader :help_formatter_aliases
15
+ attr_reader :commands, :options, :help_formatter_aliases
26
16
 
27
17
  ##
28
18
  # Initialize a new command runner. Optionally
@@ -41,7 +31,7 @@ module Commander
41
31
  # Return singleton Runner instance.
42
32
 
43
33
  def self.instance
44
- @singleton ||= new
34
+ @instance ||= new
45
35
  end
46
36
 
47
37
  ##
@@ -76,7 +66,7 @@ module Commander
76
66
  OptionParser::InvalidArgument,
77
67
  OptionParser::MissingArgument => e
78
68
  abort e.to_s
79
- rescue => e
69
+ rescue StandardError => e
80
70
  if @never_trace
81
71
  abort "error: #{e}."
82
72
  else
@@ -231,21 +221,23 @@ module Commander
231
221
  # Get active command within arguments passed to this runner.
232
222
 
233
223
  def active_command
234
- @__active_command ||= command(command_name_from_args)
224
+ @active_command ||= command(command_name_from_args)
235
225
  end
236
226
 
237
227
  ##
238
228
  # Attempts to locate a command name from within the arguments.
239
229
  # Supports multi-word commands, using the largest possible match.
230
+ # Returns the default command, if no valid commands found in the args.
240
231
 
241
232
  def command_name_from_args
242
- @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
233
+ @command_name_from_args ||= (longest_valid_command_name_from(@args) || @default_command)
243
234
  end
244
235
 
245
236
  ##
246
237
  # Returns array of valid command names found within _args_.
247
238
 
248
239
  def valid_command_names_from(*args)
240
+ remove_global_options options, args
249
241
  arg_string = args.delete_if { |value| value =~ /^-/ }.join ' '
250
242
  commands.keys.find_all { |name| name if arg_string =~ /^#{name}\b/ }
251
243
  end
@@ -254,7 +246,7 @@ module Commander
254
246
  # Help formatter instance.
255
247
 
256
248
  def help_formatter
257
- @__help_formatter ||= program(:help_formatter).new self
249
+ @help_formatter ||= program(:help_formatter).new self
258
250
  end
259
251
 
260
252
  ##
@@ -303,7 +295,7 @@ module Commander
303
295
  if args.empty?
304
296
  say help_formatter.render
305
297
  else
306
- command = command args.join(' ')
298
+ command = command(longest_valid_command_name_from(args))
307
299
  begin
308
300
  require_valid_command command
309
301
  rescue InvalidCommandError => e
@@ -328,26 +320,32 @@ module Commander
328
320
  # again for the command.
329
321
 
330
322
  def remove_global_options(options, args)
331
- # TODO: refactor with flipflop, please TJ ! have time to refactor me !
332
323
  options.each do |option|
333
- switches = option[:switches].dup
324
+ switches = option[:switches]
334
325
  next if switches.empty?
335
326
 
336
- if (switch_has_arg = switches.any? { |s| s =~ /[ =]/ })
337
- switches.map! { |s| s[0, s.index('=') || s.index(' ') || s.length] }
338
- end
339
-
327
+ option_takes_argument = switches.any? { |s| s =~ /[ =]/ }
340
328
  switches = expand_optionally_negative_switches(switches)
341
329
 
342
- past_switch, arg_removed = false, false
343
- args.delete_if do |arg|
344
- if switches.any? { |s| s[0, arg.length] == arg }
345
- arg_removed = !switch_has_arg
346
- past_switch = true
347
- elsif past_switch && !arg_removed && arg !~ /^-/
348
- arg_removed = true
330
+ option_argument_needs_removal = false
331
+ args.delete_if do |token|
332
+ break if token == '--'
333
+
334
+ # Use just the portion of the token before the = when
335
+ # comparing switches.
336
+ index_of_equals = token.index('=') if option_takes_argument
337
+ token = token[0, index_of_equals] if index_of_equals
338
+ token_contains_option_argument = !index_of_equals.nil?
339
+
340
+ if switches.any? { |s| s[0, token.length] == token }
341
+ option_argument_needs_removal =
342
+ option_takes_argument && !token_contains_option_argument
343
+ true
344
+ elsif option_argument_needs_removal && token !~ /^-/
345
+ option_argument_needs_removal = false
346
+ true
349
347
  else
350
- arg_removed = true
348
+ option_argument_needs_removal = false
351
349
  false
352
350
  end
353
351
  end
@@ -395,7 +393,7 @@ module Commander
395
393
  def global_option_proc(switches, &block)
396
394
  lambda do |value|
397
395
  unless active_command.nil?
398
- active_command.proxy_options << [Runner.switch_to_sym(switches.last), value]
396
+ active_command.global_options << [Runner.switch_to_sym(switches.last), value]
399
397
  end
400
398
  yield value if block && !value.nil?
401
399
  end
@@ -448,7 +446,17 @@ module Commander
448
446
  end
449
447
 
450
448
  def say(*args) #:nodoc:
451
- $terminal.say(*args)
449
+ HighLine.default_instance.say(*args)
450
+ end
451
+
452
+ private
453
+
454
+ ##
455
+ # Attempts to locate a command name from within the provided arguments.
456
+ # Supports multi-word commands, using the largest possible match.
457
+
458
+ def longest_valid_command_name_from(args)
459
+ valid_command_names_from(*args.dup).max
452
460
  end
453
461
  end
454
462
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tempfile'
2
4
  require 'shellwords'
3
5
 
@@ -66,7 +68,7 @@ module Commander
66
68
 
67
69
  def say_ok(*args)
68
70
  args.each do |arg|
69
- say $terminal.color(arg, :green)
71
+ say HighLine.default_instance.color(arg, :green)
70
72
  end
71
73
  end
72
74
 
@@ -80,7 +82,7 @@ module Commander
80
82
 
81
83
  def say_warning(*args)
82
84
  args.each do |arg|
83
- say $terminal.color(arg, :yellow)
85
+ say HighLine.default_instance.color(arg, :yellow)
84
86
  end
85
87
  end
86
88
 
@@ -94,7 +96,7 @@ module Commander
94
96
 
95
97
  def say_error(*args)
96
98
  args.each do |arg|
97
- say $terminal.color(arg, :red)
99
+ say HighLine.default_instance.color(arg, :red)
98
100
  end
99
101
  end
100
102
 
@@ -113,7 +115,7 @@ module Commander
113
115
  # * highligh: on_<color>
114
116
 
115
117
  def color(*args)
116
- say $terminal.color(*args)
118
+ say HighLine.default_instance.color(*args)
117
119
  end
118
120
 
119
121
  ##
@@ -218,20 +220,16 @@ module Commander
218
220
  #
219
221
 
220
222
  def io(input = nil, output = nil, &block)
223
+ orig_stdin, orig_stdout = $stdin, $stdout
221
224
  $stdin = File.new(input) if input
222
225
  $stdout = File.new(output, 'r+') if output
223
226
  return unless block
227
+
224
228
  yield
229
+ $stdin, $stdout = orig_stdin, orig_stdout
225
230
  reset_io
226
231
  end
227
232
 
228
- ##
229
- # Reset IO to initial constant streams.
230
-
231
- def reset_io
232
- $stdin, $stdout = STDIN, STDOUT
233
- end
234
-
235
233
  ##
236
234
  # Find an editor available in path. Optionally supply the _preferred_
237
235
  # editor. Returns the name as a string, nil if none is available.
@@ -274,6 +272,7 @@ module Commander
274
272
  def enable_paging
275
273
  return unless $stdout.tty?
276
274
  return unless Process.respond_to? :fork
275
+
277
276
  read, write = IO.pipe
278
277
 
279
278
  # Kernel.fork is not supported on all platforms and configurations.
@@ -324,12 +323,12 @@ module Commander
324
323
  # Implements ask_for_CLASS methods.
325
324
 
326
325
  module AskForClass
327
- DEPRECATED_CONSTANTS = [:Config, :TimeoutError, :MissingSourceFile, :NIL, :TRUE, :FALSE, :Fixnum, :Bignum, :Data].freeze
326
+ DEPRECATED_CONSTANTS = %i[Config TimeoutError MissingSourceFile NIL TRUE FALSE Fixnum Bignum Data].freeze
328
327
 
329
328
  # define methods for common classes
330
329
  [Float, Integer, String, Symbol, Regexp, Array, File, Pathname].each do |klass|
331
330
  define_method "ask_for_#{klass.to_s.downcase}" do |prompt|
332
- $terminal.ask(prompt, klass)
331
+ HighLine.default_instance.ask(prompt, klass)
333
332
  end
334
333
  end
335
334
 
@@ -338,20 +337,26 @@ module Commander
338
337
  if arguments.count != 1
339
338
  fail ArgumentError, "wrong number of arguments (given #{arguments.count}, expected 1)"
340
339
  end
340
+
341
341
  prompt = arguments.first
342
342
  requested_class = Regexp.last_match[1]
343
343
 
344
344
  # All Classes that respond to #parse
345
345
  # Ignore constants that trigger deprecation warnings
346
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)
347
+ begin
348
+ Object.const_get(const)
349
+ rescue RuntimeError
350
+ # Rescue errors in Ruby 3 for SortedSet:
351
+ # The `SortedSet` class has been extracted from the `set` library.
352
+ end
353
+ end.compact.select do |const|
354
+ const.instance_of?(Class) && const.respond_to?(:parse)
350
355
  end
351
356
 
352
357
  klass = available_classes.find { |k| k.to_s.downcase == requested_class }
353
358
  if klass
354
- $terminal.ask(prompt, klass)
359
+ HighLine.default_instance.ask(prompt, klass)
355
360
  else
356
361
  super
357
362
  end
@@ -498,7 +503,7 @@ module Commander
498
503
  steps_remaining: steps_remaining,
499
504
  total_steps: @total_steps,
500
505
  time_elapsed: format('%0.2fs', time_elapsed),
501
- time_remaining: @step > 0 ? format('%0.2fs', time_remaining) : '',
506
+ time_remaining: @step.positive? ? format('%0.2fs', time_remaining) : '',
502
507
  }.merge! @tokens
503
508
  end
504
509
 
@@ -507,11 +512,12 @@ module Commander
507
512
 
508
513
  def show
509
514
  return if finished?
515
+
510
516
  erase_line
511
517
  if completed?
512
- $terminal.say UI.replace_tokens(@complete_message, generate_tokens) if @complete_message.is_a? String
518
+ HighLine.default_instance.say UI.replace_tokens(@complete_message, generate_tokens) if @complete_message.is_a? String
513
519
  else
514
- $terminal.say UI.replace_tokens(@format, generate_tokens) << ' '
520
+ HighLine.default_instance.say UI.replace_tokens(@format, generate_tokens) << ' '
515
521
  end
516
522
  end
517
523
 
@@ -544,7 +550,7 @@ module Commander
544
550
 
545
551
  def erase_line
546
552
  # highline does not expose the output stream
547
- $terminal.instance_variable_get('@output').print "\r\e[K"
553
+ HighLine.default_instance.instance_variable_get('@output').print "\r\e[K"
548
554
  end
549
555
  end
550
556
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Commander
2
- VERSION = '4.4.6'.freeze
4
+ VERSION = '4.6.0'
3
5
  end
data/spec/command_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Commander::Command do
@@ -164,6 +166,33 @@ describe Commander::Command do
164
166
  end
165
167
  @command.run '--interval', '15'
166
168
  end
169
+
170
+ describe 'given a global option' do
171
+ before do
172
+ @command.global_options << [:global_option, 'gvalue']
173
+ end
174
+
175
+ describe 'and no command specific arguments' do
176
+ it 'provides the global option to the command action' do
177
+ @command.when_called { |_, options| expect(options.global_option).to eq('gvalue') }
178
+ @command.run
179
+ end
180
+ end
181
+
182
+ describe 'and a command specific option' do
183
+ it 'provides the global option to the command action' do
184
+ @command.when_called { |_, options| expect(options.global_option).to eq('gvalue') }
185
+ @command.run '--verbose'
186
+ end
187
+ end
188
+
189
+ describe 'and a command specific argument' do
190
+ it 'provides the global option to the command action' do
191
+ @command.when_called { |_, options| expect(options.global_option).to eq('gvalue') }
192
+ @command.run 'argument'
193
+ end
194
+ end
195
+ end
167
196
  end
168
197
  end
169
198
  end