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



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

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)



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

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

- (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



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

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:



91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/cri/command.rb', line 91

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:



117
118
119
120
# File 'lib/cri/command.rb', line 117

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:



109
110
111
112
# File 'lib/cri/command.rb', line 109

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.



363
364
365
# File 'lib/cri/command.rb', line 363

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



159
160
161
162
# File 'lib/cri/command.rb', line 159

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:



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/cri/command.rb', line 214

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



192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/cri/command.rb', line 192

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:



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/cri/command.rb', line 170

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



147
148
149
150
151
152
# File 'lib/cri/command.rb', line 147

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

- (String) help

The help text for this command

Returns:

  • (String)

    The help text for this command



295
296
297
298
299
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
# File 'lib/cri/command.rb', line 295

def help
  text = ''

  # Append usage
  if usage
    path = [ self.supercommand ]
    path.unshift(path[0].supercommand) until path[0].nil?
    full_usage = path[1..-1].map { |c| c.name + ' ' }.join + usage
    text << "usage: #{full_usage}\n"
  end

  # Append aliases
  unless aliases.empty?
    text << "\n"
    text << "aliases: #{aliases.join(' ')}\n"
  end

  # Append short description
  if summary
    text << "\n"
    text << summary + "\n"
  end

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

  # Append subcommands
  unless self.commands.empty?
    text << "\n"
    text << (self.supercommand ? 'subcommands' : 'commands') << ":\n"
    text << "\n"
    length = self.commands.inject(0) { |m,c| [ m, c.name.size ].max }
    self.commands.each do |cmd|
      text << sprintf("    %-#{length+4}s %s\n",
        cmd.name,
        cmd.summary)
    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(&:+).inject(0) { |m,o| [ m, o[:long].size ].max }
  groups.each_pair do |name, defs|
    unless defs.empty?
      text << "\n"
      text << "#{name}:\n"
      text << "\n"
      defs.sort { |x,y| x[:long] <=> y[:long] }.each do |opt_def|
        text << sprintf(
          "    -%1s --%-#{length+4}s %s\n",
          opt_def[:short],
          opt_def[:long],
          opt_def[:desc])
      end
    end
  end

  # Return text
  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:



135
136
137
138
139
140
141
142
143
# File 'lib/cri/command.rb', line 135

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



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/cri/command.rb', line 238

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:



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/cri/command.rb', line 274

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