Class List Method List File List

# Class: Cri::Command

Inherits:
  Object
  - Object
  - Cri::Command
  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)

- - (Array<String>) aliases

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

- - (Proc) block

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

- - (Set<Cri::Command>) commands (also: #subcommands)

  This command’s subcommands.

- - (String) description

  The long description (“description”).

- - (Boolean) hidden (also: #hidden?)

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

- - (String) name

  The name.

- - (Array<Hash>) option_definitions

  The list of option definitions.

- - (String) summary

  The short description (“summary”).

- - (Cri::Command?) supercommand

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

- - (String) usage

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

## Class Method Summary (collapse)

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

  Creates a new command using the DSL.

- + (Cri::Command) new_basic_help

  Returns a new command that implements showing help.

- + (Cri::Command) new_basic_root

  Returns a new command that has support for the `-h`/`--help` option and also has a `help` subcommand.

## Instance Method Summary (collapse)

- - (Object) <=>(other)

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

- - (void) add_command(command)

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

- - (Cri::Command) command_named(name)

  Returns the command with the given name.

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

  Returns the commands that could be referred to with the given name.

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

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

- - (Hash) global_option_definitions

  The option definitions for the command itself and all its ancestors.

- - (String) help(params = {})

  The help text for this command.

- - (Command) initialize constructor

  A new instance of Command.

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

  Modifies the command using the DSL.

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

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

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

  Runs the actual command with the given commandline arguments, not invoking any subcommands.

## Constructor Details

### - (`Command`) initialize

Returns a new instance of Command

    129
    130
    131
    132
    133

    # File 'lib/cri/command.rb', line 129

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

## Instance Attribute Details

### - (`Array<String>`) aliases

Returns 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

Returns 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

Returns This command’s subcommands

Returns:

- (`Set<Cri::Command>`) —

  This command’s subcommands

    53
    54
    55

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

    def commands
      @commands
    end

### - (`String`) description

Returns 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?

Returns 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

Returns The name

Returns:

- (`String`) —

  The name

    57
    58
    59

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

    def name
      @name
    end

### - (`Array<Hash>`) option_definitions

Returns 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

Returns 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

Returns 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

Returns 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:

- string (`String`, `nil`) (defaults to: `nil`) —

  The command definition as a string

- filename (`String`, `nil`) (defaults to: `nil`) —

  The filename corresponding to the string parameter (only useful if a string is given)

Returns:

- (`Cri::Command`) —

  The newly defined command

    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109

    # File 'lib/cri/command.rb', line 98

    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:

- (`Cri::Command`) —

  A basic help command

    124
    125
    126
    127

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

    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:

- (`Cri::Command`) —

  A basic root command

    116
    117
    118
    119

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

    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.

    392
    393
    394

    # File 'lib/cri/command.rb', line 392

    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

    166
    167
    168
    169

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

    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:

- (`Cri::Command`) —

  The command with the given name

    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234

    # File 'lib/cri/command.rb', line 221

    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

    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210

    # File 'lib/cri/command.rb', line 199

    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:

- (`Cri::Command`) —

  The subcommand

    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191

    # File 'lib/cri/command.rb', line 177

    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

Returns The option definitions for the command itself and all its ancestors

Returns:

- (`Hash`) —

  The option definitions for the command itself and all its ancestors

    154
    155
    156
    157
    158
    159

    # File 'lib/cri/command.rb', line 154

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

### - (`String`) help(params = {})

Returns The help text for this command

Returns:

- (`String`) —

  The help text for this command

    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

    # File 'lib/cri/command.rb', line 302

    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"

        shown_subcommands = self.subcommands.select { |c| !c.hidden? || is_verbose }
        length = shown_subcommands.map { |c| c.name.formatted_as_command.size }.max

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

        # Hidden notice
        if !is_verbose
          diff = self.subcommands.size - shown_subcommands.size
          case diff
          when 0
          when 1
            text << "    (1 hidden command omitted; show it with --verbose)\n"
          else
            text << "    (#{diff} hidden commands omitted; 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.keys.sort.each do |name|
        defs = groups[name]
        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:

- (`Cri::Command`) —

  The command itself

    142
    143
    144
    145
    146
    147
    148
    149
    150

    # File 'lib/cri/command.rb', line 142

    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

    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266

    # File 'lib/cri/command.rb', line 245

    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:

- (`NotImplementedError`) —

  if the command does not have an execution block

    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299

    # File 'lib/cri/command.rb', line 281

    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