commander-openflighthpc 2.0.1 → 2.2.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -138
  3. data/commander-openflighthpc.gemspec +2 -1
  4. data/lib/commander.rb +3 -4
  5. data/lib/commander/cli.rb +15 -76
  6. data/lib/commander/command.rb +81 -176
  7. data/lib/commander/error_handler.rb +62 -0
  8. data/lib/commander/help_formatters.rb +0 -4
  9. data/lib/commander/help_formatters/terminal.rb +0 -5
  10. data/lib/commander/help_formatters/terminal/command_help.erb +14 -6
  11. data/lib/commander/help_formatters/terminal/help.erb +12 -4
  12. data/lib/commander/runner.rb +92 -130
  13. data/lib/commander/version.rb +1 -1
  14. metadata +21 -46
  15. data/bin/commander +0 -104
  16. data/lib/commander/blank.rb +0 -7
  17. data/lib/commander/core_ext.rb +0 -2
  18. data/lib/commander/core_ext/array.rb +0 -24
  19. data/lib/commander/core_ext/object.rb +0 -8
  20. data/lib/commander/help_formatters/terminal/subcommand_help.erb +0 -23
  21. data/lib/commander/help_formatters/terminal_compact.rb +0 -11
  22. data/lib/commander/help_formatters/terminal_compact/command_help.erb +0 -26
  23. data/lib/commander/help_formatters/terminal_compact/help.erb +0 -29
  24. data/lib/commander/help_formatters/terminal_compact/subcommand_help.erb +0 -15
  25. data/lib/commander/import.rb +0 -5
  26. data/lib/commander/patches/decimal-integer.rb +0 -17
  27. data/lib/commander/patches/help_formatter_binding.rb +0 -15
  28. data/lib/commander/patches/implicit-short-tags.rb +0 -75
  29. data/lib/commander/patches/priority_sort.rb +0 -21
  30. data/lib/commander/patches/validate_inputs.rb +0 -79
  31. data/lib/commander/platform.rb +0 -7
  32. data/spec/command_spec.rb +0 -157
  33. data/spec/configure_spec.rb +0 -37
  34. data/spec/core_ext/array_spec.rb +0 -18
  35. data/spec/core_ext/object_spec.rb +0 -19
  36. data/spec/help_formatters/terminal_compact_spec.rb +0 -69
  37. data/spec/help_formatters/terminal_spec.rb +0 -67
  38. data/spec/methods_spec.rb +0 -61
  39. data/spec/patches/validate_inputs_spec.rb +0 -84
  40. data/spec/runner_spec.rb +0 -672
  41. data/spec/spec_helper.rb +0 -79
  42. data/spec/ui_spec.rb +0 -30
@@ -0,0 +1,62 @@
1
+ require 'paint'
2
+
3
+ module Commander
4
+ ##
5
+ # Internal error class to delay rendering help text
6
+ # This is required as the help command points directly to stdout
7
+ # In general this has a bit of a code smell to it, and should
8
+ # not be used publicly
9
+ class InternalCallableError < StandardError
10
+ attr_accessor :callable
11
+
12
+ def initialize(msg = nil, &block)
13
+ super(msg)
14
+ self.callable = block
15
+ end
16
+
17
+ def call
18
+ callable.call if callable
19
+ end
20
+ end
21
+
22
+ ErrorHandler = Struct.new(:program_name, :trace) do
23
+ def parse_trace(*raw_args)
24
+ # Do not modify the original array
25
+ args = raw_args.dup
26
+
27
+ # Determines if there is a --trace flag before a --
28
+ trace_index = args.index do |a|
29
+ if a == '--trace'
30
+ self.trace = true
31
+ elsif a == '--'
32
+ break
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ # Removes the --trace flag if required
39
+ args.tap { |a| a.delete_at(trace_index) if trace_index }
40
+ end
41
+
42
+ def start
43
+ yield(self) if block_given?
44
+ rescue => e
45
+ $stderr.puts e.full_message if trace
46
+
47
+ error_msg = "#{Paint[program_name, '#2794d8']}: #{Paint[e.to_s, :red, :bright]}"
48
+ exit_code = e.respond_to?(:exit_code) ? e.exit_code.to_i : 1
49
+ case e
50
+ when InternalCallableError
51
+ # See: https://shapeshed.com/unix-exit-codes/
52
+ exit_code = 126
53
+ $stderr.puts error_msg
54
+ e.call
55
+ else
56
+ $stderr.puts error_msg
57
+ end
58
+ exit(exit_code)
59
+ end
60
+ end
61
+ end
62
+
@@ -2,16 +2,12 @@ module Commander
2
2
  module HelpFormatter
3
3
  autoload :Base, 'commander/help_formatters/base'
4
4
  autoload :Terminal, 'commander/help_formatters/terminal'
5
- autoload :TerminalCompact, 'commander/help_formatters/terminal_compact'
6
5
 
7
6
  class Context
8
7
  def initialize(target)
9
8
  @target = target
10
9
  end
11
10
 
12
- # NOTE: `get_binding` has been stubbed! This version will be ignored
13
- # See patch for actual version
14
- prepend Patches::HelpFormatterBinding
15
11
  def get_binding
16
12
  @target.instance_eval { binding }.tap do |bind|
17
13
  decorate_binding(bind)
@@ -11,11 +11,6 @@ module Commander
11
11
  template(:command_help).result(Context.new(command).get_binding)
12
12
  end
13
13
 
14
- def render_subcommand(command)
15
- bind = ProgramContext.new(@runner).get_binding({cmd: command})
16
- template(:subcommand_help).result(bind)
17
- end
18
-
19
14
  def template(name)
20
15
  ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal', "#{name}.erb")), nil, '-')
21
16
  end
@@ -22,14 +22,22 @@
22
22
  <%= command %>
23
23
  <% end -%>
24
24
  <% end -%>
25
- <% unless @options.empty? -%>
25
+ <% unless slop.options.empty? -%>
26
26
 
27
27
  <%= $terminal.color "OPTIONS", :bold %>:
28
- <% for option in @options -%>
29
-
30
- <%= option[:switches].join ', ' %>
31
- <%= Commander::HelpFormatter.indent 8, option[:description] %><% if option[:default] %>
32
- <%= $terminal.color "Default", :bold %>: <%= option[:default] %><% end %>
28
+ <% slop.options.each do |option| -%>
29
+
30
+ <% tag = if [Slop::BoolOption, Slop::NullOption].include?(option.class)
31
+ nil
32
+ elsif meta = option.config[:meta]
33
+ meta
34
+ else
35
+ option.key.upcase
36
+ end
37
+ -%>
38
+ <%= option.flags.join ', ' %> <%= tag %>
39
+ <%= Commander::HelpFormatter.indent 8, option.desc %><% if option.default_value %>
40
+ <%= $terminal.color "Default", :bold %>: <%= option.default_value %><% end %>
33
41
  <% end -%>
34
42
  <% end -%>
35
43
 
@@ -28,12 +28,20 @@
28
28
  <%= "%-#{max_aliases_length}s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] -%>
29
29
  <% end %>
30
30
  <% end %>
31
- <% unless @help_options.empty? -%>
31
+ <% unless global_slop.options.empty? -%>
32
32
  <%= $terminal.color "GLOBAL OPTIONS", :bold %>:
33
- <% for option in @help_options -%>
33
+ <% global_slop.options.each do |global| -%>
34
34
 
35
- <%= option[:switches].join ', ' %>
36
- <%= option[:description] %>
35
+ <% tag = if [Slop::BoolOption, Slop::NullOption]
36
+ nil
37
+ elsif meta = option.config[:meta]
38
+ meta
39
+ else
40
+ option.key.upcase
41
+ end
42
+ -%>
43
+ <%= global.flags.join ', ' %> <%= tag %>
44
+ <%= Commander::HelpFormatter.indent 8, global.desc %>
37
45
  <% end -%>
38
46
  <% end -%>
39
47
  <% if program :help -%>
@@ -1,4 +1,4 @@
1
- require 'paint'
1
+ require 'ostruct'
2
2
 
3
3
  module Commander
4
4
  class Runner
@@ -14,9 +14,8 @@ module Commander
14
14
  attr_reader :commands
15
15
 
16
16
  ##
17
- # Global options.
18
-
19
- attr_reader :options
17
+ # The global Slop Options
18
+ attr_reader :global_slop
20
19
 
21
20
  ##
22
21
  # Hash of help formatter aliases.
@@ -33,8 +32,8 @@ module Commander
33
32
 
34
33
  def initialize(*inputs)
35
34
  @program, @commands, @default_command, \
36
- @options, @aliases, @args = inputs.map(&:dup)
37
- @args.reject! { |a| a == '--trace' }
35
+ @global_slop, @aliases, @args = inputs.map(&:dup)
36
+
38
37
  @commands['help'] ||= Command.new('help').tap do |c|
39
38
  c.syntax = "#{program(:name)} help [command]"
40
39
  c.description = 'Display global or [command] help documentation'
@@ -49,15 +48,58 @@ module Commander
49
48
 
50
49
  ##
51
50
  # Run command parsing and execution process
52
- # NOTE: This method does not have error handling, see: run!
51
+ INBUILT_ERRORS = [
52
+ OptionParser::InvalidOption,
53
+ Command::CommandUsageError,
54
+ InvalidCommandError
55
+ ]
53
56
 
54
57
  def run
55
58
  require_program :version, :description
56
59
 
57
- parse_global_options
58
- remove_global_options options, @args
60
+ # Determine where the arguments/ options start
61
+ remaining_args = if alias? command_name_from_args
62
+ @aliases[command_name_from_args.to_s] + args_without_command_name
63
+ else
64
+ args_without_command_name
65
+ end
59
66
 
60
- run_active_command
67
+ # Combines the global and command options into a single parser
68
+ global_opts = global_slop.options
69
+ command_opts = active_command? ? active_command.slop.options : []
70
+ opts = [*global_opts, *command_opts]
71
+ parser = Slop::Parser.new(opts)
72
+
73
+ # Parsers the arguments/opts and fetches the config
74
+ parser.parse(remaining_args)
75
+ opts = OpenStruct.new parser.parse(remaining_args).to_h
76
+ remaining_args = parser.arguments
77
+ config = program(:config).dup
78
+
79
+ if opts.version
80
+ # Return the version
81
+ say version
82
+ exit 0
83
+ elsif opts.help && active_command?
84
+ # Return help for the active_command
85
+ run_help_command([active_command!.name])
86
+ elsif active_command?
87
+ # Run the active_command
88
+ active_command.run!(remaining_args, opts, config)
89
+ else
90
+ # Return generic help
91
+ run_help_command('')
92
+ end
93
+ rescue => original
94
+ error = original
95
+ if INBUILT_ERRORS.include?(error.class)
96
+ error = InternalCallableError.new(error.message) do
97
+ $stderr.puts "\nUsage:\n\n"
98
+ name = active_command? ? active_command.name : :error
99
+ run_help_command([name])
100
+ end
101
+ end
102
+ raise error
61
103
  end
62
104
 
63
105
  ##
@@ -100,20 +142,39 @@ module Commander
100
142
  @aliases.include? name.to_s
101
143
  end
102
144
 
103
- ##
104
- # Check if a command _name_ exists.
105
-
106
- def command_exists?(name)
107
- @commands[name.to_s]
108
- end
109
-
110
145
  #:stopdoc:
111
146
 
112
147
  ##
113
148
  # Get active command within arguments passed to this runner.
149
+ # It will try an run the default if arguments have been provided
150
+ # It can not run a default command that is flags-only
151
+ # This is to provide consistent behaviour to --help
152
+ #
114
153
 
115
154
  def active_command
116
- @__active_command ||= command(command_name_from_args)
155
+ @__active_command ||= begin
156
+ if named_command = command(command_name_from_args)
157
+ named_command
158
+ elsif default_command? && flagless_args_string.length > 0
159
+ default_command
160
+ end
161
+ end
162
+ end
163
+
164
+ def active_command!
165
+ active_command.tap { |c| require_valid_command(c) }
166
+ end
167
+
168
+ def active_command?
169
+ active_command ? true : false
170
+ end
171
+
172
+ def default_command
173
+ @__default_command ||= command(@default_command)
174
+ end
175
+
176
+ def default_command?
177
+ default_command ? true : false
117
178
  end
118
179
 
119
180
  ##
@@ -121,15 +182,20 @@ module Commander
121
182
  # Supports multi-word commands, using the largest possible match.
122
183
 
123
184
  def command_name_from_args
124
- @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
185
+ @__command_name_from_args ||= valid_command_names_from(*@args.dup).sort.last
186
+ end
187
+
188
+ def flagless_args_string
189
+ @flagless_args_string ||= @args.reject { |value| value =~ /^-/ }.join ' '
125
190
  end
126
191
 
127
192
  ##
128
193
  # Returns array of valid command names found within _args_.
129
194
 
130
195
  def valid_command_names_from(*args)
131
- arg_string = args.delete_if { |value| value =~ /^-/ }.join ' '
132
- commands.keys.find_all { |name| name if arg_string =~ /^#{name}\b/ }
196
+ commands.keys.find_all do |name|
197
+ name if flagless_args_string =~ /^#{name}(?![[:graph:]])/
198
+ end
133
199
  end
134
200
 
135
201
  ##
@@ -145,7 +211,7 @@ module Commander
145
211
  def args_without_command_name
146
212
  removed = []
147
213
  parts = command_name_from_args.split rescue []
148
- @args.dup.delete_if do |arg|
214
+ @args.reject do |arg|
149
215
  removed << arg if parts.include?(arg) && !removed.include?(arg)
150
216
  end
151
217
  end
@@ -155,37 +221,17 @@ module Commander
155
221
 
156
222
  def help_formatter_alias_defaults
157
223
  {
158
- compact: HelpFormatter::TerminalCompact,
159
224
  }
160
225
  end
161
226
 
162
- ##
163
- # Limit commands to those which are subcommands of the one that is active
164
- def limit_commands_to_subcommands(command)
165
- commands.select! do |other_sym, _|
166
- other = other_sym.to_s
167
- # Do not match sub-sub commands (matches for a second space)
168
- if /\A#{command.name}\s.*\s/.match?(other)
169
- false
170
- # Do match regular sub commands
171
- elsif /\A#{command.name}\s/.match?(other)
172
- true
173
- # Do not match any other commands
174
- else
175
- false
176
- end
177
- end
178
- end
179
-
180
227
  ##
181
228
  # Creates default commands such as 'help' which is
182
229
  # essentially the same as using the --help switch.
183
230
  def run_help_command(args)
184
231
  UI.enable_paging if program(:help_paging)
185
- @help_commands = @commands.dup
232
+ @help_commands = @commands.reject { |_, v| v.hidden(false) }.to_h
186
233
  if args.empty? || args[0] == :error
187
- @help_options = @options
188
- @help_commands.reject! { |k, v| !!v.hidden }
234
+ @help_options = []
189
235
  old_wrap = $terminal.wrap_at
190
236
  $terminal.wrap_at = nil
191
237
  program(:nobanner, true) if args[0] == :error
@@ -194,54 +240,17 @@ module Commander
194
240
  else
195
241
  command = command args.join(' ')
196
242
  require_valid_command command
197
- if command.sub_command_group?
198
- limit_commands_to_subcommands(command)
199
- say help_formatter.render_subcommand(command)
200
- else
201
- say help_formatter.render_command(command)
202
- end
243
+ say help_formatter.render_command(command)
203
244
  end
204
245
  end
205
246
 
206
247
  ##
207
248
  # Raises InvalidCommandError when a _command_ is not found.
208
249
 
209
- def require_valid_command(command = active_command)
250
+ def require_valid_command(command)
210
251
  fail InvalidCommandError, 'invalid command', caller if command.nil?
211
252
  end
212
253
 
213
- ##
214
- # Removes global _options_ from _args_. This prevents an invalid
215
- # option error from occurring when options are parsed
216
- # again for the command.
217
-
218
- def remove_global_options(options, args)
219
- # TODO: refactor with flipflop, please TJ ! have time to refactor me !
220
- options.each do |option|
221
- switches = option[:switches].dup
222
- next if switches.empty?
223
-
224
- if (switch_has_arg = switches.any? { |s| s =~ /[ =]/ })
225
- switches.map! { |s| s[0, s.index('=') || s.index(' ') || s.length] }
226
- end
227
-
228
- switches = expand_optionally_negative_switches(switches)
229
-
230
- past_switch, arg_removed = false, false
231
- args.delete_if do |arg|
232
- if switches.any? { |s| s == arg }
233
- arg_removed = !switch_has_arg
234
- past_switch = true
235
- elsif past_switch && !arg_removed && arg !~ /^-/
236
- arg_removed = true
237
- else
238
- arg_removed = true
239
- false
240
- end
241
- end
242
- end
243
- end
244
-
245
254
  # expand switches of the style '--[no-]blah' into both their
246
255
  # '--blah' and '--no-blah' variants, so that they can be
247
256
  # properly detected and removed
@@ -256,41 +265,6 @@ module Commander
256
265
  end
257
266
  end
258
267
 
259
- ##
260
- # Parse global command options.
261
-
262
- def parse_global_options
263
- parser = options.inject(OptionParser.new) do |options, option|
264
- options.on(*option[:args], &global_option_proc(option[:switches], &option[:proc]))
265
- end
266
-
267
- options = @args.dup
268
- begin
269
- parser.parse!(options)
270
- rescue OptionParser::InvalidOption => e
271
- # Remove the offending args and retry.
272
- options = options.reject { |o| e.args.include?(o) }
273
- retry
274
- end
275
- end
276
-
277
- ##
278
- # Returns a proc allowing for commands to inherit global options.
279
- # This functionality works whether a block is present for the global
280
- # option or not, so simple switches such as --verbose can be used
281
- # without a block, and used throughout all commands.
282
-
283
- def global_option_proc(switches, &block)
284
- lambda do |value|
285
- unless active_command.nil?
286
- active_command.proxy_options << [Runner.switch_to_sym(switches.last), value]
287
- end
288
- if block && !value.nil?
289
- instance_exec(value, &block)
290
- end
291
- end
292
- end
293
-
294
268
  ##
295
269
  # Raises a CommandError when the program any of the _keys_ are not present, or empty.
296
270
 
@@ -325,18 +299,6 @@ module Commander
325
299
  switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil
326
300
  end
327
301
 
328
- ##
329
- # Run the active command.
330
-
331
- def run_active_command
332
- require_valid_command
333
- if alias? command_name_from_args
334
- active_command.run(*(@aliases[command_name_from_args.to_s] + args_without_command_name))
335
- else
336
- active_command.run(*args_without_command_name)
337
- end
338
- end
339
-
340
302
  def say(*args) #:nodoc:
341
303
  $terminal.say(*args)
342
304
  end