commander-openflighthpc 1.0.0.pre.alpha1

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