Class: Jinx::CLI::Command

Inherits:
Application show all
Defined in:
lib/jinx/cli/command.rb

Overview

Command-line parser and executor.

Constant Summary collapse

DEF_OPTS =

The default options that apply to all commands.

[
  [:help, "-h", "--help", "Display this help message"],
  [:file, "--file FILE", "Configuration file containing other options"],
  [:log, "--log FILE", "Log file"],
  [:debug, "--debug", "Display debug log messages"],
  [:quiet, "-q", "--quiet", "Suppress printing messages to stdout"],
  [:verbose, "-v", "--verbose", "Print additional messages to stdout"]
]

Instance Method Summary collapse

Methods inherited from Application

#start

Constructor Details

#initialize(specs = Array::EMPTY_ARRAY) {|hash| ... } ⇒ Command

Command line application wrapper. The specs parameter is an array of command line option and argument specifications as follows:

The option specification has format:

option, short, long, class, description

where:

  • option is the option symbol, e.g. :output

  • short is the short option form, e.g. “-o”

  • long is the long option form, e.g. “–output FILE”

  • class is the option value class, e.g. Integer

  • description is the option usage, e.g. “Output file”

The option, long and description items are required; the short and class items can be omitted.

The argument specification is an array in the form:

arg, text

where:

  • arg is the argument symbol, e.g. :input

  • text is the usage message text, e.g. ‘input’, ‘[input]’ or ‘input …’

Both arg and text are required.

Built-in options include the following:

  • --help : print the help message and exit

  • --verbose : print additional information to the console

  • –log FILE : log file

  • --debug : print debug messages to the log

  • –file FILE: file containing other options

  • --quiet: suppress printing messages to stdout

This class processes these built-in options. Subclasses are responsible for processing any remaining options.

Parameters:

Yields:

  • (hash)

    the command execution block

Yield Parameters:

  • hash ({Symbol => Object})

    the argument and option symbol => value hash



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/jinx/cli/command.rb', line 59

def initialize(specs=Array::EMPTY_ARRAY, &executor)
  @executor = executor
  # Validate the specifications.
  unless Array === specs then
    raise ArgumentError.new("Command-line specification is not an array: #{specs.qp}")
  end
  invalid = specs.detect { |spec| spec.size < 2 }
  if invalid then
    raise ArgumentError.new("Command-line argument specification is missing text: #{invalid.qp}")
  end
  # Options start with a dash, arguments are whatever is left.
  @opt_specs, @arg_specs = specs.partition { |spec| spec[1][0, 1] == '-' }
  # Add the default option specifications.
  @opt_specs.concat(DEF_OPTS)
  # The application name is the command.
  super(File.basename($0, ".bat"))
end

Instance Method Details

#call_executor(opts) ⇒ Object (private)

Parameters:



107
108
109
110
# File 'lib/jinx/cli/command.rb', line 107

def call_executor(opts)
   if @executor.nil? then raise CommandError.new("Command #{self} does not have an execution block") end
   @executor.call(opts)
end

#fail(message = nil) ⇒ Object (private)

Prints the given error message and the program usage, then exits with status 1.



202
203
204
# File 'lib/jinx/cli/command.rb', line 202

def fail(message=nil)
  halt(message, 1)
end

#get_args{Symbol => Object} (private)

Collects the non-option command line arguments.

Returns:



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
# File 'lib/jinx/cli/command.rb', line 137

def get_args
  return Hash::EMPTY_HASH if ARGV.empty?
  if @arg_specs.empty? then too_many_arguments end
  # Collect the arguments from the command line.
  args = {}
  # The number of command line arguments or all but the last argument specifications,
  # whichever is less. The last argument can have more than one value, indicated by
  # the argument specification form '...', so it is processed separately below.
  n = [ARGV.size, @arg_specs.size - 1].min
  # the single-valued arguments
  n.times { |i| args[@arg_specs[i].first] = ARGV[i] }
  # Process the last argument.
  if n < ARGV.size then
    spec = @arg_specs.last
    arg, form = spec[0], spec[1]
    # A multi-valued last argument is the residual command argument array.
    # A single-valued last argument is the last value, if there is exactly one.
    # Otherwise, there are too many arguments.
    if form.index('...') then
      args[arg] = ARGV[n..-1]
    elsif @arg_specs.size == ARGV.size then
      args[arg] = ARGV[n]
    else
      too_many_arguments
    end
  end
  args
end

#get_opts{Symbol => Object} (private)

Collects the command line options.

Returns:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/jinx/cli/command.rb', line 115

def get_opts
  # the options hash
  opts = {}
  # the option parser
  OptionParser.new do |parser|
    # The help argument string is comprised of the argument specification labels.
    arg_s = @arg_specs.map { |spec| spec[1] }.join(' ')
    # Build the usage message.
    parser.banner = "Usage: #{parser.program_name} [options] #{arg_s}"
    parser.separator ""
    parser.separator "Options:"
    # parse the options
    opts = parse(parser)
    # grab the usage message
    @usage = parser.help
  end
  opts
end

#halt(message = nil, status = 0) ⇒ Object (private)

Prints the given message and program usage, then exits with the given status.



207
208
209
210
211
# File 'lib/jinx/cli/command.rb', line 207

def halt(message=nil, status=0)
  puts(message) if message
  puts(@usage)
  exit(status)
end

#handle_options(opts) ⇒ Object (private)

Processes the built-in options as follows:

  • :help - print the usage message and exit

  • :file FILE - load the options specified in the given file

Parameters:



187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/jinx/cli/command.rb', line 187

def handle_options(opts)
  # if help, then print usage and exit
  if opts[:help] then halt end
  # If there is a file option, then load additional options from the file.
  file = opts.delete(:file)
  if file then
    fopts = File.open(file).map { |line| line.chomp }.split(' ').flatten
    ARGV.concat(fopts)
    OptionParser.new do |p|
      opts.merge!(parse(p)) { |ov, nv| ov ? ov : nv }
    end
  end
end

#parse(parser) ⇒ {Symbol => Object} (private)

Returns the option => value hash.

Parameters:

  • parser (OptionParser)

    the option parser

Returns:



172
173
174
175
176
177
178
179
180
# File 'lib/jinx/cli/command.rb', line 172

def parse(parser)
  opts = {}
  @opt_specs.each do |opt, *spec|
    parser.on_tail(*spec) { |v| opts[opt] = v }
  end
  # build the option => value hash 
  parser.parse!
  opts
end

#run {|hash| ... } ⇒ Object

Runs this command by calling the block given to this method, if provided, otherwise the block given to #initialize option or argument symbol => value hash.

Yields:

  • (hash)

    the command execution block

Yield Parameters:

  • hash ({Symbol => Object})

    the argument and option symbol => value hash



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/jinx/cli/command.rb', line 82

def run
  # the option => value hash
  opts = get_opts
  # this base class's options
  handle_options(opts)
  # add the argument => value hash
  opts.merge!(get_args)
  # call the block
  log(INFO, "Starting #{@appname}...")
  block_given? ? yield(opts) : call_executor(opts)
end

#too_many_argumentsObject (private)



166
167
168
# File 'lib/jinx/cli/command.rb', line 166

def too_many_arguments
  halt("Too many arguments - expected #{@arg_specs.size}, found: #{ARGV.join(' ')}.", 1)
end