Class: Commander::Runner
Overview
Runner
The engine behind Commander's DSL: holds the registered commands, global options, and program metadata, parses ARGV, and dispatches to the matched Commander::Command. Accessed as a singleton (Runner.instance) by Commander::Delegates so that the top-level +command+/+program+/+run!+ methods all operate on the same runner.
Defined Under Namespace
Classes: CommandError, InvalidCommandError
Instance Attribute Summary collapse
-
#commands ⇒ Object
readonly
Returns the value of attribute commands.
-
#help_formatter_aliases ⇒ Object
readonly
Returns the value of attribute help_formatter_aliases.
-
#options ⇒ Object
readonly
Returns the value of attribute 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.
-
#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.
-
#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:.
-
#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.
30 31 32 33 34 35 36 37 |
# File 'lib/commander/runner.rb', line 30 def initialize(args = ARGV) @args, @commands, @aliases, @options = args, {}, {}, [] @help_formatter_aliases = help_formatter_alias_defaults @program = program_defaults @always_trace = false @never_trace = false create_default_commands end |
Instance Attribute Details
#commands ⇒ Object (readonly)
Returns the value of attribute commands.
25 26 27 |
# File 'lib/commander/runner.rb', line 25 def commands @commands end |
#help_formatter_aliases ⇒ Object (readonly)
Returns the value of attribute help_formatter_aliases.
25 26 27 |
# File 'lib/commander/runner.rb', line 25 def help_formatter_aliases @help_formatter_aliases end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
25 26 27 |
# File 'lib/commander/runner.rb', line 25 def @options end |
Class Method Details
.instance ⇒ Object
Return singleton Runner instance.
41 42 43 |
# File 'lib/commander/runner.rb', line 41 def self.instance @instance ||= new end |
.separate_switches_from_description(*args) ⇒ Object
Return switches and description separated from the args passed.
396 397 398 399 400 |
# File 'lib/commander/runner.rb', line 396 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
413 414 415 |
# File 'lib/commander/runner.rb', line 413 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.
218 219 220 |
# File 'lib/commander/runner.rb', line 218 def active_command @active_command ||= command(command_name_from_args) end |
#add_command(command) ⇒ Object
Add a command object to this runner.
198 199 200 |
# File 'lib/commander/runner.rb', line 198 def add_command(command) @commands[command.name] = command end |
#alias?(name) ⇒ Boolean
Check if command name is an alias.
204 205 206 |
# File 'lib/commander/runner.rb', line 204 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.
184 185 186 187 |
# File 'lib/commander/runner.rb', line 184 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)
94 95 96 97 |
# File 'lib/commander/runner.rb', line 94 def always_trace! @always_trace = true @never_trace = false end |
#args_without_command_name ⇒ Object
Return arguments without the command name.
246 247 248 249 250 251 252 |
# File 'lib/commander/runner.rb', line 246 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
163 164 165 166 |
# File 'lib/commander/runner.rb', line 163 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.
210 211 212 |
# File 'lib/commander/runner.rb', line 210 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. Returns the default command, if no valid commands found in the args.
226 227 228 |
# File 'lib/commander/runner.rb', line 226 def command_name_from_args @command_name_from_args ||= longest_valid_command_name_from(@args) || @default_command end |
#create_default_commands ⇒ Object
Creates default commands such as 'help' which is essentially the same as using the --help switch.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/commander/runner.rb', line 275 def create_default_commands command :help do |c| c.syntax = 'commander help [command]' c.description = 'Display global or [command] help documentation' c.example 'Display global help', 'command help' c.example "Display help for 'foo'", 'command help foo' c.when_called do |args, | UI.enable_paging if program(:help_paging) if args.empty? say help_formatter.render else command = command(longest_valid_command_name_from(args)) begin require_valid_command command rescue InvalidCommandError => e abort "#{e}. Use --help for more information" end say help_formatter.render_command(command) end end end end |
#default_command(name) ⇒ Object
Default command name to be used when no other command is found in the arguments.
192 193 194 |
# File 'lib/commander/runner.rb', line 192 def default_command(name) @default_command = name 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
344 345 346 347 348 349 350 351 352 353 |
# File 'lib/commander/runner.rb', line 344 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.
171 172 173 174 175 176 177 178 179 |
# File 'lib/commander/runner.rb', line 171 def global_option(*args, &block) switches, description = Runner.separate_switches_from_description(*args) @options << { 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.
377 378 379 380 381 382 383 384 |
# File 'lib/commander/runner.rb', line 377 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.
240 241 242 |
# File 'lib/commander/runner.rb', line 240 def help_formatter @help_formatter ||= program(:help_formatter).new self end |
#help_formatter_alias_defaults ⇒ Object
Returns hash of help formatter alias defaults.
256 257 258 259 260 |
# File 'lib/commander/runner.rb', line 256 def help_formatter_alias_defaults { compact: HelpFormatter::TerminalCompact, } end |
#never_trace! ⇒ Object
Hide the trace option from the help menus and don't add it as a global option
101 102 103 104 |
# File 'lib/commander/runner.rb', line 101 def never_trace! @never_trace = true @always_trace = false end |
#parse_global_options ⇒ Object
Parse global command options.
357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/commander/runner.rb', line 357 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)
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/commander/runner.rb', line 134 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.length == 1 ? args[0] : args end @program[key] end end |
#program_defaults ⇒ Object
Returns hash of program defaults.
264 265 266 267 268 269 270 |
# File 'lib/commander/runner.rb', line 264 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.
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/commander/runner.rb', line 308 def (, args) .each do |option| switches = option[:switches] next if switches.empty? option_takes_argument = switches.any? { |s| s =~ /[ =]/ } switches = (switches) option_argument_needs_removal = false args.delete_if do |token| break if token == '--' # Use just the portion of the token before the = when # comparing switches. index_of_equals = token.index('=') if option_takes_argument token = token[0, index_of_equals] if index_of_equals token_contains_option_argument = !index_of_equals.nil? if switches.any? { |s| s[0, token.length] == token } option_argument_needs_removal = option_takes_argument && !token_contains_option_argument true elsif option_argument_needs_removal && token !~ /^-/ option_argument_needs_removal = false true else option_argument_needs_removal = false false end end end end |
#require_program(*keys) ⇒ Object
Raises a CommandError when the program any of the keys are not present, or empty.
388 389 390 391 392 |
# File 'lib/commander/runner.rb', line 388 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.
300 301 302 |
# File 'lib/commander/runner.rb', line 300 def require_valid_command(command = active_command) fail InvalidCommandError, 'invalid command', caller if command.nil? end |
#run! ⇒ Object
Run command parsing and execution process.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/commander/runner.rb', line 47 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('-v', '--version', 'Display version information') do say version return end global_option('-t', '--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 abort "#{e}. Use --help for more information" rescue \ OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e abort e.to_s rescue StandardError => e if @never_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.
419 420 421 422 423 424 425 426 |
# File 'lib/commander/runner.rb', line 419 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:
428 429 430 |
# File 'lib/commander/runner.rb', line 428 def say(*args) # :nodoc: HighLine.default_instance.say(*args) end |
#valid_command_names_from(*args) ⇒ Object
Returns array of valid command names found within args.
232 233 234 235 236 |
# File 'lib/commander/runner.rb', line 232 def valid_command_names_from(*args) , 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.
88 89 90 |
# File 'lib/commander/runner.rb', line 88 def version format('%s %s', program(:name), program(:version)) end |