281 lines
7.9 KiB
Ruby
281 lines
7.9 KiB
Ruby
|
module Mercenary
|
||
|
class Command
|
||
|
attr_reader :name
|
||
|
attr_reader :description
|
||
|
attr_reader :syntax
|
||
|
attr_accessor :options
|
||
|
attr_accessor :commands
|
||
|
attr_accessor :actions
|
||
|
attr_reader :map
|
||
|
attr_accessor :parent
|
||
|
attr_reader :trace
|
||
|
attr_reader :aliases
|
||
|
|
||
|
# Public: Creates a new Command
|
||
|
#
|
||
|
# name - the name of the command
|
||
|
# parent - (optional) the instancce of Mercenary::Command which you wish to
|
||
|
# be the parent of this command
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def initialize(name, parent = nil)
|
||
|
@name = name
|
||
|
@options = []
|
||
|
@commands = {}
|
||
|
@actions = []
|
||
|
@map = {}
|
||
|
@parent = parent
|
||
|
@trace = false
|
||
|
@aliases = []
|
||
|
end
|
||
|
|
||
|
# Public: Sets or gets the command version
|
||
|
#
|
||
|
# version - the command version (optional)
|
||
|
#
|
||
|
# Returns the version and sets it if an argument is non-nil
|
||
|
def version(version = nil)
|
||
|
@version = version if version
|
||
|
@version
|
||
|
end
|
||
|
|
||
|
# Public: Sets or gets the syntax string
|
||
|
#
|
||
|
# syntax - the string which describes this command's usage syntax (optional)
|
||
|
#
|
||
|
# Returns the syntax string and sets it if an argument is present
|
||
|
def syntax(syntax = nil)
|
||
|
@syntax = syntax if syntax
|
||
|
syntax_list = []
|
||
|
if parent
|
||
|
syntax_list << parent.syntax.to_s.gsub(/<[\w\s-]+>/, '').gsub(/\[[\w\s-]+\]/, '').strip
|
||
|
end
|
||
|
syntax_list << (@syntax || name.to_s)
|
||
|
syntax_list.join(" ")
|
||
|
end
|
||
|
|
||
|
# Public: Sets or gets the command description
|
||
|
#
|
||
|
# description - the description of what the command does (optional)
|
||
|
#
|
||
|
# Returns the description and sets it if an argument is present
|
||
|
def description(desc = nil)
|
||
|
@description = desc if desc
|
||
|
@description
|
||
|
end
|
||
|
|
||
|
# Public: Sets the default command
|
||
|
#
|
||
|
# command_name - the command name to be executed in the event no args are
|
||
|
# present
|
||
|
#
|
||
|
# Returns the default command if there is one, `nil` otherwise
|
||
|
def default_command(command_name = nil)
|
||
|
if command_name
|
||
|
if commands.has_key?(command_name)
|
||
|
@default_command = commands[command_name] if command_name
|
||
|
@default_command
|
||
|
else
|
||
|
raise ArgumentError.new("'#{command_name}' couldn't be found in this command's list of commands.")
|
||
|
end
|
||
|
else
|
||
|
@default_command
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Public: Adds an option switch
|
||
|
#
|
||
|
# sym - the variable key which is used to identify the value of the switch
|
||
|
# at runtime in the options hash
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def option(sym, *options)
|
||
|
new_option = Option.new(sym, options)
|
||
|
@options << new_option
|
||
|
@map[new_option] = sym
|
||
|
end
|
||
|
|
||
|
# Public: Adds a subcommand
|
||
|
#
|
||
|
# cmd_name - the name of the command
|
||
|
# block - a block accepting the new instance of Mercenary::Command to be
|
||
|
# modified (optional)
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def command(cmd_name)
|
||
|
cmd = Command.new(cmd_name, self)
|
||
|
yield cmd
|
||
|
@commands[cmd_name] = cmd
|
||
|
end
|
||
|
|
||
|
# Public: Add an alias for this command's name to be attached to the parent
|
||
|
#
|
||
|
# cmd_name - the name of the alias
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def alias(cmd_name)
|
||
|
logger.debug "adding alias to parent for self: '#{cmd_name}'"
|
||
|
aliases << cmd_name
|
||
|
@parent.commands[cmd_name] = self
|
||
|
end
|
||
|
|
||
|
# Public: Add an action Proc to be executed at runtime
|
||
|
#
|
||
|
# block - the Proc to be executed at runtime
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def action(&block)
|
||
|
@actions << block
|
||
|
end
|
||
|
|
||
|
# Public: Fetch a Logger (stdlib)
|
||
|
#
|
||
|
# level - the logger level (a Logger constant, see docs for more info)
|
||
|
#
|
||
|
# Returns the instance of Logger
|
||
|
def logger(level = nil)
|
||
|
unless @logger
|
||
|
@logger = Logger.new(STDOUT)
|
||
|
@logger.level = level || Logger::INFO
|
||
|
@logger.formatter = proc do |severity, datetime, progname, msg|
|
||
|
"#{identity} | " << "#{severity.downcase.capitalize}:".ljust(7) << " #{msg}\n"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@logger.level = level unless level.nil?
|
||
|
@logger
|
||
|
end
|
||
|
|
||
|
# Public: Run the command
|
||
|
#
|
||
|
# argv - an array of string args
|
||
|
# opts - the instance of OptionParser
|
||
|
# config - the output config hash
|
||
|
#
|
||
|
# Returns the command to be executed
|
||
|
def go(argv, opts, config)
|
||
|
opts.banner = "Usage: #{syntax}"
|
||
|
process_options(opts, config)
|
||
|
add_default_options(opts)
|
||
|
|
||
|
if argv[0] && cmd = commands[argv[0].to_sym]
|
||
|
logger.debug "Found subcommand '#{cmd.name}'"
|
||
|
argv.shift
|
||
|
cmd.go(argv, opts, config)
|
||
|
else
|
||
|
logger.debug "No additional command found, time to exec"
|
||
|
self
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Public: Add this command's options to OptionParser and set a default
|
||
|
# action of setting the value of the option to the inputted hash
|
||
|
#
|
||
|
# opts - instance of OptionParser
|
||
|
# config - the Hash in which the option values should be placed
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def process_options(opts, config)
|
||
|
options.each do |option|
|
||
|
opts.on(*option.for_option_parser) do |x|
|
||
|
config[map[option]] = x
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Public: Add version and help options to the command
|
||
|
#
|
||
|
# opts - instance of OptionParser
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def add_default_options(opts)
|
||
|
option 'show_help', '-h', '--help', 'Show this message'
|
||
|
option 'show_version', '-v', '--version', 'Print the name and version'
|
||
|
option 'show_backtrace', '-t', '--trace', 'Show the full backtrace when an error occurs'
|
||
|
opts.on("-v", "--version", "Print the version") do
|
||
|
puts "#{name} #{version}"
|
||
|
exit(0)
|
||
|
end
|
||
|
|
||
|
opts.on('-t', '--trace', 'Show full backtrace if an error occurs') do
|
||
|
@trace = true
|
||
|
end
|
||
|
|
||
|
opts.on_tail("-h", "--help", "Show this message") do
|
||
|
puts self
|
||
|
exit
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Public: Execute all actions given the inputted args and options
|
||
|
#
|
||
|
# argv - (optional) command-line args (sans opts)
|
||
|
# config - (optional) the Hash configuration of string key to value
|
||
|
#
|
||
|
# Returns nothing
|
||
|
def execute(argv = [], config = {})
|
||
|
if actions.empty? && !default_command.nil?
|
||
|
default_command.execute
|
||
|
else
|
||
|
actions.each { |a| a.call(argv, config) }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Public: Check if this command has a subcommand
|
||
|
#
|
||
|
# sub_command - the name of the subcommand
|
||
|
#
|
||
|
# Returns true if this command is the parent of a command of name
|
||
|
# 'sub_command' and false otherwise
|
||
|
def has_command?(sub_command)
|
||
|
commands.keys.include?(sub_command)
|
||
|
end
|
||
|
|
||
|
# Public: Identify this command
|
||
|
#
|
||
|
# Returns a string which identifies this command
|
||
|
def ident
|
||
|
"<Command name=#{identity}>"
|
||
|
end
|
||
|
|
||
|
# Public: Get the full identity (name & version) of this command
|
||
|
#
|
||
|
# Returns a string containing the name and version if it exists
|
||
|
def identity
|
||
|
"#{full_name} #{version if version}".strip
|
||
|
end
|
||
|
|
||
|
# Public: Get the name of the current command plus that of
|
||
|
# its parent commands
|
||
|
#
|
||
|
# Returns the full name of the command
|
||
|
def full_name
|
||
|
the_name = []
|
||
|
the_name << parent.full_name if parent && parent.full_name
|
||
|
the_name << name
|
||
|
the_name.join(" ")
|
||
|
end
|
||
|
|
||
|
# Public: Return all the names and aliases for this command.
|
||
|
#
|
||
|
# Returns a comma-separated String list of the name followed by its aliases
|
||
|
def names_and_aliases
|
||
|
([name.to_s] + aliases).compact.join(", ")
|
||
|
end
|
||
|
|
||
|
# Public: Build a string containing a summary of the command
|
||
|
#
|
||
|
# Returns a one-line summary of the command.
|
||
|
def summarize
|
||
|
" #{names_and_aliases.ljust(20)} #{description}"
|
||
|
end
|
||
|
|
||
|
# Public: Build a string containing the command name, options and any subcommands
|
||
|
#
|
||
|
# Returns the string identifying this command, its options and its subcommands
|
||
|
def to_s
|
||
|
Presenter.new(self).print_command
|
||
|
end
|
||
|
end
|
||
|
end
|