commander-openflighthpc 2.0.2 → 2.1.0

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