1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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 85 86 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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
require "clamp/messages"
require "clamp/errors"
require "clamp/help"
require "clamp/option/declaration"
require "clamp/option/parsing"
require "clamp/parameter/declaration"
require "clamp/parameter/parsing"
require "clamp/subcommand/declaration"
require "clamp/subcommand/parsing"
module Clamp
# {Command} models a shell command. Each command invocation is a new object.
# Command options and parameters are represented as attributes
# (see {Command::Declaration}).
#
# The main entry-point is {#run}, which uses {#parse} to populate attributes based
# on an array of command-line arguments, then calls {#execute} (which you provide)
# to make it go.
#
class Command
# Create a command execution.
#
# @param [String] invocation_path the path used to invoke the command
# @param [Hash] context additional data the command may need
#
def initialize(invocation_path, context = {})
@invocation_path = invocation_path
@context = context
end
# @return [String] the path used to invoke this command
#
attr_reader :invocation_path
# @return [Array<String>] unconsumed command-line arguments
#
attr_reader :remaining_arguments
# Parse command-line arguments.
#
# @param [Array<String>] arguments command-line arguments
# @return [Array<String>] unconsumed arguments
#
def parse(arguments)
@remaining_arguments = arguments.dup
parse_options
parse_parameters
parse_subcommand
handle_remaining_arguments
end
# Run the command, with the specified arguments.
#
# This calls {#parse} to process the command-line arguments,
# then delegates to {#execute}.
#
# @param [Array<String>] arguments command-line arguments
#
def run(arguments)
parse(arguments)
execute
end
# Execute the command (assuming that all options/parameters have been set).
#
# This method is designed to be overridden in sub-classes.
#
def execute
raise "you need to define #execute"
end
# @return [String] usage documentation for this command
#
def help
self.class.help(invocation_path)
end
# Abort with subcommand missing usage error
#
# @ param [String] name subcommand_name
def subcommand_missing(name)
signal_usage_error(Clamp.message(:no_such_subcommand, :name => name))
end
include Clamp::Option::Parsing
include Clamp::Parameter::Parsing
include Clamp::Subcommand::Parsing
protected
attr_accessor :context
def handle_remaining_arguments
signal_usage_error Clamp.message(:too_many_arguments) unless remaining_arguments.empty?
end
private
def signal_usage_error(message)
e = UsageError.new(message, self)
e.set_backtrace(caller)
raise e
end
def signal_error(message, options = {})
status = options.fetch(:status, 1)
e = ExecutionError.new(message, self, status)
e.set_backtrace(caller)
raise e
end
def request_help
raise HelpWanted, self
end
class << self
include Clamp::Option::Declaration
include Clamp::Parameter::Declaration
include Clamp::Subcommand::Declaration
include Help
# Create an instance of this command class, and run it.
#
# @param [String] invocation_path the path used to invoke the command
# @param [Array<String>] arguments command-line arguments
# @param [Hash] context additional data the command may need
#
def run(invocation_path = File.basename($PROGRAM_NAME), arguments = ARGV, context = {})
new(invocation_path, context).run(arguments)
rescue Clamp::UsageError => e
$stderr.puts "ERROR: #{e.message}"
$stderr.puts ""
$stderr.puts "See: '#{e.command.invocation_path} --help'"
exit(1)
rescue Clamp::HelpWanted => e
puts e.command.help
rescue Clamp::ExecutionError => e
$stderr.puts "ERROR: #{e.message}"
exit(e.status)
rescue SignalException => e
exit(128 + e.signo)
end
end
end
end
|