commander-openflighthpc 1.0.0.pre.alpha1

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +44 -0
  5. data/.rubocop_todo.yml +77 -0
  6. data/.travis.yml +14 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE +51 -0
  10. data/Manifest +38 -0
  11. data/README.md +492 -0
  12. data/Rakefile +13 -0
  13. data/bin/commander +104 -0
  14. data/commander-openflighthpc.gemspec +32 -0
  15. data/lib/commander.rb +36 -0
  16. data/lib/commander/blank.rb +7 -0
  17. data/lib/commander/command.rb +263 -0
  18. data/lib/commander/configure.rb +14 -0
  19. data/lib/commander/core_ext.rb +2 -0
  20. data/lib/commander/core_ext/array.rb +24 -0
  21. data/lib/commander/core_ext/object.rb +8 -0
  22. data/lib/commander/delegates.rb +27 -0
  23. data/lib/commander/help_formatters.rb +52 -0
  24. data/lib/commander/help_formatters/base.rb +24 -0
  25. data/lib/commander/help_formatters/terminal.rb +24 -0
  26. data/lib/commander/help_formatters/terminal/command_help.erb +35 -0
  27. data/lib/commander/help_formatters/terminal/help.erb +36 -0
  28. data/lib/commander/help_formatters/terminal/subcommand_help.erb +23 -0
  29. data/lib/commander/help_formatters/terminal_compact.rb +11 -0
  30. data/lib/commander/help_formatters/terminal_compact/command_help.erb +26 -0
  31. data/lib/commander/help_formatters/terminal_compact/help.erb +29 -0
  32. data/lib/commander/help_formatters/terminal_compact/subcommand_help.erb +15 -0
  33. data/lib/commander/import.rb +5 -0
  34. data/lib/commander/methods.rb +11 -0
  35. data/lib/commander/patches/decimal-integer.rb +17 -0
  36. data/lib/commander/patches/help_formatter_binding.rb +15 -0
  37. data/lib/commander/patches/implicit-short-tags.rb +75 -0
  38. data/lib/commander/patches/option_defaults.rb +23 -0
  39. data/lib/commander/patches/validate_inputs.rb +76 -0
  40. data/lib/commander/platform.rb +7 -0
  41. data/lib/commander/runner.rb +493 -0
  42. data/lib/commander/user_interaction.rb +551 -0
  43. data/lib/commander/version.rb +3 -0
  44. data/spec/command_spec.rb +157 -0
  45. data/spec/configure_spec.rb +37 -0
  46. data/spec/core_ext/array_spec.rb +18 -0
  47. data/spec/core_ext/object_spec.rb +19 -0
  48. data/spec/help_formatters/terminal_compact_spec.rb +69 -0
  49. data/spec/help_formatters/terminal_spec.rb +67 -0
  50. data/spec/methods_spec.rb +61 -0
  51. data/spec/patches/validate_inputs_spec.rb +84 -0
  52. data/spec/runner_spec.rb +672 -0
  53. data/spec/spec_helper.rb +79 -0
  54. data/spec/ui_spec.rb +30 -0
  55. metadata +183 -0
@@ -0,0 +1,23 @@
1
+ module Commander
2
+ module Patches
3
+ module OptionDefaults
4
+ # I can't remember what this patch does, but I found it in the code
5
+ # base. It is better if this magic is kept separate
6
+ def option(*args, &block)
7
+ default = nil
8
+ args.delete_if do |v|
9
+ if v.is_a?(Hash) && v.key?(:default)
10
+ default = v[:default]
11
+ true
12
+ else
13
+ false
14
+ end
15
+ end
16
+ opt = super
17
+ opt.tap do |h|
18
+ h.merge!( { default: default } ) unless default.nil?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Commander
4
+ module Patches
5
+ # An error in the usage of a command; will happen in practise and error
6
+ # message should be shown along with command usage info.
7
+ class CommandUsageError < StandardError; end
8
+
9
+ module ValidateInputs
10
+ # This is used to switch the Patch off during the original test of
11
+ # Commander. It is VERY MUCH a hack but it works
12
+ PatchEnabled = true
13
+
14
+ def call(args = [])
15
+ return super unless PatchEnabled
16
+ return super if syntax_parts[0..1] == ['commander', 'help']
17
+
18
+ # Use defined syntax to validate how many args this command can be
19
+ # passed.
20
+ assert_correct_number_of_args!(args)
21
+
22
+ # Invoke original method.
23
+ super(args)
24
+ end
25
+
26
+ private
27
+
28
+ def assert_correct_number_of_args!(args)
29
+ return if primary_command_word == 'help'
30
+ if too_many_args?(args)
31
+ raise CommandUsageError, "excess arguments for command '#{primary_command_word}'"
32
+ elsif too_few_args?(args)
33
+ raise CommandUsageError, "insufficient arguments for command '#{primary_command_word}'"
34
+ end
35
+ end
36
+
37
+ def syntax_parts
38
+ @syntax_parts ||= syntax.split.tap do |parts|
39
+ while part = parts.shift do
40
+ break if part == primary_command_word || parts.length == 0
41
+ end
42
+ end
43
+ end
44
+
45
+ def primary_command_word
46
+ name.split.last
47
+ end
48
+
49
+ def total_argument_count
50
+ syntax_parts.length
51
+ end
52
+
53
+ def optional_argument_count
54
+ syntax_parts.select do |part|
55
+ part[0] == '[' && part[-1] == ']'
56
+ end.length
57
+ end
58
+
59
+ def variable_arg?
60
+ syntax_parts.any? {|part| part[-4..-1] == '...]' || part[-3..-1] == '...'}
61
+ end
62
+
63
+ def required_argument_count
64
+ total_argument_count - optional_argument_count
65
+ end
66
+
67
+ def too_many_args?(args)
68
+ !variable_arg? && args.length > total_argument_count
69
+ end
70
+
71
+ def too_few_args?(args)
72
+ args.length < required_argument_count
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,7 @@
1
+ module Commander
2
+ module Platform
3
+ def self.jruby?
4
+ defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'jruby')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,493 @@
1
+ module Commander
2
+ class Runner
3
+ #--
4
+ # Exceptions
5
+ #++
6
+
7
+ class CommandError < StandardError; end
8
+ class InvalidCommandError < CommandError; end
9
+
10
+ ##
11
+ # Array of commands.
12
+
13
+ attr_reader :commands
14
+
15
+ ##
16
+ # Global options.
17
+
18
+ attr_reader :options
19
+
20
+ ##
21
+ # Hash of help formatter aliases.
22
+
23
+ attr_reader :help_formatter_aliases
24
+
25
+ ##
26
+ # Initialize a new command runner. Optionally
27
+ # supplying _args_ for mocking, or arbitrary usage.
28
+
29
+ def initialize(args = ARGV)
30
+ @args, @commands, @aliases, @options = args, {}, {}, []
31
+ @help_formatter_aliases = help_formatter_alias_defaults
32
+ @program = program_defaults
33
+ @always_trace = false
34
+ @never_trace = false
35
+ @silent_trace = false
36
+ @error_handler = nil
37
+ create_default_commands
38
+ end
39
+
40
+ ##
41
+ # Return singleton Runner instance.
42
+
43
+ def self.instance
44
+ @singleton ||= new
45
+ end
46
+
47
+ ##
48
+ # Run command parsing and execution process.
49
+
50
+ def run!
51
+ trace = @always_trace || false
52
+ require_program :version, :description
53
+ trap('INT') { abort program(:int_message) } if program(:int_message)
54
+ trap('INT') { program(:int_block).call } if program(:int_block)
55
+ global_option('-h', '--help', 'Display help documentation') do
56
+ args = @args - %w(-h --help)
57
+ command(:help).run(*args)
58
+ return
59
+ end
60
+ global_option('--version', 'Display version information') do
61
+ say version
62
+ return
63
+ end
64
+ global_option('--trace', 'Display backtrace when an error occurs') { trace = true } unless @never_trace || @always_trace
65
+ parse_global_options
66
+ remove_global_options options, @args
67
+ if trace
68
+ run_active_command
69
+ else
70
+ begin
71
+ run_active_command
72
+ rescue InvalidCommandError => e
73
+ error_handler&.call(e) ||
74
+ abort("#{e}. Use --help for more information")
75
+ rescue \
76
+ OptionParser::InvalidOption,
77
+ OptionParser::InvalidArgument,
78
+ OptionParser::MissingArgument => e
79
+ error_handler&.call(e) ||
80
+ abort(e.to_s)
81
+ rescue => e
82
+ error_handler&.call(e) ||
83
+ if @never_trace || @silent_trace
84
+ abort("error: #{e}.")
85
+ else
86
+ abort("error: #{e}. Use --trace to view backtrace")
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Return program version.
94
+
95
+ def version
96
+ format('%s %s', program(:name), program(:version))
97
+ end
98
+
99
+ ##
100
+ # Enable tracing on all executions (bypasses --trace)
101
+
102
+ def always_trace!
103
+ @always_trace = true
104
+ @never_trace = false
105
+ @silent_trace = false
106
+ end
107
+
108
+ ##
109
+ # Hide the trace option from the help menus and don't add it as a global option
110
+
111
+ def never_trace!
112
+ @always_trace = false
113
+ @never_trace = true
114
+ @silent_trace = false
115
+ end
116
+
117
+ ##
118
+ # Includes the trace option in the help but not in the error message
119
+
120
+ def silent_trace!
121
+ @always_trace = false
122
+ @never_trace = false
123
+ @silent_trace = true
124
+ end
125
+
126
+ ##
127
+ # Set a handler to be used for advanced exception handling
128
+
129
+ def error_handler(&block)
130
+ @error_handler = block if block
131
+ @error_handler
132
+ end
133
+
134
+ ##
135
+ # Assign program information.
136
+ #
137
+ # === Examples
138
+ #
139
+ # # Set data
140
+ # program :name, 'Commander'
141
+ # program :version, Commander::VERSION
142
+ # program :description, 'Commander utility program.'
143
+ # program :help, 'Copyright', '2008 TJ Holowaychuk'
144
+ # program :help, 'Anything', 'You want'
145
+ # program :int_message 'Bye bye!'
146
+ # program :help_formatter, :compact
147
+ # program :help_formatter, Commander::HelpFormatter::TerminalCompact
148
+ #
149
+ # # Get data
150
+ # program :name # => 'Commander'
151
+ #
152
+ # === Keys
153
+ #
154
+ # :version (required) Program version triple, ex: '0.0.1'
155
+ # :description (required) Program description
156
+ # :name Program name, defaults to basename of executable
157
+ # :help_formatter Defaults to Commander::HelpFormatter::Terminal
158
+ # :help Allows addition of arbitrary global help blocks
159
+ # :help_paging Flag for toggling help paging
160
+ # :int_message Message to display when interrupted (CTRL + C)
161
+ #
162
+
163
+ def program(key, *args, &block)
164
+ if key == :help && !args.empty?
165
+ @program[:help] ||= {}
166
+ @program[:help][args.first] = args.at(1)
167
+ elsif key == :help_formatter && !args.empty?
168
+ @program[key] = (@help_formatter_aliases[args.first] || args.first)
169
+ elsif block
170
+ @program[key] = block
171
+ else
172
+ unless args.empty?
173
+ @program[key] = args.count == 1 ? args[0] : args
174
+ end
175
+ @program[key]
176
+ end
177
+ end
178
+
179
+ ##
180
+ # Creates and yields a command instance when a block is passed.
181
+ # Otherwise attempts to return the command, raising InvalidCommandError when
182
+ # it does not exist.
183
+ #
184
+ # === Examples
185
+ #
186
+ # command :my_command do |c|
187
+ # c.when_called do |args|
188
+ # # Code
189
+ # end
190
+ # end
191
+ #
192
+
193
+ def command(name, &block)
194
+ yield add_command(Commander::Command.new(name)) if block
195
+ @commands[name.to_s]
196
+ end
197
+
198
+ ##
199
+ # Add a global option; follows the same syntax as Command#option
200
+ # This would be used for switches such as --version, --trace, etc.
201
+
202
+ def global_option(*args, &block)
203
+ switches, description = Runner.separate_switches_from_description(*args)
204
+ @options << {
205
+ args: args,
206
+ proc: block,
207
+ switches: switches,
208
+ description: description,
209
+ }
210
+ end
211
+
212
+ ##
213
+ # Alias command _name_ with _alias_name_. Optionally _args_ may be passed
214
+ # as if they were being passed straight to the original command via the command-line.
215
+
216
+ def alias_command(alias_name, name, *args)
217
+ @commands[alias_name.to_s] = command name
218
+ @aliases[alias_name.to_s] = args
219
+ end
220
+
221
+ ##
222
+ # Default command _name_ to be used when no other
223
+ # command is found in the arguments.
224
+
225
+ def default_command(name)
226
+ @default_command = name
227
+ end
228
+
229
+ ##
230
+ # Add a command object to this runner.
231
+
232
+ def add_command(command)
233
+ @commands[command.name] = command
234
+ end
235
+
236
+ ##
237
+ # Check if command _name_ is an alias.
238
+
239
+ def alias?(name)
240
+ @aliases.include? name.to_s
241
+ end
242
+
243
+ ##
244
+ # Check if a command _name_ exists.
245
+
246
+ def command_exists?(name)
247
+ @commands[name.to_s]
248
+ end
249
+
250
+ #:stopdoc:
251
+
252
+ ##
253
+ # Get active command within arguments passed to this runner.
254
+
255
+ def active_command
256
+ @__active_command ||= command(command_name_from_args)
257
+ end
258
+
259
+ ##
260
+ # Attempts to locate a command name from within the arguments.
261
+ # Supports multi-word commands, using the largest possible match.
262
+
263
+ def command_name_from_args
264
+ @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
265
+ end
266
+
267
+ ##
268
+ # Returns array of valid command names found within _args_.
269
+
270
+ def valid_command_names_from(*args)
271
+ arg_string = args.delete_if { |value| value =~ /^-/ }.join ' '
272
+ commands.keys.find_all { |name| name if arg_string =~ /^#{name}\b/ }
273
+ end
274
+
275
+ ##
276
+ # Help formatter instance.
277
+
278
+ def help_formatter
279
+ @__help_formatter ||= program(:help_formatter).new self
280
+ end
281
+
282
+ ##
283
+ # Return arguments without the command name.
284
+
285
+ def args_without_command_name
286
+ removed = []
287
+ parts = command_name_from_args.split rescue []
288
+ @args.dup.delete_if do |arg|
289
+ removed << arg if parts.include?(arg) && !removed.include?(arg)
290
+ end
291
+ end
292
+
293
+ ##
294
+ # Returns hash of help formatter alias defaults.
295
+
296
+ def help_formatter_alias_defaults
297
+ {
298
+ compact: HelpFormatter::TerminalCompact,
299
+ }
300
+ end
301
+
302
+ ##
303
+ # Returns hash of program defaults.
304
+
305
+ def program_defaults
306
+ {
307
+ help_formatter: HelpFormatter::Terminal,
308
+ name: File.basename($PROGRAM_NAME),
309
+ help_paging: true,
310
+ }
311
+ end
312
+
313
+ ##
314
+ # Limit commands to those which are subcommands of the one that is active
315
+ def limit_commands_to_subcommands(command)
316
+ commands.reject! { |k, v|
317
+ (k.to_s == command.name) ? true : !k.to_s.start_with?("#{command.name} ")
318
+ }
319
+ end
320
+
321
+ ##
322
+ # Creates default commands such as 'help' which is
323
+ # essentially the same as using the --help switch.
324
+
325
+ def create_default_commands
326
+ command :help do |c|
327
+ c.syntax = 'commander help [command]'
328
+ c.description = 'Display global or [command] help documentation'
329
+ c.example 'Display global help', 'command help'
330
+ c.example "Display help for 'foo'", 'command help foo'
331
+ c.when_called do |args, _options|
332
+ UI.enable_paging if program(:help_paging)
333
+ @help_commands = @commands.dup
334
+ if args.empty?
335
+ @help_options = @options.reject {|o| o[:switches].first == '--trace'}
336
+ @help_commands.reject! { |k, v| !!v.hidden }
337
+ say help_formatter.render
338
+ else
339
+ command = command args.join(' ')
340
+ begin
341
+ require_valid_command command
342
+ rescue InvalidCommandError => e
343
+ error_handler&.call(e) ||
344
+ abort("#{e}. Use --help for more information")
345
+ end
346
+ if command.sub_command_group?
347
+ limit_commands_to_subcommands(command)
348
+ say help_formatter.render_subcommand(command)
349
+ else
350
+ say help_formatter.render_command(command)
351
+ end
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ ##
358
+ # Raises InvalidCommandError when a _command_ is not found.
359
+
360
+ def require_valid_command(command = active_command)
361
+ fail InvalidCommandError, 'invalid command', caller if command.nil?
362
+ end
363
+
364
+ ##
365
+ # Removes global _options_ from _args_. This prevents an invalid
366
+ # option error from occurring when options are parsed
367
+ # again for the command.
368
+
369
+ def remove_global_options(options, args)
370
+ # TODO: refactor with flipflop, please TJ ! have time to refactor me !
371
+ options.each do |option|
372
+ switches = option[:switches].dup
373
+ next if switches.empty?
374
+
375
+ if (switch_has_arg = switches.any? { |s| s =~ /[ =]/ })
376
+ switches.map! { |s| s[0, s.index('=') || s.index(' ') || s.length] }
377
+ end
378
+
379
+ switches = expand_optionally_negative_switches(switches)
380
+
381
+ past_switch, arg_removed = false, false
382
+ args.delete_if do |arg|
383
+ if switches.any? { |s| s == arg }
384
+ arg_removed = !switch_has_arg
385
+ past_switch = true
386
+ elsif past_switch && !arg_removed && arg !~ /^-/
387
+ arg_removed = true
388
+ else
389
+ arg_removed = true
390
+ false
391
+ end
392
+ end
393
+ end
394
+ end
395
+
396
+ # expand switches of the style '--[no-]blah' into both their
397
+ # '--blah' and '--no-blah' variants, so that they can be
398
+ # properly detected and removed
399
+ def expand_optionally_negative_switches(switches)
400
+ switches.reduce([]) do |memo, val|
401
+ if val =~ /\[no-\]/
402
+ memo << val.gsub(/\[no-\]/, '')
403
+ memo << val.gsub(/\[no-\]/, 'no-')
404
+ else
405
+ memo << val
406
+ end
407
+ end
408
+ end
409
+
410
+ ##
411
+ # Parse global command options.
412
+
413
+ def parse_global_options
414
+ parser = options.inject(OptionParser.new) do |options, option|
415
+ options.on(*option[:args], &global_option_proc(option[:switches], &option[:proc]))
416
+ end
417
+
418
+ options = @args.dup
419
+ begin
420
+ parser.parse!(options)
421
+ rescue OptionParser::InvalidOption => e
422
+ # Remove the offending args and retry.
423
+ options = options.reject { |o| e.args.include?(o) }
424
+ retry
425
+ end
426
+ end
427
+
428
+ ##
429
+ # Returns a proc allowing for commands to inherit global options.
430
+ # This functionality works whether a block is present for the global
431
+ # option or not, so simple switches such as --verbose can be used
432
+ # without a block, and used throughout all commands.
433
+
434
+ def global_option_proc(switches, &block)
435
+ lambda do |value|
436
+ unless active_command.nil?
437
+ active_command.proxy_options << [Runner.switch_to_sym(switches.last), value]
438
+ end
439
+ yield value if block && !value.nil?
440
+ end
441
+ end
442
+
443
+ ##
444
+ # Raises a CommandError when the program any of the _keys_ are not present, or empty.
445
+
446
+ def require_program(*keys)
447
+ keys.each do |key|
448
+ fail CommandError, "program #{key} required" if program(key).nil? || program(key).empty?
449
+ end
450
+ end
451
+
452
+ ##
453
+ # Return switches and description separated from the _args_ passed.
454
+
455
+ def self.separate_switches_from_description(*args)
456
+ switches = args.find_all { |arg| arg.to_s =~ /^-/ }
457
+ description = args.last if args.last.is_a?(String) && !args.last.match(/^-/)
458
+ [switches, description]
459
+ end
460
+
461
+ ##
462
+ # Attempts to generate a method name symbol from +switch+.
463
+ # For example:
464
+ #
465
+ # -h # => :h
466
+ # --trace # => :trace
467
+ # --some-switch # => :some_switch
468
+ # --[no-]feature # => :feature
469
+ # --file FILE # => :file
470
+ # --list of,things # => :list
471
+ #
472
+
473
+ def self.switch_to_sym(switch)
474
+ switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil
475
+ end
476
+
477
+ ##
478
+ # Run the active command.
479
+
480
+ def run_active_command
481
+ require_valid_command
482
+ if alias? command_name_from_args
483
+ active_command.run(*(@aliases[command_name_from_args.to_s] + args_without_command_name))
484
+ else
485
+ active_command.run(*args_without_command_name)
486
+ end
487
+ end
488
+
489
+ def say(*args) #:nodoc:
490
+ $terminal.say(*args)
491
+ end
492
+ end
493
+ end