File: command.rb

package info (click to toggle)
ruby-commander 4.6.0-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 360 kB
  • sloc: ruby: 1,971; makefile: 9
file content (219 lines) | stat: -rw-r--r-- 6,316 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# frozen_string_literal: true

require 'optparse'

module Commander
  class Command
    attr_accessor :name, :examples, :syntax, :description, :summary, :proxy_options, :options
    attr_reader :global_options

    ##
    # Options struct.

    class Options
      include Blank

      def initialize
        @table = {}
      end

      def __hash__
        @table
      end

      def method_missing(meth, *args)
        meth.to_s =~ /=$/ ? @table[meth.to_s.chop.to_sym] = args.first : @table[meth]
      end

      def default(defaults = {})
        @table = defaults.merge! @table
      end

      def inspect
        "<Commander::Command::Options #{ __hash__.map { |k, v| "#{k}=#{v.inspect}" }.join(', ') }>"
      end
    end

    ##
    # Initialize new command with specified _name_.

    def initialize(name)
      @name, @examples, @when_called = name.to_s, [], []
      @options, @proxy_options = [], []
      @global_options = []
    end

    ##
    # Add a usage example for this command.
    #
    # Usage examples are later displayed in help documentation
    # created by the help formatters.
    #
    # === Examples
    #
    #   command :something do |c|
    #     c.example "Should do something", "my_command something"
    #   end
    #

    def example(description, command)
      @examples << [description, command]
    end

    ##
    # Add an option.
    #
    # Options are parsed via OptionParser so view it
    # for additional usage documentation. A block may optionally be
    # passed to handle the option, otherwise the _options_ struct seen below
    # contains the results of this option. This handles common formats such as:
    #
    #   -h, --help          options.help           # => bool
    #   --[no-]feature      options.feature        # => bool
    #   --large-switch      options.large_switch   # => bool
    #   --file FILE         options.file           # => file passed
    #   --list WORDS        options.list           # => array
    #   --date [DATE]       options.date           # => date or nil when optional argument not set
    #
    # === Examples
    #
    #   command :something do |c|
    #     c.option '--recursive', 'Do something recursively'
    #     c.option '--file FILE', 'Specify a file'
    #     c.option('--info', 'Display info') { puts "handle with block" }
    #     c.option '--[no-]feature', 'With or without feature'
    #     c.option '--list FILES', Array, 'List the files specified'
    #
    #     c.when_called do |args, options|
    #       do_something_recursively if options.recursive
    #       do_something_with_file options.file if options.file
    #     end
    #   end
    #
    # === Help Formatters
    #
    # This method also parses the arguments passed in order to determine
    # which were switches, and which were descriptions for the
    # option which can later be used within help formatters
    # using option[:switches] and option[:description].
    #
    # === Input Parsing
    #
    # Since Commander utilizes OptionParser you can pre-parse and evaluate
    # option arguments. Simply require 'optparse/time', or 'optparse/date', as these
    # objects must respond to #parse.
    #
    #   c.option '--time TIME', Time
    #   c.option '--date [DATE]', Date
    #

    def option(*args, &block)
      switches, description = Runner.separate_switches_from_description(*args)
      proc = block || option_proc(switches)
      @options << {
        args: args,
        proc: proc,
        switches: switches,
        description: description,
      }
    end

    ##
    # Handle execution of command. The handler may be a class,
    # object, or block (see examples below).
    #
    # === Examples
    #
    #   # Simple block handling
    #   c.when_called do |args, options|
    #      # do something
    #   end
    #
    #   # Create inst of Something and pass args / options
    #   c.when_called MyLib::Command::Something
    #
    #   # Create inst of Something and use arbitrary method
    #    c.when_called MyLib::Command::Something, :some_method
    #
    #   # Pass an object to handle callback (requires method symbol)
    #   c.when_called SomeObject, :some_method
    #

    def when_called(*args, &block)
      fail ArgumentError, 'must pass an object, class, or block.' if args.empty? && !block

      @when_called = block ? [block] : args
    end
    alias action when_called

    ##
    # Run the command with _args_.
    #
    # * parses options, call option blocks
    # * invokes when_called proc
    #

    def run(*args)
      call parse_options_and_call_procs(*args)
    end

    #:stopdoc:

    ##
    # Parses options and calls associated procs,
    # returning the arguments remaining.

    def parse_options_and_call_procs(*args)
      return args if args.empty?

      # empty proxy_options before populating via OptionParser
      # prevents duplication of options if the command is run twice
      proxy_options.clear
      @options.each_with_object(OptionParser.new) do |option, opts|
        opts.on(*option[:args], &option[:proc])
        opts
      end.parse! args
    end

    ##
    # Call the commands when_called block with _args_.

    def call(args = [])
      object, meth = @when_called[0, 2]
      meth ||= :call
      options = proxy_option_struct

      case object
      when Proc then object.call(args, options)
      when Class then meth == :call ? object.new(args, options) : object.new.send(meth, args, options)
      else object&.send(meth, args, options)
      end
    end

    ##
    # Creates an Options instance populated with the option values
    # collected by the #option_proc.

    def proxy_option_struct
      (global_options + proxy_options).each_with_object(Options.new) do |(option, value), options|
        # options that are present will evaluate to true
        value = true if value.nil?
        options.__send__ :"#{option}=", value
        options
      end
    end

    ##
    # Option proxy proc used when a block is not explicitly passed
    # via the #option method. This allows commander to auto-populate
    # and work with option values.

    def option_proc(switches)
      ->(value) { proxy_options << [Runner.switch_to_sym(switches.last), value] }
    end

    def inspect
      "<Commander::Command:#{name}>"
    end
  end
end