Class: CLAide::Command
- Inherits:
-
Object
- Object
- CLAide::Command
- Defined in:
- lib/claide/command.rb,
lib/claide/command/banner.rb,
lib/claide/command/plugin_manager.rb,
lib/claide/command/argument_suggester.rb
Overview
This class is used to build a command-line interface
Each command is represented by a subclass of this class, which may be nested to create more granular commands.
Following is an overview of the types of commands and what they should do.
Any command type
- Inherit from the command class under which the command should be nested.
- Set Command.summary to a brief description of the command.
- Override Command.options to return the options it handles and their
descriptions and prepending them to the results of calling
super
. - Override #initialize if it handles any parameters.
- Override #validate! to check if the required parameters the command handles are valid, or call #help! in case they’re not.
Abstract command
The following is needed for an abstract command:
- Set Command.abstract_command to
true
. - Subclass the command.
When the optional Command.description is specified, it will be shown at the top of the command’s help banner.
Normal command
The following is needed for a normal command:
- Set Command.arguments to the description of the arguments this command handles.
- Override #run to perform the actual work.
When the optional Command.description is specified, it will be shown underneath the usage section of the command’s help banner. Otherwise this defaults to Command.summary.
Defined Under Namespace
Classes: ArgumentSuggester, Banner, PluginManager
Constant Summary collapse
- DEFAULT_ROOT_OPTIONS =
[ ['--version', 'Show the version of the tool'], ]
- DEFAULT_OPTIONS =
[ ['--verbose', 'Show more debugging information'], ['--no-ansi', 'Show output without ANSI codes'], ['--help', 'Show help banner of specified command'], ]
Class Attribute Summary collapse
-
.abstract_command ⇒ Boolean
(also: abstract_command?)
Indicates whether or not this command can actually perform work of itself, or that it only contains subcommands.
-
.ansi_output ⇒ Boolean
(also: ansi_output?)
The default value for #ansi_output.
-
.command ⇒ String
The name of the command.
-
.default_subcommand ⇒ String
The subcommand which an abstract command should invoke by default.
-
.description ⇒ String
A longer description of the command, which is shown underneath the usage section of the command’s help banner.
-
.ignore_in_command_lookup ⇒ Boolean
(also: ignore_in_command_lookup?)
Indicates whether or not this command is used during command parsing and whether or not it should be shown in the help banner or to show its subcommands instead.
-
.plugin_prefixes ⇒ Array<String>
The prefixes used to search for CLAide plugins.
-
.summary ⇒ String
A brief description of the command, which is shown next to the command in the help banner of a parent command.
-
.version ⇒ String
The version of the command.
Instance Attribute Summary collapse
-
#ansi_output ⇒ Boolean
(also: #ansi_output?)
Set to
true
if Command.ansi_output returnstrue
and the user did not specify the--no-ansi
option. -
#help_arg ⇒ Boolean
(also: #help?)
Set to
true
if initialized with a--help
flag. -
#invoked_as_default ⇒ Bool
(also: #invoked_as_default?)
Whether the command was invoked by an abstract command by default.
-
#verbose ⇒ Boolean
(also: #verbose?)
Set to
true
if the user specifies the--verbose
option.
Class Method Summary collapse
-
.arguments ⇒ Array<Argument>
A list of arguments the command handles.
- .arguments=(arguments) ⇒ Object
-
.arguments_array=(arguments) ⇒ Object
protected
Handle deprecated form of self.arguments as an Array
> like in:. -
.arguments_string=(arguments) ⇒ Object
protected
Handle deprecated form of self.arguments as a String, like in:.
-
.find_subcommand(name) ⇒ CLAide::Command?
Searches the list of subcommands that should not be ignored for command lookup for a subcommand with the given
name
. -
.full_command ⇒ String
The full command up-to this command, as it would be looked up during parsing.
-
.handle_exception(command, exception) ⇒ void
Presents an exception to the user in a short manner in case of an
InformativeError
or in long form in other cases,. -
.invoke(*args) ⇒ Object
Convenience method.
-
.load_default_subcommand(argv) ⇒ Command
Returns the default subcommand initialized with the given arguments.
-
.options ⇒ Array<Array>
Should be overridden by a subclass if it handles any options.
-
.parse(argv) ⇒ Command
An instance of the command class that was matched by going through the arguments in the parameters and drilling down command classes.
-
.plugin_prefix=(prefix) ⇒ Object
protected
Handle depracted form of assigning a plugin prefix.
-
.report_error(exception) ⇒ void
Allows the application to perform custom error reporting, by overriding this method.
-
.root_command? ⇒ Bool
Whether this is the root command class.
-
.run(argv = []) ⇒ void
Instantiates the command class matching the parameters through Command.parse, validates it through #validate!, and runs it through #run.
-
.subcommands ⇒ Array<Class>
A list of all command classes that are nested under this command.
-
.subcommands_for_command_lookup ⇒ Array<Class>
A list of command classes that are nested under this command or the subcommands of those command classes in case the command class should be ignored in command lookup.
Instance Method Summary collapse
-
#banner! ⇒ void
protected
Print banner and exit.
-
#handle_root_options(argv) ⇒ Bool
Handles root commands options if appropriate.
- #help!(error_message = nil) ⇒ void protected
-
#initialize(argv) ⇒ Command
constructor
Subclasses should override this method to remove the arguments/options they support from
argv
before callingsuper
. -
#invoked_command_class ⇒ Command
protected
Returns the class of the invoked command.
-
#print_version ⇒ Object
Prints the version of the command optionally including plugins.
-
#run ⇒ void
This method should be overridden by the command class to perform its work.
-
#validate! ⇒ void
Raises a Help exception if the
--help
option is specified, ifargv
still contains remaining arguments/options by the time it reaches this implementation, or when called on an ‘abstract command’.
Constructor Details
#initialize(argv) ⇒ Command
Subclasses should override this method to remove the arguments/options
they support from argv
before calling super
.
The super
implementation sets the #verbose attribute based on whether
or not the --verbose
option is specified; and the #ansi_output
attribute to false
if ansi_output returns true
, but the
user specified the --no-ansi
option.
521 522 523 524 525 526 527 |
# File 'lib/claide/command.rb', line 521 def initialize(argv) argv = ARGV.coerce(argv) @verbose = argv.flag?('verbose') @ansi_output = argv.flag?('ansi', Command.ansi_output?) @argv = argv @help_arg = argv.flag?('help') end |
Class Attribute Details
.abstract_command ⇒ Boolean Also known as: abstract_command?
Returns Indicates whether or not this command can actually perform work of itself, or that it only contains subcommands.
52 53 54 |
# File 'lib/claide/command.rb', line 52 def abstract_command @abstract_command end |
.ansi_output ⇒ Boolean Also known as: ansi_output?
Returns The default value for #ansi_output. This
defaults to true
if STDOUT
is connected to a TTY and
String
has the instance methods #red
, #green
, and
#yellow
(which are defined by, for instance, the
colored gem).
127 128 129 130 131 132 |
# File 'lib/claide/command.rb', line 127 def ansi_output if @ansi_output.nil? @ansi_output = STDOUT.tty? end @ansi_output end |
.command ⇒ String
Returns The name of the command. Defaults to a snake-cased version of the class’ name.
139 140 141 142 143 |
# File 'lib/claide/command.rb', line 139 def command @command ||= name.split('::').last.gsub(/[A-Z]+[a-z]*/) do |part| part.downcase << '-' end[0..-2] end |
.default_subcommand ⇒ String
Returns The subcommand which an abstract command should invoke by default.
70 71 72 |
# File 'lib/claide/command.rb', line 70 def default_subcommand @default_subcommand end |
.description ⇒ String
Returns A longer description of the command, which is shown underneath the usage section of the command’s help banner. Any indentation in this value will be ignored.
81 82 83 |
# File 'lib/claide/command.rb', line 81 def description @description end |
.ignore_in_command_lookup ⇒ Boolean Also known as: ignore_in_command_lookup?
Returns Indicates whether or not this command is used during command parsing and whether or not it should be shown in the help banner or to show its subcommands instead.
Setting this to true
implies it’s an abstract command.
61 62 63 |
# File 'lib/claide/command.rb', line 61 def ignore_in_command_lookup @ignore_in_command_lookup end |
.plugin_prefixes ⇒ Array<String>
Returns The prefixes used to search for CLAide plugins.
Plugins are loaded via their <plugin_prefix>_plugin.rb
file.
Defaults to search for claide
plugins.
87 88 89 |
# File 'lib/claide/command.rb', line 87 def plugin_prefixes @plugin_prefixes ||= ['claide'] end |
.summary ⇒ String
Returns A brief description of the command, which is shown next to the command in the help banner of a parent command.
75 76 77 |
# File 'lib/claide/command.rb', line 75 def summary @summary end |
.version ⇒ String
Returns The version of the command. This value will be printed
by the --version
flag if used for the root command.
149 150 151 |
# File 'lib/claide/command.rb', line 149 def version @version end |
Instance Attribute Details
#ansi_output ⇒ Boolean Also known as: ansi_output?
If you want to make use of this value for your own configuration, you
should check the value after calling the super
#initialize
implementation.
Set to true
if ansi_output returns true
and the user
did not specify the --no-ansi
option.
497 498 499 |
# File 'lib/claide/command.rb', line 497 def ansi_output @ansi_output end |
#help_arg ⇒ Boolean Also known as: help?
Set to true
if initialized with a --help
flag
506 507 508 |
# File 'lib/claide/command.rb', line 506 def help_arg @help_arg end |
#invoked_as_default ⇒ Bool Also known as: invoked_as_default?
Returns Whether the command was invoked by an abstract command by default.
550 551 552 |
# File 'lib/claide/command.rb', line 550 def invoked_as_default @invoked_as_default end |
#verbose ⇒ Boolean Also known as: verbose?
If you want to make use of this value for your own configuration, you
should check the value after calling the super
#initialize
implementation.
Set to true
if the user specifies the --verbose
option.
483 484 485 |
# File 'lib/claide/command.rb', line 483 def verbose @verbose end |
Class Method Details
.arguments ⇒ Array<Argument>
Returns A list of arguments the command handles. This is shown in the usage section of the command’s help banner. Each Argument in the array represents an argument by its name (or list of alternatives) and whether it's required or optional.
98 99 100 |
# File 'lib/claide/command.rb', line 98 def arguments @arguments ||= [] end |
.arguments=(arguments) ⇒ Object
Remove deprecation
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/claide/command.rb', line 109 def arguments=(arguments) if arguments.is_a?(Array) if arguments.empty? || arguments[0].is_a?(Argument) @arguments = arguments else self.arguments_array = arguments end else self.arguments_string = arguments end end |
.arguments_array=(arguments) ⇒ Object (protected)
Remove deprecated format support
Handle deprecated form of self.arguments as an
Array
self.arguments = [ ['NAME', :required], ['QUERY', :optional] ]
631 632 633 634 635 636 637 638 639 |
# File 'lib/claide/command.rb', line 631 def self.arguments_array=(arguments) warn '[!] The signature of CLAide#arguments has changed. ' \ "Use CLAide::Argument (#{self}: `#{arguments}`)".ansi.yellow @arguments = arguments.map do |(name_str, type)| names = name_str.split('|') required = (type == :required) Argument.new(names, required) end end |
.arguments_string=(arguments) ⇒ Object (protected)
Remove deprecated format support
Handle deprecated form of self.arguments as a String, like in:
self.arguments = 'NAME [QUERY]'
647 648 649 650 651 652 653 654 655 656 657 |
# File 'lib/claide/command.rb', line 647 def self.arguments_string=(arguments) warn '[!] The specification of arguments as a string has been' \ " deprecated #{self}: `#{arguments}`".ansi.yellow @arguments = arguments.split(' ').map do |argument| if argument.start_with?('[') Argument.new(argument.sub(/\[(.*)\]/, '\1').split('|'), false) else Argument.new(argument.split('|'), true) end end end |
.find_subcommand(name) ⇒ CLAide::Command?
Searches the list of subcommands that should not be ignored for command
lookup for a subcommand with the given name
.
210 211 212 |
# File 'lib/claide/command.rb', line 210 def self.find_subcommand(name) subcommands_for_command_lookup.find { |sc| sc.command == name } end |
.full_command ⇒ String
Returns The full command up-to this command, as it would be looked up during parsing.
163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/claide/command.rb', line 163 def self.full_command if superclass == Command ignore_in_command_lookup? ? '' : command else if ignore_in_command_lookup? superclass.full_command else "#{superclass.full_command} #{command}" end end end |
.handle_exception(command, exception) ⇒ void
This method returns an undefined value.
Presents an exception to the user in a short manner in case of an
InformativeError
or in long form in other cases,
387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/claide/command.rb', line 387 def self.handle_exception(command, exception) if exception.is_a?(InformativeError) puts exception. if command.nil? || command.verbose? puts puts(*exception.backtrace) end exit exception.exit_status else report_error(exception) end end |
.invoke(*args) ⇒ Object
This method validate! the command before running it, but contrary to
Convenience method. Instantiate the command and run it with the provided arguments at once.
CLAide::Command::run, it does not load plugins nor exit on failure. It is up to the caller to rescue any possible exception raised.
541 542 543 544 545 |
# File 'lib/claide/command.rb', line 541 def self.invoke(*args) command = new(ARGV.new(args.flatten)) command.validate! command.run end |
.load_default_subcommand(argv) ⇒ Command
Returns the default subcommand initialized with the given arguments.
366 367 368 369 370 371 372 373 374 |
# File 'lib/claide/command.rb', line 366 def self.load_default_subcommand(argv) unless subcommand = find_subcommand(default_subcommand) raise 'Unable to find the default subcommand ' \ "`#{default_subcommand}` for command `#{self}`." end result = subcommand.parse(argv) result.invoked_as_default = true result end |
.options ⇒ Array<Array>
Should be overridden by a subclass if it handles any options.
The subclass has to combine the result of calling super
and its own
list of options. The recommended way of doing this is by concatenating
to this classes’ own options.
251 252 253 254 255 256 257 |
# File 'lib/claide/command.rb', line 251 def self. if root_command? DEFAULT_ROOT_OPTIONS + DEFAULT_OPTIONS else DEFAULT_OPTIONS end end |
.parse(argv) ⇒ Command
Returns An instance of the command class that was matched by going through the arguments in the parameters and drilling down command classes.
347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/claide/command.rb', line 347 def self.parse(argv) argv = ARGV.coerce(argv) cmd = argv.arguments.first if cmd && subcommand = find_subcommand(cmd) argv.shift_argument subcommand.parse(argv) elsif abstract_command? && default_subcommand load_default_subcommand(argv) else new(argv) end end |
.plugin_prefix=(prefix) ⇒ Object (protected)
Remove deprecated form.
Handle depracted form of assigning a plugin prefix.
663 664 665 666 667 |
# File 'lib/claide/command.rb', line 663 def self.plugin_prefix=(prefix) warn '[!] The specification of a singular plugin prefix has been ' \ "deprecated. Use `#{self}::plugin_prefixes` instead." plugin_prefixes << prefix end |
.report_error(exception) ⇒ void
This method returns an undefined value.
Allows the application to perform custom error reporting, by overriding this method.
414 415 416 417 418 419 420 421 |
# File 'lib/claide/command.rb', line 414 def self.report_error(exception) plugins = PluginManager.plugins_involved_in_exception(exception) unless plugins.empty? puts '[!] The exception involves the following plugins:' \ "\n - #{plugins.join("\n - ")}\n".ansi.yellow end raise exception end |
.root_command? ⇒ Bool
Returns Whether this is the root command class.
177 178 179 |
# File 'lib/claide/command.rb', line 177 def self.root_command? superclass == CLAide::Command end |
.run(argv = []) ⇒ void
The ANSI support is configured before running a command to allow the same process to run multiple commands with different settings. For example a process with ANSI output enabled might want to programmatically invoke another command with the output enabled.
This method returns an undefined value.
Instantiates the command class matching the parameters through parse, validates it through #validate!, and runs it through #run.
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/claide/command.rb', line 324 def self.run(argv = []) plugin_prefixes.each do |plugin_prefix| PluginManager.load_plugins(plugin_prefix) end argv = ARGV.coerce(argv) command = parse(argv) ANSI.disabled = !command.ansi_output? unless command.(argv) command.validate! command.run end rescue Object => exception handle_exception(command, exception) end |
.subcommands ⇒ Array<Class>
Returns A list of all command classes that are nested under this command.
184 185 186 |
# File 'lib/claide/command.rb', line 184 def self.subcommands @subcommands ||= [] end |
.subcommands_for_command_lookup ⇒ Array<Class>
Returns A list of command classes that are nested under this command or the subcommands of those command classes in case the command class should be ignored in command lookup.
192 193 194 195 196 197 198 199 200 |
# File 'lib/claide/command.rb', line 192 def self.subcommands_for_command_lookup subcommands.map do |subcommand| if subcommand.ignore_in_command_lookup? subcommand.subcommands_for_command_lookup else subcommand end end.flatten end |
Instance Method Details
#banner! ⇒ void (protected)
Calling this method exits the current process.
This method returns an undefined value.
Print banner and exit
618 619 620 |
# File 'lib/claide/command.rb', line 618 def invoked_command_class. end |
#handle_root_options(argv) ⇒ Bool
Handles root commands options if appropriate.
288 289 290 291 292 293 294 295 |
# File 'lib/claide/command.rb', line 288 def (argv) return false unless self.class.root_command? if argv.flag?('version') print_version return true end false end |
#help!(error_message = nil) ⇒ void (protected)
This method returns an undefined value.
608 609 610 |
# File 'lib/claide/command.rb', line 608 def help!( = nil) invoked_command_class.help!() end |
#invoked_command_class ⇒ Command (protected)
Returns the class of the invoked command
590 591 592 593 594 595 596 |
# File 'lib/claide/command.rb', line 590 def invoked_command_class if invoked_as_default? self.class.superclass else self.class end end |
#print_version ⇒ Object
Prints the version of the command optionally including plugins.
299 300 301 302 303 304 305 306 |
# File 'lib/claide/command.rb', line 299 def print_version puts self.class.version if verbose? PluginManager.specifications.each do |spec| puts "#{spec.name}: #{spec.version}" end end end |
#run ⇒ void
This method returns an undefined value.
This method should be overridden by the command class to perform its work.
579 580 581 582 |
# File 'lib/claide/command.rb', line 579 def run raise 'A subclass should override the `CLAide::Command#run` method to ' \ 'actually perform some work.' end |
#validate! ⇒ void
This method returns an undefined value.
Raises a Help exception if the --help
option is specified, if argv
still contains remaining arguments/options by the time it reaches this
implementation, or when called on an ‘abstract command’.
Subclasses should call super
before doing their own validation. This
way when the user specifies the --help
flag a help banner is shown,
instead of possible actual validation errors.
565 566 567 568 569 570 571 572 |
# File 'lib/claide/command.rb', line 565 def validate! if help? unless @argv.empty? argument = @argv.remainder.first help! ArgumentSuggester.new(argument, self.class).suggestion end help! if self.class.abstract_command? end |