commander 4.4.6 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
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