Class: Cri::Command

Inherits:
Object
  • Object
show all
Defined in:
lib/cri/command.rb

Overview

Cri::Command represents a command that can be executed on the commandline. It is also used for the commandline tool itself.

Defined Under Namespace

Classes: OptionParserPartitioningDelegate

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Command) initialize

A new instance of Command



127
128
129
130
131
# File 'lib/cri/command.rb', line 127

def initialize
  @aliases            = Set.new
  @commands           = Set.new
  @option_definitions = Set.new
end

Instance Attribute Details

- (Array<String>) aliases

A list of aliases for this command that can be used to invoke this command

Returns:

  • (Array<String>)

    A list of aliases for this command that can be used to invoke this command



61
62
63
# File 'lib/cri/command.rb', line 61

def aliases
  @aliases
end

- (Proc) block

The block that should be executed when invoking this command (ignored for commands with subcommands)

Returns:

  • (Proc)

    The block that should be executed when invoking this command (ignored for commands with subcommands)



83
84
85
# File 'lib/cri/command.rb', line 83

def block
  @block
end

- (Set<Cri::Command>) commands Also known as: subcommands

This command’s subcommands

Returns:



53
54
55
# File 'lib/cri/command.rb', line 53

def commands
  @commands
end

- (String) description

The long description (“description”)

Returns:

  • (String)

    The long description (“description”)



67
68
69
# File 'lib/cri/command.rb', line 67

def description
  @description
end

- (Boolean) hidden Also known as: hidden?

True if the command is hidden (e.g. because it is deprecated), false otherwise

Returns:

  • (Boolean)

    true if the command is hidden (e.g. because it is deprecated), false otherwise



75
76
77
# File 'lib/cri/command.rb', line 75

def hidden
  @hidden
end

- (String) name

The name

Returns:



57
58
59
# File 'lib/cri/command.rb', line 57

def name
  @name
end

- (Array<Hash>) option_definitions

The list of option definitions

Returns:

  • (Array<Hash>)

    The list of option definitions



79
80
81
# File 'lib/cri/command.rb', line 79

def option_definitions
  @option_definitions
end

- (String) summary

The short description (“summary”)

Returns:

  • (String)

    The short description (“summary”)



64
65
66
# File 'lib/cri/command.rb', line 64

def summary
  @summary
end

- (Cri::Command?) supercommand

This command’s supercommand, or nil if the command has no supercommand

Returns:

  • (Cri::Command, nil)

    This command’s supercommand, or nil if the command has no supercommand



50
51
52
# File 'lib/cri/command.rb', line 50

def supercommand
  @supercommand
end

- (String) usage

The usage, without the “usage:” prefix and without the supercommands’ names.

Returns:

  • (String)

    The usage, without the “usage:” prefix and without the supercommands’ names.



71
72
73
# File 'lib/cri/command.rb', line 71

def usage
  @usage
end

Class Method Details

+ (Cri::Command) define(string = nil, filename = nil, &block)

Creates a new command using the DSL. If a string is given, the command will be defined using the string; if a block is given, the block will be used instead.

If the block has one parameter, the block will be executed in the same context with the command DSL as its parameter. If the block has no parameters, the block will be executed in the context of the DSL.

Parameters:

  • The (String, nil)

    string containing the command’s definition

Returns:



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/cri/command.rb', line 96

def self.define(string=nil, filename=nil, &block)
  dsl = Cri::CommandDSL.new
  if string
    args = filename ? [ string, filename ] : [ string ]
    dsl.instance_eval(*args)
  elsif [ -1, 0 ].include? block.arity
    dsl.instance_eval(&block)
  else
    block.call(dsl)
  end
  dsl.command
end

+ (Cri::Command) new_basic_help

Returns a new command that implements showing help.

Returns:



122
123
124
125
# File 'lib/cri/command.rb', line 122

def self.new_basic_help
  filename = File.dirname(__FILE__) + '/commands/basic_help.rb'
  self.define(File.read(filename))
end

+ (Cri::Command) new_basic_root

Returns a new command that has support for the -h/--help option and also has a help subcommand. It is intended to be modified (adding name, summary, description, other subcommands, …)

Returns:



114
115
116
117
# File 'lib/cri/command.rb', line 114

def self.new_basic_root
  filename = File.dirname(__FILE__) + '/commands/basic_root.rb'
  self.define(File.read(filename))
end

Instance Method Details

- (Object) <=>(other)

Compares this command's name to the other given command's name.



396
397
398
# File 'lib/cri/command.rb', line 396

def <=>(other)
  self.name <=> other.name
end

- (void) add_command(command)

This method returns an undefined value.

Adds the given command as a subcommand to the current command.

Parameters:

  • command (Cri::Command)

    The command to add as a subcommand



164
165
166
167
# File 'lib/cri/command.rb', line 164

def add_command(command)
  @commands << command
  command.supercommand = self
end

- (Cri::Command) command_named(name)

Returns the command with the given name. This method will display error messages and exit in case of an error (unknown or ambiguous command).

The name can be a full command name, a partial command name (e.g. “com” for “commit”) or an aliased command name (e.g. “ci” for “commit”).

Parameters:

  • name (String)

    The full, partial or aliases name of the command

Returns:



219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/cri/command.rb', line 219

def command_named(name)
  commands = commands_named(name)

  if commands.size < 1
    $stderr.puts "#{self.name}: unknown command '#{name}'\n"
    exit 1
  elsif commands.size > 1
    $stderr.puts "#{self.name}: '#{name}' is ambiguous:"
    $stderr.puts "  #{commands.map { |c| c.name }.sort.join(' ') }"
    exit 1
  else
    commands[0]
  end
end

- (Array<Cri::Command>) commands_named(name)

Returns the commands that could be referred to with the given name. If the result contains more than one command, the name is ambiguous.

Parameters:

  • name (String)

    The full, partial or aliases name of the command

Returns:

  • (Array<Cri::Command>)

    A list of commands matching the given name



197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/cri/command.rb', line 197

def commands_named(name)
  # Find by exact name or alias
  @commands.each do |cmd|
    found = cmd.name == name || cmd.aliases.include?(name)
    return [ cmd ] if found
  end

  # Find by approximation
  @commands.select do |cmd|
    cmd.name[0, name.length] == name
  end
end

- (Cri::Command) define_command(name = nil, &block)

Defines a new subcommand for the current command using the DSL.

Parameters:

  • name (String, nil) (defaults to: nil)

    The name of the subcommand, or nil if no name should be set (yet)

Returns:



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/cri/command.rb', line 175

def define_command(name=nil, &block)
  # Execute DSL
  dsl = Cri::CommandDSL.new
  dsl.name name unless name.nil?
  if [ -1, 0 ].include? block.arity
    dsl.instance_eval(&block)
  else
    block.call(dsl)
  end

  # Create command
  cmd = dsl.command
  self.add_command(cmd)
  cmd
end

- (Hash) global_option_definitions

The option definitions for the command itself and all its ancestors

Returns:

  • (Hash)

    The option definitions for the command itself and all its ancestors



152
153
154
155
156
157
# File 'lib/cri/command.rb', line 152

def global_option_definitions
  res = Set.new
  res.merge(option_definitions)
  res.merge(supercommand.global_option_definitions) if supercommand
  res
end

- (String) help(params = {})

The help text for this command

Returns:

  • (String)

    The help text for this command



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/cri/command.rb', line 300

def help(params={})
  is_verbose = params.fetch(:verbose, false)

  text = ''

  # Append name and summary
  if summary
    text << "name".formatted_as_title << "\n"
    text << "    #{name.formatted_as_command} - #{summary}" << "\n"
    unless aliases.empty?
      text << "    aliases: " << aliases.map { |a| a.formatted_as_command }.join(' ') << "\n"
    end
  end

  # Append usage
  if usage
    path = [ self.supercommand ]
    path.unshift(path[0].supercommand) until path[0].nil?
    formatted_usage = usage.gsub(/^([^\s]+)/) { |m| m.formatted_as_command }
    full_usage = path[1..-1].map { |c| c.name.formatted_as_command + ' ' }.join + formatted_usage

    text << "\n"
    text << "usage".formatted_as_title << "\n"
    text << full_usage.wrap_and_indent(78, 4) << "\n"
  end

  # Append long description
  if description
    text << "\n"
    text << "description".formatted_as_title << "\n"
    text << description.wrap_and_indent(78, 4) + "\n"
  end

  # Append subcommands
  unless self.subcommands.empty?
    text << "\n"
    text << (self.supercommand ? 'subcommands' : 'commands').formatted_as_title
    text << "\n"

    visible_cmds, invisible_cmds = self.subcommands.partition { |c| !c.hidden? }

    commands_for_length = is_verbose ? self.subcommands : visible_cmds
    length = commands_for_length.map { |c| c.name.formatted_as_command.size }.max

    # Visible
    visible_cmds.sort_by { |cmd| cmd.name }.each do |cmd|
      text << sprintf("    %-#{length+4}s %s\n",
        cmd.name.formatted_as_command,
        cmd.summary)
    end

    # Invisible
    if is_verbose
      invisible_cmds.sort_by { |cmd| cmd.name }.each do |cmd|
        text << sprintf("    %-#{length+4}s %s\n",
          cmd.name.formatted_as_command,
          cmd.summary)
      end
    else
      case invisible_cmds.size
      when 0
      when 1
        text << "    (1 hidden command ommitted; show it with --verbose)\n"
      else
        text << "    (#{invisible_cmds.size} hidden commands ommitted; show them with --verbose)\n"
      end
    end
  end

  # Append options
  groups = { 'options' => self.option_definitions }
  if self.supercommand
    groups["options for #{self.supercommand.name}"] = self.supercommand.global_option_definitions
  end
  length = groups.values.inject(&:+).map { |o| o[:long].to_s.size }.max
  groups.each_pair do |name, defs|
    unless defs.empty?
      text << "\n"
      text << "#{name}".formatted_as_title
      text << "\n"
      ordered_defs = defs.sort_by { |x| x[:short] || x[:long] }
      ordered_defs.each do |opt_def|
        text << sprintf(
          "    %-2s %-#{length+6}s",
          opt_def[:short] ? ('-' + opt_def[:short]) : '',
          opt_def[:long] ? ('--' + opt_def[:long]) : '').formatted_as_option

        text << opt_def[:desc] << "\n"
      end
    end
  end

  text
end

- (Cri::Command) modify(&block)

Modifies the command using the DSL.

If the block has one parameter, the block will be executed in the same context with the command DSL as its parameter. If the block has no parameters, the block will be executed in the context of the DSL.

Returns:



140
141
142
143
144
145
146
147
148
# File 'lib/cri/command.rb', line 140

def modify(&block)
  dsl = Cri::CommandDSL.new(self)
  if [ -1, 0 ].include? block.arity
    dsl.instance_eval(&block)
  else
    block.call(dsl)
  end
  self
end

- (void) run(opts_and_args, parent_opts = {})

This method returns an undefined value.

Runs the command with the given commandline arguments, possibly invoking subcommands and passing on the options and arguments.

Parameters:

  • opts_and_args (Array<String>)

    A list of unparsed arguments

  • parent_opts (Hash) (defaults to: {})

    A hash of options already handled by the supercommand



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/cri/command.rb', line 243

def run(opts_and_args, parent_opts={})
  # Parse up to command name
  stuff = partition(opts_and_args)
  opts_before_subcmd, subcmd_name, opts_and_args_after_subcmd = *stuff

  if subcommands.empty? || (subcmd_name.nil? && !self.block.nil?)
    run_this(opts_and_args, parent_opts)
  else
    # Handle options
    self.handle_options(opts_before_subcmd)

    # Get command
    if subcmd_name.nil?
      $stderr.puts "#{name}: no command given"
      exit 1
    end
    subcommand = self.command_named(subcmd_name)

    # Run
    subcommand.run(opts_and_args_after_subcmd, opts_before_subcmd)
  end
end

- (void) run_this(opts_and_args, parent_opts = {})

This method returns an undefined value.

Runs the actual command with the given commandline arguments, not invoking any subcommands. If the command does not have an execution block, an error ir raised.

Parameters:

  • opts_and_args (Array<String>)

    A list of unparsed arguments

  • parent_opts (Hash) (defaults to: {})

    A hash of options already handled by the supercommand

Raises:



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/cri/command.rb', line 279

def run_this(opts_and_args, parent_opts={})
  # Parse
  parser = Cri::OptionParser.new(
    opts_and_args, self.global_option_definitions)
  self.handle_parser_errors_while { parser.run }
  local_opts  = parser.options
  global_opts = parent_opts.merge(parser.options)
  args = parser.arguments

  # Handle options
  self.handle_options(local_opts)

  # Execute
  if self.block.nil?
    raise NotImplementedError,
      "No implementation available for '#{self.name}'"
  end
  self.block.call(global_opts, args, self)
end