lib/chef-dk/commands_map.rb in chef-dk-0.12.0 vs lib/chef-dk/commands_map.rb in chef-dk-0.13.21

- old
+ new

@@ -1,115 +1,115 @@ -# -# Copyright:: Copyright (c) 2014 Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -module ChefDK - - # 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. - # 2. 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. - # 3. Other ways to mitigate the performance issue (loading deps lazily) have - # their own complications and tradeoffs and don't fully solve the problem. - # 4. 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 ChefDK.commands_map. - # You can configure a multiple commands at once in a block using - # ChefDK.commands, like so: - # - # ChefDK.commands do |c| - # # assigns `chef my-command` to the class ChefDK::Command::MyCommand. - # # The "require path" is inferred to be "chef-dk/command/my_command" - # c.builtin("my-command", :MyCommand) - # - # # Set the require path explicitly: - # c.builtin("weird-command", :WeirdoClass, require_path: "chef-dk/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 - # - class CommandsMap - NULL_ARG = Object.new - - CommandSpec = Struct.new(:name, :constant_name, :require_path, :description) - - class CommandSpec - - def instantiate - require require_path - command_class = ChefDK::Command.const_get(constant_name) - command_class.new - end - - end - - attr_reader :command_specs - - def initialize - @command_specs = {} - end - - def builtin(name, constant_name, require_path: NULL_ARG, desc: "") - if null?(require_path) - snake_case_path = name.gsub("-", "_") - require_path = "chef-dk/command/#{snake_case_path}" - end - command_specs[name] = CommandSpec.new(name, constant_name, require_path, desc) - end - - def instantiate(name) - spec_for(name).instantiate - end - - def have_command?(name) - command_specs.key?(name) - end - - def command_names - command_specs.keys - end - - def spec_for(name) - command_specs[name] - end - - private - - def null?(argument) - argument.equal?(NULL_ARG) - end - end - - def self.commands_map - @commands_map ||= CommandsMap.new - end - - def self.commands - yield commands_map - end -end - - +# +# Copyright:: Copyright (c) 2016 Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module ChefDK + + # 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. + # 2. 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. + # 3. Other ways to mitigate the performance issue (loading deps lazily) have + # their own complications and tradeoffs and don't fully solve the problem. + # 4. 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 ChefDK.commands_map. + # You can configure a multiple commands at once in a block using + # ChefDK.commands, like so: + # + # ChefDK.commands do |c| + # # assigns `chef my-command` to the class ChefDK::Command::MyCommand. + # # The "require path" is inferred to be "chef-dk/command/my_command" + # c.builtin("my-command", :MyCommand) + # + # # Set the require path explicitly: + # c.builtin("weird-command", :WeirdoClass, require_path: "chef-dk/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 + # + class CommandsMap + NULL_ARG = Object.new + + CommandSpec = Struct.new(:name, :constant_name, :require_path, :description) + + class CommandSpec + + def instantiate + require require_path + command_class = ChefDK::Command.const_get(constant_name) + command_class.new + end + + end + + attr_reader :command_specs + + def initialize + @command_specs = {} + end + + def builtin(name, constant_name, require_path: NULL_ARG, desc: "") + if null?(require_path) + snake_case_path = name.gsub("-", "_") + require_path = "chef-dk/command/#{snake_case_path}" + end + command_specs[name] = CommandSpec.new(name, constant_name, require_path, desc) + end + + def instantiate(name) + spec_for(name).instantiate + end + + def have_command?(name) + command_specs.key?(name) + end + + def command_names + command_specs.keys + end + + def spec_for(name) + command_specs[name] + end + + private + + def null?(argument) + argument.equal?(NULL_ARG) + end + end + + def self.commands_map + @commands_map ||= CommandsMap.new + end + + def self.commands + yield commands_map + end +end + +