Class: Commander::Runner
Defined Under Namespace
Classes: CommandError, InvalidCommandError
Constant Summary collapse
- DEFAULT_ERROR_HANDLER =
lambda do |runner, e| error_msg = "#{Paint[runner.program(:name), '#2794d8']}: #{Paint[e.to_s, :red, :bright]}" case e when OptionParser::InvalidOption, Commander::Runner::InvalidCommandError, Commander::Patches::CommandUsageError # Display the error message for a specific command. Most likely due to # invalid command syntax if cmd = runner.active_command $stderr.puts error_msg $stderr.puts "\nUsage:\n\n" runner.command('help').run(cmd.name) # Display the main app help text when called without `--help` elsif runner.args_without_command_name.empty? $stderr.puts "Usage:\n\n" runner.command('help').run(:error) # Display the main app help text when called with arguments. Mostly # likely an invalid syntax error else $stderr.puts error_msg $stderr.puts "\nUsage:\n\n" runner.command('help').run(:error) end # Display the help text for sub command groups when called without `--help` when SubCommandGroupError if cmd = runner.active_command $stderr.puts "Usage:\n\n" runner.command('help').run(cmd.name) end # Catch all error message for all other issues else $stderr.puts error_msg end exit(1) end
Instance Attribute Summary collapse
-
#commands ⇒ Object
readonly
Array of commands.
-
#help_formatter_aliases ⇒ Object
readonly
Hash of help formatter aliases.
-
#options ⇒ Object
readonly
Global options.
Class Method Summary collapse
-
.instance ⇒ Object
Return singleton Runner instance.
-
.separate_switches_from_description(*args) ⇒ Object
Return switches and description separated from the args passed.
-
.switch_to_sym(switch) ⇒ Object
Attempts to generate a method name symbol from
switch.
Instance Method Summary collapse
-
#active_command ⇒ Object
Get active command within arguments passed to this runner.
-
#add_command(command) ⇒ Object
Add a command object to this runner.
-
#alias?(name) ⇒ Boolean
Check if command name is an alias.
-
#alias_command(alias_name, name, *args) ⇒ Object
Alias command name with alias_name.
-
#always_trace! ⇒ Object
Enable tracing on all executions (bypasses –trace).
-
#args_without_command_name ⇒ Object
Return arguments without the command name.
-
#command(name) {|add_command(Commander::Command.new(name))| ... } ⇒ Object
Creates and yields a command instance when a block is passed.
-
#command_exists?(name) ⇒ Boolean
Check if a command name exists.
-
#command_name_from_args ⇒ Object
Attempts to locate a command name from within the arguments.
-
#create_default_commands ⇒ Object
Creates default commands such as ‘help’ which is essentially the same as using the –help switch.
-
#default_command(name) ⇒ Object
Default command name to be used when no other command is found in the arguments.
-
#error_handler(&block) ⇒ Object
Set a handler to be used for advanced exception handling.
-
#expand_optionally_negative_switches(switches) ⇒ Object
expand switches of the style ‘–[no-]blah’ into both their ‘–blah’ and ‘–no-blah’ variants, so that they can be properly detected and removed.
-
#global_option(*args, &block) ⇒ Object
Add a global option; follows the same syntax as Command#option This would be used for switches such as –version, –trace, etc.
-
#global_option_proc(switches, &block) ⇒ Object
Returns a proc allowing for commands to inherit global options.
-
#help_formatter ⇒ Object
Help formatter instance.
-
#help_formatter_alias_defaults ⇒ Object
Returns hash of help formatter alias defaults.
-
#initialize(args = ARGV) ⇒ Runner
constructor
Initialize a new command runner.
-
#limit_commands_to_subcommands(command) ⇒ Object
Limit commands to those which are subcommands of the one that is active.
-
#never_trace! ⇒ Object
Hide the trace option from the help menus and don’t add it as a global option.
-
#parse_global_options ⇒ Object
Parse global command options.
-
#program(key, *args, &block) ⇒ Object
Assign program information.
-
#program_defaults ⇒ Object
Returns hash of program defaults.
-
#remove_global_options(options, args) ⇒ Object
Removes global options from args.
-
#require_program(*keys) ⇒ Object
Raises a CommandError when the program any of the keys are not present, or empty.
-
#require_valid_command(command = active_command) ⇒ Object
Raises InvalidCommandError when a command is not found.
-
#run! ⇒ Object
Run command parsing and execution process.
-
#run_active_command ⇒ Object
Run the active command.
-
#say(*args) ⇒ Object
:nodoc:.
-
#silent_trace! ⇒ Object
Includes the trace option in the help but not in the error message.
-
#valid_command_names_from(*args) ⇒ Object
Returns array of valid command names found within args.
-
#version ⇒ Object
Return program version.
Constructor Details
#initialize(args = ARGV) ⇒ Runner
Initialize a new command runner. Optionally supplying args for mocking, or arbitrary usage.
66 67 68 69 70 71 72 73 74 75 |
# File 'lib/commander/runner.rb', line 66 def initialize(args = ARGV) @args, @commands, @aliases, = args, {}, {}, [] @help_formatter_aliases = help_formatter_alias_defaults @program = program_defaults @always_trace = false @never_trace = false @silent_trace = false @error_handler = DEFAULT_ERROR_HANDLER create_default_commands end |
Instance Attribute Details
#commands ⇒ Object (readonly)
Array of commands.
50 51 52 |
# File 'lib/commander/runner.rb', line 50 def commands @commands end |
#help_formatter_aliases ⇒ Object (readonly)
Hash of help formatter aliases.
60 61 62 |
# File 'lib/commander/runner.rb', line 60 def help_formatter_aliases @help_formatter_aliases end |
#options ⇒ Object (readonly)
Global options.
55 56 57 |
# File 'lib/commander/runner.rb', line 55 def end |
Class Method Details
.instance ⇒ Object
Return singleton Runner instance.
80 81 82 |
# File 'lib/commander/runner.rb', line 80 def self.instance @singleton ||= new end |
.separate_switches_from_description(*args) ⇒ Object
Return switches and description separated from the args passed.
506 507 508 509 510 |
# File 'lib/commander/runner.rb', line 506 def self.separate_switches_from_description(*args) switches = args.find_all { |arg| arg.to_s =~ /^-/ } description = args.last if args.last.is_a?(String) && !args.last.match(/^-/) [switches, description] end |
.switch_to_sym(switch) ⇒ Object
Attempts to generate a method name symbol from switch. For example:
-h # => :h
--trace # => :trace
--some-switch # => :some_switch
--[no-]feature # => :feature
--file FILE # => :file
--list of,things # => :list
524 525 526 |
# File 'lib/commander/runner.rb', line 524 def self.switch_to_sym(switch) switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil end |
Instance Method Details
#active_command ⇒ Object
Get active command within arguments passed to this runner.
292 293 294 |
# File 'lib/commander/runner.rb', line 292 def active_command @__active_command ||= command(command_name_from_args) end |
#add_command(command) ⇒ Object
Add a command object to this runner.
269 270 271 |
# File 'lib/commander/runner.rb', line 269 def add_command(command) @commands[command.name] = command end |
#alias?(name) ⇒ Boolean
Check if command name is an alias.
276 277 278 |
# File 'lib/commander/runner.rb', line 276 def alias?(name) @aliases.include? name.to_s end |
#alias_command(alias_name, name, *args) ⇒ Object
Alias command name with alias_name. Optionally args may be passed as if they were being passed straight to the original command via the command-line.
253 254 255 256 |
# File 'lib/commander/runner.rb', line 253 def alias_command(alias_name, name, *args) @commands[alias_name.to_s] = command name @aliases[alias_name.to_s] = args end |
#always_trace! ⇒ Object
Enable tracing on all executions (bypasses –trace)
139 140 141 142 143 |
# File 'lib/commander/runner.rb', line 139 def always_trace! @always_trace = true @never_trace = false @silent_trace = false end |
#args_without_command_name ⇒ Object
Return arguments without the command name.
322 323 324 325 326 327 328 |
# File 'lib/commander/runner.rb', line 322 def args_without_command_name removed = [] parts = command_name_from_args.split rescue [] @args.dup.delete_if do |arg| removed << arg if parts.include?(arg) && !removed.include?(arg) end end |
#command(name) {|add_command(Commander::Command.new(name))| ... } ⇒ Object
Creates and yields a command instance when a block is passed. Otherwise attempts to return the command, raising InvalidCommandError when it does not exist.
Examples
command :my_command do |c|
c.when_called do |args|
# Code
end
end
230 231 232 233 |
# File 'lib/commander/runner.rb', line 230 def command(name, &block) yield add_command(Commander::Command.new(name)) if block @commands[name.to_s] end |
#command_exists?(name) ⇒ Boolean
Check if a command name exists.
283 284 285 |
# File 'lib/commander/runner.rb', line 283 def command_exists?(name) @commands[name.to_s] end |
#command_name_from_args ⇒ Object
Attempts to locate a command name from within the arguments. Supports multi-word commands, using the largest possible match.
300 301 302 |
# File 'lib/commander/runner.rb', line 300 def command_name_from_args @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command) end |
#create_default_commands ⇒ Object
Creates default commands such as ‘help’ which is essentially the same as using the –help switch.
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/commander/runner.rb', line 372 def create_default_commands command :help do |c| c.syntax = "#{program(:name)} help [command]" c.description = 'Display global or [command] help documentation' c.example 'Display global help', "#{program(:name)} help" c.example "Display help for 'foo'", "#{program(:name)} help foo" c.when_called do |args, | UI.enable_paging if program(:help_paging) @help_commands = @commands.dup if args.empty? || args[0] == :error = .reject {|o| o[:switches].first == '--trace'} @help_commands.reject! { |k, v| !!v.hidden } old_wrap = $terminal.wrap_at $terminal.wrap_at = nil program(:nobanner, true) if args[0] == :error say help_formatter.render $terminal.wrap_at = old_wrap else command = command args.join(' ') begin require_valid_command command rescue InvalidCommandError => e error_handler&.call(self, e) || abort("#{e}. Use --help for more information") end if command.sub_command_group? limit_commands_to_subcommands(command) say help_formatter.render_subcommand(command) else say help_formatter.render_command(command) end end end end end |
#default_command(name) ⇒ Object
Default command name to be used when no other command is found in the arguments.
262 263 264 |
# File 'lib/commander/runner.rb', line 262 def default_command(name) @default_command = name end |
#error_handler(&block) ⇒ Object
Set a handler to be used for advanced exception handling
166 167 168 169 |
# File 'lib/commander/runner.rb', line 166 def error_handler(&block) @error_handler = block if block @error_handler end |
#expand_optionally_negative_switches(switches) ⇒ Object
expand switches of the style ‘–[no-]blah’ into both their ‘–blah’ and ‘–no-blah’ variants, so that they can be properly detected and removed
450 451 452 453 454 455 456 457 458 459 |
# File 'lib/commander/runner.rb', line 450 def (switches) switches.reduce([]) do |memo, val| if val =~ /\[no-\]/ memo << val.gsub(/\[no-\]/, '') memo << val.gsub(/\[no-\]/, 'no-') else memo << val end end end |
#global_option(*args, &block) ⇒ Object
Add a global option; follows the same syntax as Command#option This would be used for switches such as –version, –trace, etc.
239 240 241 242 243 244 245 246 247 |
# File 'lib/commander/runner.rb', line 239 def global_option(*args, &block) switches, description = Runner.separate_switches_from_description(*args) << { args: args, proc: block, switches: switches, description: description, } end |
#global_option_proc(switches, &block) ⇒ Object
Returns a proc allowing for commands to inherit global options. This functionality works whether a block is present for the global option or not, so simple switches such as –verbose can be used without a block, and used throughout all commands.
485 486 487 488 489 490 491 492 |
# File 'lib/commander/runner.rb', line 485 def global_option_proc(switches, &block) lambda do |value| unless active_command.nil? active_command. << [Runner.switch_to_sym(switches.last), value] end yield value if block && !value.nil? end end |
#help_formatter ⇒ Object
Help formatter instance.
315 316 317 |
# File 'lib/commander/runner.rb', line 315 def help_formatter @__help_formatter ||= program(:help_formatter).new self end |
#help_formatter_alias_defaults ⇒ Object
Returns hash of help formatter alias defaults.
333 334 335 336 337 |
# File 'lib/commander/runner.rb', line 333 def help_formatter_alias_defaults { compact: HelpFormatter::TerminalCompact, } end |
#limit_commands_to_subcommands(command) ⇒ Object
Limit commands to those which are subcommands of the one that is active
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/commander/runner.rb', line 352 def limit_commands_to_subcommands(command) commands.select! do |other_sym, _| other = other_sym.to_s # Do not match sub-sub commands (matches for a second space) if /\A#{command.name}\s.*\s/.match?(other) false # Do match regular sub commands elsif /\A#{command.name}\s/.match?(other) true # Do not match any other commands else false end end end |
#never_trace! ⇒ Object
Hide the trace option from the help menus and don’t add it as a global option
148 149 150 151 152 |
# File 'lib/commander/runner.rb', line 148 def never_trace! @always_trace = false @never_trace = true @silent_trace = false end |
#parse_global_options ⇒ Object
Parse global command options.
464 465 466 467 468 469 470 471 472 473 474 475 476 477 |
# File 'lib/commander/runner.rb', line 464 def parser = .inject(OptionParser.new) do |, option| .on(*option[:args], &global_option_proc(option[:switches], &option[:proc])) end = @args.dup begin parser.parse!() rescue OptionParser::InvalidOption => e # Remove the offending args and retry. = .reject { |o| e.args.include?(o) } retry end end |
#program(key, *args, &block) ⇒ Object
Assign program information.
Examples
# Set data
program :name, 'Commander'
program :version, Commander::VERSION
program :description, 'Commander utility program.'
program :help, 'Copyright', '2008 TJ Holowaychuk'
program :help, 'Anything', 'You want'
program :int_message 'Bye bye!'
program :help_formatter, :compact
program :help_formatter, Commander::HelpFormatter::TerminalCompact
# Get data
program :name # => 'Commander'
Keys
:version (required) Program version triple, ex: '0.0.1'
:description (required) Program description
:name Program name, defaults to basename of executable
:help_formatter Defaults to Commander::HelpFormatter::Terminal
:help Allows addition of arbitrary global help blocks
:help_paging Flag for toggling help paging
:int_message Message to display when interrupted (CTRL + C)
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/commander/runner.rb', line 200 def program(key, *args, &block) if key == :help && !args.empty? @program[:help] ||= {} @program[:help][args.first] = args.at(1) elsif key == :help_formatter && !args.empty? @program[key] = (@help_formatter_aliases[args.first] || args.first) elsif block @program[key] = block else unless args.empty? @program[key] = args.count == 1 ? args[0] : args end @program[key] end end |
#program_defaults ⇒ Object
Returns hash of program defaults.
342 343 344 345 346 347 348 |
# File 'lib/commander/runner.rb', line 342 def program_defaults { help_formatter: HelpFormatter::Terminal, name: File.basename($PROGRAM_NAME), help_paging: true, } end |
#remove_global_options(options, args) ⇒ Object
Removes global options from args. This prevents an invalid option error from occurring when options are parsed again for the command.
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/commander/runner.rb', line 420 def (, args) # TODO: refactor with flipflop, please TJ ! have time to refactor me ! .each do |option| switches = option[:switches].dup next if switches.empty? if (switch_has_arg = switches.any? { |s| s =~ /[ =]/ }) switches.map! { |s| s[0, s.index('=') || s.index(' ') || s.length] } end switches = (switches) past_switch, arg_removed = false, false args.delete_if do |arg| if switches.any? { |s| s == arg } arg_removed = !switch_has_arg past_switch = true elsif past_switch && !arg_removed && arg !~ /^-/ arg_removed = true else arg_removed = true false end end end end |
#require_program(*keys) ⇒ Object
Raises a CommandError when the program any of the keys are not present, or empty.
497 498 499 500 501 |
# File 'lib/commander/runner.rb', line 497 def require_program(*keys) keys.each do |key| fail CommandError, "program #{key} required" if program(key).nil? || program(key).empty? end end |
#require_valid_command(command = active_command) ⇒ Object
Raises InvalidCommandError when a command is not found.
411 412 413 |
# File 'lib/commander/runner.rb', line 411 def require_valid_command(command = active_command) fail InvalidCommandError, 'invalid command', caller if command.nil? end |
#run! ⇒ Object
Run command parsing and execution process.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/commander/runner.rb', line 87 def run! trace = @always_trace || false require_program :version, :description trap('INT') { abort program(:int_message) } if program(:int_message) trap('INT') { program(:int_block).call } if program(:int_block) global_option('-h', '--help', 'Display help documentation') do args = @args - %w(-h --help) command(:help).run(*args) return end global_option('--version', 'Display version information') do say version return end global_option('--trace', 'Display backtrace when an error occurs') { trace = true } unless @never_trace || @always_trace , @args if trace run_active_command else begin run_active_command rescue InvalidCommandError => e error_handler&.call(self, e) || abort("#{e}. Use --help for more information") rescue \ OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e error_handler&.call(self, e) || abort(e.to_s) rescue => e error_handler&.call(self, e) || if @never_trace || @silent_trace abort("error: #{e}.") else abort("error: #{e}. Use --trace to view backtrace") end end end end |
#run_active_command ⇒ Object
Run the active command.
531 532 533 534 535 536 537 538 |
# File 'lib/commander/runner.rb', line 531 def run_active_command require_valid_command if alias? command_name_from_args active_command.run(*(@aliases[command_name_from_args.to_s] + args_without_command_name)) else active_command.run(*args_without_command_name) end end |
#say(*args) ⇒ Object
:nodoc:
540 541 542 |
# File 'lib/commander/runner.rb', line 540 def say(*args) #:nodoc: $terminal.say(*args) end |
#silent_trace! ⇒ Object
Includes the trace option in the help but not in the error message
157 158 159 160 161 |
# File 'lib/commander/runner.rb', line 157 def silent_trace! @always_trace = false @never_trace = false @silent_trace = true end |
#valid_command_names_from(*args) ⇒ Object
Returns array of valid command names found within args.
307 308 309 310 |
# File 'lib/commander/runner.rb', line 307 def valid_command_names_from(*args) arg_string = args.delete_if { |value| value =~ /^-/ }.join ' ' commands.keys.find_all { |name| name if arg_string =~ /^#{name}\b/ } end |
#version ⇒ Object
Return program version.
132 133 134 |
# File 'lib/commander/runner.rb', line 132 def version format('%s %s', program(:name), program(:version)) end |