Class: ChefCLI::CommandsMap

Inherits:
Object
  • Object
show all
Defined in:
lib/chef-cli/commands_map.rb

Overview

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

Defined Under Namespace

Classes: CommandSpec

Constant Summary collapse

NULL_ARG =
Object.new

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCommandsMap

Returns a new instance of CommandsMap.



71
72
73
# File 'lib/chef-cli/commands_map.rb', line 71

def initialize
  @command_specs = {}
end

Instance Attribute Details

#command_specsObject (readonly)

Returns the value of attribute command_specs.



69
70
71
# File 'lib/chef-cli/commands_map.rb', line 69

def command_specs
  @command_specs
end

Instance Method Details

#builtin(name, constant_name, require_path: NULL_ARG, desc: "", hidden: false) ⇒ Object



75
76
77
78
79
80
81
# 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_namesObject



91
92
93
# File 'lib/chef-cli/commands_map.rb', line 91

def command_names
  command_specs.keys
end

#have_command?(name) ⇒ Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/chef-cli/commands_map.rb', line 87

def have_command?(name)
  command_specs.key?(name)
end

#instantiate(name) ⇒ Object



83
84
85
# File 'lib/chef-cli/commands_map.rb', line 83

def instantiate(name)
  spec_for(name).instantiate
end

#spec_for(name) ⇒ Object



95
96
97
# File 'lib/chef-cli/commands_map.rb', line 95

def spec_for(name)
  command_specs[name]
end