File: command.rb

package info (click to toggle)
ruby-clamp 1.1.1-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, trixie
  • size: 312 kB
  • sloc: ruby: 2,359; makefile: 4
file content (151 lines) | stat: -rw-r--r-- 4,146 bytes parent folder | download | duplicates (3)
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