commander-openflighthpc 2.0.2 → 2.1.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 +7 -87
  3. data/commander-openflighthpc.gemspec +1 -0
  4. data/lib/commander.rb +3 -4
  5. data/lib/commander/cli.rb +15 -76
  6. data/lib/commander/command.rb +89 -175
  7. data/lib/commander/error_handler.rb +72 -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 +12 -6
  11. data/lib/commander/help_formatters/terminal/help.erb +10 -4
  12. data/lib/commander/runner.rb +98 -129
  13. data/lib/commander/version.rb +1 -1
  14. metadata +19 -44
  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,72 @@
1
+ module Commander
2
+ ##
3
+ # Internal error class to delay rendering help text
4
+ # This is required as the help command pints directly to stdout
5
+ # In general this has a bit of a code smell to it, and should
6
+ # not be used publicly
7
+ class InternalCallableError < StandardError
8
+ attr_accessor :callable
9
+
10
+ def initialize(msg = nil, &block)
11
+ super(msg)
12
+ self.callable = block
13
+ end
14
+
15
+ def call
16
+ callable.call if callable
17
+ end
18
+ end
19
+
20
+ INTERRUPT_MSG = 'Received Interrupt!'
21
+
22
+ def self.traceable_error_handler(*args)
23
+ # Determines if there is a --trace flag before a --
24
+ trace_index = args.index do |a|
25
+ if a == '--trace'
26
+ true
27
+ elsif a == '--'
28
+ break
29
+ else
30
+ false
31
+ end
32
+ end
33
+
34
+ # Removes the --trace flag if required
35
+ new_args = args.dup
36
+ new_args.delete_at(trace_index) if trace_index
37
+
38
+ # Start the actual error handler
39
+ error_handler(!!trace_index) do
40
+ yield(new_args) if block_given?
41
+ end
42
+
43
+ rescue Interrupt
44
+ # Start Rescuing Interrupt Immediately
45
+ $stderr.puts INTERRUPT_MSG
46
+ exit 130
47
+ end
48
+
49
+ def self.error_handler(trace = false)
50
+ yield if block_given?
51
+ rescue StandardError, Interrupt => e
52
+ $stderr.puts e.full_message if trace
53
+
54
+ error_msg = e.message
55
+ exit_code = e.respond_to?(:exit_code) ? e.exit_code.to_i : 1
56
+ case e
57
+ when InternalCallableError
58
+ # See: https://shapeshed.com/unix-exit-codes/
59
+ exit_code = 126
60
+ $stderr.puts error_msg
61
+ e.call
62
+ when Interrupt
63
+ $stderr.puts INTERRUPT_MSG
64
+ # See: https://shapeshed.com/unix-exit-codes/
65
+ exit_code = 130
66
+ else
67
+ $stderr.puts error_msg
68
+ end
69
+ exit(exit_code)
70
+ end
71
+ end
72
+
@@ -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,20 @@
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
+ else
33
+ option.key.upcase
34
+ end
35
+ -%>
36
+ <%= option.flags.join ', ' %> <%= tag %>
37
+ <%= Commander::HelpFormatter.indent 8, option.desc %><% if option.default_value %>
38
+ <%= $terminal.color "Default", :bold %>: <%= option.default_value %><% end %>
33
39
  <% end -%>
34
40
  <% end -%>
35
41
 
@@ -28,12 +28,18 @@
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
+ else
38
+ option.key.upcase
39
+ end
40
+ -%>
41
+ <%= global.flags.join ', ' %> <%= tag %>
42
+ <%= global.desc %>
37
43
  <% end -%>
38
44
  <% end -%>
39
45
  <% if program :help -%>
@@ -1,4 +1,5 @@
1
1
  require 'paint'
2
+ require 'ostruct'
2
3
 
3
4
  module Commander
4
5
  class Runner
@@ -14,9 +15,8 @@ module Commander
14
15
  attr_reader :commands
15
16
 
16
17
  ##
17
- # Global options.
18
-
19
- attr_reader :options
18
+ # The global Slop Options
19
+ attr_reader :global_slop
20
20
 
21
21
  ##
22
22
  # Hash of help formatter aliases.
@@ -33,8 +33,8 @@ module Commander
33
33
 
34
34
  def initialize(*inputs)
35
35
  @program, @commands, @default_command, \
36
- @options, @aliases, @args = inputs.map(&:dup)
37
- @args.reject! { |a| a == '--trace' }
36
+ @global_slop, @aliases, @args = inputs.map(&:dup)
37
+
38
38
  @commands['help'] ||= Command.new('help').tap do |c|
39
39
  c.syntax = "#{program(:name)} help [command]"
40
40
  c.description = 'Display global or [command] help documentation'
@@ -49,15 +49,64 @@ module Commander
49
49
 
50
50
  ##
51
51
  # Run command parsing and execution process
52
- # NOTE: This method does not have error handling, see: run!
52
+ INBUILT_ERRORS = [
53
+ OptionParser::InvalidOption,
54
+ Command::CommandUsageError,
55
+ InvalidCommandError
56
+ ]
53
57
 
54
58
  def run
55
59
  require_program :version, :description
56
60
 
57
- parse_global_options
58
- remove_global_options options, @args
61
+ # Determine where the arguments/ options start
62
+ remaining_args = if alias? command_name_from_args
63
+ @aliases[command_name_from_args.to_s] + args_without_command_name
64
+ else
65
+ args_without_command_name
66
+ end
67
+
68
+ # Parses the global slop options
69
+ global_parser = Slop::Parser.new(global_slop, suppress_errors: true)
70
+ global_opts = global_parser.parse(remaining_args)
71
+ remaining_args = global_parser.arguments
72
+
73
+ # Parse the command slop options
74
+ if active_command?
75
+ local_parser = Slop::Parser.new(active_command.slop)
76
+ local_opts = local_parser.parse(remaining_args)
77
+ remaining_args = local_parser.arguments
78
+ end
59
79
 
60
- run_active_command
80
+ # Format the config and opts
81
+ config = program(:config).dup
82
+ opts = OpenStruct.new global_opts.to_h.merge(local_opts.to_h)
83
+
84
+ if opts.version
85
+ # Return the version
86
+ say version
87
+ exit 0
88
+ elsif opts.help && active_command?
89
+ # Return help for the active_command
90
+ run_help_command([active_command!.name])
91
+ elsif active_command?
92
+ # Run the active_command
93
+ active_command.run!(remaining_args, opts, config)
94
+ else
95
+ # Return generic help
96
+ run_help_command('')
97
+ end
98
+ rescue => e
99
+ msg = "#{Paint[program(:name), '#2794d8']}: #{Paint[e.to_s, :red, :bright]}"
100
+ new_error = e.exception(msg)
101
+
102
+ if INBUILT_ERRORS.include?(new_error.class)
103
+ new_error = InternalCallableError.new(e.message) do
104
+ $stderr.puts "\nUsage:\n\n"
105
+ name = active_command? ? active_command.name : :error
106
+ run_help_command([name])
107
+ end
108
+ end
109
+ raise new_error
61
110
  end
62
111
 
63
112
  ##
@@ -100,20 +149,39 @@ module Commander
100
149
  @aliases.include? name.to_s
101
150
  end
102
151
 
103
- ##
104
- # Check if a command _name_ exists.
105
-
106
- def command_exists?(name)
107
- @commands[name.to_s]
108
- end
109
-
110
152
  #:stopdoc:
111
153
 
112
154
  ##
113
155
  # Get active command within arguments passed to this runner.
156
+ # It will try an run the default if arguments have been provided
157
+ # It can not run a default command that is flags-only
158
+ # This is to provide consistent behaviour to --help
159
+ #
114
160
 
115
161
  def active_command
116
- @__active_command ||= command(command_name_from_args)
162
+ @__active_command ||= begin
163
+ if named_command = command(command_name_from_args)
164
+ named_command
165
+ elsif default_command? && flagless_args_string.length > 0
166
+ default_command
167
+ end
168
+ end
169
+ end
170
+
171
+ def active_command!
172
+ active_command.tap { |c| require_valid_command(c) }
173
+ end
174
+
175
+ def active_command?
176
+ active_command ? true : false
177
+ end
178
+
179
+ def default_command
180
+ @__default_command ||= command(@default_command)
181
+ end
182
+
183
+ def default_command?
184
+ default_command ? true : false
117
185
  end
118
186
 
119
187
  ##
@@ -121,15 +189,20 @@ module Commander
121
189
  # Supports multi-word commands, using the largest possible match.
122
190
 
123
191
  def command_name_from_args
124
- @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
192
+ @__command_name_from_args ||= valid_command_names_from(*@args.dup).sort.last
193
+ end
194
+
195
+ def flagless_args_string
196
+ @flagless_args_string ||= @args.reject { |value| value =~ /^-/ }.join ' '
125
197
  end
126
198
 
127
199
  ##
128
200
  # Returns array of valid command names found within _args_.
129
201
 
130
202
  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/ }
203
+ commands.keys.find_all do |name|
204
+ name if flagless_args_string =~ /^#{name}\b/
205
+ end
133
206
  end
134
207
 
135
208
  ##
@@ -145,7 +218,7 @@ module Commander
145
218
  def args_without_command_name
146
219
  removed = []
147
220
  parts = command_name_from_args.split rescue []
148
- @args.dup.delete_if do |arg|
221
+ @args.reject do |arg|
149
222
  removed << arg if parts.include?(arg) && !removed.include?(arg)
150
223
  end
151
224
  end
@@ -155,37 +228,17 @@ module Commander
155
228
 
156
229
  def help_formatter_alias_defaults
157
230
  {
158
- compact: HelpFormatter::TerminalCompact,
159
231
  }
160
232
  end
161
233
 
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
234
  ##
181
235
  # Creates default commands such as 'help' which is
182
236
  # essentially the same as using the --help switch.
183
237
  def run_help_command(args)
184
238
  UI.enable_paging if program(:help_paging)
185
- @help_commands = @commands.dup
239
+ @help_commands = @commands.reject { |_, v| v.hidden(false) }.to_h
186
240
  if args.empty? || args[0] == :error
187
- @help_options = @options
188
- @help_commands.reject! { |k, v| !!v.hidden }
241
+ @help_options = []
189
242
  old_wrap = $terminal.wrap_at
190
243
  $terminal.wrap_at = nil
191
244
  program(:nobanner, true) if args[0] == :error
@@ -194,54 +247,17 @@ module Commander
194
247
  else
195
248
  command = command args.join(' ')
196
249
  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
250
+ say help_formatter.render_command(command)
203
251
  end
204
252
  end
205
253
 
206
254
  ##
207
255
  # Raises InvalidCommandError when a _command_ is not found.
208
256
 
209
- def require_valid_command(command = active_command)
257
+ def require_valid_command(command)
210
258
  fail InvalidCommandError, 'invalid command', caller if command.nil?
211
259
  end
212
260
 
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
261
  # expand switches of the style '--[no-]blah' into both their
246
262
  # '--blah' and '--no-blah' variants, so that they can be
247
263
  # properly detected and removed
@@ -256,41 +272,6 @@ module Commander
256
272
  end
257
273
  end
258
274
 
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
275
  ##
295
276
  # Raises a CommandError when the program any of the _keys_ are not present, or empty.
296
277
 
@@ -325,18 +306,6 @@ module Commander
325
306
  switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil
326
307
  end
327
308
 
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
309
  def say(*args) #:nodoc:
341
310
  $terminal.say(*args)
342
311
  end