class ChefCLI::CommandsMap

CommandsMap maintains a mapping of subcommand names to the files where those commands are defined and the classes that implement the commands.

In ruby it's more typical to handle this sort of thing using conventions and metaprogramming. We've implemented this approach in the past and decided against it here:

  1. Performance. As the CLI suite grows, you have to load more and more

code, including dependencies that are installed by rubygems, etc. This gets slow, and CLI apps need to be fast.

  1. You can workaround the above by having a convention mapping filename to

command name, but then you have to do a lot of work to list all of the commands, which is actually a common thing to do.

  1. Other ways to mitigate the performance issue (loading deps lazily) have

their own complications and tradeoffs and don't fully solve the problem.

  1. It's not actually that much work to maintain the mapping.

## Adding new commands globally:

A “singleton-ish” instance of this class is stored as ChefCLI.commands_map. You can configure a multiple commands at once in a block using ChefCLI.commands, like so:

ChefCLI.commands do |c|
  # assigns `chef my-command` to the class ChefCLI::Command::MyCommand.
  # The "require path" is inferred to be "chef-cli/command/my_command"
  c.builtin("my-command", :MyCommand)

  # Set the require path explicitly:
  c.builtin("weird-command", :WeirdoClass, require_path: "chef-cli/command/this_is_cray")

  # You can add a description that will show up in `chef -h` output (recommended):
  c.builtin("documented-cmd", :DocumentedCmd, desc: "A short description")
end

Constants

CommandSpec
NULL_ARG

Attributes

command_specs[R]

Public Class Methods

new() click to toggle source
# File lib/chef-cli/commands_map.rb, line 71
def initialize
  @command_specs = {}
end

Public Instance Methods

builtin(name, constant_name, require_path: NULL_ARG, desc: "", hidden: false) click to toggle source
# File lib/chef-cli/commands_map.rb, line 75
def builtin(name, constant_name, require_path: NULL_ARG, desc: "", hidden: false)
  if null?(require_path)
    snake_case_path = name.tr("-", "_")
    require_path = "chef-cli/command/#{snake_case_path}"
  end
  command_specs[name] = CommandSpec.new(name, constant_name, require_path, desc, hidden)
end
command_names() click to toggle source
# File lib/chef-cli/commands_map.rb, line 91
def command_names
  command_specs.keys
end
have_command?(name) click to toggle source
# File lib/chef-cli/commands_map.rb, line 87
def have_command?(name)
  command_specs.key?(name)
end
instantiate(name) click to toggle source
# File lib/chef-cli/commands_map.rb, line 83
def instantiate(name)
  spec_for(name).instantiate
end
spec_for(name) click to toggle source
# File lib/chef-cli/commands_map.rb, line 95
def spec_for(name)
  command_specs[name]
end

Private Instance Methods

null?(argument) click to toggle source
# File lib/chef-cli/commands_map.rb, line 101
def null?(argument)
  argument.equal?(NULL_ARG)
end