From 5592034fdb8bf3a72ab3133d69443490f9ad7b78 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 7 Apr 2011 13:18:26 -0700 Subject: (#7012) global rename of strings to faces. This just changes filenames and directories; files are exact copies rather than having additional modifications to make clearer each step of this process. This does leave a currently broken build. :/ --- lib/puppet/application/faces.rb | 95 +++++++++ lib/puppet/application/faces_base.rb | 150 +++++++++++++ lib/puppet/application/string.rb | 95 --------- lib/puppet/application/string_base.rb | 150 ------------- lib/puppet/faces.rb | 104 +++++++++ lib/puppet/faces/action.rb | 121 +++++++++++ lib/puppet/faces/action_builder.rb | 31 +++ lib/puppet/faces/action_manager.rb | 49 +++++ lib/puppet/faces/catalog.rb | 40 ++++ lib/puppet/faces/catalog/select.rb | 10 + lib/puppet/faces/certificate.rb | 46 ++++ lib/puppet/faces/certificate_request.rb | 4 + lib/puppet/faces/certificate_revocation_list.rb | 4 + lib/puppet/faces/config.rb | 12 ++ lib/puppet/faces/configurer.rb | 12 ++ lib/puppet/faces/faces_collection.rb | 123 +++++++++++ lib/puppet/faces/facts.rb | 18 ++ lib/puppet/faces/file.rb | 5 + lib/puppet/faces/indirector.rb | 94 +++++++++ lib/puppet/faces/key.rb | 4 + lib/puppet/faces/node.rb | 5 + lib/puppet/faces/option.rb | 82 ++++++++ lib/puppet/faces/option_builder.rb | 25 +++ lib/puppet/faces/option_manager.rb | 56 +++++ lib/puppet/faces/report.rb | 15 ++ lib/puppet/faces/resource.rb | 4 + lib/puppet/faces/resource_type.rb | 4 + lib/puppet/faces/status.rb | 4 + lib/puppet/string.rb | 104 --------- lib/puppet/string/action.rb | 121 ----------- lib/puppet/string/action_builder.rb | 31 --- lib/puppet/string/action_manager.rb | 49 ----- lib/puppet/string/catalog.rb | 40 ---- lib/puppet/string/catalog/select.rb | 10 - lib/puppet/string/certificate.rb | 46 ---- lib/puppet/string/certificate_request.rb | 4 - lib/puppet/string/certificate_revocation_list.rb | 4 - lib/puppet/string/config.rb | 12 -- lib/puppet/string/configurer.rb | 12 -- lib/puppet/string/facts.rb | 18 -- lib/puppet/string/file.rb | 5 - lib/puppet/string/indirector.rb | 94 --------- lib/puppet/string/key.rb | 4 - lib/puppet/string/node.rb | 5 - lib/puppet/string/option.rb | 82 -------- lib/puppet/string/option_builder.rb | 25 --- lib/puppet/string/option_manager.rb | 56 ----- lib/puppet/string/report.rb | 15 -- lib/puppet/string/resource.rb | 4 - lib/puppet/string/resource_type.rb | 4 - lib/puppet/string/status.rb | 4 - lib/puppet/string/string_collection.rb | 123 ----------- spec/unit/application/faces_base_spec.rb | 185 ++++++++++++++++ spec/unit/application/faces_spec.rb | 10 + spec/unit/application/string_base_spec.rb | 185 ---------------- spec/unit/application/string_spec.rb | 10 - spec/unit/faces/action_builder_spec.rb | 59 ++++++ spec/unit/faces/action_manager_spec.rb | 233 +++++++++++++++++++++ spec/unit/faces/action_spec.rb | 173 +++++++++++++++ spec/unit/faces/catalog_spec.rb | 6 + spec/unit/faces/certificate_request_spec.rb | 6 + .../unit/faces/certificate_revocation_list_spec.rb | 6 + spec/unit/faces/certificate_spec.rb | 14 ++ spec/unit/faces/config_spec.rb | 24 +++ spec/unit/faces/configurer_spec.rb | 24 +++ spec/unit/faces/faces_collection_spec.rb | 184 ++++++++++++++++ spec/unit/faces/facts_spec.rb | 21 ++ spec/unit/faces/file_spec.rb | 6 + spec/unit/faces/indirector_spec.rb | 56 +++++ spec/unit/faces/key_spec.rb | 6 + spec/unit/faces/node_spec.rb | 9 + spec/unit/faces/option_builder_spec.rb | 29 +++ spec/unit/faces/option_spec.rb | 75 +++++++ spec/unit/faces/report_spec.rb | 6 + spec/unit/faces/resource_spec.rb | 6 + spec/unit/faces/resource_type_spec.rb | 6 + spec/unit/faces_spec.rb | 144 +++++++++++++ spec/unit/string/action_builder_spec.rb | 59 ------ spec/unit/string/action_manager_spec.rb | 233 --------------------- spec/unit/string/action_spec.rb | 173 --------------- spec/unit/string/catalog_spec.rb | 6 - spec/unit/string/certificate_request_spec.rb | 6 - .../string/certificate_revocation_list_spec.rb | 6 - spec/unit/string/certificate_spec.rb | 14 -- spec/unit/string/config_spec.rb | 24 --- spec/unit/string/configurer_spec.rb | 24 --- spec/unit/string/facts_spec.rb | 21 -- spec/unit/string/file_spec.rb | 6 - spec/unit/string/indirector_spec.rb | 56 ----- spec/unit/string/key_spec.rb | 6 - spec/unit/string/node_spec.rb | 9 - spec/unit/string/option_builder_spec.rb | 29 --- spec/unit/string/option_spec.rb | 75 ------- spec/unit/string/report_spec.rb | 6 - spec/unit/string/resource_spec.rb | 6 - spec/unit/string/resource_type_spec.rb | 6 - spec/unit/string/string_collection_spec.rb | 184 ---------------- spec/unit/string_spec.rb | 144 ------------- 98 files changed, 2405 insertions(+), 2405 deletions(-) create mode 100644 lib/puppet/application/faces.rb create mode 100644 lib/puppet/application/faces_base.rb delete mode 100644 lib/puppet/application/string.rb delete mode 100644 lib/puppet/application/string_base.rb create mode 100644 lib/puppet/faces.rb create mode 100644 lib/puppet/faces/action.rb create mode 100644 lib/puppet/faces/action_builder.rb create mode 100644 lib/puppet/faces/action_manager.rb create mode 100644 lib/puppet/faces/catalog.rb create mode 100644 lib/puppet/faces/catalog/select.rb create mode 100644 lib/puppet/faces/certificate.rb create mode 100644 lib/puppet/faces/certificate_request.rb create mode 100644 lib/puppet/faces/certificate_revocation_list.rb create mode 100644 lib/puppet/faces/config.rb create mode 100644 lib/puppet/faces/configurer.rb create mode 100644 lib/puppet/faces/faces_collection.rb create mode 100644 lib/puppet/faces/facts.rb create mode 100644 lib/puppet/faces/file.rb create mode 100644 lib/puppet/faces/indirector.rb create mode 100644 lib/puppet/faces/key.rb create mode 100644 lib/puppet/faces/node.rb create mode 100644 lib/puppet/faces/option.rb create mode 100644 lib/puppet/faces/option_builder.rb create mode 100644 lib/puppet/faces/option_manager.rb create mode 100644 lib/puppet/faces/report.rb create mode 100644 lib/puppet/faces/resource.rb create mode 100644 lib/puppet/faces/resource_type.rb create mode 100644 lib/puppet/faces/status.rb delete mode 100644 lib/puppet/string.rb delete mode 100644 lib/puppet/string/action.rb delete mode 100644 lib/puppet/string/action_builder.rb delete mode 100644 lib/puppet/string/action_manager.rb delete mode 100644 lib/puppet/string/catalog.rb delete mode 100644 lib/puppet/string/catalog/select.rb delete mode 100644 lib/puppet/string/certificate.rb delete mode 100644 lib/puppet/string/certificate_request.rb delete mode 100644 lib/puppet/string/certificate_revocation_list.rb delete mode 100644 lib/puppet/string/config.rb delete mode 100644 lib/puppet/string/configurer.rb delete mode 100644 lib/puppet/string/facts.rb delete mode 100644 lib/puppet/string/file.rb delete mode 100644 lib/puppet/string/indirector.rb delete mode 100644 lib/puppet/string/key.rb delete mode 100644 lib/puppet/string/node.rb delete mode 100644 lib/puppet/string/option.rb delete mode 100644 lib/puppet/string/option_builder.rb delete mode 100644 lib/puppet/string/option_manager.rb delete mode 100644 lib/puppet/string/report.rb delete mode 100644 lib/puppet/string/resource.rb delete mode 100644 lib/puppet/string/resource_type.rb delete mode 100644 lib/puppet/string/status.rb delete mode 100644 lib/puppet/string/string_collection.rb create mode 100755 spec/unit/application/faces_base_spec.rb create mode 100755 spec/unit/application/faces_spec.rb delete mode 100755 spec/unit/application/string_base_spec.rb delete mode 100755 spec/unit/application/string_spec.rb create mode 100755 spec/unit/faces/action_builder_spec.rb create mode 100755 spec/unit/faces/action_manager_spec.rb create mode 100755 spec/unit/faces/action_spec.rb create mode 100755 spec/unit/faces/catalog_spec.rb create mode 100755 spec/unit/faces/certificate_request_spec.rb create mode 100755 spec/unit/faces/certificate_revocation_list_spec.rb create mode 100755 spec/unit/faces/certificate_spec.rb create mode 100755 spec/unit/faces/config_spec.rb create mode 100755 spec/unit/faces/configurer_spec.rb create mode 100755 spec/unit/faces/faces_collection_spec.rb create mode 100755 spec/unit/faces/facts_spec.rb create mode 100755 spec/unit/faces/file_spec.rb create mode 100755 spec/unit/faces/indirector_spec.rb create mode 100755 spec/unit/faces/key_spec.rb create mode 100755 spec/unit/faces/node_spec.rb create mode 100644 spec/unit/faces/option_builder_spec.rb create mode 100644 spec/unit/faces/option_spec.rb create mode 100755 spec/unit/faces/report_spec.rb create mode 100755 spec/unit/faces/resource_spec.rb create mode 100755 spec/unit/faces/resource_type_spec.rb create mode 100755 spec/unit/faces_spec.rb delete mode 100755 spec/unit/string/action_builder_spec.rb delete mode 100755 spec/unit/string/action_manager_spec.rb delete mode 100755 spec/unit/string/action_spec.rb delete mode 100755 spec/unit/string/catalog_spec.rb delete mode 100755 spec/unit/string/certificate_request_spec.rb delete mode 100755 spec/unit/string/certificate_revocation_list_spec.rb delete mode 100755 spec/unit/string/certificate_spec.rb delete mode 100755 spec/unit/string/config_spec.rb delete mode 100755 spec/unit/string/configurer_spec.rb delete mode 100755 spec/unit/string/facts_spec.rb delete mode 100755 spec/unit/string/file_spec.rb delete mode 100755 spec/unit/string/indirector_spec.rb delete mode 100755 spec/unit/string/key_spec.rb delete mode 100755 spec/unit/string/node_spec.rb delete mode 100644 spec/unit/string/option_builder_spec.rb delete mode 100644 spec/unit/string/option_spec.rb delete mode 100755 spec/unit/string/report_spec.rb delete mode 100755 spec/unit/string/resource_spec.rb delete mode 100755 spec/unit/string/resource_type_spec.rb delete mode 100755 spec/unit/string/string_collection_spec.rb delete mode 100755 spec/unit/string_spec.rb diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb new file mode 100644 index 000000000..0a6a798ce --- /dev/null +++ b/lib/puppet/application/faces.rb @@ -0,0 +1,95 @@ +require 'puppet/application' +require 'puppet/string' + +class Puppet::Application::String < Puppet::Application + + should_parse_config + run_mode :agent + + option("--debug", "-d") do |arg| + Puppet::Util::Log.level = :debug + end + + option("--verbose", "-v") do + Puppet::Util::Log.level = :info + end + + def list(*arguments) + if arguments.empty? + arguments = %w{terminuses actions} + end + strings.each do |name| + str = "#{name}:\n" + if arguments.include?("terminuses") + begin + terms = terminus_classes(name.to_sym) + str << "\tTerminuses: #{terms.join(", ")}\n" + rescue => detail + puts detail.backtrace if Puppet[:trace] + $stderr.puts "Could not load terminuses for #{name}: #{detail}" + end + end + + if arguments.include?("actions") + begin + actions = actions(name.to_sym) + str << "\tActions: #{actions.join(", ")}\n" + rescue => detail + puts detail.backtrace if Puppet[:trace] + $stderr.puts "Could not load actions for #{name}: #{detail}" + end + end + + print str + end + end + + attr_accessor :verb, :name, :arguments + + def main + # Call the method associated with the provided action (e.g., 'find'). + send(verb, *arguments) + end + + def setup + Puppet::Util::Log.newdestination :console + + load_applications # Call this to load all of the apps + + @verb, @arguments = command_line.args + @arguments ||= [] + + validate + end + + def validate + unless verb + raise "You must specify 'find', 'search', 'save', or 'destroy' as a verb; 'save' probably does not work right now" + end + + unless respond_to?(verb) + raise "Command '#{verb}' not found for 'string'" + end + end + + def strings + Puppet::String.strings + end + + def terminus_classes(indirection) + Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort + end + + def actions(indirection) + return [] unless string = Puppet::String[indirection, '0.0.1'] + string.load_actions + return string.actions.sort { |a, b| a.to_s <=> b.to_s } + end + + def load_applications + command_line.available_subcommands.each do |app| + command_line.require_application app + end + end +end + diff --git a/lib/puppet/application/faces_base.rb b/lib/puppet/application/faces_base.rb new file mode 100644 index 000000000..09d02c079 --- /dev/null +++ b/lib/puppet/application/faces_base.rb @@ -0,0 +1,150 @@ +require 'puppet/application' +require 'puppet/string' + +class Puppet::Application::StringBase < Puppet::Application + should_parse_config + run_mode :agent + + option("--debug", "-d") do |arg| + Puppet::Util::Log.level = :debug + end + + option("--verbose", "-v") do + Puppet::Util::Log.level = :info + end + + option("--format FORMAT") do |arg| + @format = arg.to_sym + end + + option("--mode RUNMODE", "-r") do |arg| + raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) + self.class.run_mode(arg.to_sym) + set_run_mode self.class.run_mode + end + + + attr_accessor :string, :action, :type, :arguments, :format + attr_writer :exit_code + + # This allows you to set the exit code if you don't want to just exit + # immediately but you need to indicate a failure. + def exit_code + @exit_code || 0 + end + + # Override this if you need custom rendering. + def render(result) + render_method = Puppet::Network::FormatHandler.format(format).render_method + if render_method == "to_pson" + jj result + exit(0) + else + result.send(render_method) + end + end + + def preinit + super + trap(:INT) do + $stderr.puts "Cancelling String" + exit(0) + end + + # We need to parse enough of the command line out early, to identify what + # the action is, so that we can obtain the full set of options to parse. + + # TODO: These should be configurable versions, through a global + # '--version' option, but we don't implement that yet... --daniel 2011-03-29 + @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym + @string = Puppet::String[@type, :current] + @format = @string.default_format + + # Now, walk the command line and identify the action. We skip over + # arguments based on introspecting the action and all, and find the first + # non-option word to use as the action. + action = nil + index = -1 + until @action or (index += 1) >= command_line.args.length do + item = command_line.args[index] + if item =~ /^-/ then + option = @string.options.find do |name| + item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ + end + if option then + option = @string.get_option(option) + # If we have an inline argument, just carry on. We don't need to + # care about optional vs mandatory in that case because we do a real + # parse later, and that will totally take care of raising the error + # when we get there. --daniel 2011-04-04 + if option.takes_argument? and !item.index('=') then + index += 1 unless + (option.optional_argument? and command_line.args[index + 1] =~ /^-/) + end + elsif option = find_global_settings_argument(item) then + unless Puppet.settings.boolean? option.name then + # As far as I can tell, we treat non-bool options as always having + # a mandatory argument. --daniel 2011-04-05 + index += 1 # ...so skip the argument. + end + else + raise ArgumentError, "Unknown option #{item.sub(/=.*$/, '').inspect}" + end + else + action = @string.get_action(item.to_sym) + if action.nil? then + raise ArgumentError, "#{@string} does not have an #{item.inspect} action!" + end + @action = action + end + end + + @action or raise ArgumentError, "No action given on the command line!" + + # Finally, we can interact with the default option code to build behaviour + # around the full set of options we now know we support. + @action.options.each do |option| + option = @action.get_option(option) # make it the object. + self.class.option(*option.optparse) # ...and make the CLI parse it. + end + end + + def find_global_settings_argument(item) + Puppet.settings.each do |name, object| + object.optparse_args.each do |arg| + next unless arg =~ /^-/ + # sadly, we have to emulate some of optparse here... + pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ + pattern.match item and return object + end + end + return nil # nothing found. + end + + def setup + Puppet::Util::Log.newdestination :console + + @arguments = command_line.args + + # Note: because of our definition of where the action is set, we end up + # with it *always* being the first word of the remaining set of command + # line arguments. So, strip that off when we construct the arguments to + # pass down to the string action. --daniel 2011-04-04 + @arguments.delete_at(0) + + # We copy all of the app options to the end of the call; This allows each + # action to read in the options. This replaces the older model where we + # would invoke the action with options set as global state in the + # interface object. --daniel 2011-03-28 + @arguments << options + end + + + def main + # Call the method associated with the provided action (e.g., 'find'). + if result = @string.send(@action.name, *arguments) + puts render(result) + end + exit(exit_code) + end +end diff --git a/lib/puppet/application/string.rb b/lib/puppet/application/string.rb deleted file mode 100644 index 0a6a798ce..000000000 --- a/lib/puppet/application/string.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'puppet/application' -require 'puppet/string' - -class Puppet::Application::String < Puppet::Application - - should_parse_config - run_mode :agent - - option("--debug", "-d") do |arg| - Puppet::Util::Log.level = :debug - end - - option("--verbose", "-v") do - Puppet::Util::Log.level = :info - end - - def list(*arguments) - if arguments.empty? - arguments = %w{terminuses actions} - end - strings.each do |name| - str = "#{name}:\n" - if arguments.include?("terminuses") - begin - terms = terminus_classes(name.to_sym) - str << "\tTerminuses: #{terms.join(", ")}\n" - rescue => detail - puts detail.backtrace if Puppet[:trace] - $stderr.puts "Could not load terminuses for #{name}: #{detail}" - end - end - - if arguments.include?("actions") - begin - actions = actions(name.to_sym) - str << "\tActions: #{actions.join(", ")}\n" - rescue => detail - puts detail.backtrace if Puppet[:trace] - $stderr.puts "Could not load actions for #{name}: #{detail}" - end - end - - print str - end - end - - attr_accessor :verb, :name, :arguments - - def main - # Call the method associated with the provided action (e.g., 'find'). - send(verb, *arguments) - end - - def setup - Puppet::Util::Log.newdestination :console - - load_applications # Call this to load all of the apps - - @verb, @arguments = command_line.args - @arguments ||= [] - - validate - end - - def validate - unless verb - raise "You must specify 'find', 'search', 'save', or 'destroy' as a verb; 'save' probably does not work right now" - end - - unless respond_to?(verb) - raise "Command '#{verb}' not found for 'string'" - end - end - - def strings - Puppet::String.strings - end - - def terminus_classes(indirection) - Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort - end - - def actions(indirection) - return [] unless string = Puppet::String[indirection, '0.0.1'] - string.load_actions - return string.actions.sort { |a, b| a.to_s <=> b.to_s } - end - - def load_applications - command_line.available_subcommands.each do |app| - command_line.require_application app - end - end -end - diff --git a/lib/puppet/application/string_base.rb b/lib/puppet/application/string_base.rb deleted file mode 100644 index 09d02c079..000000000 --- a/lib/puppet/application/string_base.rb +++ /dev/null @@ -1,150 +0,0 @@ -require 'puppet/application' -require 'puppet/string' - -class Puppet::Application::StringBase < Puppet::Application - should_parse_config - run_mode :agent - - option("--debug", "-d") do |arg| - Puppet::Util::Log.level = :debug - end - - option("--verbose", "-v") do - Puppet::Util::Log.level = :info - end - - option("--format FORMAT") do |arg| - @format = arg.to_sym - end - - option("--mode RUNMODE", "-r") do |arg| - raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) - self.class.run_mode(arg.to_sym) - set_run_mode self.class.run_mode - end - - - attr_accessor :string, :action, :type, :arguments, :format - attr_writer :exit_code - - # This allows you to set the exit code if you don't want to just exit - # immediately but you need to indicate a failure. - def exit_code - @exit_code || 0 - end - - # Override this if you need custom rendering. - def render(result) - render_method = Puppet::Network::FormatHandler.format(format).render_method - if render_method == "to_pson" - jj result - exit(0) - else - result.send(render_method) - end - end - - def preinit - super - trap(:INT) do - $stderr.puts "Cancelling String" - exit(0) - end - - # We need to parse enough of the command line out early, to identify what - # the action is, so that we can obtain the full set of options to parse. - - # TODO: These should be configurable versions, through a global - # '--version' option, but we don't implement that yet... --daniel 2011-03-29 - @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym - @string = Puppet::String[@type, :current] - @format = @string.default_format - - # Now, walk the command line and identify the action. We skip over - # arguments based on introspecting the action and all, and find the first - # non-option word to use as the action. - action = nil - index = -1 - until @action or (index += 1) >= command_line.args.length do - item = command_line.args[index] - if item =~ /^-/ then - option = @string.options.find do |name| - item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ - end - if option then - option = @string.get_option(option) - # If we have an inline argument, just carry on. We don't need to - # care about optional vs mandatory in that case because we do a real - # parse later, and that will totally take care of raising the error - # when we get there. --daniel 2011-04-04 - if option.takes_argument? and !item.index('=') then - index += 1 unless - (option.optional_argument? and command_line.args[index + 1] =~ /^-/) - end - elsif option = find_global_settings_argument(item) then - unless Puppet.settings.boolean? option.name then - # As far as I can tell, we treat non-bool options as always having - # a mandatory argument. --daniel 2011-04-05 - index += 1 # ...so skip the argument. - end - else - raise ArgumentError, "Unknown option #{item.sub(/=.*$/, '').inspect}" - end - else - action = @string.get_action(item.to_sym) - if action.nil? then - raise ArgumentError, "#{@string} does not have an #{item.inspect} action!" - end - @action = action - end - end - - @action or raise ArgumentError, "No action given on the command line!" - - # Finally, we can interact with the default option code to build behaviour - # around the full set of options we now know we support. - @action.options.each do |option| - option = @action.get_option(option) # make it the object. - self.class.option(*option.optparse) # ...and make the CLI parse it. - end - end - - def find_global_settings_argument(item) - Puppet.settings.each do |name, object| - object.optparse_args.each do |arg| - next unless arg =~ /^-/ - # sadly, we have to emulate some of optparse here... - pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ - pattern.match item and return object - end - end - return nil # nothing found. - end - - def setup - Puppet::Util::Log.newdestination :console - - @arguments = command_line.args - - # Note: because of our definition of where the action is set, we end up - # with it *always* being the first word of the remaining set of command - # line arguments. So, strip that off when we construct the arguments to - # pass down to the string action. --daniel 2011-04-04 - @arguments.delete_at(0) - - # We copy all of the app options to the end of the call; This allows each - # action to read in the options. This replaces the older model where we - # would invoke the action with options set as global state in the - # interface object. --daniel 2011-03-28 - @arguments << options - end - - - def main - # Call the method associated with the provided action (e.g., 'find'). - if result = @string.send(@action.name, *arguments) - puts render(result) - end - exit(exit_code) - end -end diff --git a/lib/puppet/faces.rb b/lib/puppet/faces.rb new file mode 100644 index 000000000..517cf4506 --- /dev/null +++ b/lib/puppet/faces.rb @@ -0,0 +1,104 @@ +require 'puppet' +require 'puppet/util/autoload' + +class Puppet::String + require 'puppet/string/string_collection' + + require 'puppet/string/action_manager' + include Puppet::String::ActionManager + extend Puppet::String::ActionManager + + require 'puppet/string/option_manager' + include Puppet::String::OptionManager + extend Puppet::String::OptionManager + + include Puppet::Util + + class << self + # This is just so we can search for actions. We only use its + # list of directories to search. + # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb + def autoloader + @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/string") + end + + def strings + Puppet::String::StringCollection.strings + end + + def string?(name, version) + Puppet::String::StringCollection.string?(name, version) + end + + def register(instance) + Puppet::String::StringCollection.register(instance) + end + + def define(name, version, &block) + if string?(name, version) + string = Puppet::String::StringCollection[name, version] + else + string = self.new(name, version) + Puppet::String::StringCollection.register(string) + string.load_actions + end + + string.instance_eval(&block) if block_given? + + return string + end + + alias :[] :define + end + + attr_accessor :default_format + + def set_default_format(format) + self.default_format = format.to_sym + end + + attr_accessor :type, :verb, :version, :arguments + attr_reader :name + + def initialize(name, version, &block) + unless Puppet::String::StringCollection.validate_version(version) + raise ArgumentError, "Cannot create string #{name.inspect} with invalid version number '#{version}'!" + end + + @name = Puppet::String::StringCollection.underscorize(name) + @version = version + @default_format = :pson + + instance_eval(&block) if block_given? + end + + # Try to find actions defined in other files. + def load_actions + path = "puppet/string/#{name}" + + loaded = [] + [path, "#{name}@#{version}/#{path}"].each do |path| + Puppet::String.autoloader.search_directories.each do |dir| + fdir = ::File.join(dir, path) + next unless FileTest.directory?(fdir) + + Dir.chdir(fdir) do + Dir.glob("*.rb").each do |file| + aname = file.sub(/\.rb/, '') + if loaded.include?(aname) + Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + next + end + loaded << aname + Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + require "#{Dir.pwd}/#{aname}" + end + end + end + end + end + + def to_s + "Puppet::String[#{name.inspect}, #{version.inspect}]" + end +end diff --git a/lib/puppet/faces/action.rb b/lib/puppet/faces/action.rb new file mode 100644 index 000000000..0f5032ffb --- /dev/null +++ b/lib/puppet/faces/action.rb @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +require 'puppet/string' +require 'puppet/string/option' + +class Puppet::String::Action + attr_reader :name + + def to_s + "#{@string}##{@name}" + end + + def initialize(string, name, attrs = {}) + raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ + @string = string + @name = name.to_sym + @options = {} + attrs.each do |k, v| send("#{k}=", v) end + end + + # Initially, this was defined to allow the @action.invoke pattern, which is + # a very natural way to invoke behaviour given our introspection + # capabilities. Heck, our initial plan was to have the string delegate to + # the action object for invocation and all. + # + # It turns out that we have a binding problem to solve: @string was bound to + # the parent class, not the subclass instance, and we don't pass the + # appropriate context or change the binding enough to make this work. + # + # We could hack around it, by either mandating that you pass the context in + # to invoke, or try to get the binding right, but that has probably got + # subtleties that we don't instantly think of – especially around threads. + # + # So, we are pulling this method for now, and will return it to life when we + # have the time to resolve the problem. For now, you should replace... + # + # @action = @string.get_action(name) + # @action.invoke(arg1, arg2, arg3) + # + # ...with... + # + # @action = @string.get_action(name) + # @string.send(@action.name, arg1, arg2, arg3) + # + # I understand that is somewhat cumbersome, but it functions as desired. + # --daniel 2011-03-31 + # + # PS: This code is left present, but commented, to support this chunk of + # documentation, for the benefit of the reader. + # + # def invoke(*args, &block) + # @string.send(name, *args, &block) + # end + + def when_invoked=(block) + # We need to build an instance method as a wrapper, using normal code, to + # be able to expose argument defaulting between the caller and definer in + # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. + # + # In future this also gives us a place to hook in additional behaviour + # such as calling out to the action instance to validate and coerce + # parameters, which avoids any exciting context switching and all. + # + # Hopefully we can improve this when we finally shuffle off the last of + # Ruby 1.8 support, but that looks to be a few "enterprise" release eras + # away, so we are pretty stuck with this for now. + # + # Patches to make this work more nicely with Ruby 1.9 using runtime + # version checking and all are welcome, but they can't actually help if + # the results are not totally hidden away in here. + # + # Incidentally, we though about vendoring evil-ruby and actually adjusting + # the internal C structure implementation details under the hood to make + # this stuff work, because it would have been cleaner. Which gives you an + # idea how motivated we were to make this cleaner. Sorry. --daniel 2011-03-31 + + internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym + file = __FILE__ + "+eval" + line = __LINE__ + 1 + wrapper = "def #{@name}(*args, &block) + args << {} unless args.last.is_a? Hash + args << block if block_given? + self.__send__(#{internal_name.inspect}, *args) + end" + + if @string.is_a?(Class) + @string.class_eval do eval wrapper, nil, file, line end + @string.define_method(internal_name, &block) + else + @string.instance_eval do eval wrapper, nil, file, line end + @string.meta_def(internal_name, &block) + end + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + elsif conflict = @string.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@string}" + end + end + + option.aliases.each do |name| + @options[name] = option + end + + option + end + + def option?(name) + @options.include? name.to_sym + end + + def options + (@options.keys + @string.options).sort + end + + def get_option(name) + @options[name.to_sym] || @string.get_option(name) + end +end diff --git a/lib/puppet/faces/action_builder.rb b/lib/puppet/faces/action_builder.rb new file mode 100644 index 000000000..e7c03273b --- /dev/null +++ b/lib/puppet/faces/action_builder.rb @@ -0,0 +1,31 @@ +require 'puppet/string' +require 'puppet/string/action' + +class Puppet::String::ActionBuilder + attr_reader :action + + def self.build(string, name, &block) + raise "Action #{name.inspect} must specify a block" unless block + new(string, name, &block).action + end + + private + def initialize(string, name, &block) + @string = string + @action = Puppet::String::Action.new(string, name) + instance_eval(&block) + end + + # Ideally the method we're defining here would be added to the action, and a + # method on the string would defer to it, but we can't get scope correct, + # so we stick with this. --daniel 2011-03-24 + def when_invoked(&block) + raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action + @action.when_invoked = block + end + + def option(*declaration, &block) + option = Puppet::String::OptionBuilder.build(@action, *declaration, &block) + @action.add_option(option) + end +end diff --git a/lib/puppet/faces/action_manager.rb b/lib/puppet/faces/action_manager.rb new file mode 100644 index 000000000..9f0aa7582 --- /dev/null +++ b/lib/puppet/faces/action_manager.rb @@ -0,0 +1,49 @@ +require 'puppet/string/action_builder' + +module Puppet::String::ActionManager + # Declare that this app can take a specific action, and provide + # the code to do so. + def action(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + action = Puppet::String::ActionBuilder.build(self, name, &block) + @actions[action.name] = action + end + + # This is the short-form of an action definition; it doesn't use the + # builder, just creates the action directly from the block. + def script(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + @actions[name] = Puppet::String::Action.new(self, name, :when_invoked => block) + end + + def actions + @actions ||= {} + result = @actions.keys + + if self.is_a?(Class) and superclass.respond_to?(:actions) + result += superclass.actions + elsif self.class.respond_to?(:actions) + result += self.class.actions + end + result.sort + end + + def get_action(name) + @actions ||= {} + result = @actions[name.to_sym] + if result.nil? + if self.is_a?(Class) and superclass.respond_to?(:get_action) + result = superclass.get_action(name) + elsif self.class.respond_to?(:get_action) + result = self.class.get_action(name) + end + end + return result + end + + def action?(name) + actions.include?(name.to_sym) + end +end diff --git a/lib/puppet/faces/catalog.rb b/lib/puppet/faces/catalog.rb new file mode 100644 index 000000000..441c7ee7d --- /dev/null +++ b/lib/puppet/faces/catalog.rb @@ -0,0 +1,40 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:catalog, '0.0.1') do + action(:apply) do + when_invoked do |catalog, options| + report = Puppet::Transaction::Report.new("apply") + report.configuration_version = catalog.version + + Puppet::Util::Log.newdestination(report) + + begin + benchmark(:notice, "Finished catalog run") do + catalog.apply(:report => report) + end + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Failed to apply catalog: #{detail}" + end + + report.finalize_report + report + end + end + + action(:download) do + when_invoked do |certname, facts, options| + Puppet::Resource::Catalog.terminus_class = :rest + facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} + catalog = nil + retrieval_duration = thinmark do + catalog = Puppet::String[:catalog, '0.0.1'].find(certname, facts_to_upload) + end + catalog = catalog.to_ral + catalog.finalize + catalog.retrieval_duration = retrieval_duration + catalog.write_class_file + catalog + end + end +end diff --git a/lib/puppet/faces/catalog/select.rb b/lib/puppet/faces/catalog/select.rb new file mode 100644 index 000000000..11670e2e7 --- /dev/null +++ b/lib/puppet/faces/catalog/select.rb @@ -0,0 +1,10 @@ +# Select and show a list of resources of a given type. +Puppet::String.define(:catalog, '0.0.1') do + action :select do + when_invoked do |host, type, options| + catalog = Puppet::Resource::Catalog.indirection.find(host) + + catalog.resources.reject { |res| res.type != type }.each { |res| puts res } + end + end +end diff --git a/lib/puppet/faces/certificate.rb b/lib/puppet/faces/certificate.rb new file mode 100644 index 000000000..e8773ae2e --- /dev/null +++ b/lib/puppet/faces/certificate.rb @@ -0,0 +1,46 @@ +require 'puppet/string/indirector' +require 'puppet/ssl/host' + +Puppet::String::Indirector.define(:certificate, '0.0.1') do + # REVISIT: This should use a pre-invoke hook to run the common code that + # needs to happen before we invoke any action; that would be much nicer than + # the "please repeat yourself" stuff found in here right now. + # + # option "--ca-location LOCATION" do + # type [:whatever, :location, :symbols] + # hook :before do |value| + # Puppet::SSL::Host.ca_location = value + # end + # end + # + # ...but should I pass the arguments as well? + # --daniel 2011-04-05 + option "--ca-location LOCATION" + + action :generate do + when_invoked do |name, options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + host = Puppet::SSL::Host.new(name) + host.generate_certificate_request + host.certificate_request.class.indirection.save(host.certificate_request) + end + end + + action :list do + when_invoked do |options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + Puppet::SSL::Host.indirection.search("*", { + :for => :certificate_request, + }).map { |h| h.inspect } + end + end + + action :sign do + when_invoked do |name, options| + Puppet::SSL::Host.ca_location = options[:ca_location].to_sym + host = Puppet::SSL::Host.new(name) + host.desired_state = 'signed' + Puppet::SSL::Host.indirection.save(host) + end + end +end diff --git a/lib/puppet/faces/certificate_request.rb b/lib/puppet/faces/certificate_request.rb new file mode 100644 index 000000000..218b40b98 --- /dev/null +++ b/lib/puppet/faces/certificate_request.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:certificate_request, '0.0.1') do +end diff --git a/lib/puppet/faces/certificate_revocation_list.rb b/lib/puppet/faces/certificate_revocation_list.rb new file mode 100644 index 000000000..9731b4f2d --- /dev/null +++ b/lib/puppet/faces/certificate_revocation_list.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:certificate_revocation_list, '0.0.1') do +end diff --git a/lib/puppet/faces/config.rb b/lib/puppet/faces/config.rb new file mode 100644 index 000000000..8a9417148 --- /dev/null +++ b/lib/puppet/faces/config.rb @@ -0,0 +1,12 @@ +require 'puppet/string' + +Puppet::String.define(:config, '0.0.1') do + action(:print) do + when_invoked do |*args| + options = args.pop + Puppet.settings[:configprint] = args.join(",") + Puppet.settings.print_config_options + nil + end + end +end diff --git a/lib/puppet/faces/configurer.rb b/lib/puppet/faces/configurer.rb new file mode 100644 index 000000000..257f97e90 --- /dev/null +++ b/lib/puppet/faces/configurer.rb @@ -0,0 +1,12 @@ +require 'puppet/string' + +Puppet::String.define(:configurer, '0.0.1') do + action(:synchronize) do + when_invoked do |certname, options| + facts = Puppet::String[:facts, '0.0.1'].find(certname) + catalog = Puppet::String[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::String[:catalog, '0.0.1'].apply(catalog) + report + end + end +end diff --git a/lib/puppet/faces/faces_collection.rb b/lib/puppet/faces/faces_collection.rb new file mode 100644 index 000000000..ecd99359d --- /dev/null +++ b/lib/puppet/faces/faces_collection.rb @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +require 'puppet/string' + +module Puppet::String::StringCollection + SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ + + @strings = Hash.new { |hash, key| hash[key] = {} } + + def self.strings + unless @loaded + @loaded = true + $LOAD_PATH.each do |dir| + next unless FileTest.directory?(dir) + Dir.chdir(dir) do + Dir.glob("puppet/string/v*/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| + iname = file.sub(/\.rb/, '') + begin + require iname + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" + end + end + end + end + end + return @strings.keys + end + + def self.validate_version(version) + !!(SEMVER_VERSION =~ version.to_s) + end + + def self.cmp_versions(a, b) + a, b = [a, b].map do |x| + parts = SEMVER_VERSION.match(x).to_a[1..4] + parts[0..2] = parts[0..2].map { |e| e.to_i } + parts + end + + cmp = a[0..2] <=> b[0..2] + if cmp == 0 + cmp = a[3] <=> b[3] + cmp = +1 if a[3].empty? && !b[3].empty? + cmp = -1 if b[3].empty? && !a[3].empty? + end + cmp + end + + def self.[](name, version) + @strings[underscorize(name)][version] if string?(name, version) + end + + def self.string?(name, version) + name = underscorize(name) + return true if @strings[name].has_key?(version) + + # We always load the current version file; the common case is that we have + # the expected version and any compatibility versions in the same file, + # the default. Which means that this is almost always the case. + # + # We use require to avoid executing the code multiple times, like any + # other Ruby library that we might want to use. --daniel 2011-04-06 + begin + require "puppet/string/#{name}" + + # If we wanted :current, we need to index to find that; direct version + # requests just work™ as they go. --daniel 2011-04-06 + if version == :current then + # We need to find current out of this. This is the largest version + # number that doesn't have a dedicated on-disk file present; those + # represent "experimental" versions of strings, which we don't fully + # support yet. + # + # We walk the versions from highest to lowest and take the first version + # that is not defined in an explicitly versioned file on disk as the + # current version. + # + # This constrains us to only ship experimental versions with *one* + # version in the file, not multiple, but given you can't reliably load + # them except by side-effect when you ignore that rule this seems safe + # enough... + # + # Given those constraints, and that we are not going to ship a versioned + # interface that is not :current in this release, we are going to leave + # these thoughts in place, and just punt on the actual versioning. + # + # When we upgrade the core to support multiple versions we can solve the + # problems then; as lazy as possible. + # + # We do support multiple versions in the same file, though, so we sort + # versions here and return the last item in that set. + # + # --daniel 2011-04-06 + latest_ver = @strings[name].keys.sort {|a, b| cmp_versions(a, b) }.last + @strings[name][:current] = @strings[name][latest_ver] + end + rescue LoadError => e + raise unless e.message =~ %r{-- puppet/string/#{name}$} + # ...guess we didn't find the file; return a much better problem. + end + + # Now, either we have the version in our set of strings, or we didn't find + # the version they were looking for. In the future we will support + # loading versioned stuff from some look-aside part of the Ruby load path, + # but we don't need that right now. + # + # So, this comment is a place-holder for that. --daniel 2011-04-06 + return !! @strings[name].has_key?(version) + end + + def self.register(string) + @strings[underscorize(string.name)][string.version] = string + end + + def self.underscorize(name) + unless name.to_s =~ /^[-_a-z]+$/i then + raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid string name" + end + + name.to_s.downcase.split(/[-_]/).join('_').to_sym + end +end diff --git a/lib/puppet/faces/facts.rb b/lib/puppet/faces/facts.rb new file mode 100644 index 000000000..6bd9904b0 --- /dev/null +++ b/lib/puppet/faces/facts.rb @@ -0,0 +1,18 @@ +require 'puppet/string/indirector' +require 'puppet/node/facts' + +Puppet::String::Indirector.define(:facts, '0.0.1') do + set_default_format :yaml + + # Upload our facts to the server + action(:upload) do + when_invoked do |options| + Puppet::Node::Facts.indirection.terminus_class = :facter + facts = Puppet::Node::Facts.indirection.find(Puppet[:certname]) + Puppet::Node::Facts.indirection.terminus_class = :rest + Puppet::Node::Facts.indirection.save(facts) + Puppet.notice "Uploaded facts for '#{Puppet[:certname]}'" + nil + end + end +end diff --git a/lib/puppet/faces/file.rb b/lib/puppet/faces/file.rb new file mode 100644 index 000000000..cc5737f28 --- /dev/null +++ b/lib/puppet/faces/file.rb @@ -0,0 +1,5 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:file, '0.0.1') do + set_indirection_name :file_bucket_file +end diff --git a/lib/puppet/faces/indirector.rb b/lib/puppet/faces/indirector.rb new file mode 100644 index 000000000..0c7d043bb --- /dev/null +++ b/lib/puppet/faces/indirector.rb @@ -0,0 +1,94 @@ +require 'puppet' +require 'puppet/string' + +class Puppet::String::Indirector < Puppet::String + option "--terminus TERMINUS" do + desc "REVISIT: You can select a terminus, which has some bigger effect +that we should describe in this file somehow." + end + + def self.indirections + Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort + end + + def self.terminus_classes(indirection) + Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort + end + + def call_indirection_method(method, *args) + options = args.pop + options.has_key?(:terminus) and set_terminus(options[:terminus]) + + begin + result = indirection.__send__(method, *args) + rescue => detail + puts detail.backtrace if Puppet[:trace] + raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" + end + + indirection.reset_terminus_class + return result + end + + action :destroy do + when_invoked { |*args| call_indirection_method(:destroy, *args) } + end + + action :find do + when_invoked { |*args| call_indirection_method(:find, *args) } + end + + action :save do + when_invoked { |*args| call_indirection_method(:save, *args) } + end + + action :search do + when_invoked { |*args| call_indirection_method(:search, *args) } + end + + # Print the configuration for the current terminus class + action :info do + when_invoked do |*args| + options = args.pop + options.has_key?(:terminus) and set_terminus(options[:terminus]) + + if t = indirection.terminus_class + puts "Run mode '#{Puppet.run_mode.name}': #{t}" + else + $stderr.puts "No default terminus class for run mode '#{Puppet.run_mode.name}'" + end + + indirection.reset_terminus_class + end + end + + attr_accessor :from + + def indirection_name + @indirection_name || name.to_sym + end + + # Here's your opportunity to override the indirection name. By default + # it will be the same name as the string. + def set_indirection_name(name) + @indirection_name = name + end + + # Return an indirection associated with an string, if one exists + # One usually does. + def indirection + unless @indirection + @indirection = Puppet::Indirector::Indirection.instance(indirection_name) + @indirection or raise "Could not find terminus for #{indirection_name}" + end + @indirection + end + + def set_terminus(from) + begin + indirection.terminus_class = from + rescue => detail + raise "Could not set '#{indirection.name}' terminus to '#{from}' (#{detail}); valid terminus types are #{self.class.terminus_classes(indirection.name).join(", ") }" + end + end +end diff --git a/lib/puppet/faces/key.rb b/lib/puppet/faces/key.rb new file mode 100644 index 000000000..95aceade5 --- /dev/null +++ b/lib/puppet/faces/key.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:key, '0.0.1') do +end diff --git a/lib/puppet/faces/node.rb b/lib/puppet/faces/node.rb new file mode 100644 index 000000000..bc31a2cf3 --- /dev/null +++ b/lib/puppet/faces/node.rb @@ -0,0 +1,5 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:node, '0.0.1') do + set_default_format :yaml +end diff --git a/lib/puppet/faces/option.rb b/lib/puppet/faces/option.rb new file mode 100644 index 000000000..352f7e5ef --- /dev/null +++ b/lib/puppet/faces/option.rb @@ -0,0 +1,82 @@ +require 'puppet/string' + +class Puppet::String::Option + attr_reader :parent + attr_reader :name + attr_reader :aliases + attr_reader :optparse + attr_accessor :desc + + def takes_argument? + !!@argument + end + def optional_argument? + !!@optional_argument + end + + def initialize(parent, *declaration, &block) + @parent = parent + @optparse = [] + + # Collect and sort the arguments in the declaration. + dups = {} + declaration.each do |item| + if item.is_a? String and item.to_s =~ /^-/ then + unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then + raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" + end + @optparse << item + + # Duplicate checking... + name = optparse_to_name(item) + if dup = dups[name] then + raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" + else + dups[name] = item + end + else + raise ArgumentError, "#{item.inspect} is not valid for an option argument" + end + end + + if @optparse.empty? then + raise ArgumentError, "No option declarations found while building" + end + + # Now, infer the name from the options; we prefer the first long option as + # the name, rather than just the first option. + @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) + @aliases = @optparse.map { |o| optparse_to_name(o) } + + # Do we take an argument? If so, are we consistent about it, because + # incoherence here makes our life super-difficult, and we can more easily + # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 + @argument = @optparse.any? { |o| o =~ /[ =]/ } + if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then + raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" + end + + # Is our argument optional? The rules about consistency apply here, also, + # just like they do to taking arguments at all. --daniel 2011-03-30 + @optional_argument = @optparse.any? { |o| o.include? "[" } + if @optional_argument and not @optparse.all? { |o| o.include? "[" } then + raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" + end + end + + # to_s and optparse_to_name are roughly mirrored, because they are used to + # transform strings to name symbols, and vice-versa. This isn't a full + # bidirectional transformation though. + def to_s + @name.to_s.tr('_', '-') + end + + def optparse_to_name(declaration) + unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then + raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" + end + name = found.captures.first.tr('-', '_') + raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ + name.to_sym + end +end diff --git a/lib/puppet/faces/option_builder.rb b/lib/puppet/faces/option_builder.rb new file mode 100644 index 000000000..da0d213fb --- /dev/null +++ b/lib/puppet/faces/option_builder.rb @@ -0,0 +1,25 @@ +require 'puppet/string/option' + +class Puppet::String::OptionBuilder + attr_reader :option + + def self.build(string, *declaration, &block) + new(string, *declaration, &block).option + end + + private + def initialize(string, *declaration, &block) + @string = string + @option = Puppet::String::Option.new(string, *declaration) + block and instance_eval(&block) + @option + end + + # Metaprogram the simple DSL from the option class. + Puppet::String::Option.instance_methods.grep(/=$/).each do |setter| + next if setter =~ /^=/ # special case, darn it... + + dsl = setter.sub(/=$/, '') + define_method(dsl) do |value| @option.send(setter, value) end + end +end diff --git a/lib/puppet/faces/option_manager.rb b/lib/puppet/faces/option_manager.rb new file mode 100644 index 000000000..f952ad4f0 --- /dev/null +++ b/lib/puppet/faces/option_manager.rb @@ -0,0 +1,56 @@ +require 'puppet/string/option_builder' + +module Puppet::String::OptionManager + # Declare that this app can take a specific option, and provide + # the code to do so. + def option(*declaration, &block) + add_option Puppet::String::OptionBuilder.build(self, *declaration, &block) + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + end + + actions.each do |action| + action = get_action(action) + if conflict = action.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" + end + end + end + + option.aliases.each { |name| @options[name] = option } + option + end + + def options + @options ||= {} + result = @options.keys + + if self.is_a?(Class) and superclass.respond_to?(:options) + result += superclass.options + elsif self.class.respond_to?(:options) + result += self.class.options + end + result.sort + end + + def get_option(name) + @options ||= {} + result = @options[name.to_sym] + unless result then + if self.is_a?(Class) and superclass.respond_to?(:get_option) + result = superclass.get_option(name) + elsif self.class.respond_to?(:get_option) + result = self.class.get_option(name) + end + end + return result + end + + def option?(name) + options.include? name.to_sym + end +end diff --git a/lib/puppet/faces/report.rb b/lib/puppet/faces/report.rb new file mode 100644 index 000000000..da3ca8504 --- /dev/null +++ b/lib/puppet/faces/report.rb @@ -0,0 +1,15 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:report, '0.0.1') do + action(:submit) do + when_invoked do |report, options| + begin + Puppet::Transaction::Report.terminus_class = :rest + report.save + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not send report: #{detail}" + end + end + end +end diff --git a/lib/puppet/faces/resource.rb b/lib/puppet/faces/resource.rb new file mode 100644 index 000000000..9838be0fa --- /dev/null +++ b/lib/puppet/faces/resource.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:resource, '0.0.1') do +end diff --git a/lib/puppet/faces/resource_type.rb b/lib/puppet/faces/resource_type.rb new file mode 100644 index 000000000..8ca31ea6c --- /dev/null +++ b/lib/puppet/faces/resource_type.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:resource_type, '0.0.1') do +end diff --git a/lib/puppet/faces/status.rb b/lib/puppet/faces/status.rb new file mode 100644 index 000000000..41de2bb99 --- /dev/null +++ b/lib/puppet/faces/status.rb @@ -0,0 +1,4 @@ +require 'puppet/string/indirector' + +Puppet::String::Indirector.define(:status, '0.0.1') do +end diff --git a/lib/puppet/string.rb b/lib/puppet/string.rb deleted file mode 100644 index 517cf4506..000000000 --- a/lib/puppet/string.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'puppet' -require 'puppet/util/autoload' - -class Puppet::String - require 'puppet/string/string_collection' - - require 'puppet/string/action_manager' - include Puppet::String::ActionManager - extend Puppet::String::ActionManager - - require 'puppet/string/option_manager' - include Puppet::String::OptionManager - extend Puppet::String::OptionManager - - include Puppet::Util - - class << self - # This is just so we can search for actions. We only use its - # list of directories to search. - # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb - def autoloader - @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/string") - end - - def strings - Puppet::String::StringCollection.strings - end - - def string?(name, version) - Puppet::String::StringCollection.string?(name, version) - end - - def register(instance) - Puppet::String::StringCollection.register(instance) - end - - def define(name, version, &block) - if string?(name, version) - string = Puppet::String::StringCollection[name, version] - else - string = self.new(name, version) - Puppet::String::StringCollection.register(string) - string.load_actions - end - - string.instance_eval(&block) if block_given? - - return string - end - - alias :[] :define - end - - attr_accessor :default_format - - def set_default_format(format) - self.default_format = format.to_sym - end - - attr_accessor :type, :verb, :version, :arguments - attr_reader :name - - def initialize(name, version, &block) - unless Puppet::String::StringCollection.validate_version(version) - raise ArgumentError, "Cannot create string #{name.inspect} with invalid version number '#{version}'!" - end - - @name = Puppet::String::StringCollection.underscorize(name) - @version = version - @default_format = :pson - - instance_eval(&block) if block_given? - end - - # Try to find actions defined in other files. - def load_actions - path = "puppet/string/#{name}" - - loaded = [] - [path, "#{name}@#{version}/#{path}"].each do |path| - Puppet::String.autoloader.search_directories.each do |dir| - fdir = ::File.join(dir, path) - next unless FileTest.directory?(fdir) - - Dir.chdir(fdir) do - Dir.glob("*.rb").each do |file| - aname = file.sub(/\.rb/, '') - if loaded.include?(aname) - Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - next - end - loaded << aname - Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - require "#{Dir.pwd}/#{aname}" - end - end - end - end - end - - def to_s - "Puppet::String[#{name.inspect}, #{version.inspect}]" - end -end diff --git a/lib/puppet/string/action.rb b/lib/puppet/string/action.rb deleted file mode 100644 index 0f5032ffb..000000000 --- a/lib/puppet/string/action.rb +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/string' -require 'puppet/string/option' - -class Puppet::String::Action - attr_reader :name - - def to_s - "#{@string}##{@name}" - end - - def initialize(string, name, attrs = {}) - raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ - @string = string - @name = name.to_sym - @options = {} - attrs.each do |k, v| send("#{k}=", v) end - end - - # Initially, this was defined to allow the @action.invoke pattern, which is - # a very natural way to invoke behaviour given our introspection - # capabilities. Heck, our initial plan was to have the string delegate to - # the action object for invocation and all. - # - # It turns out that we have a binding problem to solve: @string was bound to - # the parent class, not the subclass instance, and we don't pass the - # appropriate context or change the binding enough to make this work. - # - # We could hack around it, by either mandating that you pass the context in - # to invoke, or try to get the binding right, but that has probably got - # subtleties that we don't instantly think of – especially around threads. - # - # So, we are pulling this method for now, and will return it to life when we - # have the time to resolve the problem. For now, you should replace... - # - # @action = @string.get_action(name) - # @action.invoke(arg1, arg2, arg3) - # - # ...with... - # - # @action = @string.get_action(name) - # @string.send(@action.name, arg1, arg2, arg3) - # - # I understand that is somewhat cumbersome, but it functions as desired. - # --daniel 2011-03-31 - # - # PS: This code is left present, but commented, to support this chunk of - # documentation, for the benefit of the reader. - # - # def invoke(*args, &block) - # @string.send(name, *args, &block) - # end - - def when_invoked=(block) - # We need to build an instance method as a wrapper, using normal code, to - # be able to expose argument defaulting between the caller and definer in - # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. - # - # In future this also gives us a place to hook in additional behaviour - # such as calling out to the action instance to validate and coerce - # parameters, which avoids any exciting context switching and all. - # - # Hopefully we can improve this when we finally shuffle off the last of - # Ruby 1.8 support, but that looks to be a few "enterprise" release eras - # away, so we are pretty stuck with this for now. - # - # Patches to make this work more nicely with Ruby 1.9 using runtime - # version checking and all are welcome, but they can't actually help if - # the results are not totally hidden away in here. - # - # Incidentally, we though about vendoring evil-ruby and actually adjusting - # the internal C structure implementation details under the hood to make - # this stuff work, because it would have been cleaner. Which gives you an - # idea how motivated we were to make this cleaner. Sorry. --daniel 2011-03-31 - - internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym - file = __FILE__ + "+eval" - line = __LINE__ + 1 - wrapper = "def #{@name}(*args, &block) - args << {} unless args.last.is_a? Hash - args << block if block_given? - self.__send__(#{internal_name.inspect}, *args) - end" - - if @string.is_a?(Class) - @string.class_eval do eval wrapper, nil, file, line end - @string.define_method(internal_name, &block) - else - @string.instance_eval do eval wrapper, nil, file, line end - @string.meta_def(internal_name, &block) - end - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - elsif conflict = @string.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@string}" - end - end - - option.aliases.each do |name| - @options[name] = option - end - - option - end - - def option?(name) - @options.include? name.to_sym - end - - def options - (@options.keys + @string.options).sort - end - - def get_option(name) - @options[name.to_sym] || @string.get_option(name) - end -end diff --git a/lib/puppet/string/action_builder.rb b/lib/puppet/string/action_builder.rb deleted file mode 100644 index e7c03273b..000000000 --- a/lib/puppet/string/action_builder.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'puppet/string' -require 'puppet/string/action' - -class Puppet::String::ActionBuilder - attr_reader :action - - def self.build(string, name, &block) - raise "Action #{name.inspect} must specify a block" unless block - new(string, name, &block).action - end - - private - def initialize(string, name, &block) - @string = string - @action = Puppet::String::Action.new(string, name) - instance_eval(&block) - end - - # Ideally the method we're defining here would be added to the action, and a - # method on the string would defer to it, but we can't get scope correct, - # so we stick with this. --daniel 2011-03-24 - def when_invoked(&block) - raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action - @action.when_invoked = block - end - - def option(*declaration, &block) - option = Puppet::String::OptionBuilder.build(@action, *declaration, &block) - @action.add_option(option) - end -end diff --git a/lib/puppet/string/action_manager.rb b/lib/puppet/string/action_manager.rb deleted file mode 100644 index 9f0aa7582..000000000 --- a/lib/puppet/string/action_manager.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'puppet/string/action_builder' - -module Puppet::String::ActionManager - # Declare that this app can take a specific action, and provide - # the code to do so. - def action(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - action = Puppet::String::ActionBuilder.build(self, name, &block) - @actions[action.name] = action - end - - # This is the short-form of an action definition; it doesn't use the - # builder, just creates the action directly from the block. - def script(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - @actions[name] = Puppet::String::Action.new(self, name, :when_invoked => block) - end - - def actions - @actions ||= {} - result = @actions.keys - - if self.is_a?(Class) and superclass.respond_to?(:actions) - result += superclass.actions - elsif self.class.respond_to?(:actions) - result += self.class.actions - end - result.sort - end - - def get_action(name) - @actions ||= {} - result = @actions[name.to_sym] - if result.nil? - if self.is_a?(Class) and superclass.respond_to?(:get_action) - result = superclass.get_action(name) - elsif self.class.respond_to?(:get_action) - result = self.class.get_action(name) - end - end - return result - end - - def action?(name) - actions.include?(name.to_sym) - end -end diff --git a/lib/puppet/string/catalog.rb b/lib/puppet/string/catalog.rb deleted file mode 100644 index 441c7ee7d..000000000 --- a/lib/puppet/string/catalog.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:catalog, '0.0.1') do - action(:apply) do - when_invoked do |catalog, options| - report = Puppet::Transaction::Report.new("apply") - report.configuration_version = catalog.version - - Puppet::Util::Log.newdestination(report) - - begin - benchmark(:notice, "Finished catalog run") do - catalog.apply(:report => report) - end - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Failed to apply catalog: #{detail}" - end - - report.finalize_report - report - end - end - - action(:download) do - when_invoked do |certname, facts, options| - Puppet::Resource::Catalog.terminus_class = :rest - facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} - catalog = nil - retrieval_duration = thinmark do - catalog = Puppet::String[:catalog, '0.0.1'].find(certname, facts_to_upload) - end - catalog = catalog.to_ral - catalog.finalize - catalog.retrieval_duration = retrieval_duration - catalog.write_class_file - catalog - end - end -end diff --git a/lib/puppet/string/catalog/select.rb b/lib/puppet/string/catalog/select.rb deleted file mode 100644 index 11670e2e7..000000000 --- a/lib/puppet/string/catalog/select.rb +++ /dev/null @@ -1,10 +0,0 @@ -# Select and show a list of resources of a given type. -Puppet::String.define(:catalog, '0.0.1') do - action :select do - when_invoked do |host, type, options| - catalog = Puppet::Resource::Catalog.indirection.find(host) - - catalog.resources.reject { |res| res.type != type }.each { |res| puts res } - end - end -end diff --git a/lib/puppet/string/certificate.rb b/lib/puppet/string/certificate.rb deleted file mode 100644 index e8773ae2e..000000000 --- a/lib/puppet/string/certificate.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'puppet/string/indirector' -require 'puppet/ssl/host' - -Puppet::String::Indirector.define(:certificate, '0.0.1') do - # REVISIT: This should use a pre-invoke hook to run the common code that - # needs to happen before we invoke any action; that would be much nicer than - # the "please repeat yourself" stuff found in here right now. - # - # option "--ca-location LOCATION" do - # type [:whatever, :location, :symbols] - # hook :before do |value| - # Puppet::SSL::Host.ca_location = value - # end - # end - # - # ...but should I pass the arguments as well? - # --daniel 2011-04-05 - option "--ca-location LOCATION" - - action :generate do - when_invoked do |name, options| - Puppet::SSL::Host.ca_location = options[:ca_location].to_sym - host = Puppet::SSL::Host.new(name) - host.generate_certificate_request - host.certificate_request.class.indirection.save(host.certificate_request) - end - end - - action :list do - when_invoked do |options| - Puppet::SSL::Host.ca_location = options[:ca_location].to_sym - Puppet::SSL::Host.indirection.search("*", { - :for => :certificate_request, - }).map { |h| h.inspect } - end - end - - action :sign do - when_invoked do |name, options| - Puppet::SSL::Host.ca_location = options[:ca_location].to_sym - host = Puppet::SSL::Host.new(name) - host.desired_state = 'signed' - Puppet::SSL::Host.indirection.save(host) - end - end -end diff --git a/lib/puppet/string/certificate_request.rb b/lib/puppet/string/certificate_request.rb deleted file mode 100644 index 218b40b98..000000000 --- a/lib/puppet/string/certificate_request.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:certificate_request, '0.0.1') do -end diff --git a/lib/puppet/string/certificate_revocation_list.rb b/lib/puppet/string/certificate_revocation_list.rb deleted file mode 100644 index 9731b4f2d..000000000 --- a/lib/puppet/string/certificate_revocation_list.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:certificate_revocation_list, '0.0.1') do -end diff --git a/lib/puppet/string/config.rb b/lib/puppet/string/config.rb deleted file mode 100644 index 8a9417148..000000000 --- a/lib/puppet/string/config.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'puppet/string' - -Puppet::String.define(:config, '0.0.1') do - action(:print) do - when_invoked do |*args| - options = args.pop - Puppet.settings[:configprint] = args.join(",") - Puppet.settings.print_config_options - nil - end - end -end diff --git a/lib/puppet/string/configurer.rb b/lib/puppet/string/configurer.rb deleted file mode 100644 index 257f97e90..000000000 --- a/lib/puppet/string/configurer.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'puppet/string' - -Puppet::String.define(:configurer, '0.0.1') do - action(:synchronize) do - when_invoked do |certname, options| - facts = Puppet::String[:facts, '0.0.1'].find(certname) - catalog = Puppet::String[:catalog, '0.0.1'].download(certname, facts) - report = Puppet::String[:catalog, '0.0.1'].apply(catalog) - report - end - end -end diff --git a/lib/puppet/string/facts.rb b/lib/puppet/string/facts.rb deleted file mode 100644 index 6bd9904b0..000000000 --- a/lib/puppet/string/facts.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'puppet/string/indirector' -require 'puppet/node/facts' - -Puppet::String::Indirector.define(:facts, '0.0.1') do - set_default_format :yaml - - # Upload our facts to the server - action(:upload) do - when_invoked do |options| - Puppet::Node::Facts.indirection.terminus_class = :facter - facts = Puppet::Node::Facts.indirection.find(Puppet[:certname]) - Puppet::Node::Facts.indirection.terminus_class = :rest - Puppet::Node::Facts.indirection.save(facts) - Puppet.notice "Uploaded facts for '#{Puppet[:certname]}'" - nil - end - end -end diff --git a/lib/puppet/string/file.rb b/lib/puppet/string/file.rb deleted file mode 100644 index cc5737f28..000000000 --- a/lib/puppet/string/file.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:file, '0.0.1') do - set_indirection_name :file_bucket_file -end diff --git a/lib/puppet/string/indirector.rb b/lib/puppet/string/indirector.rb deleted file mode 100644 index 0c7d043bb..000000000 --- a/lib/puppet/string/indirector.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'puppet' -require 'puppet/string' - -class Puppet::String::Indirector < Puppet::String - option "--terminus TERMINUS" do - desc "REVISIT: You can select a terminus, which has some bigger effect -that we should describe in this file somehow." - end - - def self.indirections - Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort - end - - def self.terminus_classes(indirection) - Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort - end - - def call_indirection_method(method, *args) - options = args.pop - options.has_key?(:terminus) and set_terminus(options[:terminus]) - - begin - result = indirection.__send__(method, *args) - rescue => detail - puts detail.backtrace if Puppet[:trace] - raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" - end - - indirection.reset_terminus_class - return result - end - - action :destroy do - when_invoked { |*args| call_indirection_method(:destroy, *args) } - end - - action :find do - when_invoked { |*args| call_indirection_method(:find, *args) } - end - - action :save do - when_invoked { |*args| call_indirection_method(:save, *args) } - end - - action :search do - when_invoked { |*args| call_indirection_method(:search, *args) } - end - - # Print the configuration for the current terminus class - action :info do - when_invoked do |*args| - options = args.pop - options.has_key?(:terminus) and set_terminus(options[:terminus]) - - if t = indirection.terminus_class - puts "Run mode '#{Puppet.run_mode.name}': #{t}" - else - $stderr.puts "No default terminus class for run mode '#{Puppet.run_mode.name}'" - end - - indirection.reset_terminus_class - end - end - - attr_accessor :from - - def indirection_name - @indirection_name || name.to_sym - end - - # Here's your opportunity to override the indirection name. By default - # it will be the same name as the string. - def set_indirection_name(name) - @indirection_name = name - end - - # Return an indirection associated with an string, if one exists - # One usually does. - def indirection - unless @indirection - @indirection = Puppet::Indirector::Indirection.instance(indirection_name) - @indirection or raise "Could not find terminus for #{indirection_name}" - end - @indirection - end - - def set_terminus(from) - begin - indirection.terminus_class = from - rescue => detail - raise "Could not set '#{indirection.name}' terminus to '#{from}' (#{detail}); valid terminus types are #{self.class.terminus_classes(indirection.name).join(", ") }" - end - end -end diff --git a/lib/puppet/string/key.rb b/lib/puppet/string/key.rb deleted file mode 100644 index 95aceade5..000000000 --- a/lib/puppet/string/key.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:key, '0.0.1') do -end diff --git a/lib/puppet/string/node.rb b/lib/puppet/string/node.rb deleted file mode 100644 index bc31a2cf3..000000000 --- a/lib/puppet/string/node.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:node, '0.0.1') do - set_default_format :yaml -end diff --git a/lib/puppet/string/option.rb b/lib/puppet/string/option.rb deleted file mode 100644 index 352f7e5ef..000000000 --- a/lib/puppet/string/option.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'puppet/string' - -class Puppet::String::Option - attr_reader :parent - attr_reader :name - attr_reader :aliases - attr_reader :optparse - attr_accessor :desc - - def takes_argument? - !!@argument - end - def optional_argument? - !!@optional_argument - end - - def initialize(parent, *declaration, &block) - @parent = parent - @optparse = [] - - # Collect and sort the arguments in the declaration. - dups = {} - declaration.each do |item| - if item.is_a? String and item.to_s =~ /^-/ then - unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then - raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" - end - @optparse << item - - # Duplicate checking... - name = optparse_to_name(item) - if dup = dups[name] then - raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" - else - dups[name] = item - end - else - raise ArgumentError, "#{item.inspect} is not valid for an option argument" - end - end - - if @optparse.empty? then - raise ArgumentError, "No option declarations found while building" - end - - # Now, infer the name from the options; we prefer the first long option as - # the name, rather than just the first option. - @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) - @aliases = @optparse.map { |o| optparse_to_name(o) } - - # Do we take an argument? If so, are we consistent about it, because - # incoherence here makes our life super-difficult, and we can more easily - # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 - @argument = @optparse.any? { |o| o =~ /[ =]/ } - if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then - raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" - end - - # Is our argument optional? The rules about consistency apply here, also, - # just like they do to taking arguments at all. --daniel 2011-03-30 - @optional_argument = @optparse.any? { |o| o.include? "[" } - if @optional_argument and not @optparse.all? { |o| o.include? "[" } then - raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" - end - end - - # to_s and optparse_to_name are roughly mirrored, because they are used to - # transform strings to name symbols, and vice-versa. This isn't a full - # bidirectional transformation though. - def to_s - @name.to_s.tr('_', '-') - end - - def optparse_to_name(declaration) - unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then - raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" - end - name = found.captures.first.tr('-', '_') - raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ - name.to_sym - end -end diff --git a/lib/puppet/string/option_builder.rb b/lib/puppet/string/option_builder.rb deleted file mode 100644 index da0d213fb..000000000 --- a/lib/puppet/string/option_builder.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'puppet/string/option' - -class Puppet::String::OptionBuilder - attr_reader :option - - def self.build(string, *declaration, &block) - new(string, *declaration, &block).option - end - - private - def initialize(string, *declaration, &block) - @string = string - @option = Puppet::String::Option.new(string, *declaration) - block and instance_eval(&block) - @option - end - - # Metaprogram the simple DSL from the option class. - Puppet::String::Option.instance_methods.grep(/=$/).each do |setter| - next if setter =~ /^=/ # special case, darn it... - - dsl = setter.sub(/=$/, '') - define_method(dsl) do |value| @option.send(setter, value) end - end -end diff --git a/lib/puppet/string/option_manager.rb b/lib/puppet/string/option_manager.rb deleted file mode 100644 index f952ad4f0..000000000 --- a/lib/puppet/string/option_manager.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'puppet/string/option_builder' - -module Puppet::String::OptionManager - # Declare that this app can take a specific option, and provide - # the code to do so. - def option(*declaration, &block) - add_option Puppet::String::OptionBuilder.build(self, *declaration, &block) - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - end - - actions.each do |action| - action = get_action(action) - if conflict = action.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" - end - end - end - - option.aliases.each { |name| @options[name] = option } - option - end - - def options - @options ||= {} - result = @options.keys - - if self.is_a?(Class) and superclass.respond_to?(:options) - result += superclass.options - elsif self.class.respond_to?(:options) - result += self.class.options - end - result.sort - end - - def get_option(name) - @options ||= {} - result = @options[name.to_sym] - unless result then - if self.is_a?(Class) and superclass.respond_to?(:get_option) - result = superclass.get_option(name) - elsif self.class.respond_to?(:get_option) - result = self.class.get_option(name) - end - end - return result - end - - def option?(name) - options.include? name.to_sym - end -end diff --git a/lib/puppet/string/report.rb b/lib/puppet/string/report.rb deleted file mode 100644 index da3ca8504..000000000 --- a/lib/puppet/string/report.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:report, '0.0.1') do - action(:submit) do - when_invoked do |report, options| - begin - Puppet::Transaction::Report.terminus_class = :rest - report.save - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not send report: #{detail}" - end - end - end -end diff --git a/lib/puppet/string/resource.rb b/lib/puppet/string/resource.rb deleted file mode 100644 index 9838be0fa..000000000 --- a/lib/puppet/string/resource.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:resource, '0.0.1') do -end diff --git a/lib/puppet/string/resource_type.rb b/lib/puppet/string/resource_type.rb deleted file mode 100644 index 8ca31ea6c..000000000 --- a/lib/puppet/string/resource_type.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:resource_type, '0.0.1') do -end diff --git a/lib/puppet/string/status.rb b/lib/puppet/string/status.rb deleted file mode 100644 index 41de2bb99..000000000 --- a/lib/puppet/string/status.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/string/indirector' - -Puppet::String::Indirector.define(:status, '0.0.1') do -end diff --git a/lib/puppet/string/string_collection.rb b/lib/puppet/string/string_collection.rb deleted file mode 100644 index ecd99359d..000000000 --- a/lib/puppet/string/string_collection.rb +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/string' - -module Puppet::String::StringCollection - SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ - - @strings = Hash.new { |hash, key| hash[key] = {} } - - def self.strings - unless @loaded - @loaded = true - $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - Dir.chdir(dir) do - Dir.glob("puppet/string/v*/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| - iname = file.sub(/\.rb/, '') - begin - require iname - rescue Exception => detail - puts detail.backtrace if Puppet[:trace] - raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" - end - end - end - end - end - return @strings.keys - end - - def self.validate_version(version) - !!(SEMVER_VERSION =~ version.to_s) - end - - def self.cmp_versions(a, b) - a, b = [a, b].map do |x| - parts = SEMVER_VERSION.match(x).to_a[1..4] - parts[0..2] = parts[0..2].map { |e| e.to_i } - parts - end - - cmp = a[0..2] <=> b[0..2] - if cmp == 0 - cmp = a[3] <=> b[3] - cmp = +1 if a[3].empty? && !b[3].empty? - cmp = -1 if b[3].empty? && !a[3].empty? - end - cmp - end - - def self.[](name, version) - @strings[underscorize(name)][version] if string?(name, version) - end - - def self.string?(name, version) - name = underscorize(name) - return true if @strings[name].has_key?(version) - - # We always load the current version file; the common case is that we have - # the expected version and any compatibility versions in the same file, - # the default. Which means that this is almost always the case. - # - # We use require to avoid executing the code multiple times, like any - # other Ruby library that we might want to use. --daniel 2011-04-06 - begin - require "puppet/string/#{name}" - - # If we wanted :current, we need to index to find that; direct version - # requests just work™ as they go. --daniel 2011-04-06 - if version == :current then - # We need to find current out of this. This is the largest version - # number that doesn't have a dedicated on-disk file present; those - # represent "experimental" versions of strings, which we don't fully - # support yet. - # - # We walk the versions from highest to lowest and take the first version - # that is not defined in an explicitly versioned file on disk as the - # current version. - # - # This constrains us to only ship experimental versions with *one* - # version in the file, not multiple, but given you can't reliably load - # them except by side-effect when you ignore that rule this seems safe - # enough... - # - # Given those constraints, and that we are not going to ship a versioned - # interface that is not :current in this release, we are going to leave - # these thoughts in place, and just punt on the actual versioning. - # - # When we upgrade the core to support multiple versions we can solve the - # problems then; as lazy as possible. - # - # We do support multiple versions in the same file, though, so we sort - # versions here and return the last item in that set. - # - # --daniel 2011-04-06 - latest_ver = @strings[name].keys.sort {|a, b| cmp_versions(a, b) }.last - @strings[name][:current] = @strings[name][latest_ver] - end - rescue LoadError => e - raise unless e.message =~ %r{-- puppet/string/#{name}$} - # ...guess we didn't find the file; return a much better problem. - end - - # Now, either we have the version in our set of strings, or we didn't find - # the version they were looking for. In the future we will support - # loading versioned stuff from some look-aside part of the Ruby load path, - # but we don't need that right now. - # - # So, this comment is a place-holder for that. --daniel 2011-04-06 - return !! @strings[name].has_key?(version) - end - - def self.register(string) - @strings[underscorize(string.name)][string.version] = string - end - - def self.underscorize(name) - unless name.to_s =~ /^[-_a-z]+$/i then - raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid string name" - end - - name.to_s.downcase.split(/[-_]/).join('_').to_sym - end -end diff --git a/spec/unit/application/faces_base_spec.rb b/spec/unit/application/faces_base_spec.rb new file mode 100755 index 000000000..3f8ae73b6 --- /dev/null +++ b/spec/unit/application/faces_base_spec.rb @@ -0,0 +1,185 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/application/string_base' +require 'tmpdir' + +class Puppet::Application::StringBase::Basetest < Puppet::Application::StringBase +end + +describe Puppet::Application::StringBase do + before :all do + @dir = Dir.mktmpdir + $LOAD_PATH.push(@dir) + FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') + File.open(File.join(@dir, 'puppet', 'string', 'basetest.rb'), 'w') do |f| + f.puts "Puppet::String.define(:basetest, '0.0.1')" + end + + Puppet::String.define(:basetest, '0.0.1') do + option("--[no-]boolean") + option("--mandatory MANDATORY") + option("--optional [OPTIONAL]") + + action :foo do + option("--action") + when_invoked { |*args| args.length } + end + end + end + + after :all do + FileUtils.remove_entry_secure @dir + $LOAD_PATH.pop + end + + let :app do + app = Puppet::Application::StringBase::Basetest.new + app.stubs(:exit) + app.stubs(:puts) + app.command_line.stubs(:subcommand_name).returns 'subcommand' + Puppet::Util::Log.stubs(:newdestination) + app + end + + describe "#find_global_settings_argument" do + it "should not match --ca to --ca-location" do + option = mock('ca option', :optparse_args => ["--ca"]) + Puppet.settings.expects(:each).yields(:ca, option) + + app.find_global_settings_argument("--ca-location").should be_nil + end + end + + describe "#preinit" do + before :each do + app.command_line.stubs(:args).returns %w{} + end + + describe "parsing the command line" do + context "with just an action" do + before :all do + app.command_line.stubs(:args).returns %w{foo} + app.preinit + end + + it "should set the string based on the type" do + app.string.name.should == :basetest + end + + it "should set the format based on the string default" do + app.format.should == :pson + end + + it "should find the action" do + app.action.should be + app.action.name.should == :foo + end + end + + it "should fail if no action is given" do + expect { app.preinit }. + should raise_error ArgumentError, /No action given/ + end + + it "should report a sensible error when options with = fail" do + app.command_line.stubs(:args).returns %w{--action=bar foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--action"/ + end + + it "should fail if an action option is before the action" do + app.command_line.stubs(:args).returns %w{--action foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--action"/ + end + + it "should fail if an unknown option is before the action" do + app.command_line.stubs(:args).returns %w{--bar foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--bar"/ + end + + it "should not fail if an unknown option is after the action" do + app.command_line.stubs(:args).returns %w{foo --bar} + app.preinit + app.action.name.should == :foo + app.string.should_not be_option :bar + app.action.should_not be_option :bar + end + + it "should accept --bar as an argument to a mandatory option after action" do + app.command_line.stubs(:args).returns %w{foo --mandatory --bar} + app.preinit and app.parse_options + app.action.name.should == :foo + app.options.should == { :mandatory => "--bar" } + end + + it "should accept --bar as an argument to a mandatory option before action" do + app.command_line.stubs(:args).returns %w{--mandatory --bar foo} + app.preinit and app.parse_options + app.action.name.should == :foo + app.options.should == { :mandatory => "--bar" } + end + + it "should not skip when --foo=bar is given" do + app.command_line.stubs(:args).returns %w{--mandatory=bar --bar foo} + expect { app.preinit }. + should raise_error ArgumentError, /Unknown option "--bar"/ + end + + { "boolean options before" => %w{--trace foo}, + "boolean options after" => %w{foo --trace} + }.each do |name, args| + it "should accept global boolean settings #{name} the action" do + app.command_line.stubs(:args).returns args + app.preinit && app.parse_options + Puppet[:trace].should be_true + end + end + + { "before" => %w{--syslogfacility user1 foo}, + " after" => %w{foo --syslogfacility user1} + }.each do |name, args| + it "should accept global settings with arguments #{name} the action" do + app.command_line.stubs(:args).returns args + app.preinit && app.parse_options + Puppet[:syslogfacility].should == "user1" + end + end + end + end + + describe "#setup" do + it "should remove the action name from the arguments" do + app.command_line.stubs(:args).returns %w{--mandatory --bar foo} + app.preinit and app.parse_options and app.setup + app.arguments.should == [{ :mandatory => "--bar" }] + end + + it "should pass positional arguments" do + app.command_line.stubs(:args).returns %w{--mandatory --bar foo bar baz quux} + app.preinit and app.parse_options and app.setup + app.arguments.should == ['bar', 'baz', 'quux', { :mandatory => "--bar" }] + end + end + + describe "#main" do + before do + app.string = Puppet::String[:basetest, '0.0.1'] + app.action = app.string.get_action(:foo) + app.format = :pson + app.arguments = ["myname", "myarg"] + end + + it "should send the specified verb and name to the string" do + app.string.expects(:foo).with(*app.arguments) + app.main + end + + it "should use its render method to render any result" do + app.expects(:render).with(app.arguments.length + 1) + app.main + end + end +end diff --git a/spec/unit/application/faces_spec.rb b/spec/unit/application/faces_spec.rb new file mode 100755 index 000000000..13af0a546 --- /dev/null +++ b/spec/unit/application/faces_spec.rb @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/application/string' + +describe Puppet::Application::String do + it "should be an application" do + Puppet::Application::String.superclass.should equal(Puppet::Application) + end +end diff --git a/spec/unit/application/string_base_spec.rb b/spec/unit/application/string_base_spec.rb deleted file mode 100755 index 3f8ae73b6..000000000 --- a/spec/unit/application/string_base_spec.rb +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/application/string_base' -require 'tmpdir' - -class Puppet::Application::StringBase::Basetest < Puppet::Application::StringBase -end - -describe Puppet::Application::StringBase do - before :all do - @dir = Dir.mktmpdir - $LOAD_PATH.push(@dir) - FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') - File.open(File.join(@dir, 'puppet', 'string', 'basetest.rb'), 'w') do |f| - f.puts "Puppet::String.define(:basetest, '0.0.1')" - end - - Puppet::String.define(:basetest, '0.0.1') do - option("--[no-]boolean") - option("--mandatory MANDATORY") - option("--optional [OPTIONAL]") - - action :foo do - option("--action") - when_invoked { |*args| args.length } - end - end - end - - after :all do - FileUtils.remove_entry_secure @dir - $LOAD_PATH.pop - end - - let :app do - app = Puppet::Application::StringBase::Basetest.new - app.stubs(:exit) - app.stubs(:puts) - app.command_line.stubs(:subcommand_name).returns 'subcommand' - Puppet::Util::Log.stubs(:newdestination) - app - end - - describe "#find_global_settings_argument" do - it "should not match --ca to --ca-location" do - option = mock('ca option', :optparse_args => ["--ca"]) - Puppet.settings.expects(:each).yields(:ca, option) - - app.find_global_settings_argument("--ca-location").should be_nil - end - end - - describe "#preinit" do - before :each do - app.command_line.stubs(:args).returns %w{} - end - - describe "parsing the command line" do - context "with just an action" do - before :all do - app.command_line.stubs(:args).returns %w{foo} - app.preinit - end - - it "should set the string based on the type" do - app.string.name.should == :basetest - end - - it "should set the format based on the string default" do - app.format.should == :pson - end - - it "should find the action" do - app.action.should be - app.action.name.should == :foo - end - end - - it "should fail if no action is given" do - expect { app.preinit }. - should raise_error ArgumentError, /No action given/ - end - - it "should report a sensible error when options with = fail" do - app.command_line.stubs(:args).returns %w{--action=bar foo} - expect { app.preinit }. - should raise_error ArgumentError, /Unknown option "--action"/ - end - - it "should fail if an action option is before the action" do - app.command_line.stubs(:args).returns %w{--action foo} - expect { app.preinit }. - should raise_error ArgumentError, /Unknown option "--action"/ - end - - it "should fail if an unknown option is before the action" do - app.command_line.stubs(:args).returns %w{--bar foo} - expect { app.preinit }. - should raise_error ArgumentError, /Unknown option "--bar"/ - end - - it "should not fail if an unknown option is after the action" do - app.command_line.stubs(:args).returns %w{foo --bar} - app.preinit - app.action.name.should == :foo - app.string.should_not be_option :bar - app.action.should_not be_option :bar - end - - it "should accept --bar as an argument to a mandatory option after action" do - app.command_line.stubs(:args).returns %w{foo --mandatory --bar} - app.preinit and app.parse_options - app.action.name.should == :foo - app.options.should == { :mandatory => "--bar" } - end - - it "should accept --bar as an argument to a mandatory option before action" do - app.command_line.stubs(:args).returns %w{--mandatory --bar foo} - app.preinit and app.parse_options - app.action.name.should == :foo - app.options.should == { :mandatory => "--bar" } - end - - it "should not skip when --foo=bar is given" do - app.command_line.stubs(:args).returns %w{--mandatory=bar --bar foo} - expect { app.preinit }. - should raise_error ArgumentError, /Unknown option "--bar"/ - end - - { "boolean options before" => %w{--trace foo}, - "boolean options after" => %w{foo --trace} - }.each do |name, args| - it "should accept global boolean settings #{name} the action" do - app.command_line.stubs(:args).returns args - app.preinit && app.parse_options - Puppet[:trace].should be_true - end - end - - { "before" => %w{--syslogfacility user1 foo}, - " after" => %w{foo --syslogfacility user1} - }.each do |name, args| - it "should accept global settings with arguments #{name} the action" do - app.command_line.stubs(:args).returns args - app.preinit && app.parse_options - Puppet[:syslogfacility].should == "user1" - end - end - end - end - - describe "#setup" do - it "should remove the action name from the arguments" do - app.command_line.stubs(:args).returns %w{--mandatory --bar foo} - app.preinit and app.parse_options and app.setup - app.arguments.should == [{ :mandatory => "--bar" }] - end - - it "should pass positional arguments" do - app.command_line.stubs(:args).returns %w{--mandatory --bar foo bar baz quux} - app.preinit and app.parse_options and app.setup - app.arguments.should == ['bar', 'baz', 'quux', { :mandatory => "--bar" }] - end - end - - describe "#main" do - before do - app.string = Puppet::String[:basetest, '0.0.1'] - app.action = app.string.get_action(:foo) - app.format = :pson - app.arguments = ["myname", "myarg"] - end - - it "should send the specified verb and name to the string" do - app.string.expects(:foo).with(*app.arguments) - app.main - end - - it "should use its render method to render any result" do - app.expects(:render).with(app.arguments.length + 1) - app.main - end - end -end diff --git a/spec/unit/application/string_spec.rb b/spec/unit/application/string_spec.rb deleted file mode 100755 index 13af0a546..000000000 --- a/spec/unit/application/string_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/application/string' - -describe Puppet::Application::String do - it "should be an application" do - Puppet::Application::String.superclass.should equal(Puppet::Application) - end -end diff --git a/spec/unit/faces/action_builder_spec.rb b/spec/unit/faces/action_builder_spec.rb new file mode 100755 index 000000000..5f6f1c08f --- /dev/null +++ b/spec/unit/faces/action_builder_spec.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/string/action_builder' + +describe Puppet::String::ActionBuilder do + describe "::build" do + it "should build an action" do + action = Puppet::String::ActionBuilder.build(nil, :foo) do + end + action.should be_a(Puppet::String::Action) + action.name.should == :foo + end + + it "should define a method on the string which invokes the action" do + string = Puppet::String.new(:action_builder_test_string, '0.0.1') + action = Puppet::String::ActionBuilder.build(string, :foo) do + when_invoked do + "invoked the method" + end + end + + string.foo.should == "invoked the method" + end + + it "should require a block" do + lambda { Puppet::String::ActionBuilder.build(nil, :foo) }. + should raise_error("Action :foo must specify a block") + end + + describe "when handling options" do + let :string do Puppet::String.new(:option_handling, '0.0.1') end + + it "should have a #option DSL function" do + method = nil + Puppet::String::ActionBuilder.build(string, :foo) do + method = self.method(:option) + end + method.should be + end + + it "should define an option without a block" do + action = Puppet::String::ActionBuilder.build(string, :foo) do + option "--bar" + end + action.should be_option :bar + end + + it "should accept an empty block" do + action = Puppet::String::ActionBuilder.build(string, :foo) do + option "--bar" do + # This space left deliberately blank. + end + end + action.should be_option :bar + end + end + end +end diff --git a/spec/unit/faces/action_manager_spec.rb b/spec/unit/faces/action_manager_spec.rb new file mode 100755 index 000000000..b8baf80a5 --- /dev/null +++ b/spec/unit/faces/action_manager_spec.rb @@ -0,0 +1,233 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +# This is entirely an internal class for String, so we have to load it instead of our class. +require 'puppet/string' + +class ActionManagerTester + include Puppet::String::ActionManager +end + +describe Puppet::String::ActionManager do + subject { ActionManagerTester.new } + + describe "when included in a class" do + it "should be able to define an action" do + subject.action(:foo) do + when_invoked { "something "} + end + end + + it "should be able to define a 'script' style action" do + subject.script :bar do + "a bar is where beer is found" + end + end + + it "should be able to list defined actions" do + subject.action(:foo) do + when_invoked { "something" } + end + subject.action(:bar) do + when_invoked { "something" } + end + + subject.actions.should =~ [:foo, :bar] + end + + it "should list 'script' actions" do + subject.script :foo do "foo" end + subject.actions.should =~ [:foo] + end + + it "should list both script and normal actions" do + subject.action :foo do + when_invoked do "foo" end + end + subject.script :bar do "a bar is where beer is found" end + + subject.actions.should =~ [:foo, :bar] + end + + it "should be able to indicate when an action is defined" do + subject.action(:foo) do + when_invoked { "something" } + end + + subject.should be_action(:foo) + end + + it "should indicate an action is defined for script actions" do + subject.script :foo do "foo" end + subject.should be_action :foo + end + + it "should correctly treat action names specified as strings" do + subject.action(:foo) do + when_invoked { "something" } + end + + subject.should be_action("foo") + end + end + + describe "when used to extend a class" do + subject { Class.new.extend(Puppet::String::ActionManager) } + + it "should be able to define an action" do + subject.action(:foo) do + when_invoked { "something "} + end + end + + it "should be able to list defined actions" do + subject.action(:foo) do + when_invoked { "something" } + end + subject.action(:bar) do + when_invoked { "something" } + end + + subject.actions.should include(:bar) + subject.actions.should include(:foo) + end + + it "should be able to indicate when an action is defined" do + subject.action(:foo) { "something" } + subject.should be_action(:foo) + end + end + + describe "when used both at the class and instance level" do + before do + @klass = Class.new do + include Puppet::String::ActionManager + extend Puppet::String::ActionManager + end + @instance = @klass.new + end + + it "should be able to define an action at the class level" do + @klass.action(:foo) do + when_invoked { "something "} + end + end + + it "should create an instance method when an action is defined at the class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.foo.should == "something" + end + + it "should be able to define an action at the instance level" do + @instance.action(:foo) do + when_invoked { "something "} + end + end + + it "should create an instance method when an action is defined at the instance level" do + @instance.action(:foo) do + when_invoked { "something" } + end + @instance.foo.should == "something" + end + + it "should be able to list actions defined at the class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @klass.action(:bar) do + when_invoked { "something" } + end + + @klass.actions.should include(:bar) + @klass.actions.should include(:foo) + end + + it "should be able to list actions defined at the instance level" do + @instance.action(:foo) do + when_invoked { "something" } + end + @instance.action(:bar) do + when_invoked { "something" } + end + + @instance.actions.should include(:bar) + @instance.actions.should include(:foo) + end + + it "should be able to list actions defined at both instance and class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.action(:bar) do + when_invoked { "something" } + end + + @instance.actions.should include(:bar) + @instance.actions.should include(:foo) + end + + it "should be able to indicate when an action is defined at the class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.should be_action(:foo) + end + + it "should be able to indicate when an action is defined at the instance level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.should be_action(:foo) + end + + it "should list actions defined in superclasses" do + @subclass = Class.new(@klass) + @instance = @subclass.new + + @klass.action(:parent) do + when_invoked { "a" } + end + @subclass.action(:sub) do + when_invoked { "a" } + end + @instance.action(:instance) do + when_invoked { "a" } + end + + @instance.should be_action(:parent) + @instance.should be_action(:sub) + @instance.should be_action(:instance) + end + + it "should create an instance method when an action is defined in a superclass" do + @subclass = Class.new(@klass) + @instance = @subclass.new + + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.foo.should == "something" + end + end + + describe "#get_action" do + let :parent_class do + parent_class = Class.new(Puppet::String) + parent_class.action(:foo) {} + parent_class + end + + it "should check that we can find inherited actions when we are a class" do + Class.new(parent_class).get_action(:foo).name.should == :foo + end + + it "should check that we can find inherited actions when we are an instance" do + instance = parent_class.new(:foo, '0.0.0') + instance.get_action(:foo).name.should == :foo + end + end +end diff --git a/spec/unit/faces/action_spec.rb b/spec/unit/faces/action_spec.rb new file mode 100755 index 000000000..b6fe87a63 --- /dev/null +++ b/spec/unit/faces/action_spec.rb @@ -0,0 +1,173 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/string/action' + +describe Puppet::String::Action do + describe "when validating the action name" do + [nil, '', 'foo bar', '-foobar'].each do |input| + it "should treat #{input.inspect} as an invalid name" do + expect { Puppet::String::Action.new(nil, input) }. + should raise_error(/is an invalid action name/) + end + end + end + + describe "when invoking" do + it "should be able to call other actions on the same object" do + string = Puppet::String.new(:my_string, '0.0.1') do + action(:foo) do + when_invoked { 25 } + end + + action(:bar) do + when_invoked { "the value of foo is '#{foo}'" } + end + end + string.foo.should == 25 + string.bar.should == "the value of foo is '25'" + end + + # bar is a class action calling a class action + # quux is a class action calling an instance action + # baz is an instance action calling a class action + # qux is an instance action calling an instance action + it "should be able to call other actions on the same object when defined on a class" do + class Puppet::String::MyStringBaseClass < Puppet::String + action(:foo) do + when_invoked { 25 } + end + + action(:bar) do + when_invoked { "the value of foo is '#{foo}'" } + end + + action(:quux) do + when_invoked { "qux told me #{qux}" } + end + end + + string = Puppet::String::MyStringBaseClass.new(:my_inherited_string, '0.0.1') do + action(:baz) do + when_invoked { "the value of foo in baz is '#{foo}'" } + end + + action(:qux) do + when_invoked { baz } + end + end + string.foo.should == 25 + string.bar.should == "the value of foo is '25'" + string.quux.should == "qux told me the value of foo in baz is '25'" + string.baz.should == "the value of foo in baz is '25'" + string.qux.should == "the value of foo in baz is '25'" + end + + context "when calling the Ruby API" do + let :string do + Puppet::String.new(:ruby_api, '1.0.0') do + action :bar do + when_invoked do |options| + options + end + end + end + end + + it "should work when no options are supplied" do + options = string.bar + options.should == {} + end + + it "should work when options are supplied" do + options = string.bar :bar => "beer" + options.should == { :bar => "beer" } + end + end + end + + describe "with action-level options" do + it "should support options with an empty block" do + string = Puppet::String.new(:action_level_options, '0.0.1') do + action :foo do + option "--bar" do + # this line left deliberately blank + end + end + end + + string.should_not be_option :bar + string.get_action(:foo).should be_option :bar + end + + it "should return only action level options when there are no string options" do + string = Puppet::String.new(:action_level_options, '0.0.1') do + action :foo do option "--bar" end + end + + string.get_action(:foo).options.should =~ [:bar] + end + + describe "with both string and action options" do + let :string do + Puppet::String.new(:action_level_options, '0.0.1') do + action :foo do option "--bar" end + action :baz do option "--bim" end + option "--quux" + end + end + + it "should return combined string and action options" do + string.get_action(:foo).options.should =~ [:bar, :quux] + end + + it "should fetch options that the string inherited" do + parent = Class.new(Puppet::String) + parent.option "--foo" + child = parent.new(:inherited_options, '0.0.1') do + option "--bar" + action :action do option "--baz" end + end + + action = child.get_action(:action) + action.should be + + [:baz, :bar, :foo].each do |name| + action.get_option(name).should be_an_instance_of Puppet::String::Option + end + end + + it "should get an action option when asked" do + string.get_action(:foo).get_option(:bar). + should be_an_instance_of Puppet::String::Option + end + + it "should get a string option when asked" do + string.get_action(:foo).get_option(:quux). + should be_an_instance_of Puppet::String::Option + end + + it "should return options only for this action" do + string.get_action(:baz).options.should =~ [:bim, :quux] + end + end + + it_should_behave_like "things that declare options" do + def add_options_to(&block) + string = Puppet::String.new(:with_options, '0.0.1') do + action(:foo, &block) + end + string.get_action(:foo) + end + end + + it "should fail when a string option duplicates an action option" do + expect { + Puppet::String.new(:action_level_options, '0.0.1') do + option "--foo" + action :bar do option "--foo" end + end + }.should raise_error ArgumentError, /Option foo conflicts with existing option foo/i + end + end +end diff --git a/spec/unit/faces/catalog_spec.rb b/spec/unit/faces/catalog_spec.rb new file mode 100755 index 000000000..70dadd54b --- /dev/null +++ b/spec/unit/faces/catalog_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:catalog, '0.0.1'] do +end diff --git a/spec/unit/faces/certificate_request_spec.rb b/spec/unit/faces/certificate_request_spec.rb new file mode 100755 index 000000000..d0a8288b3 --- /dev/null +++ b/spec/unit/faces/certificate_request_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:certificate_request, '0.0.1'] do +end diff --git a/spec/unit/faces/certificate_revocation_list_spec.rb b/spec/unit/faces/certificate_revocation_list_spec.rb new file mode 100755 index 000000000..9168fb8ce --- /dev/null +++ b/spec/unit/faces/certificate_revocation_list_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:certificate_revocation_list, '0.0.1'] do +end diff --git a/spec/unit/faces/certificate_spec.rb b/spec/unit/faces/certificate_spec.rb new file mode 100755 index 000000000..9fdc5aab8 --- /dev/null +++ b/spec/unit/faces/certificate_spec.rb @@ -0,0 +1,14 @@ +require 'puppet/ssl/host' + +describe Puppet::String[:certificate, '0.0.1'] do + it "should have a ca-location option" do + subject.should be_option :ca_location + end + + it "should set the ca location when invoked" do + pending "#6983: This is broken in the actual string..." + Puppet::SSL::Host.expects(:ca_location=).with(:foo) + Puppet::SSL::Host.indirection.expects(:save) + subject.sign :ca_location => :foo + end +end diff --git a/spec/unit/faces/config_spec.rb b/spec/unit/faces/config_spec.rb new file mode 100755 index 000000000..9919fef87 --- /dev/null +++ b/spec/unit/faces/config_spec.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:config, '0.0.1'] do + it "should use Settings#print_config_options when asked to print" do + Puppet.settings.stubs(:puts) + Puppet.settings.expects(:print_config_options) + subject.print + end + + it "should set 'configprint' to all desired values and call print_config_options when a specific value is provided" do + Puppet.settings.stubs(:puts) + Puppet.settings.expects(:print_config_options) + subject.print("libdir", "ssldir") + Puppet.settings[:configprint].should == "libdir,ssldir" + end + + it "should always return nil" do + Puppet.settings.stubs(:puts) + Puppet.settings.expects(:print_config_options) + subject.print("libdir").should be_nil + end +end diff --git a/spec/unit/faces/configurer_spec.rb b/spec/unit/faces/configurer_spec.rb new file mode 100755 index 000000000..1b428ef20 --- /dev/null +++ b/spec/unit/faces/configurer_spec.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/indirector/catalog/rest' +require 'tempfile' + +describe Puppet::String[:configurer, '0.0.1'] do + describe "#synchronize" do + it "should retrieve and apply a catalog and return a report" do + dirname = Dir.mktmpdir("puppetdir") + Puppet[:vardir] = dirname + Puppet[:confdir] = dirname + @catalog = Puppet::Resource::Catalog.new + @file = Puppet::Resource.new(:file, File.join(dirname, "tmp_dir_resource"), :parameters => {:ensure => :present}) + @catalog.add_resource(@file) + Puppet::Resource::Catalog::Rest.any_instance.stubs(:find).returns(@catalog) + + report = subject.synchronize("foo") + + report.kind.should == "apply" + report.status.should == "changed" + end + end +end diff --git a/spec/unit/faces/faces_collection_spec.rb b/spec/unit/faces/faces_collection_spec.rb new file mode 100755 index 000000000..fab647da0 --- /dev/null +++ b/spec/unit/faces/faces_collection_spec.rb @@ -0,0 +1,184 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'tmpdir' + +describe Puppet::String::StringCollection do + # To avoid cross-pollution we have to save and restore both the hash + # containing all the string data, and the array used by require. Restoring + # both means that we don't leak side-effects across the code. --daniel 2011-04-06 + before :each do + @original_strings = subject.instance_variable_get("@strings").dup + @original_required = $".dup + subject.instance_variable_get("@strings").clear + end + + after :each do + subject.instance_variable_set("@strings", @original_strings) + $".clear ; @original_required.each do |item| $" << item end + end + + describe "::strings" do + it "REVISIT: should have some tests here, if we describe it" + end + + describe "::validate_version" do + it 'should permit three number versions' do + subject.validate_version('10.10.10').should == true + end + + it 'should permit versions with appended descriptions' do + subject.validate_version('10.10.10beta').should == true + end + + it 'should not permit versions with more than three numbers' do + subject.validate_version('1.2.3.4').should == false + end + + it 'should not permit versions with only two numbers' do + subject.validate_version('10.10').should == false + end + + it 'should not permit versions with only one number' do + subject.validate_version('123').should == false + end + + it 'should not permit versions with text in any position but at the end' do + subject.validate_version('v1.1.1').should == false + end + end + + describe "::[]" do + before :each do + subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 + end + + before :each do + @dir = Dir.mktmpdir + @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') + $LOAD_PATH.push(@dir) + end + + after :each do + FileUtils.remove_entry_secure @dir + $LOAD_PATH.pop + end + + it "should return the string with the given name" do + subject["foo", '0.0.1'].should == 10 + end + + it "should attempt to load the string if it isn't found" do + subject.expects(:require).with('puppet/string/bar') + subject["bar", '0.0.1'] + end + + it "should attempt to load the default string for the specified version :current" do + subject.expects(:require).never # except... + subject.expects(:require).with('puppet/string/fozzie') + subject['fozzie', :current] + end + end + + describe "::string?" do + before :each do + subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 + end + + it "should return true if the string specified is registered" do + subject.string?("foo", '0.0.1').should == true + end + + it "should attempt to require the string if it is not registered" do + subject.expects(:require).with do |file| + subject.instance_variable_get("@strings")[:bar]['0.0.1'] = true + file == 'puppet/string/bar' + end + subject.string?("bar", '0.0.1').should == true + end + + it "should return true if requiring the string registered it" do + subject.stubs(:require).with do + subject.instance_variable_get("@strings")[:bar]['0.0.1'] = 20 + end + end + + it "should return false if the string is not registered" do + subject.stubs(:require).returns(true) + subject.string?("bar", '0.0.1').should be_false + end + + it "should return false if the string file itself is missing" do + subject.stubs(:require). + raises(LoadError, 'no such file to load -- puppet/string/bar') + subject.string?("bar", '0.0.1').should be_false + end + + it "should register the version loaded by `:current` as `:current`" do + subject.expects(:require).with do |file| + subject.instance_variable_get("@strings")[:huzzah]['2.0.1'] = :huzzah_string + file == 'puppet/string/huzzah' + end + subject.string?("huzzah", :current) + subject.instance_variable_get("@strings")[:huzzah][:current].should == :huzzah_string + end + + context "with something on disk" do + before :each do + write_scratch_string :huzzah do |fh| + fh.puts < {'0.0.1' => string}} + end + end + + describe "::underscorize" do + faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] + valid = { + "Foo" => :foo, + :Foo => :foo, + "foo_bar" => :foo_bar, + :foo_bar => :foo_bar, + "foo-bar" => :foo_bar, + :"foo-bar" => :foo_bar, + } + + valid.each do |input, expect| + it "should map #{input.inspect} to #{expect.inspect}" do + result = subject.underscorize(input) + result.should == expect + end + end + + faulty.each do |input| + it "should fail when presented with #{input.inspect} (#{input.class})" do + expect { subject.underscorize(input) }. + should raise_error ArgumentError, /not a valid string name/ + end + end + end +end diff --git a/spec/unit/faces/facts_spec.rb b/spec/unit/faces/facts_spec.rb new file mode 100755 index 000000000..9b7024724 --- /dev/null +++ b/spec/unit/faces/facts_spec.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:facts, '0.0.1'] do + it "should define an 'upload' fact" do + subject.should be_action(:upload) + end + + it "should set its default format to :yaml" do + subject.default_format.should == :yaml + end + + describe "when uploading" do + it "should set the terminus_class to :facter" + + it "should set the cach_eclass to :rest" + + it "should find the current certname" + end +end diff --git a/spec/unit/faces/file_spec.rb b/spec/unit/faces/file_spec.rb new file mode 100755 index 000000000..f1b9302be --- /dev/null +++ b/spec/unit/faces/file_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:file, '0.0.1'] do +end diff --git a/spec/unit/faces/indirector_spec.rb b/spec/unit/faces/indirector_spec.rb new file mode 100755 index 000000000..cb85eaa05 --- /dev/null +++ b/spec/unit/faces/indirector_spec.rb @@ -0,0 +1,56 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/string/indirector' + +describe Puppet::String::Indirector do + subject do + instance = Puppet::String::Indirector.new(:test, '0.0.1') + indirection = stub('indirection', + :name => :stub_indirection, + :reset_terminus_class => nil) + instance.stubs(:indirection).returns indirection + instance + end + + it "should be able to return a list of indirections" do + Puppet::String::Indirector.indirections.should be_include("catalog") + end + + it "should be able to return a list of terminuses for a given indirection" do + Puppet::String::Indirector.terminus_classes(:catalog).should be_include("compiler") + end + + describe "as an instance" do + it "should be able to determine its indirection" do + # Loading actions here an get, um, complicated + Puppet::String.stubs(:load_actions) + Puppet::String::Indirector.new(:catalog, '0.0.1').indirection.should equal(Puppet::Resource::Catalog.indirection) + end + end + + [:find, :search, :save, :destroy].each do |method| + it "should define a '#{method}' action" do + Puppet::String::Indirector.should be_action(method) + end + + it "should call the indirection method when the '#{method}' action is invoked" do + subject.indirection.expects(method).with(:test, "myargs") + subject.send(method, :test, "myargs") + end + end + + it "should be able to override its indirection name" do + subject.set_indirection_name :foo + subject.indirection_name.should == :foo + end + + it "should be able to set its terminus class" do + subject.indirection.expects(:terminus_class=).with(:myterm) + subject.set_terminus(:myterm) + end + + it "should define a class-level 'info' action" do + Puppet::String::Indirector.should be_action(:info) + end +end diff --git a/spec/unit/faces/key_spec.rb b/spec/unit/faces/key_spec.rb new file mode 100755 index 000000000..fe3532d23 --- /dev/null +++ b/spec/unit/faces/key_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:key, '0.0.1'] do +end diff --git a/spec/unit/faces/node_spec.rb b/spec/unit/faces/node_spec.rb new file mode 100755 index 000000000..520cc0f5e --- /dev/null +++ b/spec/unit/faces/node_spec.rb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:node, '0.0.1'] do + it "should set its default format to :yaml" do + subject.default_format.should == :yaml + end +end diff --git a/spec/unit/faces/option_builder_spec.rb b/spec/unit/faces/option_builder_spec.rb new file mode 100644 index 000000000..9e913c29c --- /dev/null +++ b/spec/unit/faces/option_builder_spec.rb @@ -0,0 +1,29 @@ +require 'puppet/string/option_builder' + +describe Puppet::String::OptionBuilder do + let :string do Puppet::String.new(:option_builder_testing, '0.0.1') end + + it "should be able to construct an option without a block" do + Puppet::String::OptionBuilder.build(string, "--foo"). + should be_an_instance_of Puppet::String::Option + end + + describe "when using the DSL block" do + it "should work with an empty block" do + option = Puppet::String::OptionBuilder.build(string, "--foo") do + # This block deliberately left blank. + end + + option.should be_an_instance_of Puppet::String::Option + end + + it "should support documentation declarations" do + text = "this is the description" + option = Puppet::String::OptionBuilder.build(string, "--foo") do + desc text + end + option.should be_an_instance_of Puppet::String::Option + option.desc.should == text + end + end +end diff --git a/spec/unit/faces/option_spec.rb b/spec/unit/faces/option_spec.rb new file mode 100644 index 000000000..f4f62ec37 --- /dev/null +++ b/spec/unit/faces/option_spec.rb @@ -0,0 +1,75 @@ +require 'puppet/string/option' + +describe Puppet::String::Option do + let :string do Puppet::String.new(:option_testing, '0.0.1') end + + describe "#optparse_to_name" do + ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix| + { "--foo" => :foo, "-f" => :f }.each do |base, expect| + input = base + postfix + it "should map #{input.inspect} to #{expect.inspect}" do + option = Puppet::String::Option.new(string, input) + option.name.should == expect + end + end + end + + [:foo, 12, nil, {}, []].each do |input| + it "should fail sensible when given #{input.inspect}" do + expect { Puppet::String::Option.new(string, input) }. + should raise_error ArgumentError, /is not valid for an option argument/ + end + end + + ["-foo", "-foo=BAR", "-foo BAR"].each do |input| + it "should fail with a single dash for long option #{input.inspect}" do + expect { Puppet::String::Option.new(string, input) }. + should raise_error ArgumentError, /long options need two dashes \(--\)/ + end + end + end + + it "requires a string when created" do + expect { Puppet::String::Option.new }. + should raise_error ArgumentError, /wrong number of arguments/ + end + + it "also requires some declaration arguments when created" do + expect { Puppet::String::Option.new(string) }. + should raise_error ArgumentError, /No option declarations found/ + end + + it "should infer the name from an optparse string" do + option = Puppet::String::Option.new(string, "--foo") + option.name.should == :foo + end + + it "should infer the name when multiple optparse strings are given" do + option = Puppet::String::Option.new(string, "--foo", "-f") + option.name.should == :foo + end + + it "should prefer the first long option name over a short option name" do + option = Puppet::String::Option.new(string, "-f", "--foo") + option.name.should == :foo + end + + it "should create an instance when given a string and name" do + Puppet::String::Option.new(string, "--foo"). + should be_instance_of Puppet::String::Option + end + + describe "#to_s" do + it "should transform a symbol into a string" do + option = Puppet::String::Option.new(string, "--foo") + option.name.should == :foo + option.to_s.should == "foo" + end + + it "should use - rather than _ to separate words in strings but not symbols" do + option = Puppet::String::Option.new(string, "--foo-bar") + option.name.should == :foo_bar + option.to_s.should == "foo-bar" + end + end +end diff --git a/spec/unit/faces/report_spec.rb b/spec/unit/faces/report_spec.rb new file mode 100755 index 000000000..2e206dd06 --- /dev/null +++ b/spec/unit/faces/report_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:report, '0.0.1'] do +end diff --git a/spec/unit/faces/resource_spec.rb b/spec/unit/faces/resource_spec.rb new file mode 100755 index 000000000..f4e618616 --- /dev/null +++ b/spec/unit/faces/resource_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:resource, '0.0.1'] do +end diff --git a/spec/unit/faces/resource_type_spec.rb b/spec/unit/faces/resource_type_spec.rb new file mode 100755 index 000000000..d12ec00ce --- /dev/null +++ b/spec/unit/faces/resource_type_spec.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +describe Puppet::String[:resource_type, '0.0.1'] do +end diff --git a/spec/unit/faces_spec.rb b/spec/unit/faces_spec.rb new file mode 100755 index 000000000..9b7cd887c --- /dev/null +++ b/spec/unit/faces_spec.rb @@ -0,0 +1,144 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb') + +describe Puppet::String do + before :all do + @strings = Puppet::String::StringCollection.instance_variable_get("@strings").dup + end + + before :each do + Puppet::String::StringCollection.instance_variable_get("@strings").clear + end + + after :all do + Puppet::String::StringCollection.instance_variable_set("@strings", @strings) + end + + describe "#define" do + it "should register the string" do + string = Puppet::String.define(:string_test_register, '0.0.1') + string.should == Puppet::String[:string_test_register, '0.0.1'] + end + + it "should load actions" do + Puppet::String.any_instance.expects(:load_actions) + Puppet::String.define(:string_test_load_actions, '0.0.1') + end + + it "should require a version number" do + proc { Puppet::String.define(:no_version) }.should raise_error(ArgumentError) + end + end + + describe "#initialize" do + it "should require a version number" do + proc { Puppet::String.new(:no_version) }.should raise_error(ArgumentError) + end + + it "should require a valid version number" do + proc { Puppet::String.new(:bad_version, 'Rasins') }.should raise_error(ArgumentError) + end + + it "should instance-eval any provided block" do + face = Puppet::String.new(:string_test_block, '0.0.1') do + action(:something) do + when_invoked { "foo" } + end + end + + face.something.should == "foo" + end + end + + it "should have a name" do + Puppet::String.new(:me, '0.0.1').name.should == :me + end + + it "should stringify with its own name" do + Puppet::String.new(:me, '0.0.1').to_s.should =~ /\bme\b/ + end + + it "should allow overriding of the default format" do + face = Puppet::String.new(:me, '0.0.1') + face.set_default_format :foo + face.default_format.should == :foo + end + + it "should default to :pson for its format" do + Puppet::String.new(:me, '0.0.1').default_format.should == :pson + end + + # Why? + it "should create a class-level autoloader" do + Puppet::String.autoloader.should be_instance_of(Puppet::Util::Autoload) + end + + it "should try to require strings that are not known" do + Puppet::String::StringCollection.expects(:require).with "puppet/string/foo" + Puppet::String[:foo, '0.0.1'] + end + + it "should be able to load all actions in all search paths" + + + it_should_behave_like "things that declare options" do + def add_options_to(&block) + Puppet::String.new(:with_options, '0.0.1', &block) + end + end + + describe "with string-level options" do + it "should not return any action-level options" do + string = Puppet::String.new(:with_options, '0.0.1') do + option "--foo" + option "--bar" + action :baz do + option "--quux" + end + end + string.options.should =~ [:foo, :bar] + end + + it "should fail when a string option duplicates an action option" do + expect { + Puppet::String.new(:action_level_options, '0.0.1') do + action :bar do option "--foo" end + option "--foo" + end + }.should raise_error ArgumentError, /Option foo conflicts with existing option foo on/i + end + + it "should work when two actions have the same option" do + string = Puppet::String.new(:with_options, '0.0.1') do + action :foo do option "--quux" end + action :bar do option "--quux" end + end + + string.get_action(:foo).options.should =~ [:quux] + string.get_action(:bar).options.should =~ [:quux] + end + end + + describe "with inherited options" do + let :string do + parent = Class.new(Puppet::String) + parent.option("--inherited") + string = parent.new(:example, '0.2.1') + string.option("--local") + string + end + + describe "#options" do + it "should list inherited options" do + string.options.should =~ [:inherited, :local] + end + end + + describe "#get_option" do + it "should return an inherited option object" do + string.get_option(:inherited).should be_an_instance_of Puppet::String::Option + end + end + end +end diff --git a/spec/unit/string/action_builder_spec.rb b/spec/unit/string/action_builder_spec.rb deleted file mode 100755 index 5f6f1c08f..000000000 --- a/spec/unit/string/action_builder_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/string/action_builder' - -describe Puppet::String::ActionBuilder do - describe "::build" do - it "should build an action" do - action = Puppet::String::ActionBuilder.build(nil, :foo) do - end - action.should be_a(Puppet::String::Action) - action.name.should == :foo - end - - it "should define a method on the string which invokes the action" do - string = Puppet::String.new(:action_builder_test_string, '0.0.1') - action = Puppet::String::ActionBuilder.build(string, :foo) do - when_invoked do - "invoked the method" - end - end - - string.foo.should == "invoked the method" - end - - it "should require a block" do - lambda { Puppet::String::ActionBuilder.build(nil, :foo) }. - should raise_error("Action :foo must specify a block") - end - - describe "when handling options" do - let :string do Puppet::String.new(:option_handling, '0.0.1') end - - it "should have a #option DSL function" do - method = nil - Puppet::String::ActionBuilder.build(string, :foo) do - method = self.method(:option) - end - method.should be - end - - it "should define an option without a block" do - action = Puppet::String::ActionBuilder.build(string, :foo) do - option "--bar" - end - action.should be_option :bar - end - - it "should accept an empty block" do - action = Puppet::String::ActionBuilder.build(string, :foo) do - option "--bar" do - # This space left deliberately blank. - end - end - action.should be_option :bar - end - end - end -end diff --git a/spec/unit/string/action_manager_spec.rb b/spec/unit/string/action_manager_spec.rb deleted file mode 100755 index b8baf80a5..000000000 --- a/spec/unit/string/action_manager_spec.rb +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -# This is entirely an internal class for String, so we have to load it instead of our class. -require 'puppet/string' - -class ActionManagerTester - include Puppet::String::ActionManager -end - -describe Puppet::String::ActionManager do - subject { ActionManagerTester.new } - - describe "when included in a class" do - it "should be able to define an action" do - subject.action(:foo) do - when_invoked { "something "} - end - end - - it "should be able to define a 'script' style action" do - subject.script :bar do - "a bar is where beer is found" - end - end - - it "should be able to list defined actions" do - subject.action(:foo) do - when_invoked { "something" } - end - subject.action(:bar) do - when_invoked { "something" } - end - - subject.actions.should =~ [:foo, :bar] - end - - it "should list 'script' actions" do - subject.script :foo do "foo" end - subject.actions.should =~ [:foo] - end - - it "should list both script and normal actions" do - subject.action :foo do - when_invoked do "foo" end - end - subject.script :bar do "a bar is where beer is found" end - - subject.actions.should =~ [:foo, :bar] - end - - it "should be able to indicate when an action is defined" do - subject.action(:foo) do - when_invoked { "something" } - end - - subject.should be_action(:foo) - end - - it "should indicate an action is defined for script actions" do - subject.script :foo do "foo" end - subject.should be_action :foo - end - - it "should correctly treat action names specified as strings" do - subject.action(:foo) do - when_invoked { "something" } - end - - subject.should be_action("foo") - end - end - - describe "when used to extend a class" do - subject { Class.new.extend(Puppet::String::ActionManager) } - - it "should be able to define an action" do - subject.action(:foo) do - when_invoked { "something "} - end - end - - it "should be able to list defined actions" do - subject.action(:foo) do - when_invoked { "something" } - end - subject.action(:bar) do - when_invoked { "something" } - end - - subject.actions.should include(:bar) - subject.actions.should include(:foo) - end - - it "should be able to indicate when an action is defined" do - subject.action(:foo) { "something" } - subject.should be_action(:foo) - end - end - - describe "when used both at the class and instance level" do - before do - @klass = Class.new do - include Puppet::String::ActionManager - extend Puppet::String::ActionManager - end - @instance = @klass.new - end - - it "should be able to define an action at the class level" do - @klass.action(:foo) do - when_invoked { "something "} - end - end - - it "should create an instance method when an action is defined at the class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.foo.should == "something" - end - - it "should be able to define an action at the instance level" do - @instance.action(:foo) do - when_invoked { "something "} - end - end - - it "should create an instance method when an action is defined at the instance level" do - @instance.action(:foo) do - when_invoked { "something" } - end - @instance.foo.should == "something" - end - - it "should be able to list actions defined at the class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @klass.action(:bar) do - when_invoked { "something" } - end - - @klass.actions.should include(:bar) - @klass.actions.should include(:foo) - end - - it "should be able to list actions defined at the instance level" do - @instance.action(:foo) do - when_invoked { "something" } - end - @instance.action(:bar) do - when_invoked { "something" } - end - - @instance.actions.should include(:bar) - @instance.actions.should include(:foo) - end - - it "should be able to list actions defined at both instance and class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.action(:bar) do - when_invoked { "something" } - end - - @instance.actions.should include(:bar) - @instance.actions.should include(:foo) - end - - it "should be able to indicate when an action is defined at the class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.should be_action(:foo) - end - - it "should be able to indicate when an action is defined at the instance level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.should be_action(:foo) - end - - it "should list actions defined in superclasses" do - @subclass = Class.new(@klass) - @instance = @subclass.new - - @klass.action(:parent) do - when_invoked { "a" } - end - @subclass.action(:sub) do - when_invoked { "a" } - end - @instance.action(:instance) do - when_invoked { "a" } - end - - @instance.should be_action(:parent) - @instance.should be_action(:sub) - @instance.should be_action(:instance) - end - - it "should create an instance method when an action is defined in a superclass" do - @subclass = Class.new(@klass) - @instance = @subclass.new - - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.foo.should == "something" - end - end - - describe "#get_action" do - let :parent_class do - parent_class = Class.new(Puppet::String) - parent_class.action(:foo) {} - parent_class - end - - it "should check that we can find inherited actions when we are a class" do - Class.new(parent_class).get_action(:foo).name.should == :foo - end - - it "should check that we can find inherited actions when we are an instance" do - instance = parent_class.new(:foo, '0.0.0') - instance.get_action(:foo).name.should == :foo - end - end -end diff --git a/spec/unit/string/action_spec.rb b/spec/unit/string/action_spec.rb deleted file mode 100755 index b6fe87a63..000000000 --- a/spec/unit/string/action_spec.rb +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/string/action' - -describe Puppet::String::Action do - describe "when validating the action name" do - [nil, '', 'foo bar', '-foobar'].each do |input| - it "should treat #{input.inspect} as an invalid name" do - expect { Puppet::String::Action.new(nil, input) }. - should raise_error(/is an invalid action name/) - end - end - end - - describe "when invoking" do - it "should be able to call other actions on the same object" do - string = Puppet::String.new(:my_string, '0.0.1') do - action(:foo) do - when_invoked { 25 } - end - - action(:bar) do - when_invoked { "the value of foo is '#{foo}'" } - end - end - string.foo.should == 25 - string.bar.should == "the value of foo is '25'" - end - - # bar is a class action calling a class action - # quux is a class action calling an instance action - # baz is an instance action calling a class action - # qux is an instance action calling an instance action - it "should be able to call other actions on the same object when defined on a class" do - class Puppet::String::MyStringBaseClass < Puppet::String - action(:foo) do - when_invoked { 25 } - end - - action(:bar) do - when_invoked { "the value of foo is '#{foo}'" } - end - - action(:quux) do - when_invoked { "qux told me #{qux}" } - end - end - - string = Puppet::String::MyStringBaseClass.new(:my_inherited_string, '0.0.1') do - action(:baz) do - when_invoked { "the value of foo in baz is '#{foo}'" } - end - - action(:qux) do - when_invoked { baz } - end - end - string.foo.should == 25 - string.bar.should == "the value of foo is '25'" - string.quux.should == "qux told me the value of foo in baz is '25'" - string.baz.should == "the value of foo in baz is '25'" - string.qux.should == "the value of foo in baz is '25'" - end - - context "when calling the Ruby API" do - let :string do - Puppet::String.new(:ruby_api, '1.0.0') do - action :bar do - when_invoked do |options| - options - end - end - end - end - - it "should work when no options are supplied" do - options = string.bar - options.should == {} - end - - it "should work when options are supplied" do - options = string.bar :bar => "beer" - options.should == { :bar => "beer" } - end - end - end - - describe "with action-level options" do - it "should support options with an empty block" do - string = Puppet::String.new(:action_level_options, '0.0.1') do - action :foo do - option "--bar" do - # this line left deliberately blank - end - end - end - - string.should_not be_option :bar - string.get_action(:foo).should be_option :bar - end - - it "should return only action level options when there are no string options" do - string = Puppet::String.new(:action_level_options, '0.0.1') do - action :foo do option "--bar" end - end - - string.get_action(:foo).options.should =~ [:bar] - end - - describe "with both string and action options" do - let :string do - Puppet::String.new(:action_level_options, '0.0.1') do - action :foo do option "--bar" end - action :baz do option "--bim" end - option "--quux" - end - end - - it "should return combined string and action options" do - string.get_action(:foo).options.should =~ [:bar, :quux] - end - - it "should fetch options that the string inherited" do - parent = Class.new(Puppet::String) - parent.option "--foo" - child = parent.new(:inherited_options, '0.0.1') do - option "--bar" - action :action do option "--baz" end - end - - action = child.get_action(:action) - action.should be - - [:baz, :bar, :foo].each do |name| - action.get_option(name).should be_an_instance_of Puppet::String::Option - end - end - - it "should get an action option when asked" do - string.get_action(:foo).get_option(:bar). - should be_an_instance_of Puppet::String::Option - end - - it "should get a string option when asked" do - string.get_action(:foo).get_option(:quux). - should be_an_instance_of Puppet::String::Option - end - - it "should return options only for this action" do - string.get_action(:baz).options.should =~ [:bim, :quux] - end - end - - it_should_behave_like "things that declare options" do - def add_options_to(&block) - string = Puppet::String.new(:with_options, '0.0.1') do - action(:foo, &block) - end - string.get_action(:foo) - end - end - - it "should fail when a string option duplicates an action option" do - expect { - Puppet::String.new(:action_level_options, '0.0.1') do - option "--foo" - action :bar do option "--foo" end - end - }.should raise_error ArgumentError, /Option foo conflicts with existing option foo/i - end - end -end diff --git a/spec/unit/string/catalog_spec.rb b/spec/unit/string/catalog_spec.rb deleted file mode 100755 index 70dadd54b..000000000 --- a/spec/unit/string/catalog_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:catalog, '0.0.1'] do -end diff --git a/spec/unit/string/certificate_request_spec.rb b/spec/unit/string/certificate_request_spec.rb deleted file mode 100755 index d0a8288b3..000000000 --- a/spec/unit/string/certificate_request_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:certificate_request, '0.0.1'] do -end diff --git a/spec/unit/string/certificate_revocation_list_spec.rb b/spec/unit/string/certificate_revocation_list_spec.rb deleted file mode 100755 index 9168fb8ce..000000000 --- a/spec/unit/string/certificate_revocation_list_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:certificate_revocation_list, '0.0.1'] do -end diff --git a/spec/unit/string/certificate_spec.rb b/spec/unit/string/certificate_spec.rb deleted file mode 100755 index 9fdc5aab8..000000000 --- a/spec/unit/string/certificate_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'puppet/ssl/host' - -describe Puppet::String[:certificate, '0.0.1'] do - it "should have a ca-location option" do - subject.should be_option :ca_location - end - - it "should set the ca location when invoked" do - pending "#6983: This is broken in the actual string..." - Puppet::SSL::Host.expects(:ca_location=).with(:foo) - Puppet::SSL::Host.indirection.expects(:save) - subject.sign :ca_location => :foo - end -end diff --git a/spec/unit/string/config_spec.rb b/spec/unit/string/config_spec.rb deleted file mode 100755 index 9919fef87..000000000 --- a/spec/unit/string/config_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:config, '0.0.1'] do - it "should use Settings#print_config_options when asked to print" do - Puppet.settings.stubs(:puts) - Puppet.settings.expects(:print_config_options) - subject.print - end - - it "should set 'configprint' to all desired values and call print_config_options when a specific value is provided" do - Puppet.settings.stubs(:puts) - Puppet.settings.expects(:print_config_options) - subject.print("libdir", "ssldir") - Puppet.settings[:configprint].should == "libdir,ssldir" - end - - it "should always return nil" do - Puppet.settings.stubs(:puts) - Puppet.settings.expects(:print_config_options) - subject.print("libdir").should be_nil - end -end diff --git a/spec/unit/string/configurer_spec.rb b/spec/unit/string/configurer_spec.rb deleted file mode 100755 index 1b428ef20..000000000 --- a/spec/unit/string/configurer_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/indirector/catalog/rest' -require 'tempfile' - -describe Puppet::String[:configurer, '0.0.1'] do - describe "#synchronize" do - it "should retrieve and apply a catalog and return a report" do - dirname = Dir.mktmpdir("puppetdir") - Puppet[:vardir] = dirname - Puppet[:confdir] = dirname - @catalog = Puppet::Resource::Catalog.new - @file = Puppet::Resource.new(:file, File.join(dirname, "tmp_dir_resource"), :parameters => {:ensure => :present}) - @catalog.add_resource(@file) - Puppet::Resource::Catalog::Rest.any_instance.stubs(:find).returns(@catalog) - - report = subject.synchronize("foo") - - report.kind.should == "apply" - report.status.should == "changed" - end - end -end diff --git a/spec/unit/string/facts_spec.rb b/spec/unit/string/facts_spec.rb deleted file mode 100755 index 9b7024724..000000000 --- a/spec/unit/string/facts_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:facts, '0.0.1'] do - it "should define an 'upload' fact" do - subject.should be_action(:upload) - end - - it "should set its default format to :yaml" do - subject.default_format.should == :yaml - end - - describe "when uploading" do - it "should set the terminus_class to :facter" - - it "should set the cach_eclass to :rest" - - it "should find the current certname" - end -end diff --git a/spec/unit/string/file_spec.rb b/spec/unit/string/file_spec.rb deleted file mode 100755 index f1b9302be..000000000 --- a/spec/unit/string/file_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:file, '0.0.1'] do -end diff --git a/spec/unit/string/indirector_spec.rb b/spec/unit/string/indirector_spec.rb deleted file mode 100755 index cb85eaa05..000000000 --- a/spec/unit/string/indirector_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/string/indirector' - -describe Puppet::String::Indirector do - subject do - instance = Puppet::String::Indirector.new(:test, '0.0.1') - indirection = stub('indirection', - :name => :stub_indirection, - :reset_terminus_class => nil) - instance.stubs(:indirection).returns indirection - instance - end - - it "should be able to return a list of indirections" do - Puppet::String::Indirector.indirections.should be_include("catalog") - end - - it "should be able to return a list of terminuses for a given indirection" do - Puppet::String::Indirector.terminus_classes(:catalog).should be_include("compiler") - end - - describe "as an instance" do - it "should be able to determine its indirection" do - # Loading actions here an get, um, complicated - Puppet::String.stubs(:load_actions) - Puppet::String::Indirector.new(:catalog, '0.0.1').indirection.should equal(Puppet::Resource::Catalog.indirection) - end - end - - [:find, :search, :save, :destroy].each do |method| - it "should define a '#{method}' action" do - Puppet::String::Indirector.should be_action(method) - end - - it "should call the indirection method when the '#{method}' action is invoked" do - subject.indirection.expects(method).with(:test, "myargs") - subject.send(method, :test, "myargs") - end - end - - it "should be able to override its indirection name" do - subject.set_indirection_name :foo - subject.indirection_name.should == :foo - end - - it "should be able to set its terminus class" do - subject.indirection.expects(:terminus_class=).with(:myterm) - subject.set_terminus(:myterm) - end - - it "should define a class-level 'info' action" do - Puppet::String::Indirector.should be_action(:info) - end -end diff --git a/spec/unit/string/key_spec.rb b/spec/unit/string/key_spec.rb deleted file mode 100755 index fe3532d23..000000000 --- a/spec/unit/string/key_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:key, '0.0.1'] do -end diff --git a/spec/unit/string/node_spec.rb b/spec/unit/string/node_spec.rb deleted file mode 100755 index 520cc0f5e..000000000 --- a/spec/unit/string/node_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:node, '0.0.1'] do - it "should set its default format to :yaml" do - subject.default_format.should == :yaml - end -end diff --git a/spec/unit/string/option_builder_spec.rb b/spec/unit/string/option_builder_spec.rb deleted file mode 100644 index 9e913c29c..000000000 --- a/spec/unit/string/option_builder_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'puppet/string/option_builder' - -describe Puppet::String::OptionBuilder do - let :string do Puppet::String.new(:option_builder_testing, '0.0.1') end - - it "should be able to construct an option without a block" do - Puppet::String::OptionBuilder.build(string, "--foo"). - should be_an_instance_of Puppet::String::Option - end - - describe "when using the DSL block" do - it "should work with an empty block" do - option = Puppet::String::OptionBuilder.build(string, "--foo") do - # This block deliberately left blank. - end - - option.should be_an_instance_of Puppet::String::Option - end - - it "should support documentation declarations" do - text = "this is the description" - option = Puppet::String::OptionBuilder.build(string, "--foo") do - desc text - end - option.should be_an_instance_of Puppet::String::Option - option.desc.should == text - end - end -end diff --git a/spec/unit/string/option_spec.rb b/spec/unit/string/option_spec.rb deleted file mode 100644 index f4f62ec37..000000000 --- a/spec/unit/string/option_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'puppet/string/option' - -describe Puppet::String::Option do - let :string do Puppet::String.new(:option_testing, '0.0.1') end - - describe "#optparse_to_name" do - ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix| - { "--foo" => :foo, "-f" => :f }.each do |base, expect| - input = base + postfix - it "should map #{input.inspect} to #{expect.inspect}" do - option = Puppet::String::Option.new(string, input) - option.name.should == expect - end - end - end - - [:foo, 12, nil, {}, []].each do |input| - it "should fail sensible when given #{input.inspect}" do - expect { Puppet::String::Option.new(string, input) }. - should raise_error ArgumentError, /is not valid for an option argument/ - end - end - - ["-foo", "-foo=BAR", "-foo BAR"].each do |input| - it "should fail with a single dash for long option #{input.inspect}" do - expect { Puppet::String::Option.new(string, input) }. - should raise_error ArgumentError, /long options need two dashes \(--\)/ - end - end - end - - it "requires a string when created" do - expect { Puppet::String::Option.new }. - should raise_error ArgumentError, /wrong number of arguments/ - end - - it "also requires some declaration arguments when created" do - expect { Puppet::String::Option.new(string) }. - should raise_error ArgumentError, /No option declarations found/ - end - - it "should infer the name from an optparse string" do - option = Puppet::String::Option.new(string, "--foo") - option.name.should == :foo - end - - it "should infer the name when multiple optparse strings are given" do - option = Puppet::String::Option.new(string, "--foo", "-f") - option.name.should == :foo - end - - it "should prefer the first long option name over a short option name" do - option = Puppet::String::Option.new(string, "-f", "--foo") - option.name.should == :foo - end - - it "should create an instance when given a string and name" do - Puppet::String::Option.new(string, "--foo"). - should be_instance_of Puppet::String::Option - end - - describe "#to_s" do - it "should transform a symbol into a string" do - option = Puppet::String::Option.new(string, "--foo") - option.name.should == :foo - option.to_s.should == "foo" - end - - it "should use - rather than _ to separate words in strings but not symbols" do - option = Puppet::String::Option.new(string, "--foo-bar") - option.name.should == :foo_bar - option.to_s.should == "foo-bar" - end - end -end diff --git a/spec/unit/string/report_spec.rb b/spec/unit/string/report_spec.rb deleted file mode 100755 index 2e206dd06..000000000 --- a/spec/unit/string/report_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:report, '0.0.1'] do -end diff --git a/spec/unit/string/resource_spec.rb b/spec/unit/string/resource_spec.rb deleted file mode 100755 index f4e618616..000000000 --- a/spec/unit/string/resource_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:resource, '0.0.1'] do -end diff --git a/spec/unit/string/resource_type_spec.rb b/spec/unit/string/resource_type_spec.rb deleted file mode 100755 index d12ec00ce..000000000 --- a/spec/unit/string/resource_type_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -describe Puppet::String[:resource_type, '0.0.1'] do -end diff --git a/spec/unit/string/string_collection_spec.rb b/spec/unit/string/string_collection_spec.rb deleted file mode 100755 index fab647da0..000000000 --- a/spec/unit/string/string_collection_spec.rb +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'tmpdir' - -describe Puppet::String::StringCollection do - # To avoid cross-pollution we have to save and restore both the hash - # containing all the string data, and the array used by require. Restoring - # both means that we don't leak side-effects across the code. --daniel 2011-04-06 - before :each do - @original_strings = subject.instance_variable_get("@strings").dup - @original_required = $".dup - subject.instance_variable_get("@strings").clear - end - - after :each do - subject.instance_variable_set("@strings", @original_strings) - $".clear ; @original_required.each do |item| $" << item end - end - - describe "::strings" do - it "REVISIT: should have some tests here, if we describe it" - end - - describe "::validate_version" do - it 'should permit three number versions' do - subject.validate_version('10.10.10').should == true - end - - it 'should permit versions with appended descriptions' do - subject.validate_version('10.10.10beta').should == true - end - - it 'should not permit versions with more than three numbers' do - subject.validate_version('1.2.3.4').should == false - end - - it 'should not permit versions with only two numbers' do - subject.validate_version('10.10').should == false - end - - it 'should not permit versions with only one number' do - subject.validate_version('123').should == false - end - - it 'should not permit versions with text in any position but at the end' do - subject.validate_version('v1.1.1').should == false - end - end - - describe "::[]" do - before :each do - subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 - end - - before :each do - @dir = Dir.mktmpdir - @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') - $LOAD_PATH.push(@dir) - end - - after :each do - FileUtils.remove_entry_secure @dir - $LOAD_PATH.pop - end - - it "should return the string with the given name" do - subject["foo", '0.0.1'].should == 10 - end - - it "should attempt to load the string if it isn't found" do - subject.expects(:require).with('puppet/string/bar') - subject["bar", '0.0.1'] - end - - it "should attempt to load the default string for the specified version :current" do - subject.expects(:require).never # except... - subject.expects(:require).with('puppet/string/fozzie') - subject['fozzie', :current] - end - end - - describe "::string?" do - before :each do - subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 - end - - it "should return true if the string specified is registered" do - subject.string?("foo", '0.0.1').should == true - end - - it "should attempt to require the string if it is not registered" do - subject.expects(:require).with do |file| - subject.instance_variable_get("@strings")[:bar]['0.0.1'] = true - file == 'puppet/string/bar' - end - subject.string?("bar", '0.0.1').should == true - end - - it "should return true if requiring the string registered it" do - subject.stubs(:require).with do - subject.instance_variable_get("@strings")[:bar]['0.0.1'] = 20 - end - end - - it "should return false if the string is not registered" do - subject.stubs(:require).returns(true) - subject.string?("bar", '0.0.1').should be_false - end - - it "should return false if the string file itself is missing" do - subject.stubs(:require). - raises(LoadError, 'no such file to load -- puppet/string/bar') - subject.string?("bar", '0.0.1').should be_false - end - - it "should register the version loaded by `:current` as `:current`" do - subject.expects(:require).with do |file| - subject.instance_variable_get("@strings")[:huzzah]['2.0.1'] = :huzzah_string - file == 'puppet/string/huzzah' - end - subject.string?("huzzah", :current) - subject.instance_variable_get("@strings")[:huzzah][:current].should == :huzzah_string - end - - context "with something on disk" do - before :each do - write_scratch_string :huzzah do |fh| - fh.puts < {'0.0.1' => string}} - end - end - - describe "::underscorize" do - faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] - valid = { - "Foo" => :foo, - :Foo => :foo, - "foo_bar" => :foo_bar, - :foo_bar => :foo_bar, - "foo-bar" => :foo_bar, - :"foo-bar" => :foo_bar, - } - - valid.each do |input, expect| - it "should map #{input.inspect} to #{expect.inspect}" do - result = subject.underscorize(input) - result.should == expect - end - end - - faulty.each do |input| - it "should fail when presented with #{input.inspect} (#{input.class})" do - expect { subject.underscorize(input) }. - should raise_error ArgumentError, /not a valid string name/ - end - end - end -end diff --git a/spec/unit/string_spec.rb b/spec/unit/string_spec.rb deleted file mode 100755 index 9b7cd887c..000000000 --- a/spec/unit/string_spec.rb +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb') - -describe Puppet::String do - before :all do - @strings = Puppet::String::StringCollection.instance_variable_get("@strings").dup - end - - before :each do - Puppet::String::StringCollection.instance_variable_get("@strings").clear - end - - after :all do - Puppet::String::StringCollection.instance_variable_set("@strings", @strings) - end - - describe "#define" do - it "should register the string" do - string = Puppet::String.define(:string_test_register, '0.0.1') - string.should == Puppet::String[:string_test_register, '0.0.1'] - end - - it "should load actions" do - Puppet::String.any_instance.expects(:load_actions) - Puppet::String.define(:string_test_load_actions, '0.0.1') - end - - it "should require a version number" do - proc { Puppet::String.define(:no_version) }.should raise_error(ArgumentError) - end - end - - describe "#initialize" do - it "should require a version number" do - proc { Puppet::String.new(:no_version) }.should raise_error(ArgumentError) - end - - it "should require a valid version number" do - proc { Puppet::String.new(:bad_version, 'Rasins') }.should raise_error(ArgumentError) - end - - it "should instance-eval any provided block" do - face = Puppet::String.new(:string_test_block, '0.0.1') do - action(:something) do - when_invoked { "foo" } - end - end - - face.something.should == "foo" - end - end - - it "should have a name" do - Puppet::String.new(:me, '0.0.1').name.should == :me - end - - it "should stringify with its own name" do - Puppet::String.new(:me, '0.0.1').to_s.should =~ /\bme\b/ - end - - it "should allow overriding of the default format" do - face = Puppet::String.new(:me, '0.0.1') - face.set_default_format :foo - face.default_format.should == :foo - end - - it "should default to :pson for its format" do - Puppet::String.new(:me, '0.0.1').default_format.should == :pson - end - - # Why? - it "should create a class-level autoloader" do - Puppet::String.autoloader.should be_instance_of(Puppet::Util::Autoload) - end - - it "should try to require strings that are not known" do - Puppet::String::StringCollection.expects(:require).with "puppet/string/foo" - Puppet::String[:foo, '0.0.1'] - end - - it "should be able to load all actions in all search paths" - - - it_should_behave_like "things that declare options" do - def add_options_to(&block) - Puppet::String.new(:with_options, '0.0.1', &block) - end - end - - describe "with string-level options" do - it "should not return any action-level options" do - string = Puppet::String.new(:with_options, '0.0.1') do - option "--foo" - option "--bar" - action :baz do - option "--quux" - end - end - string.options.should =~ [:foo, :bar] - end - - it "should fail when a string option duplicates an action option" do - expect { - Puppet::String.new(:action_level_options, '0.0.1') do - action :bar do option "--foo" end - option "--foo" - end - }.should raise_error ArgumentError, /Option foo conflicts with existing option foo on/i - end - - it "should work when two actions have the same option" do - string = Puppet::String.new(:with_options, '0.0.1') do - action :foo do option "--quux" end - action :bar do option "--quux" end - end - - string.get_action(:foo).options.should =~ [:quux] - string.get_action(:bar).options.should =~ [:quux] - end - end - - describe "with inherited options" do - let :string do - parent = Class.new(Puppet::String) - parent.option("--inherited") - string = parent.new(:example, '0.2.1') - string.option("--local") - string - end - - describe "#options" do - it "should list inherited options" do - string.options.should =~ [:inherited, :local] - end - end - - describe "#get_option" do - it "should return an inherited option object" do - string.get_option(:inherited).should be_an_instance_of Puppet::String::Option - end - end - end -end -- cgit From 8d144d0bf5116c5f04522f2b4cd75699f6480f8e Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 7 Apr 2011 14:20:35 -0700 Subject: (#7012) Update references in code to use face(s) The codebase is now using the new name, faces, uniformly to reference the objects contained. All tests pass. --- lib/puppet/application/config.rb | 4 +- lib/puppet/application/configurer.rb | 6 +- lib/puppet/application/faces.rb | 18 +- lib/puppet/application/faces_base.rb | 24 +-- lib/puppet/application/indirection_base.rb | 4 +- lib/puppet/faces.rb | 56 ++++--- lib/puppet/faces/action.rb | 50 +++--- lib/puppet/faces/action_builder.rb | 22 +-- lib/puppet/faces/action_manager.rb | 8 +- lib/puppet/faces/catalog.rb | 6 +- lib/puppet/faces/catalog/select.rb | 2 +- lib/puppet/faces/certificate.rb | 4 +- lib/puppet/faces/certificate_request.rb | 4 +- lib/puppet/faces/certificate_revocation_list.rb | 4 +- lib/puppet/faces/config.rb | 4 +- lib/puppet/faces/configurer.rb | 10 +- lib/puppet/faces/face_collection.rb | 125 ++++++++++++++ lib/puppet/faces/faces_collection.rb | 123 -------------- lib/puppet/faces/facts.rb | 4 +- lib/puppet/faces/file.rb | 4 +- lib/puppet/faces/indirector.rb | 10 +- lib/puppet/faces/key.rb | 4 +- lib/puppet/faces/node.rb | 4 +- lib/puppet/faces/option.rb | 8 +- lib/puppet/faces/option_builder.rb | 16 +- lib/puppet/faces/option_manager.rb | 6 +- lib/puppet/faces/report.rb | 4 +- lib/puppet/faces/resource.rb | 4 +- lib/puppet/faces/resource_type.rb | 4 +- lib/puppet/faces/status.rb | 4 +- spec/spec_helper.rb | 8 +- spec/unit/application/certificate_spec.rb | 7 +- spec/unit/application/config_spec.rb | 4 +- spec/unit/application/faces_base_spec.rb | 32 ++-- spec/unit/application/faces_spec.rb | 6 +- spec/unit/application/indirection_base_spec.rb | 8 +- spec/unit/faces/action_builder_spec.rb | 26 +-- spec/unit/faces/action_manager_spec.rb | 16 +- spec/unit/faces/action_spec.rb | 82 ++++----- spec/unit/faces/catalog_spec.rb | 2 +- spec/unit/faces/certificate_request_spec.rb | 2 +- .../unit/faces/certificate_revocation_list_spec.rb | 2 +- spec/unit/faces/certificate_spec.rb | 4 +- spec/unit/faces/config_spec.rb | 2 +- spec/unit/faces/configurer_spec.rb | 2 +- spec/unit/faces/face_collection_spec.rb | 184 +++++++++++++++++++++ spec/unit/faces/faces_collection_spec.rb | 184 --------------------- spec/unit/faces/facts_spec.rb | 2 +- spec/unit/faces/file_spec.rb | 2 +- spec/unit/faces/indirector_spec.rb | 18 +- spec/unit/faces/key_spec.rb | 2 +- spec/unit/faces/node_spec.rb | 2 +- spec/unit/faces/option_builder_spec.rb | 18 +- spec/unit/faces/option_spec.rb | 36 ++-- spec/unit/faces/report_spec.rb | 2 +- spec/unit/faces/resource_spec.rb | 2 +- spec/unit/faces/resource_type_spec.rb | 2 +- spec/unit/faces_spec.rb | 75 ++++----- 58 files changed, 639 insertions(+), 639 deletions(-) create mode 100644 lib/puppet/faces/face_collection.rb delete mode 100644 lib/puppet/faces/faces_collection.rb create mode 100755 spec/unit/faces/face_collection_spec.rb delete mode 100755 spec/unit/faces/faces_collection_spec.rb diff --git a/lib/puppet/application/config.rb b/lib/puppet/application/config.rb index f6559277b..41a46c339 100644 --- a/lib/puppet/application/config.rb +++ b/lib/puppet/application/config.rb @@ -1,4 +1,4 @@ -require 'puppet/application/string_base' +require 'puppet/application/faces_base' -class Puppet::Application::Config < Puppet::Application::StringBase +class Puppet::Application::Config < Puppet::Application::FacesBase end diff --git a/lib/puppet/application/configurer.rb b/lib/puppet/application/configurer.rb index be018338f..751e6b4d7 100644 --- a/lib/puppet/application/configurer.rb +++ b/lib/puppet/application/configurer.rb @@ -1,5 +1,5 @@ require 'puppet/application' -require 'puppet/string' +require 'puppet/faces' class Puppet::Application::Configurer < Puppet::Application should_parse_config @@ -17,7 +17,7 @@ class Puppet::Application::Configurer < Puppet::Application end def run_command - report = Puppet::String[:configurer, '0.0.1'].synchronize(Puppet[:certname]) - Puppet::String[:report, '0.0.1'].submit(report) + report = Puppet::Faces[:configurer, '0.0.1'].synchronize(Puppet[:certname]) + Puppet::Faces[:report, '0.0.1'].submit(report) end end diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb index 0a6a798ce..904a0cccc 100644 --- a/lib/puppet/application/faces.rb +++ b/lib/puppet/application/faces.rb @@ -1,7 +1,7 @@ require 'puppet/application' -require 'puppet/string' +require 'puppet/faces' -class Puppet::Application::String < Puppet::Application +class Puppet::Application::Faces < Puppet::Application should_parse_config run_mode :agent @@ -18,7 +18,7 @@ class Puppet::Application::String < Puppet::Application if arguments.empty? arguments = %w{terminuses actions} end - strings.each do |name| + faces.each do |name| str = "#{name}:\n" if arguments.include?("terminuses") begin @@ -68,12 +68,12 @@ class Puppet::Application::String < Puppet::Application end unless respond_to?(verb) - raise "Command '#{verb}' not found for 'string'" + raise "Command '#{verb}' not found for 'faces'" end end - def strings - Puppet::String.strings + def faces + Puppet::Faces.faces end def terminus_classes(indirection) @@ -81,9 +81,9 @@ class Puppet::Application::String < Puppet::Application end def actions(indirection) - return [] unless string = Puppet::String[indirection, '0.0.1'] - string.load_actions - return string.actions.sort { |a, b| a.to_s <=> b.to_s } + return [] unless faces = Puppet::Faces[indirection, '0.0.1'] + faces.load_actions + return faces.actions.sort { |a, b| a.to_s <=> b.to_s } end def load_applications diff --git a/lib/puppet/application/faces_base.rb b/lib/puppet/application/faces_base.rb index 09d02c079..6d66ee8a1 100644 --- a/lib/puppet/application/faces_base.rb +++ b/lib/puppet/application/faces_base.rb @@ -1,7 +1,7 @@ require 'puppet/application' -require 'puppet/string' +require 'puppet/faces' -class Puppet::Application::StringBase < Puppet::Application +class Puppet::Application::FacesBase < Puppet::Application should_parse_config run_mode :agent @@ -24,7 +24,7 @@ class Puppet::Application::StringBase < Puppet::Application end - attr_accessor :string, :action, :type, :arguments, :format + attr_accessor :face, :action, :type, :arguments, :format attr_writer :exit_code # This allows you to set the exit code if you don't want to just exit @@ -47,7 +47,7 @@ class Puppet::Application::StringBase < Puppet::Application def preinit super trap(:INT) do - $stderr.puts "Cancelling String" + $stderr.puts "Cancelling Face" exit(0) end @@ -57,8 +57,8 @@ class Puppet::Application::StringBase < Puppet::Application # TODO: These should be configurable versions, through a global # '--version' option, but we don't implement that yet... --daniel 2011-03-29 @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym - @string = Puppet::String[@type, :current] - @format = @string.default_format + @face = Puppet::Faces[@type, :current] + @format = @face.default_format # Now, walk the command line and identify the action. We skip over # arguments based on introspecting the action and all, and find the first @@ -68,11 +68,11 @@ class Puppet::Application::StringBase < Puppet::Application until @action or (index += 1) >= command_line.args.length do item = command_line.args[index] if item =~ /^-/ then - option = @string.options.find do |name| + option = @face.options.find do |name| item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ end if option then - option = @string.get_option(option) + option = @face.get_option(option) # If we have an inline argument, just carry on. We don't need to # care about optional vs mandatory in that case because we do a real # parse later, and that will totally take care of raising the error @@ -91,9 +91,9 @@ class Puppet::Application::StringBase < Puppet::Application raise ArgumentError, "Unknown option #{item.sub(/=.*$/, '').inspect}" end else - action = @string.get_action(item.to_sym) + action = @face.get_action(item.to_sym) if action.nil? then - raise ArgumentError, "#{@string} does not have an #{item.inspect} action!" + raise ArgumentError, "#{@face} does not have an #{item.inspect} action!" end @action = action end @@ -129,7 +129,7 @@ class Puppet::Application::StringBase < Puppet::Application # Note: because of our definition of where the action is set, we end up # with it *always* being the first word of the remaining set of command # line arguments. So, strip that off when we construct the arguments to - # pass down to the string action. --daniel 2011-04-04 + # pass down to the face action. --daniel 2011-04-04 @arguments.delete_at(0) # We copy all of the app options to the end of the call; This allows each @@ -142,7 +142,7 @@ class Puppet::Application::StringBase < Puppet::Application def main # Call the method associated with the provided action (e.g., 'find'). - if result = @string.send(@action.name, *arguments) + if result = @face.send(@action.name, *arguments) puts render(result) end exit(exit_code) diff --git a/lib/puppet/application/indirection_base.rb b/lib/puppet/application/indirection_base.rb index cfa1ea529..7455ebedf 100644 --- a/lib/puppet/application/indirection_base.rb +++ b/lib/puppet/application/indirection_base.rb @@ -1,4 +1,4 @@ -require 'puppet/application/string_base' +require 'puppet/application/faces_base' -class Puppet::Application::IndirectionBase < Puppet::Application::StringBase +class Puppet::Application::IndirectionBase < Puppet::Application::FacesBase end diff --git a/lib/puppet/faces.rb b/lib/puppet/faces.rb index 517cf4506..07a745480 100644 --- a/lib/puppet/faces.rb +++ b/lib/puppet/faces.rb @@ -1,16 +1,16 @@ require 'puppet' require 'puppet/util/autoload' -class Puppet::String - require 'puppet/string/string_collection' +class Puppet::Faces + require 'puppet/faces/face_collection' - require 'puppet/string/action_manager' - include Puppet::String::ActionManager - extend Puppet::String::ActionManager + require 'puppet/faces/action_manager' + include Puppet::Faces::ActionManager + extend Puppet::Faces::ActionManager - require 'puppet/string/option_manager' - include Puppet::String::OptionManager - extend Puppet::String::OptionManager + require 'puppet/faces/option_manager' + include Puppet::Faces::OptionManager + extend Puppet::Faces::OptionManager include Puppet::Util @@ -19,33 +19,35 @@ class Puppet::String # list of directories to search. # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb def autoloader - @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/string") + @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/faces") end - def strings - Puppet::String::StringCollection.strings + def faces + Puppet::Faces::FaceCollection.faces end - def string?(name, version) - Puppet::String::StringCollection.string?(name, version) + def face?(name, version) + Puppet::Faces::FaceCollection.face?(name, version) end def register(instance) - Puppet::String::StringCollection.register(instance) + Puppet::Faces::FaceCollection.register(instance) end def define(name, version, &block) - if string?(name, version) - string = Puppet::String::StringCollection[name, version] + if face?(name, version) + face = Puppet::Faces::FaceCollection[name, version] else - string = self.new(name, version) - Puppet::String::StringCollection.register(string) - string.load_actions + face = self.new(name, version) + Puppet::Faces::FaceCollection.register(face) + # REVISIT: Shouldn't this be delayed until *after* we evaluate the + # current block, not done before? --daniel 2011-04-07 + face.load_actions end - string.instance_eval(&block) if block_given? + face.instance_eval(&block) if block_given? - return string + return face end alias :[] :define @@ -61,11 +63,11 @@ class Puppet::String attr_reader :name def initialize(name, version, &block) - unless Puppet::String::StringCollection.validate_version(version) - raise ArgumentError, "Cannot create string #{name.inspect} with invalid version number '#{version}'!" + unless Puppet::Faces::FaceCollection.validate_version(version) + raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" end - @name = Puppet::String::StringCollection.underscorize(name) + @name = Puppet::Faces::FaceCollection.underscorize(name) @version = version @default_format = :pson @@ -74,11 +76,11 @@ class Puppet::String # Try to find actions defined in other files. def load_actions - path = "puppet/string/#{name}" + path = "puppet/faces/#{name}" loaded = [] [path, "#{name}@#{version}/#{path}"].each do |path| - Puppet::String.autoloader.search_directories.each do |dir| + Puppet::Faces.autoloader.search_directories.each do |dir| fdir = ::File.join(dir, path) next unless FileTest.directory?(fdir) @@ -99,6 +101,6 @@ class Puppet::String end def to_s - "Puppet::String[#{name.inspect}, #{version.inspect}]" + "Puppet::Faces[#{name.inspect}, #{version.inspect}]" end end diff --git a/lib/puppet/faces/action.rb b/lib/puppet/faces/action.rb index 0f5032ffb..58d2c6003 100644 --- a/lib/puppet/faces/action.rb +++ b/lib/puppet/faces/action.rb @@ -1,28 +1,26 @@ # -*- coding: utf-8 -*- -require 'puppet/string' -require 'puppet/string/option' +require 'puppet/faces' +require 'puppet/faces/option' -class Puppet::String::Action - attr_reader :name - - def to_s - "#{@string}##{@name}" - end - - def initialize(string, name, attrs = {}) +class Puppet::Faces::Action + def initialize(face, name, attrs = {}) raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ - @string = string + @face = face @name = name.to_sym @options = {} attrs.each do |k, v| send("#{k}=", v) end end + attr_reader :name + def to_s() "#{@face}##{@name}" end + + # Initially, this was defined to allow the @action.invoke pattern, which is # a very natural way to invoke behaviour given our introspection - # capabilities. Heck, our initial plan was to have the string delegate to + # capabilities. Heck, our initial plan was to have the faces delegate to # the action object for invocation and all. # - # It turns out that we have a binding problem to solve: @string was bound to + # It turns out that we have a binding problem to solve: @face was bound to # the parent class, not the subclass instance, and we don't pass the # appropriate context or change the binding enough to make this work. # @@ -33,13 +31,13 @@ class Puppet::String::Action # So, we are pulling this method for now, and will return it to life when we # have the time to resolve the problem. For now, you should replace... # - # @action = @string.get_action(name) + # @action = @face.get_action(name) # @action.invoke(arg1, arg2, arg3) # # ...with... # - # @action = @string.get_action(name) - # @string.send(@action.name, arg1, arg2, arg3) + # @action = @face.get_action(name) + # @face.send(@action.name, arg1, arg2, arg3) # # I understand that is somewhat cumbersome, but it functions as desired. # --daniel 2011-03-31 @@ -48,7 +46,7 @@ class Puppet::String::Action # documentation, for the benefit of the reader. # # def invoke(*args, &block) - # @string.send(name, *args, &block) + # @face.send(name, *args, &block) # end def when_invoked=(block) @@ -82,12 +80,12 @@ class Puppet::String::Action self.__send__(#{internal_name.inspect}, *args) end" - if @string.is_a?(Class) - @string.class_eval do eval wrapper, nil, file, line end - @string.define_method(internal_name, &block) + if @face.is_a?(Class) + @face.class_eval do eval wrapper, nil, file, line end + @face.define_method(internal_name, &block) else - @string.instance_eval do eval wrapper, nil, file, line end - @string.meta_def(internal_name, &block) + @face.instance_eval do eval wrapper, nil, file, line end + @face.meta_def(internal_name, &block) end end @@ -95,8 +93,8 @@ class Puppet::String::Action option.aliases.each do |name| if conflict = get_option(name) then raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - elsif conflict = @string.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@string}" + elsif conflict = @face.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@face}" end end @@ -112,10 +110,10 @@ class Puppet::String::Action end def options - (@options.keys + @string.options).sort + (@options.keys + @face.options).sort end def get_option(name) - @options[name.to_sym] || @string.get_option(name) + @options[name.to_sym] || @face.get_option(name) end end diff --git a/lib/puppet/faces/action_builder.rb b/lib/puppet/faces/action_builder.rb index e7c03273b..a67068926 100644 --- a/lib/puppet/faces/action_builder.rb +++ b/lib/puppet/faces/action_builder.rb @@ -1,31 +1,31 @@ -require 'puppet/string' -require 'puppet/string/action' +require 'puppet/faces' +require 'puppet/faces/action' -class Puppet::String::ActionBuilder +class Puppet::Faces::ActionBuilder attr_reader :action - def self.build(string, name, &block) + def self.build(face, name, &block) raise "Action #{name.inspect} must specify a block" unless block - new(string, name, &block).action + new(face, name, &block).action end private - def initialize(string, name, &block) - @string = string - @action = Puppet::String::Action.new(string, name) + def initialize(face, name, &block) + @face = face + @action = Puppet::Faces::Action.new(face, name) instance_eval(&block) end # Ideally the method we're defining here would be added to the action, and a - # method on the string would defer to it, but we can't get scope correct, - # so we stick with this. --daniel 2011-03-24 + # method on the face would defer to it, but we can't get scope correct, so + # we stick with this. --daniel 2011-03-24 def when_invoked(&block) raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action @action.when_invoked = block end def option(*declaration, &block) - option = Puppet::String::OptionBuilder.build(@action, *declaration, &block) + option = Puppet::Faces::OptionBuilder.build(@action, *declaration, &block) @action.add_option(option) end end diff --git a/lib/puppet/faces/action_manager.rb b/lib/puppet/faces/action_manager.rb index 9f0aa7582..6c0036bd8 100644 --- a/lib/puppet/faces/action_manager.rb +++ b/lib/puppet/faces/action_manager.rb @@ -1,12 +1,12 @@ -require 'puppet/string/action_builder' +require 'puppet/faces/action_builder' -module Puppet::String::ActionManager +module Puppet::Faces::ActionManager # Declare that this app can take a specific action, and provide # the code to do so. def action(name, &block) @actions ||= {} raise "Action #{name} already defined for #{self}" if action?(name) - action = Puppet::String::ActionBuilder.build(self, name, &block) + action = Puppet::Faces::ActionBuilder.build(self, name, &block) @actions[action.name] = action end @@ -15,7 +15,7 @@ module Puppet::String::ActionManager def script(name, &block) @actions ||= {} raise "Action #{name} already defined for #{self}" if action?(name) - @actions[name] = Puppet::String::Action.new(self, name, :when_invoked => block) + @actions[name] = Puppet::Faces::Action.new(self, name, :when_invoked => block) end def actions diff --git a/lib/puppet/faces/catalog.rb b/lib/puppet/faces/catalog.rb index 441c7ee7d..2e2168ac4 100644 --- a/lib/puppet/faces/catalog.rb +++ b/lib/puppet/faces/catalog.rb @@ -1,6 +1,6 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:catalog, '0.0.1') do +Puppet::Faces::Indirector.define(:catalog, '0.0.1') do action(:apply) do when_invoked do |catalog, options| report = Puppet::Transaction::Report.new("apply") @@ -28,7 +28,7 @@ Puppet::String::Indirector.define(:catalog, '0.0.1') do facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} catalog = nil retrieval_duration = thinmark do - catalog = Puppet::String[:catalog, '0.0.1'].find(certname, facts_to_upload) + catalog = Puppet::Faces[:catalog, '0.0.1'].find(certname, facts_to_upload) end catalog = catalog.to_ral catalog.finalize diff --git a/lib/puppet/faces/catalog/select.rb b/lib/puppet/faces/catalog/select.rb index 11670e2e7..e29d19970 100644 --- a/lib/puppet/faces/catalog/select.rb +++ b/lib/puppet/faces/catalog/select.rb @@ -1,5 +1,5 @@ # Select and show a list of resources of a given type. -Puppet::String.define(:catalog, '0.0.1') do +Puppet::Faces.define(:catalog, '0.0.1') do action :select do when_invoked do |host, type, options| catalog = Puppet::Resource::Catalog.indirection.find(host) diff --git a/lib/puppet/faces/certificate.rb b/lib/puppet/faces/certificate.rb index e8773ae2e..b10bee579 100644 --- a/lib/puppet/faces/certificate.rb +++ b/lib/puppet/faces/certificate.rb @@ -1,7 +1,7 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' require 'puppet/ssl/host' -Puppet::String::Indirector.define(:certificate, '0.0.1') do +Puppet::Faces::Indirector.define(:certificate, '0.0.1') do # REVISIT: This should use a pre-invoke hook to run the common code that # needs to happen before we invoke any action; that would be much nicer than # the "please repeat yourself" stuff found in here right now. diff --git a/lib/puppet/faces/certificate_request.rb b/lib/puppet/faces/certificate_request.rb index 218b40b98..5e91bdb7f 100644 --- a/lib/puppet/faces/certificate_request.rb +++ b/lib/puppet/faces/certificate_request.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:certificate_request, '0.0.1') do +Puppet::Faces::Indirector.define(:certificate_request, '0.0.1') do end diff --git a/lib/puppet/faces/certificate_revocation_list.rb b/lib/puppet/faces/certificate_revocation_list.rb index 9731b4f2d..2f2d72874 100644 --- a/lib/puppet/faces/certificate_revocation_list.rb +++ b/lib/puppet/faces/certificate_revocation_list.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:certificate_revocation_list, '0.0.1') do +Puppet::Faces::Indirector.define(:certificate_revocation_list, '0.0.1') do end diff --git a/lib/puppet/faces/config.rb b/lib/puppet/faces/config.rb index 8a9417148..647bf5052 100644 --- a/lib/puppet/faces/config.rb +++ b/lib/puppet/faces/config.rb @@ -1,6 +1,6 @@ -require 'puppet/string' +require 'puppet/faces' -Puppet::String.define(:config, '0.0.1') do +Puppet::Faces.define(:config, '0.0.1') do action(:print) do when_invoked do |*args| options = args.pop diff --git a/lib/puppet/faces/configurer.rb b/lib/puppet/faces/configurer.rb index 257f97e90..d40987697 100644 --- a/lib/puppet/faces/configurer.rb +++ b/lib/puppet/faces/configurer.rb @@ -1,11 +1,11 @@ -require 'puppet/string' +require 'puppet/faces' -Puppet::String.define(:configurer, '0.0.1') do +Puppet::Faces.define(:configurer, '0.0.1') do action(:synchronize) do when_invoked do |certname, options| - facts = Puppet::String[:facts, '0.0.1'].find(certname) - catalog = Puppet::String[:catalog, '0.0.1'].download(certname, facts) - report = Puppet::String[:catalog, '0.0.1'].apply(catalog) + facts = Puppet::Faces[:facts, '0.0.1'].find(certname) + catalog = Puppet::Faces[:catalog, '0.0.1'].download(certname, facts) + report = Puppet::Faces[:catalog, '0.0.1'].apply(catalog) report end end diff --git a/lib/puppet/faces/face_collection.rb b/lib/puppet/faces/face_collection.rb new file mode 100644 index 000000000..e6ee709d6 --- /dev/null +++ b/lib/puppet/faces/face_collection.rb @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +require 'puppet/faces' + +module Puppet::Faces::FaceCollection + SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ + + @faces = Hash.new { |hash, key| hash[key] = {} } + + def self.faces + unless @loaded + @loaded = true + $LOAD_PATH.each do |dir| + next unless FileTest.directory?(dir) + Dir.chdir(dir) do + # REVISIT: This is wrong!!!! We don't name files like that ever, + # so we should no longer match things like this. Damnit!!! --daniel 2011-04-07 + Dir.glob("puppet/faces/v*/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| + iname = file.sub(/\.rb/, '') + begin + require iname + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" + end + end + end + end + end + return @faces.keys + end + + def self.validate_version(version) + !!(SEMVER_VERSION =~ version.to_s) + end + + def self.cmp_semver(a, b) + a, b = [a, b].map do |x| + parts = SEMVER_VERSION.match(x).to_a[1..4] + parts[0..2] = parts[0..2].map { |e| e.to_i } + parts + end + + cmp = a[0..2] <=> b[0..2] + if cmp == 0 + cmp = a[3] <=> b[3] + cmp = +1 if a[3].empty? && !b[3].empty? + cmp = -1 if b[3].empty? && !a[3].empty? + end + cmp + end + + def self.[](name, version) + @faces[underscorize(name)][version] if face?(name, version) + end + + def self.face?(name, version) + name = underscorize(name) + return true if @faces[name].has_key?(version) + + # We always load the current version file; the common case is that we have + # the expected version and any compatibility versions in the same file, + # the default. Which means that this is almost always the case. + # + # We use require to avoid executing the code multiple times, like any + # other Ruby library that we might want to use. --daniel 2011-04-06 + begin + require "puppet/faces/#{name}" + + # If we wanted :current, we need to index to find that; direct version + # requests just work™ as they go. --daniel 2011-04-06 + if version == :current then + # We need to find current out of this. This is the largest version + # number that doesn't have a dedicated on-disk file present; those + # represent "experimental" versions of faces, which we don't fully + # support yet. + # + # We walk the versions from highest to lowest and take the first version + # that is not defined in an explicitly versioned file on disk as the + # current version. + # + # This constrains us to only ship experimental versions with *one* + # version in the file, not multiple, but given you can't reliably load + # them except by side-effect when you ignore that rule this seems safe + # enough... + # + # Given those constraints, and that we are not going to ship a versioned + # interface that is not :current in this release, we are going to leave + # these thoughts in place, and just punt on the actual versioning. + # + # When we upgrade the core to support multiple versions we can solve the + # problems then; as lazy as possible. + # + # We do support multiple versions in the same file, though, so we sort + # versions here and return the last item in that set. + # + # --daniel 2011-04-06 + latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last + @faces[name][:current] = @faces[name][latest_ver] + end + rescue LoadError => e + raise unless e.message =~ %r{-- puppet/faces/#{name}$} + # ...guess we didn't find the file; return a much better problem. + end + + # Now, either we have the version in our set of faces, or we didn't find + # the version they were looking for. In the future we will support + # loading versioned stuff from some look-aside part of the Ruby load path, + # but we don't need that right now. + # + # So, this comment is a place-holder for that. --daniel 2011-04-06 + return !! @faces[name].has_key?(version) + end + + def self.register(face) + @faces[underscorize(face.name)][face.version] = face + end + + def self.underscorize(name) + unless name.to_s =~ /^[-_a-z]+$/i then + raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name" + end + + name.to_s.downcase.split(/[-_]/).join('_').to_sym + end +end diff --git a/lib/puppet/faces/faces_collection.rb b/lib/puppet/faces/faces_collection.rb deleted file mode 100644 index ecd99359d..000000000 --- a/lib/puppet/faces/faces_collection.rb +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/string' - -module Puppet::String::StringCollection - SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ - - @strings = Hash.new { |hash, key| hash[key] = {} } - - def self.strings - unless @loaded - @loaded = true - $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - Dir.chdir(dir) do - Dir.glob("puppet/string/v*/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| - iname = file.sub(/\.rb/, '') - begin - require iname - rescue Exception => detail - puts detail.backtrace if Puppet[:trace] - raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" - end - end - end - end - end - return @strings.keys - end - - def self.validate_version(version) - !!(SEMVER_VERSION =~ version.to_s) - end - - def self.cmp_versions(a, b) - a, b = [a, b].map do |x| - parts = SEMVER_VERSION.match(x).to_a[1..4] - parts[0..2] = parts[0..2].map { |e| e.to_i } - parts - end - - cmp = a[0..2] <=> b[0..2] - if cmp == 0 - cmp = a[3] <=> b[3] - cmp = +1 if a[3].empty? && !b[3].empty? - cmp = -1 if b[3].empty? && !a[3].empty? - end - cmp - end - - def self.[](name, version) - @strings[underscorize(name)][version] if string?(name, version) - end - - def self.string?(name, version) - name = underscorize(name) - return true if @strings[name].has_key?(version) - - # We always load the current version file; the common case is that we have - # the expected version and any compatibility versions in the same file, - # the default. Which means that this is almost always the case. - # - # We use require to avoid executing the code multiple times, like any - # other Ruby library that we might want to use. --daniel 2011-04-06 - begin - require "puppet/string/#{name}" - - # If we wanted :current, we need to index to find that; direct version - # requests just work™ as they go. --daniel 2011-04-06 - if version == :current then - # We need to find current out of this. This is the largest version - # number that doesn't have a dedicated on-disk file present; those - # represent "experimental" versions of strings, which we don't fully - # support yet. - # - # We walk the versions from highest to lowest and take the first version - # that is not defined in an explicitly versioned file on disk as the - # current version. - # - # This constrains us to only ship experimental versions with *one* - # version in the file, not multiple, but given you can't reliably load - # them except by side-effect when you ignore that rule this seems safe - # enough... - # - # Given those constraints, and that we are not going to ship a versioned - # interface that is not :current in this release, we are going to leave - # these thoughts in place, and just punt on the actual versioning. - # - # When we upgrade the core to support multiple versions we can solve the - # problems then; as lazy as possible. - # - # We do support multiple versions in the same file, though, so we sort - # versions here and return the last item in that set. - # - # --daniel 2011-04-06 - latest_ver = @strings[name].keys.sort {|a, b| cmp_versions(a, b) }.last - @strings[name][:current] = @strings[name][latest_ver] - end - rescue LoadError => e - raise unless e.message =~ %r{-- puppet/string/#{name}$} - # ...guess we didn't find the file; return a much better problem. - end - - # Now, either we have the version in our set of strings, or we didn't find - # the version they were looking for. In the future we will support - # loading versioned stuff from some look-aside part of the Ruby load path, - # but we don't need that right now. - # - # So, this comment is a place-holder for that. --daniel 2011-04-06 - return !! @strings[name].has_key?(version) - end - - def self.register(string) - @strings[underscorize(string.name)][string.version] = string - end - - def self.underscorize(name) - unless name.to_s =~ /^[-_a-z]+$/i then - raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid string name" - end - - name.to_s.downcase.split(/[-_]/).join('_').to_sym - end -end diff --git a/lib/puppet/faces/facts.rb b/lib/puppet/faces/facts.rb index 6bd9904b0..33eacef38 100644 --- a/lib/puppet/faces/facts.rb +++ b/lib/puppet/faces/facts.rb @@ -1,7 +1,7 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' require 'puppet/node/facts' -Puppet::String::Indirector.define(:facts, '0.0.1') do +Puppet::Faces::Indirector.define(:facts, '0.0.1') do set_default_format :yaml # Upload our facts to the server diff --git a/lib/puppet/faces/file.rb b/lib/puppet/faces/file.rb index cc5737f28..e8ad18c17 100644 --- a/lib/puppet/faces/file.rb +++ b/lib/puppet/faces/file.rb @@ -1,5 +1,5 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:file, '0.0.1') do +Puppet::Faces::Indirector.define(:file, '0.0.1') do set_indirection_name :file_bucket_file end diff --git a/lib/puppet/faces/indirector.rb b/lib/puppet/faces/indirector.rb index 0c7d043bb..f72260017 100644 --- a/lib/puppet/faces/indirector.rb +++ b/lib/puppet/faces/indirector.rb @@ -1,7 +1,7 @@ require 'puppet' -require 'puppet/string' +require 'puppet/faces' -class Puppet::String::Indirector < Puppet::String +class Puppet::Faces::Indirector < Puppet::Faces option "--terminus TERMINUS" do desc "REVISIT: You can select a terminus, which has some bigger effect that we should describe in this file somehow." @@ -68,13 +68,13 @@ that we should describe in this file somehow." @indirection_name || name.to_sym end - # Here's your opportunity to override the indirection name. By default - # it will be the same name as the string. + # Here's your opportunity to override the indirection name. By default it + # will be the same name as the face. def set_indirection_name(name) @indirection_name = name end - # Return an indirection associated with an string, if one exists + # Return an indirection associated with a face, if one exists; # One usually does. def indirection unless @indirection diff --git a/lib/puppet/faces/key.rb b/lib/puppet/faces/key.rb index 95aceade5..7b6ad52ac 100644 --- a/lib/puppet/faces/key.rb +++ b/lib/puppet/faces/key.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:key, '0.0.1') do +Puppet::Faces::Indirector.define(:key, '0.0.1') do end diff --git a/lib/puppet/faces/node.rb b/lib/puppet/faces/node.rb index bc31a2cf3..7eed0df91 100644 --- a/lib/puppet/faces/node.rb +++ b/lib/puppet/faces/node.rb @@ -1,5 +1,5 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:node, '0.0.1') do +Puppet::Faces::Indirector.define(:node, '0.0.1') do set_default_format :yaml end diff --git a/lib/puppet/faces/option.rb b/lib/puppet/faces/option.rb index 352f7e5ef..7d3ed37ca 100644 --- a/lib/puppet/faces/option.rb +++ b/lib/puppet/faces/option.rb @@ -1,6 +1,6 @@ -require 'puppet/string' +require 'puppet/faces' -class Puppet::String::Option +class Puppet::Faces::Option attr_reader :parent attr_reader :name attr_reader :aliases @@ -65,8 +65,8 @@ class Puppet::String::Option end # to_s and optparse_to_name are roughly mirrored, because they are used to - # transform strings to name symbols, and vice-versa. This isn't a full - # bidirectional transformation though. + # transform options to name symbols, and vice-versa. This isn't a full + # bidirectional transformation though. --daniel 2011-04-07 def to_s @name.to_s.tr('_', '-') end diff --git a/lib/puppet/faces/option_builder.rb b/lib/puppet/faces/option_builder.rb index da0d213fb..0b6667546 100644 --- a/lib/puppet/faces/option_builder.rb +++ b/lib/puppet/faces/option_builder.rb @@ -1,22 +1,22 @@ -require 'puppet/string/option' +require 'puppet/faces/option' -class Puppet::String::OptionBuilder +class Puppet::Faces::OptionBuilder attr_reader :option - def self.build(string, *declaration, &block) - new(string, *declaration, &block).option + def self.build(face, *declaration, &block) + new(face, *declaration, &block).option end private - def initialize(string, *declaration, &block) - @string = string - @option = Puppet::String::Option.new(string, *declaration) + def initialize(face, *declaration, &block) + @face = face + @option = Puppet::Faces::Option.new(face, *declaration) block and instance_eval(&block) @option end # Metaprogram the simple DSL from the option class. - Puppet::String::Option.instance_methods.grep(/=$/).each do |setter| + Puppet::Faces::Option.instance_methods.grep(/=$/).each do |setter| next if setter =~ /^=/ # special case, darn it... dsl = setter.sub(/=$/, '') diff --git a/lib/puppet/faces/option_manager.rb b/lib/puppet/faces/option_manager.rb index f952ad4f0..02a73afc3 100644 --- a/lib/puppet/faces/option_manager.rb +++ b/lib/puppet/faces/option_manager.rb @@ -1,10 +1,10 @@ -require 'puppet/string/option_builder' +require 'puppet/faces/option_builder' -module Puppet::String::OptionManager +module Puppet::Faces::OptionManager # Declare that this app can take a specific option, and provide # the code to do so. def option(*declaration, &block) - add_option Puppet::String::OptionBuilder.build(self, *declaration, &block) + add_option Puppet::Faces::OptionBuilder.build(self, *declaration, &block) end def add_option(option) diff --git a/lib/puppet/faces/report.rb b/lib/puppet/faces/report.rb index da3ca8504..23a518981 100644 --- a/lib/puppet/faces/report.rb +++ b/lib/puppet/faces/report.rb @@ -1,6 +1,6 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:report, '0.0.1') do +Puppet::Faces::Indirector.define(:report, '0.0.1') do action(:submit) do when_invoked do |report, options| begin diff --git a/lib/puppet/faces/resource.rb b/lib/puppet/faces/resource.rb index 9838be0fa..60b0d94db 100644 --- a/lib/puppet/faces/resource.rb +++ b/lib/puppet/faces/resource.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:resource, '0.0.1') do +Puppet::Faces::Indirector.define(:resource, '0.0.1') do end diff --git a/lib/puppet/faces/resource_type.rb b/lib/puppet/faces/resource_type.rb index 8ca31ea6c..4321d65e7 100644 --- a/lib/puppet/faces/resource_type.rb +++ b/lib/puppet/faces/resource_type.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:resource_type, '0.0.1') do +Puppet::Faces::Indirector.define(:resource_type, '0.0.1') do end diff --git a/lib/puppet/faces/status.rb b/lib/puppet/faces/status.rb index 41de2bb99..e035f281f 100644 --- a/lib/puppet/faces/status.rb +++ b/lib/puppet/faces/status.rb @@ -1,4 +1,4 @@ -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -Puppet::String::Indirector.define(:status, '0.0.1') do +Puppet::Faces::Indirector.define(:status, '0.0.1') do end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bb71fca73..3c8cd4fa4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,7 +4,7 @@ $LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib') require 'mocha' require 'puppet' -require 'puppet/string' +require 'puppet/faces' require 'rspec' Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| @@ -28,7 +28,7 @@ RSpec.configure do |config| @load_path_scratch_dir = Dir.mktmpdir $LOAD_PATH.push @load_path_scratch_dir - FileUtils.mkdir_p(File.join @load_path_scratch_dir, 'puppet', 'string') + FileUtils.mkdir_p(File.join @load_path_scratch_dir, 'puppet', 'faces') end config.after :each do @@ -41,10 +41,10 @@ RSpec.configure do |config| FileUtils.remove_entry_secure @load_path_scratch_dir end - def write_scratch_string(name) + def write_scratch_faces(name) fail "you need to supply a block: do |fh| fh.puts 'content' end" unless block_given? fail "name should be a symbol" unless name.is_a? Symbol - filename = File.join(@load_path_scratch_dir, 'puppet', 'string', "#{name}.rb") + filename = File.join(@load_path_scratch_dir, 'puppet', 'faces', "#{name}.rb") File.open(filename, 'w') do |fh| yield fh end diff --git a/spec/unit/application/certificate_spec.rb b/spec/unit/application/certificate_spec.rb index 3d2215ded..6153d9538 100755 --- a/spec/unit/application/certificate_spec.rb +++ b/spec/unit/application/certificate_spec.rb @@ -1,12 +1,9 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') require 'puppet/application/certificate' describe Puppet::Application::Certificate do it "should have a 'ca-location' option" do - # REVISIT: This is delegated from the string, and we will have a test - # there, so is this actually a valuable test? + # REVISIT: This is delegated from the face, and we will have a test there, + # so is this actually a valuable test? --daniel 2011-04-07 subject.command_line.stubs(:args).returns %w{list} subject.preinit subject.should respond_to(:handle_ca_location) diff --git a/spec/unit/application/config_spec.rb b/spec/unit/application/config_spec.rb index a45adc8d3..066df6a51 100755 --- a/spec/unit/application/config_spec.rb +++ b/spec/unit/application/config_spec.rb @@ -4,7 +4,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') require 'puppet/application/config' describe Puppet::Application::Config do - it "should be a subclass of Puppet::Application::StringBase" do - Puppet::Application::Config.superclass.should equal(Puppet::Application::StringBase) + it "should be a subclass of Puppet::Application::FacesBase" do + Puppet::Application::Config.superclass.should equal(Puppet::Application::FacesBase) end end diff --git a/spec/unit/application/faces_base_spec.rb b/spec/unit/application/faces_base_spec.rb index 3f8ae73b6..6d8815f44 100755 --- a/spec/unit/application/faces_base_spec.rb +++ b/spec/unit/application/faces_base_spec.rb @@ -1,22 +1,22 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/application/string_base' +require 'puppet/application/faces_base' require 'tmpdir' -class Puppet::Application::StringBase::Basetest < Puppet::Application::StringBase +class Puppet::Application::FacesBase::Basetest < Puppet::Application::FacesBase end -describe Puppet::Application::StringBase do +describe Puppet::Application::FacesBase do before :all do @dir = Dir.mktmpdir $LOAD_PATH.push(@dir) - FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') - File.open(File.join(@dir, 'puppet', 'string', 'basetest.rb'), 'w') do |f| - f.puts "Puppet::String.define(:basetest, '0.0.1')" + FileUtils.mkdir_p(File.join @dir, 'puppet', 'faces') + File.open(File.join(@dir, 'puppet', 'faces', 'basetest.rb'), 'w') do |f| + f.puts "Puppet::Faces.define(:basetest, '0.0.1')" end - Puppet::String.define(:basetest, '0.0.1') do + Puppet::Faces.define(:basetest, '0.0.1') do option("--[no-]boolean") option("--mandatory MANDATORY") option("--optional [OPTIONAL]") @@ -34,7 +34,7 @@ describe Puppet::Application::StringBase do end let :app do - app = Puppet::Application::StringBase::Basetest.new + app = Puppet::Application::FacesBase::Basetest.new app.stubs(:exit) app.stubs(:puts) app.command_line.stubs(:subcommand_name).returns 'subcommand' @@ -63,11 +63,11 @@ describe Puppet::Application::StringBase do app.preinit end - it "should set the string based on the type" do - app.string.name.should == :basetest + it "should set the faces based on the type" do + app.face.name.should == :basetest end - it "should set the format based on the string default" do + it "should set the format based on the faces default" do app.format.should == :pson end @@ -104,7 +104,7 @@ describe Puppet::Application::StringBase do app.command_line.stubs(:args).returns %w{foo --bar} app.preinit app.action.name.should == :foo - app.string.should_not be_option :bar + app.face.should_not be_option :bar app.action.should_not be_option :bar end @@ -166,14 +166,14 @@ describe Puppet::Application::StringBase do describe "#main" do before do - app.string = Puppet::String[:basetest, '0.0.1'] - app.action = app.string.get_action(:foo) + app.face = Puppet::Faces[:basetest, '0.0.1'] + app.action = app.face.get_action(:foo) app.format = :pson app.arguments = ["myname", "myarg"] end - it "should send the specified verb and name to the string" do - app.string.expects(:foo).with(*app.arguments) + it "should send the specified verb and name to the faces" do + app.face.expects(:foo).with(*app.arguments) app.main end diff --git a/spec/unit/application/faces_spec.rb b/spec/unit/application/faces_spec.rb index 13af0a546..d945c40b5 100755 --- a/spec/unit/application/faces_spec.rb +++ b/spec/unit/application/faces_spec.rb @@ -1,10 +1,10 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/application/string' +require 'puppet/application/faces' -describe Puppet::Application::String do +describe Puppet::Application::Faces do it "should be an application" do - Puppet::Application::String.superclass.should equal(Puppet::Application) + Puppet::Application::Faces.superclass.should equal(Puppet::Application) end end diff --git a/spec/unit/application/indirection_base_spec.rb b/spec/unit/application/indirection_base_spec.rb index 66b3009fb..a73cf4fca 100755 --- a/spec/unit/application/indirection_base_spec.rb +++ b/spec/unit/application/indirection_base_spec.rb @@ -2,19 +2,19 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') require 'puppet/application/indirection_base' -require 'puppet/string/indirector' +require 'puppet/faces/indirector' ######################################################################## # Stub for testing; the names are critical, sadly. --daniel 2011-03-30 class Puppet::Application::TestIndirection < Puppet::Application::IndirectionBase end -string = Puppet::String::Indirector.define(:testindirection, '0.0.1') do +face = Puppet::Faces::Indirector.define(:testindirection, '0.0.1') do end # REVISIT: This horror is required because we don't allow anything to be # :current except for if it lives on, and is loaded from, disk. --daniel 2011-03-29 -string.version = :current -Puppet::String.register(string) +face.version = :current +Puppet::Faces.register(face) ######################################################################## diff --git a/spec/unit/faces/action_builder_spec.rb b/spec/unit/faces/action_builder_spec.rb index 5f6f1c08f..5c8b98849 100755 --- a/spec/unit/faces/action_builder_spec.rb +++ b/spec/unit/faces/action_builder_spec.rb @@ -1,53 +1,53 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/string/action_builder' +require 'puppet/faces/action_builder' -describe Puppet::String::ActionBuilder do +describe Puppet::Faces::ActionBuilder do describe "::build" do it "should build an action" do - action = Puppet::String::ActionBuilder.build(nil, :foo) do + action = Puppet::Faces::ActionBuilder.build(nil, :foo) do end - action.should be_a(Puppet::String::Action) + action.should be_a(Puppet::Faces::Action) action.name.should == :foo end - it "should define a method on the string which invokes the action" do - string = Puppet::String.new(:action_builder_test_string, '0.0.1') - action = Puppet::String::ActionBuilder.build(string, :foo) do + it "should define a method on the face which invokes the action" do + face = Puppet::Faces.new(:action_builder_test_faces, '0.0.1') + action = Puppet::Faces::ActionBuilder.build(face, :foo) do when_invoked do "invoked the method" end end - string.foo.should == "invoked the method" + face.foo.should == "invoked the method" end it "should require a block" do - lambda { Puppet::String::ActionBuilder.build(nil, :foo) }. + expect { Puppet::Faces::ActionBuilder.build(nil, :foo) }. should raise_error("Action :foo must specify a block") end describe "when handling options" do - let :string do Puppet::String.new(:option_handling, '0.0.1') end + let :face do Puppet::Faces.new(:option_handling, '0.0.1') end it "should have a #option DSL function" do method = nil - Puppet::String::ActionBuilder.build(string, :foo) do + Puppet::Faces::ActionBuilder.build(face, :foo) do method = self.method(:option) end method.should be end it "should define an option without a block" do - action = Puppet::String::ActionBuilder.build(string, :foo) do + action = Puppet::Faces::ActionBuilder.build(face, :foo) do option "--bar" end action.should be_option :bar end it "should accept an empty block" do - action = Puppet::String::ActionBuilder.build(string, :foo) do + action = Puppet::Faces::ActionBuilder.build(face, :foo) do option "--bar" do # This space left deliberately blank. end diff --git a/spec/unit/faces/action_manager_spec.rb b/spec/unit/faces/action_manager_spec.rb index b8baf80a5..61d1c1df5 100755 --- a/spec/unit/faces/action_manager_spec.rb +++ b/spec/unit/faces/action_manager_spec.rb @@ -2,14 +2,14 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -# This is entirely an internal class for String, so we have to load it instead of our class. -require 'puppet/string' +# This is entirely an internal class for Faces, so we have to load it instead of our class. +require 'puppet/faces' class ActionManagerTester - include Puppet::String::ActionManager + include Puppet::Faces::ActionManager end -describe Puppet::String::ActionManager do +describe Puppet::Faces::ActionManager do subject { ActionManagerTester.new } describe "when included in a class" do @@ -73,7 +73,7 @@ describe Puppet::String::ActionManager do end describe "when used to extend a class" do - subject { Class.new.extend(Puppet::String::ActionManager) } + subject { Class.new.extend(Puppet::Faces::ActionManager) } it "should be able to define an action" do subject.action(:foo) do @@ -102,8 +102,8 @@ describe Puppet::String::ActionManager do describe "when used both at the class and instance level" do before do @klass = Class.new do - include Puppet::String::ActionManager - extend Puppet::String::ActionManager + include Puppet::Faces::ActionManager + extend Puppet::Faces::ActionManager end @instance = @klass.new end @@ -216,7 +216,7 @@ describe Puppet::String::ActionManager do describe "#get_action" do let :parent_class do - parent_class = Class.new(Puppet::String) + parent_class = Class.new(Puppet::Faces) parent_class.action(:foo) {} parent_class end diff --git a/spec/unit/faces/action_spec.rb b/spec/unit/faces/action_spec.rb index b6fe87a63..c087744b1 100755 --- a/spec/unit/faces/action_spec.rb +++ b/spec/unit/faces/action_spec.rb @@ -1,13 +1,13 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/string/action' +require 'puppet/faces/action' -describe Puppet::String::Action do +describe Puppet::Faces::Action do describe "when validating the action name" do [nil, '', 'foo bar', '-foobar'].each do |input| it "should treat #{input.inspect} as an invalid name" do - expect { Puppet::String::Action.new(nil, input) }. + expect { Puppet::Faces::Action.new(nil, input) }. should raise_error(/is an invalid action name/) end end @@ -15,7 +15,7 @@ describe Puppet::String::Action do describe "when invoking" do it "should be able to call other actions on the same object" do - string = Puppet::String.new(:my_string, '0.0.1') do + face = Puppet::Faces.new(:my_face, '0.0.1') do action(:foo) do when_invoked { 25 } end @@ -24,8 +24,8 @@ describe Puppet::String::Action do when_invoked { "the value of foo is '#{foo}'" } end end - string.foo.should == 25 - string.bar.should == "the value of foo is '25'" + face.foo.should == 25 + face.bar.should == "the value of foo is '25'" end # bar is a class action calling a class action @@ -33,7 +33,7 @@ describe Puppet::String::Action do # baz is an instance action calling a class action # qux is an instance action calling an instance action it "should be able to call other actions on the same object when defined on a class" do - class Puppet::String::MyStringBaseClass < Puppet::String + class Puppet::Faces::MyFacesBaseClass < Puppet::Faces action(:foo) do when_invoked { 25 } end @@ -47,7 +47,7 @@ describe Puppet::String::Action do end end - string = Puppet::String::MyStringBaseClass.new(:my_inherited_string, '0.0.1') do + face = Puppet::Faces::MyFacesBaseClass.new(:my_inherited_face, '0.0.1') do action(:baz) do when_invoked { "the value of foo in baz is '#{foo}'" } end @@ -56,16 +56,16 @@ describe Puppet::String::Action do when_invoked { baz } end end - string.foo.should == 25 - string.bar.should == "the value of foo is '25'" - string.quux.should == "qux told me the value of foo in baz is '25'" - string.baz.should == "the value of foo in baz is '25'" - string.qux.should == "the value of foo in baz is '25'" + face.foo.should == 25 + face.bar.should == "the value of foo is '25'" + face.quux.should == "qux told me the value of foo in baz is '25'" + face.baz.should == "the value of foo in baz is '25'" + face.qux.should == "the value of foo in baz is '25'" end context "when calling the Ruby API" do - let :string do - Puppet::String.new(:ruby_api, '1.0.0') do + let :face do + Puppet::Faces.new(:ruby_api, '1.0.0') do action :bar do when_invoked do |options| options @@ -75,12 +75,12 @@ describe Puppet::String::Action do end it "should work when no options are supplied" do - options = string.bar + options = face.bar options.should == {} end it "should work when options are supplied" do - options = string.bar :bar => "beer" + options = face.bar :bar => "beer" options.should == { :bar => "beer" } end end @@ -88,7 +88,7 @@ describe Puppet::String::Action do describe "with action-level options" do it "should support options with an empty block" do - string = Puppet::String.new(:action_level_options, '0.0.1') do + face = Puppet::Faces.new(:action_level_options, '0.0.1') do action :foo do option "--bar" do # this line left deliberately blank @@ -96,33 +96,33 @@ describe Puppet::String::Action do end end - string.should_not be_option :bar - string.get_action(:foo).should be_option :bar + face.should_not be_option :bar + face.get_action(:foo).should be_option :bar end - it "should return only action level options when there are no string options" do - string = Puppet::String.new(:action_level_options, '0.0.1') do + it "should return only action level options when there are no face options" do + face = Puppet::Faces.new(:action_level_options, '0.0.1') do action :foo do option "--bar" end end - string.get_action(:foo).options.should =~ [:bar] + face.get_action(:foo).options.should =~ [:bar] end - describe "with both string and action options" do - let :string do - Puppet::String.new(:action_level_options, '0.0.1') do + describe "with both face and action options" do + let :face do + Puppet::Faces.new(:action_level_options, '0.0.1') do action :foo do option "--bar" end action :baz do option "--bim" end option "--quux" end end - it "should return combined string and action options" do - string.get_action(:foo).options.should =~ [:bar, :quux] + it "should return combined face and action options" do + face.get_action(:foo).options.should =~ [:bar, :quux] end - it "should fetch options that the string inherited" do - parent = Class.new(Puppet::String) + it "should fetch options that the face inherited" do + parent = Class.new(Puppet::Faces) parent.option "--foo" child = parent.new(:inherited_options, '0.0.1') do option "--bar" @@ -133,37 +133,37 @@ describe Puppet::String::Action do action.should be [:baz, :bar, :foo].each do |name| - action.get_option(name).should be_an_instance_of Puppet::String::Option + action.get_option(name).should be_an_instance_of Puppet::Faces::Option end end it "should get an action option when asked" do - string.get_action(:foo).get_option(:bar). - should be_an_instance_of Puppet::String::Option + face.get_action(:foo).get_option(:bar). + should be_an_instance_of Puppet::Faces::Option end - it "should get a string option when asked" do - string.get_action(:foo).get_option(:quux). - should be_an_instance_of Puppet::String::Option + it "should get a face option when asked" do + face.get_action(:foo).get_option(:quux). + should be_an_instance_of Puppet::Faces::Option end it "should return options only for this action" do - string.get_action(:baz).options.should =~ [:bim, :quux] + face.get_action(:baz).options.should =~ [:bim, :quux] end end it_should_behave_like "things that declare options" do def add_options_to(&block) - string = Puppet::String.new(:with_options, '0.0.1') do + face = Puppet::Faces.new(:with_options, '0.0.1') do action(:foo, &block) end - string.get_action(:foo) + face.get_action(:foo) end end - it "should fail when a string option duplicates an action option" do + it "should fail when a face option duplicates an action option" do expect { - Puppet::String.new(:action_level_options, '0.0.1') do + Puppet::Faces.new(:action_level_options, '0.0.1') do option "--foo" action :bar do option "--foo" end end diff --git a/spec/unit/faces/catalog_spec.rb b/spec/unit/faces/catalog_spec.rb index 70dadd54b..7621a864a 100755 --- a/spec/unit/faces/catalog_spec.rb +++ b/spec/unit/faces/catalog_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:catalog, '0.0.1'] do +describe Puppet::Faces[:catalog, '0.0.1'] do end diff --git a/spec/unit/faces/certificate_request_spec.rb b/spec/unit/faces/certificate_request_spec.rb index d0a8288b3..637ea8c38 100755 --- a/spec/unit/faces/certificate_request_spec.rb +++ b/spec/unit/faces/certificate_request_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:certificate_request, '0.0.1'] do +describe Puppet::Faces[:certificate_request, '0.0.1'] do end diff --git a/spec/unit/faces/certificate_revocation_list_spec.rb b/spec/unit/faces/certificate_revocation_list_spec.rb index 9168fb8ce..e319b18e2 100755 --- a/spec/unit/faces/certificate_revocation_list_spec.rb +++ b/spec/unit/faces/certificate_revocation_list_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:certificate_revocation_list, '0.0.1'] do +describe Puppet::Faces[:certificate_revocation_list, '0.0.1'] do end diff --git a/spec/unit/faces/certificate_spec.rb b/spec/unit/faces/certificate_spec.rb index 9fdc5aab8..ba264f967 100755 --- a/spec/unit/faces/certificate_spec.rb +++ b/spec/unit/faces/certificate_spec.rb @@ -1,12 +1,12 @@ require 'puppet/ssl/host' -describe Puppet::String[:certificate, '0.0.1'] do +describe Puppet::Faces[:certificate, '0.0.1'] do it "should have a ca-location option" do subject.should be_option :ca_location end it "should set the ca location when invoked" do - pending "#6983: This is broken in the actual string..." + pending "#6983: This is broken in the actual faces..." Puppet::SSL::Host.expects(:ca_location=).with(:foo) Puppet::SSL::Host.indirection.expects(:save) subject.sign :ca_location => :foo diff --git a/spec/unit/faces/config_spec.rb b/spec/unit/faces/config_spec.rb index 9919fef87..2eb04a81b 100755 --- a/spec/unit/faces/config_spec.rb +++ b/spec/unit/faces/config_spec.rb @@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:config, '0.0.1'] do +describe Puppet::Faces[:config, '0.0.1'] do it "should use Settings#print_config_options when asked to print" do Puppet.settings.stubs(:puts) Puppet.settings.expects(:print_config_options) diff --git a/spec/unit/faces/configurer_spec.rb b/spec/unit/faces/configurer_spec.rb index 1b428ef20..270888ca1 100755 --- a/spec/unit/faces/configurer_spec.rb +++ b/spec/unit/faces/configurer_spec.rb @@ -4,7 +4,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') require 'puppet/indirector/catalog/rest' require 'tempfile' -describe Puppet::String[:configurer, '0.0.1'] do +describe Puppet::Faces[:configurer, '0.0.1'] do describe "#synchronize" do it "should retrieve and apply a catalog and return a report" do dirname = Dir.mktmpdir("puppetdir") diff --git a/spec/unit/faces/face_collection_spec.rb b/spec/unit/faces/face_collection_spec.rb new file mode 100755 index 000000000..30147a548 --- /dev/null +++ b/spec/unit/faces/face_collection_spec.rb @@ -0,0 +1,184 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'tmpdir' + +describe Puppet::Faces::FaceCollection do + # To avoid cross-pollution we have to save and restore both the hash + # containing all the faces data, and the array used by require. Restoring + # both means that we don't leak side-effects across the code. --daniel 2011-04-06 + before :each do + @original_faces = subject.instance_variable_get("@faces").dup + @original_required = $".dup + subject.instance_variable_get("@faces").clear + end + + after :each do + subject.instance_variable_set("@faces", @original_faces) + $".clear ; @original_required.each do |item| $" << item end + end + + describe "::faces" do + it "REVISIT: should have some tests here, if we describe it" + end + + describe "::validate_version" do + it 'should permit three number versions' do + subject.validate_version('10.10.10').should == true + end + + it 'should permit versions with appended descriptions' do + subject.validate_version('10.10.10beta').should == true + end + + it 'should not permit versions with more than three numbers' do + subject.validate_version('1.2.3.4').should == false + end + + it 'should not permit versions with only two numbers' do + subject.validate_version('10.10').should == false + end + + it 'should not permit versions with only one number' do + subject.validate_version('123').should == false + end + + it 'should not permit versions with text in any position but at the end' do + subject.validate_version('v1.1.1').should == false + end + end + + describe "::[]" do + before :each do + subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + end + + before :each do + @dir = Dir.mktmpdir + @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'faces') + $LOAD_PATH.push(@dir) + end + + after :each do + FileUtils.remove_entry_secure @dir + $LOAD_PATH.pop + end + + it "should return the faces with the given name" do + subject["foo", '0.0.1'].should == 10 + end + + it "should attempt to load the faces if it isn't found" do + subject.expects(:require).with('puppet/faces/bar') + subject["bar", '0.0.1'] + end + + it "should attempt to load the default faces for the specified version :current" do + subject.expects(:require).never # except... + subject.expects(:require).with('puppet/faces/fozzie') + subject['fozzie', :current] + end + end + + describe "::face?" do + before :each do + subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + end + + it "should return true if the faces specified is registered" do + subject.face?("foo", '0.0.1').should == true + end + + it "should attempt to require the faces if it is not registered" do + subject.expects(:require).with do |file| + subject.instance_variable_get("@faces")[:bar]['0.0.1'] = true + file == 'puppet/faces/bar' + end + subject.face?("bar", '0.0.1').should == true + end + + it "should return true if requiring the faces registered it" do + subject.stubs(:require).with do + subject.instance_variable_get("@faces")[:bar]['0.0.1'] = 20 + end + end + + it "should return false if the faces is not registered" do + subject.stubs(:require).returns(true) + subject.face?("bar", '0.0.1').should be_false + end + + it "should return false if the faces file itself is missing" do + subject.stubs(:require). + raises(LoadError, 'no such file to load -- puppet/faces/bar') + subject.face?("bar", '0.0.1').should be_false + end + + it "should register the version loaded by `:current` as `:current`" do + subject.expects(:require).with do |file| + subject.instance_variable_get("@faces")[:huzzah]['2.0.1'] = :huzzah_faces + file == 'puppet/faces/huzzah' + end + subject.face?("huzzah", :current) + subject.instance_variable_get("@faces")[:huzzah][:current].should == :huzzah_faces + end + + context "with something on disk" do + before :each do + write_scratch_faces :huzzah do |fh| + fh.puts < {'0.0.1' => faces}} + end + end + + describe "::underscorize" do + faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] + valid = { + "Foo" => :foo, + :Foo => :foo, + "foo_bar" => :foo_bar, + :foo_bar => :foo_bar, + "foo-bar" => :foo_bar, + :"foo-bar" => :foo_bar, + } + + valid.each do |input, expect| + it "should map #{input.inspect} to #{expect.inspect}" do + result = subject.underscorize(input) + result.should == expect + end + end + + faulty.each do |input| + it "should fail when presented with #{input.inspect} (#{input.class})" do + expect { subject.underscorize(input) }. + should raise_error ArgumentError, /not a valid face name/ + end + end + end +end diff --git a/spec/unit/faces/faces_collection_spec.rb b/spec/unit/faces/faces_collection_spec.rb deleted file mode 100755 index fab647da0..000000000 --- a/spec/unit/faces/faces_collection_spec.rb +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'tmpdir' - -describe Puppet::String::StringCollection do - # To avoid cross-pollution we have to save and restore both the hash - # containing all the string data, and the array used by require. Restoring - # both means that we don't leak side-effects across the code. --daniel 2011-04-06 - before :each do - @original_strings = subject.instance_variable_get("@strings").dup - @original_required = $".dup - subject.instance_variable_get("@strings").clear - end - - after :each do - subject.instance_variable_set("@strings", @original_strings) - $".clear ; @original_required.each do |item| $" << item end - end - - describe "::strings" do - it "REVISIT: should have some tests here, if we describe it" - end - - describe "::validate_version" do - it 'should permit three number versions' do - subject.validate_version('10.10.10').should == true - end - - it 'should permit versions with appended descriptions' do - subject.validate_version('10.10.10beta').should == true - end - - it 'should not permit versions with more than three numbers' do - subject.validate_version('1.2.3.4').should == false - end - - it 'should not permit versions with only two numbers' do - subject.validate_version('10.10').should == false - end - - it 'should not permit versions with only one number' do - subject.validate_version('123').should == false - end - - it 'should not permit versions with text in any position but at the end' do - subject.validate_version('v1.1.1').should == false - end - end - - describe "::[]" do - before :each do - subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 - end - - before :each do - @dir = Dir.mktmpdir - @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'string') - $LOAD_PATH.push(@dir) - end - - after :each do - FileUtils.remove_entry_secure @dir - $LOAD_PATH.pop - end - - it "should return the string with the given name" do - subject["foo", '0.0.1'].should == 10 - end - - it "should attempt to load the string if it isn't found" do - subject.expects(:require).with('puppet/string/bar') - subject["bar", '0.0.1'] - end - - it "should attempt to load the default string for the specified version :current" do - subject.expects(:require).never # except... - subject.expects(:require).with('puppet/string/fozzie') - subject['fozzie', :current] - end - end - - describe "::string?" do - before :each do - subject.instance_variable_get("@strings")[:foo]['0.0.1'] = 10 - end - - it "should return true if the string specified is registered" do - subject.string?("foo", '0.0.1').should == true - end - - it "should attempt to require the string if it is not registered" do - subject.expects(:require).with do |file| - subject.instance_variable_get("@strings")[:bar]['0.0.1'] = true - file == 'puppet/string/bar' - end - subject.string?("bar", '0.0.1').should == true - end - - it "should return true if requiring the string registered it" do - subject.stubs(:require).with do - subject.instance_variable_get("@strings")[:bar]['0.0.1'] = 20 - end - end - - it "should return false if the string is not registered" do - subject.stubs(:require).returns(true) - subject.string?("bar", '0.0.1').should be_false - end - - it "should return false if the string file itself is missing" do - subject.stubs(:require). - raises(LoadError, 'no such file to load -- puppet/string/bar') - subject.string?("bar", '0.0.1').should be_false - end - - it "should register the version loaded by `:current` as `:current`" do - subject.expects(:require).with do |file| - subject.instance_variable_get("@strings")[:huzzah]['2.0.1'] = :huzzah_string - file == 'puppet/string/huzzah' - end - subject.string?("huzzah", :current) - subject.instance_variable_get("@strings")[:huzzah][:current].should == :huzzah_string - end - - context "with something on disk" do - before :each do - write_scratch_string :huzzah do |fh| - fh.puts < {'0.0.1' => string}} - end - end - - describe "::underscorize" do - faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] - valid = { - "Foo" => :foo, - :Foo => :foo, - "foo_bar" => :foo_bar, - :foo_bar => :foo_bar, - "foo-bar" => :foo_bar, - :"foo-bar" => :foo_bar, - } - - valid.each do |input, expect| - it "should map #{input.inspect} to #{expect.inspect}" do - result = subject.underscorize(input) - result.should == expect - end - end - - faulty.each do |input| - it "should fail when presented with #{input.inspect} (#{input.class})" do - expect { subject.underscorize(input) }. - should raise_error ArgumentError, /not a valid string name/ - end - end - end -end diff --git a/spec/unit/faces/facts_spec.rb b/spec/unit/faces/facts_spec.rb index 9b7024724..480f463e4 100755 --- a/spec/unit/faces/facts_spec.rb +++ b/spec/unit/faces/facts_spec.rb @@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:facts, '0.0.1'] do +describe Puppet::Faces[:facts, '0.0.1'] do it "should define an 'upload' fact" do subject.should be_action(:upload) end diff --git a/spec/unit/faces/file_spec.rb b/spec/unit/faces/file_spec.rb index f1b9302be..aafa88ce0 100755 --- a/spec/unit/faces/file_spec.rb +++ b/spec/unit/faces/file_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:file, '0.0.1'] do +describe Puppet::Faces[:file, '0.0.1'] do end diff --git a/spec/unit/faces/indirector_spec.rb b/spec/unit/faces/indirector_spec.rb index cb85eaa05..218694bd9 100755 --- a/spec/unit/faces/indirector_spec.rb +++ b/spec/unit/faces/indirector_spec.rb @@ -1,11 +1,11 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/string/indirector' +require 'puppet/faces/indirector' -describe Puppet::String::Indirector do +describe Puppet::Faces::Indirector do subject do - instance = Puppet::String::Indirector.new(:test, '0.0.1') + instance = Puppet::Faces::Indirector.new(:test, '0.0.1') indirection = stub('indirection', :name => :stub_indirection, :reset_terminus_class => nil) @@ -14,24 +14,24 @@ describe Puppet::String::Indirector do end it "should be able to return a list of indirections" do - Puppet::String::Indirector.indirections.should be_include("catalog") + Puppet::Faces::Indirector.indirections.should be_include("catalog") end it "should be able to return a list of terminuses for a given indirection" do - Puppet::String::Indirector.terminus_classes(:catalog).should be_include("compiler") + Puppet::Faces::Indirector.terminus_classes(:catalog).should be_include("compiler") end describe "as an instance" do it "should be able to determine its indirection" do # Loading actions here an get, um, complicated - Puppet::String.stubs(:load_actions) - Puppet::String::Indirector.new(:catalog, '0.0.1').indirection.should equal(Puppet::Resource::Catalog.indirection) + Puppet::Faces.stubs(:load_actions) + Puppet::Faces::Indirector.new(:catalog, '0.0.1').indirection.should equal(Puppet::Resource::Catalog.indirection) end end [:find, :search, :save, :destroy].each do |method| it "should define a '#{method}' action" do - Puppet::String::Indirector.should be_action(method) + Puppet::Faces::Indirector.should be_action(method) end it "should call the indirection method when the '#{method}' action is invoked" do @@ -51,6 +51,6 @@ describe Puppet::String::Indirector do end it "should define a class-level 'info' action" do - Puppet::String::Indirector.should be_action(:info) + Puppet::Faces::Indirector.should be_action(:info) end end diff --git a/spec/unit/faces/key_spec.rb b/spec/unit/faces/key_spec.rb index fe3532d23..70d4a52c1 100755 --- a/spec/unit/faces/key_spec.rb +++ b/spec/unit/faces/key_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:key, '0.0.1'] do +describe Puppet::Faces[:key, '0.0.1'] do end diff --git a/spec/unit/faces/node_spec.rb b/spec/unit/faces/node_spec.rb index 520cc0f5e..4639bdf63 100755 --- a/spec/unit/faces/node_spec.rb +++ b/spec/unit/faces/node_spec.rb @@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:node, '0.0.1'] do +describe Puppet::Faces[:node, '0.0.1'] do it "should set its default format to :yaml" do subject.default_format.should == :yaml end diff --git a/spec/unit/faces/option_builder_spec.rb b/spec/unit/faces/option_builder_spec.rb index 9e913c29c..9296ba7b6 100644 --- a/spec/unit/faces/option_builder_spec.rb +++ b/spec/unit/faces/option_builder_spec.rb @@ -1,28 +1,28 @@ -require 'puppet/string/option_builder' +require 'puppet/faces/option_builder' -describe Puppet::String::OptionBuilder do - let :string do Puppet::String.new(:option_builder_testing, '0.0.1') end +describe Puppet::Faces::OptionBuilder do + let :face do Puppet::Faces.new(:option_builder_testing, '0.0.1') end it "should be able to construct an option without a block" do - Puppet::String::OptionBuilder.build(string, "--foo"). - should be_an_instance_of Puppet::String::Option + Puppet::Faces::OptionBuilder.build(face, "--foo"). + should be_an_instance_of Puppet::Faces::Option end describe "when using the DSL block" do it "should work with an empty block" do - option = Puppet::String::OptionBuilder.build(string, "--foo") do + option = Puppet::Faces::OptionBuilder.build(face, "--foo") do # This block deliberately left blank. end - option.should be_an_instance_of Puppet::String::Option + option.should be_an_instance_of Puppet::Faces::Option end it "should support documentation declarations" do text = "this is the description" - option = Puppet::String::OptionBuilder.build(string, "--foo") do + option = Puppet::Faces::OptionBuilder.build(face, "--foo") do desc text end - option.should be_an_instance_of Puppet::String::Option + option.should be_an_instance_of Puppet::Faces::Option option.desc.should == text end end diff --git a/spec/unit/faces/option_spec.rb b/spec/unit/faces/option_spec.rb index f4f62ec37..a28fef087 100644 --- a/spec/unit/faces/option_spec.rb +++ b/spec/unit/faces/option_spec.rb @@ -1,14 +1,14 @@ -require 'puppet/string/option' +require 'puppet/faces/option' -describe Puppet::String::Option do - let :string do Puppet::String.new(:option_testing, '0.0.1') end +describe Puppet::Faces::Option do + let :face do Puppet::Faces.new(:option_testing, '0.0.1') end describe "#optparse_to_name" do ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix| { "--foo" => :foo, "-f" => :f }.each do |base, expect| input = base + postfix it "should map #{input.inspect} to #{expect.inspect}" do - option = Puppet::String::Option.new(string, input) + option = Puppet::Faces::Option.new(face, input) option.name.should == expect end end @@ -16,58 +16,58 @@ describe Puppet::String::Option do [:foo, 12, nil, {}, []].each do |input| it "should fail sensible when given #{input.inspect}" do - expect { Puppet::String::Option.new(string, input) }. + expect { Puppet::Faces::Option.new(face, input) }. should raise_error ArgumentError, /is not valid for an option argument/ end end ["-foo", "-foo=BAR", "-foo BAR"].each do |input| it "should fail with a single dash for long option #{input.inspect}" do - expect { Puppet::String::Option.new(string, input) }. + expect { Puppet::Faces::Option.new(face, input) }. should raise_error ArgumentError, /long options need two dashes \(--\)/ end end end - it "requires a string when created" do - expect { Puppet::String::Option.new }. + it "requires a face when created" do + expect { Puppet::Faces::Option.new }. should raise_error ArgumentError, /wrong number of arguments/ end it "also requires some declaration arguments when created" do - expect { Puppet::String::Option.new(string) }. + expect { Puppet::Faces::Option.new(face) }. should raise_error ArgumentError, /No option declarations found/ end it "should infer the name from an optparse string" do - option = Puppet::String::Option.new(string, "--foo") + option = Puppet::Faces::Option.new(face, "--foo") option.name.should == :foo end - it "should infer the name when multiple optparse strings are given" do - option = Puppet::String::Option.new(string, "--foo", "-f") + it "should infer the name when multiple optparse string are given" do + option = Puppet::Faces::Option.new(face, "--foo", "-f") option.name.should == :foo end it "should prefer the first long option name over a short option name" do - option = Puppet::String::Option.new(string, "-f", "--foo") + option = Puppet::Faces::Option.new(face, "-f", "--foo") option.name.should == :foo end - it "should create an instance when given a string and name" do - Puppet::String::Option.new(string, "--foo"). - should be_instance_of Puppet::String::Option + it "should create an instance when given a face and name" do + Puppet::Faces::Option.new(face, "--foo"). + should be_instance_of Puppet::Faces::Option end describe "#to_s" do it "should transform a symbol into a string" do - option = Puppet::String::Option.new(string, "--foo") + option = Puppet::Faces::Option.new(face, "--foo") option.name.should == :foo option.to_s.should == "foo" end it "should use - rather than _ to separate words in strings but not symbols" do - option = Puppet::String::Option.new(string, "--foo-bar") + option = Puppet::Faces::Option.new(face, "--foo-bar") option.name.should == :foo_bar option.to_s.should == "foo-bar" end diff --git a/spec/unit/faces/report_spec.rb b/spec/unit/faces/report_spec.rb index 2e206dd06..f7a67349c 100755 --- a/spec/unit/faces/report_spec.rb +++ b/spec/unit/faces/report_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:report, '0.0.1'] do +describe Puppet::Faces[:report, '0.0.1'] do end diff --git a/spec/unit/faces/resource_spec.rb b/spec/unit/faces/resource_spec.rb index f4e618616..0b4b24882 100755 --- a/spec/unit/faces/resource_spec.rb +++ b/spec/unit/faces/resource_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:resource, '0.0.1'] do +describe Puppet::Faces[:resource, '0.0.1'] do end diff --git a/spec/unit/faces/resource_type_spec.rb b/spec/unit/faces/resource_type_spec.rb index d12ec00ce..157066f2d 100755 --- a/spec/unit/faces/resource_type_spec.rb +++ b/spec/unit/faces/resource_type_spec.rb @@ -2,5 +2,5 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -describe Puppet::String[:resource_type, '0.0.1'] do +describe Puppet::Faces[:resource_type, '0.0.1'] do end diff --git a/spec/unit/faces_spec.rb b/spec/unit/faces_spec.rb index 9b7cd887c..586abd6b5 100755 --- a/spec/unit/faces_spec.rb +++ b/spec/unit/faces_spec.rb @@ -2,46 +2,47 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb') -describe Puppet::String do +describe Puppet::Faces do before :all do - @strings = Puppet::String::StringCollection.instance_variable_get("@strings").dup + @faces = Puppet::Faces::FaceCollection.instance_variable_get("@faces").dup end before :each do - Puppet::String::StringCollection.instance_variable_get("@strings").clear + Puppet::Faces::FaceCollection.instance_variable_get("@faces").clear end after :all do - Puppet::String::StringCollection.instance_variable_set("@strings", @strings) + Puppet::Faces::FaceCollection.instance_variable_set("@faces", @faces) end describe "#define" do - it "should register the string" do - string = Puppet::String.define(:string_test_register, '0.0.1') - string.should == Puppet::String[:string_test_register, '0.0.1'] + it "should register the face" do + face = Puppet::Faces.define(:face_test_register, '0.0.1') + face.should == Puppet::Faces[:face_test_register, '0.0.1'] end it "should load actions" do - Puppet::String.any_instance.expects(:load_actions) - Puppet::String.define(:string_test_load_actions, '0.0.1') + Puppet::Faces.any_instance.expects(:load_actions) + Puppet::Faces.define(:face_test_load_actions, '0.0.1') end it "should require a version number" do - proc { Puppet::String.define(:no_version) }.should raise_error(ArgumentError) + expect { Puppet::Faces.define(:no_version) }.should raise_error ArgumentError end end describe "#initialize" do it "should require a version number" do - proc { Puppet::String.new(:no_version) }.should raise_error(ArgumentError) + expect { Puppet::Faces.new(:no_version) }.should raise_error ArgumentError end it "should require a valid version number" do - proc { Puppet::String.new(:bad_version, 'Rasins') }.should raise_error(ArgumentError) + expect { Puppet::Faces.new(:bad_version, 'Rasins') }. + should raise_error ArgumentError end it "should instance-eval any provided block" do - face = Puppet::String.new(:string_test_block, '0.0.1') do + face = Puppet::Faces.new(:face_test_block, '0.0.1') do action(:something) do when_invoked { "foo" } end @@ -52,31 +53,31 @@ describe Puppet::String do end it "should have a name" do - Puppet::String.new(:me, '0.0.1').name.should == :me + Puppet::Faces.new(:me, '0.0.1').name.should == :me end it "should stringify with its own name" do - Puppet::String.new(:me, '0.0.1').to_s.should =~ /\bme\b/ + Puppet::Faces.new(:me, '0.0.1').to_s.should =~ /\bme\b/ end it "should allow overriding of the default format" do - face = Puppet::String.new(:me, '0.0.1') + face = Puppet::Faces.new(:me, '0.0.1') face.set_default_format :foo face.default_format.should == :foo end it "should default to :pson for its format" do - Puppet::String.new(:me, '0.0.1').default_format.should == :pson + Puppet::Faces.new(:me, '0.0.1').default_format.should == :pson end # Why? it "should create a class-level autoloader" do - Puppet::String.autoloader.should be_instance_of(Puppet::Util::Autoload) + Puppet::Faces.autoloader.should be_instance_of(Puppet::Util::Autoload) end - it "should try to require strings that are not known" do - Puppet::String::StringCollection.expects(:require).with "puppet/string/foo" - Puppet::String[:foo, '0.0.1'] + it "should try to require faces that are not known" do + Puppet::Faces::FaceCollection.expects(:require).with "puppet/faces/foo" + Puppet::Faces[:foo, '0.0.1'] end it "should be able to load all actions in all search paths" @@ -84,25 +85,25 @@ describe Puppet::String do it_should_behave_like "things that declare options" do def add_options_to(&block) - Puppet::String.new(:with_options, '0.0.1', &block) + Puppet::Faces.new(:with_options, '0.0.1', &block) end end - describe "with string-level options" do + describe "with face-level options" do it "should not return any action-level options" do - string = Puppet::String.new(:with_options, '0.0.1') do + face = Puppet::Faces.new(:with_options, '0.0.1') do option "--foo" option "--bar" action :baz do option "--quux" end end - string.options.should =~ [:foo, :bar] + face.options.should =~ [:foo, :bar] end - it "should fail when a string option duplicates an action option" do + it "should fail when a face option duplicates an action option" do expect { - Puppet::String.new(:action_level_options, '0.0.1') do + Puppet::Faces.new(:action_level_options, '0.0.1') do action :bar do option "--foo" end option "--foo" end @@ -110,34 +111,34 @@ describe Puppet::String do end it "should work when two actions have the same option" do - string = Puppet::String.new(:with_options, '0.0.1') do + face = Puppet::Faces.new(:with_options, '0.0.1') do action :foo do option "--quux" end action :bar do option "--quux" end end - string.get_action(:foo).options.should =~ [:quux] - string.get_action(:bar).options.should =~ [:quux] + face.get_action(:foo).options.should =~ [:quux] + face.get_action(:bar).options.should =~ [:quux] end end describe "with inherited options" do - let :string do - parent = Class.new(Puppet::String) + let :face do + parent = Class.new(Puppet::Faces) parent.option("--inherited") - string = parent.new(:example, '0.2.1') - string.option("--local") - string + face = parent.new(:example, '0.2.1') + face.option("--local") + face end describe "#options" do it "should list inherited options" do - string.options.should =~ [:inherited, :local] + face.options.should =~ [:inherited, :local] end end describe "#get_option" do it "should return an inherited option object" do - string.get_option(:inherited).should be_an_instance_of Puppet::String::Option + face.get_option(:inherited).should be_an_instance_of Puppet::Faces::Option end end end -- cgit From 87ed3188e65d3f5f9c2c32a409b271d1b39684b9 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 7 Apr 2011 15:44:28 -0700 Subject: (#7012) Split plumbing into Puppet::Interface This splits out the plumbing into the Puppet::Interface namespace, and uses Puppet::Faces for all the public-facing code. The fault line is "what you care about if you are using or writing a face", which is public, against "what you care about to enable either of those two", which is the plumbing. --- lib/puppet/faces.rb | 118 ++--------- lib/puppet/faces/action.rb | 119 ----------- lib/puppet/faces/action_builder.rb | 31 --- lib/puppet/faces/action_manager.rb | 49 ----- lib/puppet/faces/face_collection.rb | 125 ----------- lib/puppet/faces/option.rb | 82 -------- lib/puppet/faces/option_builder.rb | 25 --- lib/puppet/faces/option_manager.rb | 56 ----- lib/puppet/interface.rb | 106 ++++++++++ lib/puppet/interface/action.rb | 119 +++++++++++ lib/puppet/interface/action_builder.rb | 31 +++ lib/puppet/interface/action_manager.rb | 49 +++++ lib/puppet/interface/face_collection.rb | 125 +++++++++++ lib/puppet/interface/option.rb | 82 ++++++++ lib/puppet/interface/option_builder.rb | 25 +++ lib/puppet/interface/option_manager.rb | 56 +++++ spec/unit/faces/action_builder_spec.rb | 59 ------ spec/unit/faces/action_manager_spec.rb | 233 --------------------- spec/unit/faces/action_spec.rb | 173 --------------- spec/unit/faces/catalog_spec.rb | 5 +- spec/unit/faces/certificate_request_spec.rb | 5 +- .../unit/faces/certificate_revocation_list_spec.rb | 5 +- spec/unit/faces/face_collection_spec.rb | 184 ---------------- spec/unit/faces/file_spec.rb | 5 +- spec/unit/faces/key_spec.rb | 5 +- spec/unit/faces/option_builder_spec.rb | 29 --- spec/unit/faces/option_spec.rb | 75 ------- spec/unit/faces/report_spec.rb | 5 +- spec/unit/faces/resource_spec.rb | 5 +- spec/unit/faces/resource_type_spec.rb | 5 +- spec/unit/faces_spec.rb | 145 ------------- spec/unit/interface/action_builder_spec.rb | 59 ++++++ spec/unit/interface/action_manager_spec.rb | 233 +++++++++++++++++++++ spec/unit/interface/action_spec.rb | 173 +++++++++++++++ spec/unit/interface/face_collection_spec.rb | 184 ++++++++++++++++ spec/unit/interface/option_builder_spec.rb | 29 +++ spec/unit/interface/option_spec.rb | 75 +++++++ spec/unit/interface_spec.rb | 146 +++++++++++++ 38 files changed, 1512 insertions(+), 1523 deletions(-) delete mode 100644 lib/puppet/faces/action.rb delete mode 100644 lib/puppet/faces/action_builder.rb delete mode 100644 lib/puppet/faces/action_manager.rb delete mode 100644 lib/puppet/faces/face_collection.rb delete mode 100644 lib/puppet/faces/option.rb delete mode 100644 lib/puppet/faces/option_builder.rb delete mode 100644 lib/puppet/faces/option_manager.rb create mode 100644 lib/puppet/interface.rb create mode 100644 lib/puppet/interface/action.rb create mode 100644 lib/puppet/interface/action_builder.rb create mode 100644 lib/puppet/interface/action_manager.rb create mode 100644 lib/puppet/interface/face_collection.rb create mode 100644 lib/puppet/interface/option.rb create mode 100644 lib/puppet/interface/option_builder.rb create mode 100644 lib/puppet/interface/option_manager.rb delete mode 100755 spec/unit/faces/action_builder_spec.rb delete mode 100755 spec/unit/faces/action_manager_spec.rb delete mode 100755 spec/unit/faces/action_spec.rb delete mode 100755 spec/unit/faces/face_collection_spec.rb delete mode 100644 spec/unit/faces/option_builder_spec.rb delete mode 100644 spec/unit/faces/option_spec.rb delete mode 100755 spec/unit/faces_spec.rb create mode 100755 spec/unit/interface/action_builder_spec.rb create mode 100755 spec/unit/interface/action_manager_spec.rb create mode 100755 spec/unit/interface/action_spec.rb create mode 100755 spec/unit/interface/face_collection_spec.rb create mode 100644 spec/unit/interface/option_builder_spec.rb create mode 100644 spec/unit/interface/option_spec.rb create mode 100755 spec/unit/interface_spec.rb diff --git a/lib/puppet/faces.rb b/lib/puppet/faces.rb index 07a745480..947eecf24 100644 --- a/lib/puppet/faces.rb +++ b/lib/puppet/faces.rb @@ -1,106 +1,12 @@ -require 'puppet' -require 'puppet/util/autoload' - -class Puppet::Faces - require 'puppet/faces/face_collection' - - require 'puppet/faces/action_manager' - include Puppet::Faces::ActionManager - extend Puppet::Faces::ActionManager - - require 'puppet/faces/option_manager' - include Puppet::Faces::OptionManager - extend Puppet::Faces::OptionManager - - include Puppet::Util - - class << self - # This is just so we can search for actions. We only use its - # list of directories to search. - # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb - def autoloader - @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/faces") - end - - def faces - Puppet::Faces::FaceCollection.faces - end - - def face?(name, version) - Puppet::Faces::FaceCollection.face?(name, version) - end - - def register(instance) - Puppet::Faces::FaceCollection.register(instance) - end - - def define(name, version, &block) - if face?(name, version) - face = Puppet::Faces::FaceCollection[name, version] - else - face = self.new(name, version) - Puppet::Faces::FaceCollection.register(face) - # REVISIT: Shouldn't this be delayed until *after* we evaluate the - # current block, not done before? --daniel 2011-04-07 - face.load_actions - end - - face.instance_eval(&block) if block_given? - - return face - end - - alias :[] :define - end - - attr_accessor :default_format - - def set_default_format(format) - self.default_format = format.to_sym - end - - attr_accessor :type, :verb, :version, :arguments - attr_reader :name - - def initialize(name, version, &block) - unless Puppet::Faces::FaceCollection.validate_version(version) - raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" - end - - @name = Puppet::Faces::FaceCollection.underscorize(name) - @version = version - @default_format = :pson - - instance_eval(&block) if block_given? - end - - # Try to find actions defined in other files. - def load_actions - path = "puppet/faces/#{name}" - - loaded = [] - [path, "#{name}@#{version}/#{path}"].each do |path| - Puppet::Faces.autoloader.search_directories.each do |dir| - fdir = ::File.join(dir, path) - next unless FileTest.directory?(fdir) - - Dir.chdir(fdir) do - Dir.glob("*.rb").each do |file| - aname = file.sub(/\.rb/, '') - if loaded.include?(aname) - Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - next - end - loaded << aname - Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" - require "#{Dir.pwd}/#{aname}" - end - end - end - end - end - - def to_s - "Puppet::Faces[#{name.inspect}, #{version.inspect}]" - end -end +# The public name of this feature is 'faces', but we have hidden all the +# plumbing over in the 'interfaces' namespace to make clear the distinction +# between the two. +# +# This file exists to ensure that the public name is usable without revealing +# the details of the implementation; you really only need go look at anything +# under Interfaces if you are looking to extend the implementation. +# +# It isn't hidden to gratuitously hide things, just to make it easier to +# separate out the interests people will have. --daniel 2011-04-07 +require 'puppet/interface' +Puppet::Faces = Puppet::Interface diff --git a/lib/puppet/faces/action.rb b/lib/puppet/faces/action.rb deleted file mode 100644 index 58d2c6003..000000000 --- a/lib/puppet/faces/action.rb +++ /dev/null @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/faces' -require 'puppet/faces/option' - -class Puppet::Faces::Action - def initialize(face, name, attrs = {}) - raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ - @face = face - @name = name.to_sym - @options = {} - attrs.each do |k, v| send("#{k}=", v) end - end - - attr_reader :name - def to_s() "#{@face}##{@name}" end - - - # Initially, this was defined to allow the @action.invoke pattern, which is - # a very natural way to invoke behaviour given our introspection - # capabilities. Heck, our initial plan was to have the faces delegate to - # the action object for invocation and all. - # - # It turns out that we have a binding problem to solve: @face was bound to - # the parent class, not the subclass instance, and we don't pass the - # appropriate context or change the binding enough to make this work. - # - # We could hack around it, by either mandating that you pass the context in - # to invoke, or try to get the binding right, but that has probably got - # subtleties that we don't instantly think of – especially around threads. - # - # So, we are pulling this method for now, and will return it to life when we - # have the time to resolve the problem. For now, you should replace... - # - # @action = @face.get_action(name) - # @action.invoke(arg1, arg2, arg3) - # - # ...with... - # - # @action = @face.get_action(name) - # @face.send(@action.name, arg1, arg2, arg3) - # - # I understand that is somewhat cumbersome, but it functions as desired. - # --daniel 2011-03-31 - # - # PS: This code is left present, but commented, to support this chunk of - # documentation, for the benefit of the reader. - # - # def invoke(*args, &block) - # @face.send(name, *args, &block) - # end - - def when_invoked=(block) - # We need to build an instance method as a wrapper, using normal code, to - # be able to expose argument defaulting between the caller and definer in - # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. - # - # In future this also gives us a place to hook in additional behaviour - # such as calling out to the action instance to validate and coerce - # parameters, which avoids any exciting context switching and all. - # - # Hopefully we can improve this when we finally shuffle off the last of - # Ruby 1.8 support, but that looks to be a few "enterprise" release eras - # away, so we are pretty stuck with this for now. - # - # Patches to make this work more nicely with Ruby 1.9 using runtime - # version checking and all are welcome, but they can't actually help if - # the results are not totally hidden away in here. - # - # Incidentally, we though about vendoring evil-ruby and actually adjusting - # the internal C structure implementation details under the hood to make - # this stuff work, because it would have been cleaner. Which gives you an - # idea how motivated we were to make this cleaner. Sorry. --daniel 2011-03-31 - - internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym - file = __FILE__ + "+eval" - line = __LINE__ + 1 - wrapper = "def #{@name}(*args, &block) - args << {} unless args.last.is_a? Hash - args << block if block_given? - self.__send__(#{internal_name.inspect}, *args) - end" - - if @face.is_a?(Class) - @face.class_eval do eval wrapper, nil, file, line end - @face.define_method(internal_name, &block) - else - @face.instance_eval do eval wrapper, nil, file, line end - @face.meta_def(internal_name, &block) - end - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - elsif conflict = @face.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@face}" - end - end - - option.aliases.each do |name| - @options[name] = option - end - - option - end - - def option?(name) - @options.include? name.to_sym - end - - def options - (@options.keys + @face.options).sort - end - - def get_option(name) - @options[name.to_sym] || @face.get_option(name) - end -end diff --git a/lib/puppet/faces/action_builder.rb b/lib/puppet/faces/action_builder.rb deleted file mode 100644 index a67068926..000000000 --- a/lib/puppet/faces/action_builder.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'puppet/faces' -require 'puppet/faces/action' - -class Puppet::Faces::ActionBuilder - attr_reader :action - - def self.build(face, name, &block) - raise "Action #{name.inspect} must specify a block" unless block - new(face, name, &block).action - end - - private - def initialize(face, name, &block) - @face = face - @action = Puppet::Faces::Action.new(face, name) - instance_eval(&block) - end - - # Ideally the method we're defining here would be added to the action, and a - # method on the face would defer to it, but we can't get scope correct, so - # we stick with this. --daniel 2011-03-24 - def when_invoked(&block) - raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action - @action.when_invoked = block - end - - def option(*declaration, &block) - option = Puppet::Faces::OptionBuilder.build(@action, *declaration, &block) - @action.add_option(option) - end -end diff --git a/lib/puppet/faces/action_manager.rb b/lib/puppet/faces/action_manager.rb deleted file mode 100644 index 6c0036bd8..000000000 --- a/lib/puppet/faces/action_manager.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'puppet/faces/action_builder' - -module Puppet::Faces::ActionManager - # Declare that this app can take a specific action, and provide - # the code to do so. - def action(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - action = Puppet::Faces::ActionBuilder.build(self, name, &block) - @actions[action.name] = action - end - - # This is the short-form of an action definition; it doesn't use the - # builder, just creates the action directly from the block. - def script(name, &block) - @actions ||= {} - raise "Action #{name} already defined for #{self}" if action?(name) - @actions[name] = Puppet::Faces::Action.new(self, name, :when_invoked => block) - end - - def actions - @actions ||= {} - result = @actions.keys - - if self.is_a?(Class) and superclass.respond_to?(:actions) - result += superclass.actions - elsif self.class.respond_to?(:actions) - result += self.class.actions - end - result.sort - end - - def get_action(name) - @actions ||= {} - result = @actions[name.to_sym] - if result.nil? - if self.is_a?(Class) and superclass.respond_to?(:get_action) - result = superclass.get_action(name) - elsif self.class.respond_to?(:get_action) - result = self.class.get_action(name) - end - end - return result - end - - def action?(name) - actions.include?(name.to_sym) - end -end diff --git a/lib/puppet/faces/face_collection.rb b/lib/puppet/faces/face_collection.rb deleted file mode 100644 index e6ee709d6..000000000 --- a/lib/puppet/faces/face_collection.rb +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -require 'puppet/faces' - -module Puppet::Faces::FaceCollection - SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ - - @faces = Hash.new { |hash, key| hash[key] = {} } - - def self.faces - unless @loaded - @loaded = true - $LOAD_PATH.each do |dir| - next unless FileTest.directory?(dir) - Dir.chdir(dir) do - # REVISIT: This is wrong!!!! We don't name files like that ever, - # so we should no longer match things like this. Damnit!!! --daniel 2011-04-07 - Dir.glob("puppet/faces/v*/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| - iname = file.sub(/\.rb/, '') - begin - require iname - rescue Exception => detail - puts detail.backtrace if Puppet[:trace] - raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" - end - end - end - end - end - return @faces.keys - end - - def self.validate_version(version) - !!(SEMVER_VERSION =~ version.to_s) - end - - def self.cmp_semver(a, b) - a, b = [a, b].map do |x| - parts = SEMVER_VERSION.match(x).to_a[1..4] - parts[0..2] = parts[0..2].map { |e| e.to_i } - parts - end - - cmp = a[0..2] <=> b[0..2] - if cmp == 0 - cmp = a[3] <=> b[3] - cmp = +1 if a[3].empty? && !b[3].empty? - cmp = -1 if b[3].empty? && !a[3].empty? - end - cmp - end - - def self.[](name, version) - @faces[underscorize(name)][version] if face?(name, version) - end - - def self.face?(name, version) - name = underscorize(name) - return true if @faces[name].has_key?(version) - - # We always load the current version file; the common case is that we have - # the expected version and any compatibility versions in the same file, - # the default. Which means that this is almost always the case. - # - # We use require to avoid executing the code multiple times, like any - # other Ruby library that we might want to use. --daniel 2011-04-06 - begin - require "puppet/faces/#{name}" - - # If we wanted :current, we need to index to find that; direct version - # requests just work™ as they go. --daniel 2011-04-06 - if version == :current then - # We need to find current out of this. This is the largest version - # number that doesn't have a dedicated on-disk file present; those - # represent "experimental" versions of faces, which we don't fully - # support yet. - # - # We walk the versions from highest to lowest and take the first version - # that is not defined in an explicitly versioned file on disk as the - # current version. - # - # This constrains us to only ship experimental versions with *one* - # version in the file, not multiple, but given you can't reliably load - # them except by side-effect when you ignore that rule this seems safe - # enough... - # - # Given those constraints, and that we are not going to ship a versioned - # interface that is not :current in this release, we are going to leave - # these thoughts in place, and just punt on the actual versioning. - # - # When we upgrade the core to support multiple versions we can solve the - # problems then; as lazy as possible. - # - # We do support multiple versions in the same file, though, so we sort - # versions here and return the last item in that set. - # - # --daniel 2011-04-06 - latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last - @faces[name][:current] = @faces[name][latest_ver] - end - rescue LoadError => e - raise unless e.message =~ %r{-- puppet/faces/#{name}$} - # ...guess we didn't find the file; return a much better problem. - end - - # Now, either we have the version in our set of faces, or we didn't find - # the version they were looking for. In the future we will support - # loading versioned stuff from some look-aside part of the Ruby load path, - # but we don't need that right now. - # - # So, this comment is a place-holder for that. --daniel 2011-04-06 - return !! @faces[name].has_key?(version) - end - - def self.register(face) - @faces[underscorize(face.name)][face.version] = face - end - - def self.underscorize(name) - unless name.to_s =~ /^[-_a-z]+$/i then - raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name" - end - - name.to_s.downcase.split(/[-_]/).join('_').to_sym - end -end diff --git a/lib/puppet/faces/option.rb b/lib/puppet/faces/option.rb deleted file mode 100644 index 7d3ed37ca..000000000 --- a/lib/puppet/faces/option.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'puppet/faces' - -class Puppet::Faces::Option - attr_reader :parent - attr_reader :name - attr_reader :aliases - attr_reader :optparse - attr_accessor :desc - - def takes_argument? - !!@argument - end - def optional_argument? - !!@optional_argument - end - - def initialize(parent, *declaration, &block) - @parent = parent - @optparse = [] - - # Collect and sort the arguments in the declaration. - dups = {} - declaration.each do |item| - if item.is_a? String and item.to_s =~ /^-/ then - unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then - raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" - end - @optparse << item - - # Duplicate checking... - name = optparse_to_name(item) - if dup = dups[name] then - raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" - else - dups[name] = item - end - else - raise ArgumentError, "#{item.inspect} is not valid for an option argument" - end - end - - if @optparse.empty? then - raise ArgumentError, "No option declarations found while building" - end - - # Now, infer the name from the options; we prefer the first long option as - # the name, rather than just the first option. - @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) - @aliases = @optparse.map { |o| optparse_to_name(o) } - - # Do we take an argument? If so, are we consistent about it, because - # incoherence here makes our life super-difficult, and we can more easily - # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 - @argument = @optparse.any? { |o| o =~ /[ =]/ } - if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then - raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" - end - - # Is our argument optional? The rules about consistency apply here, also, - # just like they do to taking arguments at all. --daniel 2011-03-30 - @optional_argument = @optparse.any? { |o| o.include? "[" } - if @optional_argument and not @optparse.all? { |o| o.include? "[" } then - raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" - end - end - - # to_s and optparse_to_name are roughly mirrored, because they are used to - # transform options to name symbols, and vice-versa. This isn't a full - # bidirectional transformation though. --daniel 2011-04-07 - def to_s - @name.to_s.tr('_', '-') - end - - def optparse_to_name(declaration) - unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then - raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" - end - name = found.captures.first.tr('-', '_') - raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ - name.to_sym - end -end diff --git a/lib/puppet/faces/option_builder.rb b/lib/puppet/faces/option_builder.rb deleted file mode 100644 index 0b6667546..000000000 --- a/lib/puppet/faces/option_builder.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'puppet/faces/option' - -class Puppet::Faces::OptionBuilder - attr_reader :option - - def self.build(face, *declaration, &block) - new(face, *declaration, &block).option - end - - private - def initialize(face, *declaration, &block) - @face = face - @option = Puppet::Faces::Option.new(face, *declaration) - block and instance_eval(&block) - @option - end - - # Metaprogram the simple DSL from the option class. - Puppet::Faces::Option.instance_methods.grep(/=$/).each do |setter| - next if setter =~ /^=/ # special case, darn it... - - dsl = setter.sub(/=$/, '') - define_method(dsl) do |value| @option.send(setter, value) end - end -end diff --git a/lib/puppet/faces/option_manager.rb b/lib/puppet/faces/option_manager.rb deleted file mode 100644 index 02a73afc3..000000000 --- a/lib/puppet/faces/option_manager.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'puppet/faces/option_builder' - -module Puppet::Faces::OptionManager - # Declare that this app can take a specific option, and provide - # the code to do so. - def option(*declaration, &block) - add_option Puppet::Faces::OptionBuilder.build(self, *declaration, &block) - end - - def add_option(option) - option.aliases.each do |name| - if conflict = get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" - end - - actions.each do |action| - action = get_action(action) - if conflict = action.get_option(name) then - raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" - end - end - end - - option.aliases.each { |name| @options[name] = option } - option - end - - def options - @options ||= {} - result = @options.keys - - if self.is_a?(Class) and superclass.respond_to?(:options) - result += superclass.options - elsif self.class.respond_to?(:options) - result += self.class.options - end - result.sort - end - - def get_option(name) - @options ||= {} - result = @options[name.to_sym] - unless result then - if self.is_a?(Class) and superclass.respond_to?(:get_option) - result = superclass.get_option(name) - elsif self.class.respond_to?(:get_option) - result = self.class.get_option(name) - end - end - return result - end - - def option?(name) - options.include? name.to_sym - end -end diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb new file mode 100644 index 000000000..70484adfc --- /dev/null +++ b/lib/puppet/interface.rb @@ -0,0 +1,106 @@ +require 'puppet' +require 'puppet/util/autoload' + +class Puppet::Interface + require 'puppet/interface/face_collection' + + require 'puppet/interface/action_manager' + include Puppet::Interface::ActionManager + extend Puppet::Interface::ActionManager + + require 'puppet/interface/option_manager' + include Puppet::Interface::OptionManager + extend Puppet::Interface::OptionManager + + include Puppet::Util + + class << self + # This is just so we can search for actions. We only use its + # list of directories to search. + # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb + def autoloader + @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/faces") + end + + def faces + Puppet::Interface::FaceCollection.faces + end + + def face?(name, version) + Puppet::Interface::FaceCollection.face?(name, version) + end + + def register(instance) + Puppet::Interface::FaceCollection.register(instance) + end + + def define(name, version, &block) + if face?(name, version) + face = Puppet::Interface::FaceCollection[name, version] + else + face = self.new(name, version) + Puppet::Interface::FaceCollection.register(face) + # REVISIT: Shouldn't this be delayed until *after* we evaluate the + # current block, not done before? --daniel 2011-04-07 + face.load_actions + end + + face.instance_eval(&block) if block_given? + + return face + end + + alias :[] :define + end + + attr_accessor :default_format + + def set_default_format(format) + self.default_format = format.to_sym + end + + attr_accessor :type, :verb, :version, :arguments + attr_reader :name + + def initialize(name, version, &block) + unless Puppet::Interface::FaceCollection.validate_version(version) + raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" + end + + @name = Puppet::Interface::FaceCollection.underscorize(name) + @version = version + @default_format = :pson + + instance_eval(&block) if block_given? + end + + # Try to find actions defined in other files. + def load_actions + path = "puppet/faces/#{name}" + + loaded = [] + [path, "#{name}@#{version}/#{path}"].each do |path| + Puppet::Interface.autoloader.search_directories.each do |dir| + fdir = ::File.join(dir, path) + next unless FileTest.directory?(fdir) + + Dir.chdir(fdir) do + Dir.glob("*.rb").each do |file| + aname = file.sub(/\.rb/, '') + if loaded.include?(aname) + Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + next + end + loaded << aname + Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" + require "#{Dir.pwd}/#{aname}" + end + end + end + end + end + + def to_s + "Puppet::Faces[#{name.inspect}, #{version.inspect}]" + end +end diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb new file mode 100644 index 000000000..e4a37a1f7 --- /dev/null +++ b/lib/puppet/interface/action.rb @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +require 'puppet/interface' +require 'puppet/interface/option' + +class Puppet::Interface::Action + def initialize(face, name, attrs = {}) + raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ + @face = face + @name = name.to_sym + @options = {} + attrs.each do |k, v| send("#{k}=", v) end + end + + attr_reader :name + def to_s() "#{@face}##{@name}" end + + + # Initially, this was defined to allow the @action.invoke pattern, which is + # a very natural way to invoke behaviour given our introspection + # capabilities. Heck, our initial plan was to have the faces delegate to + # the action object for invocation and all. + # + # It turns out that we have a binding problem to solve: @face was bound to + # the parent class, not the subclass instance, and we don't pass the + # appropriate context or change the binding enough to make this work. + # + # We could hack around it, by either mandating that you pass the context in + # to invoke, or try to get the binding right, but that has probably got + # subtleties that we don't instantly think of – especially around threads. + # + # So, we are pulling this method for now, and will return it to life when we + # have the time to resolve the problem. For now, you should replace... + # + # @action = @face.get_action(name) + # @action.invoke(arg1, arg2, arg3) + # + # ...with... + # + # @action = @face.get_action(name) + # @face.send(@action.name, arg1, arg2, arg3) + # + # I understand that is somewhat cumbersome, but it functions as desired. + # --daniel 2011-03-31 + # + # PS: This code is left present, but commented, to support this chunk of + # documentation, for the benefit of the reader. + # + # def invoke(*args, &block) + # @face.send(name, *args, &block) + # end + + def when_invoked=(block) + # We need to build an instance method as a wrapper, using normal code, to + # be able to expose argument defaulting between the caller and definer in + # the Ruby API. An extra method is, sadly, required for Ruby 1.8 to work. + # + # In future this also gives us a place to hook in additional behaviour + # such as calling out to the action instance to validate and coerce + # parameters, which avoids any exciting context switching and all. + # + # Hopefully we can improve this when we finally shuffle off the last of + # Ruby 1.8 support, but that looks to be a few "enterprise" release eras + # away, so we are pretty stuck with this for now. + # + # Patches to make this work more nicely with Ruby 1.9 using runtime + # version checking and all are welcome, but they can't actually help if + # the results are not totally hidden away in here. + # + # Incidentally, we though about vendoring evil-ruby and actually adjusting + # the internal C structure implementation details under the hood to make + # this stuff work, because it would have been cleaner. Which gives you an + # idea how motivated we were to make this cleaner. Sorry. --daniel 2011-03-31 + + internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym + file = __FILE__ + "+eval" + line = __LINE__ + 1 + wrapper = "def #{@name}(*args, &block) + args << {} unless args.last.is_a? Hash + args << block if block_given? + self.__send__(#{internal_name.inspect}, *args) + end" + + if @face.is_a?(Class) + @face.class_eval do eval wrapper, nil, file, line end + @face.define_method(internal_name, &block) + else + @face.instance_eval do eval wrapper, nil, file, line end + @face.meta_def(internal_name, &block) + end + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + elsif conflict = @face.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{@face}" + end + end + + option.aliases.each do |name| + @options[name] = option + end + + option + end + + def option?(name) + @options.include? name.to_sym + end + + def options + (@options.keys + @face.options).sort + end + + def get_option(name) + @options[name.to_sym] || @face.get_option(name) + end +end diff --git a/lib/puppet/interface/action_builder.rb b/lib/puppet/interface/action_builder.rb new file mode 100644 index 000000000..b08c3d023 --- /dev/null +++ b/lib/puppet/interface/action_builder.rb @@ -0,0 +1,31 @@ +require 'puppet/interface' +require 'puppet/interface/action' + +class Puppet::Interface::ActionBuilder + attr_reader :action + + def self.build(face, name, &block) + raise "Action #{name.inspect} must specify a block" unless block + new(face, name, &block).action + end + + private + def initialize(face, name, &block) + @face = face + @action = Puppet::Interface::Action.new(face, name) + instance_eval(&block) + end + + # Ideally the method we're defining here would be added to the action, and a + # method on the face would defer to it, but we can't get scope correct, so + # we stick with this. --daniel 2011-03-24 + def when_invoked(&block) + raise "when_invoked on an ActionBuilder with no corresponding Action" unless @action + @action.when_invoked = block + end + + def option(*declaration, &block) + option = Puppet::Interface::OptionBuilder.build(@action, *declaration, &block) + @action.add_option(option) + end +end diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb new file mode 100644 index 000000000..bb0e5bf57 --- /dev/null +++ b/lib/puppet/interface/action_manager.rb @@ -0,0 +1,49 @@ +require 'puppet/interface/action_builder' + +module Puppet::Interface::ActionManager + # Declare that this app can take a specific action, and provide + # the code to do so. + def action(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + action = Puppet::Interface::ActionBuilder.build(self, name, &block) + @actions[action.name] = action + end + + # This is the short-form of an action definition; it doesn't use the + # builder, just creates the action directly from the block. + def script(name, &block) + @actions ||= {} + raise "Action #{name} already defined for #{self}" if action?(name) + @actions[name] = Puppet::Interface::Action.new(self, name, :when_invoked => block) + end + + def actions + @actions ||= {} + result = @actions.keys + + if self.is_a?(Class) and superclass.respond_to?(:actions) + result += superclass.actions + elsif self.class.respond_to?(:actions) + result += self.class.actions + end + result.sort + end + + def get_action(name) + @actions ||= {} + result = @actions[name.to_sym] + if result.nil? + if self.is_a?(Class) and superclass.respond_to?(:get_action) + result = superclass.get_action(name) + elsif self.class.respond_to?(:get_action) + result = self.class.get_action(name) + end + end + return result + end + + def action?(name) + actions.include?(name.to_sym) + end +end diff --git a/lib/puppet/interface/face_collection.rb b/lib/puppet/interface/face_collection.rb new file mode 100644 index 000000000..9f7a499c2 --- /dev/null +++ b/lib/puppet/interface/face_collection.rb @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +require 'puppet/interface' + +module Puppet::Interface::FaceCollection + SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/ + + @faces = Hash.new { |hash, key| hash[key] = {} } + + def self.faces + unless @loaded + @loaded = true + $LOAD_PATH.each do |dir| + next unless FileTest.directory?(dir) + Dir.chdir(dir) do + # REVISIT: This is wrong!!!! We don't name files like that ever, + # so we should no longer match things like this. Damnit!!! --daniel 2011-04-07 + Dir.glob("puppet/faces/v*/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| + iname = file.sub(/\.rb/, '') + begin + require iname + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" + end + end + end + end + end + return @faces.keys + end + + def self.validate_version(version) + !!(SEMVER_VERSION =~ version.to_s) + end + + def self.cmp_semver(a, b) + a, b = [a, b].map do |x| + parts = SEMVER_VERSION.match(x).to_a[1..4] + parts[0..2] = parts[0..2].map { |e| e.to_i } + parts + end + + cmp = a[0..2] <=> b[0..2] + if cmp == 0 + cmp = a[3] <=> b[3] + cmp = +1 if a[3].empty? && !b[3].empty? + cmp = -1 if b[3].empty? && !a[3].empty? + end + cmp + end + + def self.[](name, version) + @faces[underscorize(name)][version] if face?(name, version) + end + + def self.face?(name, version) + name = underscorize(name) + return true if @faces[name].has_key?(version) + + # We always load the current version file; the common case is that we have + # the expected version and any compatibility versions in the same file, + # the default. Which means that this is almost always the case. + # + # We use require to avoid executing the code multiple times, like any + # other Ruby library that we might want to use. --daniel 2011-04-06 + begin + require "puppet/faces/#{name}" + + # If we wanted :current, we need to index to find that; direct version + # requests just work™ as they go. --daniel 2011-04-06 + if version == :current then + # We need to find current out of this. This is the largest version + # number that doesn't have a dedicated on-disk file present; those + # represent "experimental" versions of faces, which we don't fully + # support yet. + # + # We walk the versions from highest to lowest and take the first version + # that is not defined in an explicitly versioned file on disk as the + # current version. + # + # This constrains us to only ship experimental versions with *one* + # version in the file, not multiple, but given you can't reliably load + # them except by side-effect when you ignore that rule this seems safe + # enough... + # + # Given those constraints, and that we are not going to ship a versioned + # interface that is not :current in this release, we are going to leave + # these thoughts in place, and just punt on the actual versioning. + # + # When we upgrade the core to support multiple versions we can solve the + # problems then; as lazy as possible. + # + # We do support multiple versions in the same file, though, so we sort + # versions here and return the last item in that set. + # + # --daniel 2011-04-06 + latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last + @faces[name][:current] = @faces[name][latest_ver] + end + rescue LoadError => e + raise unless e.message =~ %r{-- puppet/faces/#{name}$} + # ...guess we didn't find the file; return a much better problem. + end + + # Now, either we have the version in our set of faces, or we didn't find + # the version they were looking for. In the future we will support + # loading versioned stuff from some look-aside part of the Ruby load path, + # but we don't need that right now. + # + # So, this comment is a place-holder for that. --daniel 2011-04-06 + return !! @faces[name].has_key?(version) + end + + def self.register(face) + @faces[underscorize(face.name)][face.version] = face + end + + def self.underscorize(name) + unless name.to_s =~ /^[-_a-z]+$/i then + raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name" + end + + name.to_s.downcase.split(/[-_]/).join('_').to_sym + end +end diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb new file mode 100644 index 000000000..ccc2fbba7 --- /dev/null +++ b/lib/puppet/interface/option.rb @@ -0,0 +1,82 @@ +require 'puppet/interface' + +class Puppet::Interface::Option + attr_reader :parent + attr_reader :name + attr_reader :aliases + attr_reader :optparse + attr_accessor :desc + + def takes_argument? + !!@argument + end + def optional_argument? + !!@optional_argument + end + + def initialize(parent, *declaration, &block) + @parent = parent + @optparse = [] + + # Collect and sort the arguments in the declaration. + dups = {} + declaration.each do |item| + if item.is_a? String and item.to_s =~ /^-/ then + unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then + raise ArgumentError, "#{item.inspect}: long options need two dashes (--)" + end + @optparse << item + + # Duplicate checking... + name = optparse_to_name(item) + if dup = dups[name] then + raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}" + else + dups[name] = item + end + else + raise ArgumentError, "#{item.inspect} is not valid for an option argument" + end + end + + if @optparse.empty? then + raise ArgumentError, "No option declarations found while building" + end + + # Now, infer the name from the options; we prefer the first long option as + # the name, rather than just the first option. + @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) + @aliases = @optparse.map { |o| optparse_to_name(o) } + + # Do we take an argument? If so, are we consistent about it, because + # incoherence here makes our life super-difficult, and we can more easily + # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 + @argument = @optparse.any? { |o| o =~ /[ =]/ } + if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then + raise ArgumentError, "Option #{@name} is inconsistent about taking an argument" + end + + # Is our argument optional? The rules about consistency apply here, also, + # just like they do to taking arguments at all. --daniel 2011-03-30 + @optional_argument = @optparse.any? { |o| o.include? "[" } + if @optional_argument and not @optparse.all? { |o| o.include? "[" } then + raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" + end + end + + # to_s and optparse_to_name are roughly mirrored, because they are used to + # transform options to name symbols, and vice-versa. This isn't a full + # bidirectional transformation though. --daniel 2011-04-07 + def to_s + @name.to_s.tr('_', '-') + end + + def optparse_to_name(declaration) + unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then + raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}" + end + name = found.captures.first.tr('-', '_') + raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ + name.to_sym + end +end diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb new file mode 100644 index 000000000..83a1906b0 --- /dev/null +++ b/lib/puppet/interface/option_builder.rb @@ -0,0 +1,25 @@ +require 'puppet/interface/option' + +class Puppet::Interface::OptionBuilder + attr_reader :option + + def self.build(face, *declaration, &block) + new(face, *declaration, &block).option + end + + private + def initialize(face, *declaration, &block) + @face = face + @option = Puppet::Interface::Option.new(face, *declaration) + block and instance_eval(&block) + @option + end + + # Metaprogram the simple DSL from the option class. + Puppet::Interface::Option.instance_methods.grep(/=$/).each do |setter| + next if setter =~ /^=/ # special case, darn it... + + dsl = setter.sub(/=$/, '') + define_method(dsl) do |value| @option.send(setter, value) end + end +end diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb new file mode 100644 index 000000000..56df9760f --- /dev/null +++ b/lib/puppet/interface/option_manager.rb @@ -0,0 +1,56 @@ +require 'puppet/interface/option_builder' + +module Puppet::Interface::OptionManager + # Declare that this app can take a specific option, and provide + # the code to do so. + def option(*declaration, &block) + add_option Puppet::Interface::OptionBuilder.build(self, *declaration, &block) + end + + def add_option(option) + option.aliases.each do |name| + if conflict = get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}" + end + + actions.each do |action| + action = get_action(action) + if conflict = action.get_option(name) then + raise ArgumentError, "Option #{option} conflicts with existing option #{conflict} on #{action}" + end + end + end + + option.aliases.each { |name| @options[name] = option } + option + end + + def options + @options ||= {} + result = @options.keys + + if self.is_a?(Class) and superclass.respond_to?(:options) + result += superclass.options + elsif self.class.respond_to?(:options) + result += self.class.options + end + result.sort + end + + def get_option(name) + @options ||= {} + result = @options[name.to_sym] + unless result then + if self.is_a?(Class) and superclass.respond_to?(:get_option) + result = superclass.get_option(name) + elsif self.class.respond_to?(:get_option) + result = self.class.get_option(name) + end + end + return result + end + + def option?(name) + options.include? name.to_sym + end +end diff --git a/spec/unit/faces/action_builder_spec.rb b/spec/unit/faces/action_builder_spec.rb deleted file mode 100755 index 5c8b98849..000000000 --- a/spec/unit/faces/action_builder_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/faces/action_builder' - -describe Puppet::Faces::ActionBuilder do - describe "::build" do - it "should build an action" do - action = Puppet::Faces::ActionBuilder.build(nil, :foo) do - end - action.should be_a(Puppet::Faces::Action) - action.name.should == :foo - end - - it "should define a method on the face which invokes the action" do - face = Puppet::Faces.new(:action_builder_test_faces, '0.0.1') - action = Puppet::Faces::ActionBuilder.build(face, :foo) do - when_invoked do - "invoked the method" - end - end - - face.foo.should == "invoked the method" - end - - it "should require a block" do - expect { Puppet::Faces::ActionBuilder.build(nil, :foo) }. - should raise_error("Action :foo must specify a block") - end - - describe "when handling options" do - let :face do Puppet::Faces.new(:option_handling, '0.0.1') end - - it "should have a #option DSL function" do - method = nil - Puppet::Faces::ActionBuilder.build(face, :foo) do - method = self.method(:option) - end - method.should be - end - - it "should define an option without a block" do - action = Puppet::Faces::ActionBuilder.build(face, :foo) do - option "--bar" - end - action.should be_option :bar - end - - it "should accept an empty block" do - action = Puppet::Faces::ActionBuilder.build(face, :foo) do - option "--bar" do - # This space left deliberately blank. - end - end - action.should be_option :bar - end - end - end -end diff --git a/spec/unit/faces/action_manager_spec.rb b/spec/unit/faces/action_manager_spec.rb deleted file mode 100755 index 61d1c1df5..000000000 --- a/spec/unit/faces/action_manager_spec.rb +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - -# This is entirely an internal class for Faces, so we have to load it instead of our class. -require 'puppet/faces' - -class ActionManagerTester - include Puppet::Faces::ActionManager -end - -describe Puppet::Faces::ActionManager do - subject { ActionManagerTester.new } - - describe "when included in a class" do - it "should be able to define an action" do - subject.action(:foo) do - when_invoked { "something "} - end - end - - it "should be able to define a 'script' style action" do - subject.script :bar do - "a bar is where beer is found" - end - end - - it "should be able to list defined actions" do - subject.action(:foo) do - when_invoked { "something" } - end - subject.action(:bar) do - when_invoked { "something" } - end - - subject.actions.should =~ [:foo, :bar] - end - - it "should list 'script' actions" do - subject.script :foo do "foo" end - subject.actions.should =~ [:foo] - end - - it "should list both script and normal actions" do - subject.action :foo do - when_invoked do "foo" end - end - subject.script :bar do "a bar is where beer is found" end - - subject.actions.should =~ [:foo, :bar] - end - - it "should be able to indicate when an action is defined" do - subject.action(:foo) do - when_invoked { "something" } - end - - subject.should be_action(:foo) - end - - it "should indicate an action is defined for script actions" do - subject.script :foo do "foo" end - subject.should be_action :foo - end - - it "should correctly treat action names specified as strings" do - subject.action(:foo) do - when_invoked { "something" } - end - - subject.should be_action("foo") - end - end - - describe "when used to extend a class" do - subject { Class.new.extend(Puppet::Faces::ActionManager) } - - it "should be able to define an action" do - subject.action(:foo) do - when_invoked { "something "} - end - end - - it "should be able to list defined actions" do - subject.action(:foo) do - when_invoked { "something" } - end - subject.action(:bar) do - when_invoked { "something" } - end - - subject.actions.should include(:bar) - subject.actions.should include(:foo) - end - - it "should be able to indicate when an action is defined" do - subject.action(:foo) { "something" } - subject.should be_action(:foo) - end - end - - describe "when used both at the class and instance level" do - before do - @klass = Class.new do - include Puppet::Faces::ActionManager - extend Puppet::Faces::ActionManager - end - @instance = @klass.new - end - - it "should be able to define an action at the class level" do - @klass.action(:foo) do - when_invoked { "something "} - end - end - - it "should create an instance method when an action is defined at the class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.foo.should == "something" - end - - it "should be able to define an action at the instance level" do - @instance.action(:foo) do - when_invoked { "something "} - end - end - - it "should create an instance method when an action is defined at the instance level" do - @instance.action(:foo) do - when_invoked { "something" } - end - @instance.foo.should == "something" - end - - it "should be able to list actions defined at the class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @klass.action(:bar) do - when_invoked { "something" } - end - - @klass.actions.should include(:bar) - @klass.actions.should include(:foo) - end - - it "should be able to list actions defined at the instance level" do - @instance.action(:foo) do - when_invoked { "something" } - end - @instance.action(:bar) do - when_invoked { "something" } - end - - @instance.actions.should include(:bar) - @instance.actions.should include(:foo) - end - - it "should be able to list actions defined at both instance and class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.action(:bar) do - when_invoked { "something" } - end - - @instance.actions.should include(:bar) - @instance.actions.should include(:foo) - end - - it "should be able to indicate when an action is defined at the class level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.should be_action(:foo) - end - - it "should be able to indicate when an action is defined at the instance level" do - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.should be_action(:foo) - end - - it "should list actions defined in superclasses" do - @subclass = Class.new(@klass) - @instance = @subclass.new - - @klass.action(:parent) do - when_invoked { "a" } - end - @subclass.action(:sub) do - when_invoked { "a" } - end - @instance.action(:instance) do - when_invoked { "a" } - end - - @instance.should be_action(:parent) - @instance.should be_action(:sub) - @instance.should be_action(:instance) - end - - it "should create an instance method when an action is defined in a superclass" do - @subclass = Class.new(@klass) - @instance = @subclass.new - - @klass.action(:foo) do - when_invoked { "something" } - end - @instance.foo.should == "something" - end - end - - describe "#get_action" do - let :parent_class do - parent_class = Class.new(Puppet::Faces) - parent_class.action(:foo) {} - parent_class - end - - it "should check that we can find inherited actions when we are a class" do - Class.new(parent_class).get_action(:foo).name.should == :foo - end - - it "should check that we can find inherited actions when we are an instance" do - instance = parent_class.new(:foo, '0.0.0') - instance.get_action(:foo).name.should == :foo - end - end -end diff --git a/spec/unit/faces/action_spec.rb b/spec/unit/faces/action_spec.rb deleted file mode 100755 index c087744b1..000000000 --- a/spec/unit/faces/action_spec.rb +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'puppet/faces/action' - -describe Puppet::Faces::Action do - describe "when validating the action name" do - [nil, '', 'foo bar', '-foobar'].each do |input| - it "should treat #{input.inspect} as an invalid name" do - expect { Puppet::Faces::Action.new(nil, input) }. - should raise_error(/is an invalid action name/) - end - end - end - - describe "when invoking" do - it "should be able to call other actions on the same object" do - face = Puppet::Faces.new(:my_face, '0.0.1') do - action(:foo) do - when_invoked { 25 } - end - - action(:bar) do - when_invoked { "the value of foo is '#{foo}'" } - end - end - face.foo.should == 25 - face.bar.should == "the value of foo is '25'" - end - - # bar is a class action calling a class action - # quux is a class action calling an instance action - # baz is an instance action calling a class action - # qux is an instance action calling an instance action - it "should be able to call other actions on the same object when defined on a class" do - class Puppet::Faces::MyFacesBaseClass < Puppet::Faces - action(:foo) do - when_invoked { 25 } - end - - action(:bar) do - when_invoked { "the value of foo is '#{foo}'" } - end - - action(:quux) do - when_invoked { "qux told me #{qux}" } - end - end - - face = Puppet::Faces::MyFacesBaseClass.new(:my_inherited_face, '0.0.1') do - action(:baz) do - when_invoked { "the value of foo in baz is '#{foo}'" } - end - - action(:qux) do - when_invoked { baz } - end - end - face.foo.should == 25 - face.bar.should == "the value of foo is '25'" - face.quux.should == "qux told me the value of foo in baz is '25'" - face.baz.should == "the value of foo in baz is '25'" - face.qux.should == "the value of foo in baz is '25'" - end - - context "when calling the Ruby API" do - let :face do - Puppet::Faces.new(:ruby_api, '1.0.0') do - action :bar do - when_invoked do |options| - options - end - end - end - end - - it "should work when no options are supplied" do - options = face.bar - options.should == {} - end - - it "should work when options are supplied" do - options = face.bar :bar => "beer" - options.should == { :bar => "beer" } - end - end - end - - describe "with action-level options" do - it "should support options with an empty block" do - face = Puppet::Faces.new(:action_level_options, '0.0.1') do - action :foo do - option "--bar" do - # this line left deliberately blank - end - end - end - - face.should_not be_option :bar - face.get_action(:foo).should be_option :bar - end - - it "should return only action level options when there are no face options" do - face = Puppet::Faces.new(:action_level_options, '0.0.1') do - action :foo do option "--bar" end - end - - face.get_action(:foo).options.should =~ [:bar] - end - - describe "with both face and action options" do - let :face do - Puppet::Faces.new(:action_level_options, '0.0.1') do - action :foo do option "--bar" end - action :baz do option "--bim" end - option "--quux" - end - end - - it "should return combined face and action options" do - face.get_action(:foo).options.should =~ [:bar, :quux] - end - - it "should fetch options that the face inherited" do - parent = Class.new(Puppet::Faces) - parent.option "--foo" - child = parent.new(:inherited_options, '0.0.1') do - option "--bar" - action :action do option "--baz" end - end - - action = child.get_action(:action) - action.should be - - [:baz, :bar, :foo].each do |name| - action.get_option(name).should be_an_instance_of Puppet::Faces::Option - end - end - - it "should get an action option when asked" do - face.get_action(:foo).get_option(:bar). - should be_an_instance_of Puppet::Faces::Option - end - - it "should get a face option when asked" do - face.get_action(:foo).get_option(:quux). - should be_an_instance_of Puppet::Faces::Option - end - - it "should return options only for this action" do - face.get_action(:baz).options.should =~ [:bim, :quux] - end - end - - it_should_behave_like "things that declare options" do - def add_options_to(&block) - face = Puppet::Faces.new(:with_options, '0.0.1') do - action(:foo, &block) - end - face.get_action(:foo) - end - end - - it "should fail when a face option duplicates an action option" do - expect { - Puppet::Faces.new(:action_level_options, '0.0.1') do - option "--foo" - action :bar do option "--foo" end - end - }.should raise_error ArgumentError, /Option foo conflicts with existing option foo/i - end - end -end diff --git a/spec/unit/faces/catalog_spec.rb b/spec/unit/faces/catalog_spec.rb index 7621a864a..71972194a 100755 --- a/spec/unit/faces/catalog_spec.rb +++ b/spec/unit/faces/catalog_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:catalog, '0.0.1'] do + it "should actually have some testing..." end diff --git a/spec/unit/faces/certificate_request_spec.rb b/spec/unit/faces/certificate_request_spec.rb index 637ea8c38..1a71a8379 100755 --- a/spec/unit/faces/certificate_request_spec.rb +++ b/spec/unit/faces/certificate_request_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:certificate_request, '0.0.1'] do + it "should actually have some tests..." end diff --git a/spec/unit/faces/certificate_revocation_list_spec.rb b/spec/unit/faces/certificate_revocation_list_spec.rb index e319b18e2..4f41edef6 100755 --- a/spec/unit/faces/certificate_revocation_list_spec.rb +++ b/spec/unit/faces/certificate_revocation_list_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:certificate_revocation_list, '0.0.1'] do + it "should actually have some tests..." end diff --git a/spec/unit/faces/face_collection_spec.rb b/spec/unit/faces/face_collection_spec.rb deleted file mode 100755 index 30147a548..000000000 --- a/spec/unit/faces/face_collection_spec.rb +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') -require 'tmpdir' - -describe Puppet::Faces::FaceCollection do - # To avoid cross-pollution we have to save and restore both the hash - # containing all the faces data, and the array used by require. Restoring - # both means that we don't leak side-effects across the code. --daniel 2011-04-06 - before :each do - @original_faces = subject.instance_variable_get("@faces").dup - @original_required = $".dup - subject.instance_variable_get("@faces").clear - end - - after :each do - subject.instance_variable_set("@faces", @original_faces) - $".clear ; @original_required.each do |item| $" << item end - end - - describe "::faces" do - it "REVISIT: should have some tests here, if we describe it" - end - - describe "::validate_version" do - it 'should permit three number versions' do - subject.validate_version('10.10.10').should == true - end - - it 'should permit versions with appended descriptions' do - subject.validate_version('10.10.10beta').should == true - end - - it 'should not permit versions with more than three numbers' do - subject.validate_version('1.2.3.4').should == false - end - - it 'should not permit versions with only two numbers' do - subject.validate_version('10.10').should == false - end - - it 'should not permit versions with only one number' do - subject.validate_version('123').should == false - end - - it 'should not permit versions with text in any position but at the end' do - subject.validate_version('v1.1.1').should == false - end - end - - describe "::[]" do - before :each do - subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 - end - - before :each do - @dir = Dir.mktmpdir - @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'faces') - $LOAD_PATH.push(@dir) - end - - after :each do - FileUtils.remove_entry_secure @dir - $LOAD_PATH.pop - end - - it "should return the faces with the given name" do - subject["foo", '0.0.1'].should == 10 - end - - it "should attempt to load the faces if it isn't found" do - subject.expects(:require).with('puppet/faces/bar') - subject["bar", '0.0.1'] - end - - it "should attempt to load the default faces for the specified version :current" do - subject.expects(:require).never # except... - subject.expects(:require).with('puppet/faces/fozzie') - subject['fozzie', :current] - end - end - - describe "::face?" do - before :each do - subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 - end - - it "should return true if the faces specified is registered" do - subject.face?("foo", '0.0.1').should == true - end - - it "should attempt to require the faces if it is not registered" do - subject.expects(:require).with do |file| - subject.instance_variable_get("@faces")[:bar]['0.0.1'] = true - file == 'puppet/faces/bar' - end - subject.face?("bar", '0.0.1').should == true - end - - it "should return true if requiring the faces registered it" do - subject.stubs(:require).with do - subject.instance_variable_get("@faces")[:bar]['0.0.1'] = 20 - end - end - - it "should return false if the faces is not registered" do - subject.stubs(:require).returns(true) - subject.face?("bar", '0.0.1').should be_false - end - - it "should return false if the faces file itself is missing" do - subject.stubs(:require). - raises(LoadError, 'no such file to load -- puppet/faces/bar') - subject.face?("bar", '0.0.1').should be_false - end - - it "should register the version loaded by `:current` as `:current`" do - subject.expects(:require).with do |file| - subject.instance_variable_get("@faces")[:huzzah]['2.0.1'] = :huzzah_faces - file == 'puppet/faces/huzzah' - end - subject.face?("huzzah", :current) - subject.instance_variable_get("@faces")[:huzzah][:current].should == :huzzah_faces - end - - context "with something on disk" do - before :each do - write_scratch_faces :huzzah do |fh| - fh.puts < {'0.0.1' => faces}} - end - end - - describe "::underscorize" do - faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] - valid = { - "Foo" => :foo, - :Foo => :foo, - "foo_bar" => :foo_bar, - :foo_bar => :foo_bar, - "foo-bar" => :foo_bar, - :"foo-bar" => :foo_bar, - } - - valid.each do |input, expect| - it "should map #{input.inspect} to #{expect.inspect}" do - result = subject.underscorize(input) - result.should == expect - end - end - - faulty.each do |input| - it "should fail when presented with #{input.inspect} (#{input.class})" do - expect { subject.underscorize(input) }. - should raise_error ArgumentError, /not a valid face name/ - end - end - end -end diff --git a/spec/unit/faces/file_spec.rb b/spec/unit/faces/file_spec.rb index aafa88ce0..fcb52c67e 100755 --- a/spec/unit/faces/file_spec.rb +++ b/spec/unit/faces/file_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:file, '0.0.1'] do + it "should actually have some tests..." end diff --git a/spec/unit/faces/key_spec.rb b/spec/unit/faces/key_spec.rb index 70d4a52c1..9b7a58706 100755 --- a/spec/unit/faces/key_spec.rb +++ b/spec/unit/faces/key_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:key, '0.0.1'] do + it "should actually have some tests..." end diff --git a/spec/unit/faces/option_builder_spec.rb b/spec/unit/faces/option_builder_spec.rb deleted file mode 100644 index 9296ba7b6..000000000 --- a/spec/unit/faces/option_builder_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'puppet/faces/option_builder' - -describe Puppet::Faces::OptionBuilder do - let :face do Puppet::Faces.new(:option_builder_testing, '0.0.1') end - - it "should be able to construct an option without a block" do - Puppet::Faces::OptionBuilder.build(face, "--foo"). - should be_an_instance_of Puppet::Faces::Option - end - - describe "when using the DSL block" do - it "should work with an empty block" do - option = Puppet::Faces::OptionBuilder.build(face, "--foo") do - # This block deliberately left blank. - end - - option.should be_an_instance_of Puppet::Faces::Option - end - - it "should support documentation declarations" do - text = "this is the description" - option = Puppet::Faces::OptionBuilder.build(face, "--foo") do - desc text - end - option.should be_an_instance_of Puppet::Faces::Option - option.desc.should == text - end - end -end diff --git a/spec/unit/faces/option_spec.rb b/spec/unit/faces/option_spec.rb deleted file mode 100644 index a28fef087..000000000 --- a/spec/unit/faces/option_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'puppet/faces/option' - -describe Puppet::Faces::Option do - let :face do Puppet::Faces.new(:option_testing, '0.0.1') end - - describe "#optparse_to_name" do - ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix| - { "--foo" => :foo, "-f" => :f }.each do |base, expect| - input = base + postfix - it "should map #{input.inspect} to #{expect.inspect}" do - option = Puppet::Faces::Option.new(face, input) - option.name.should == expect - end - end - end - - [:foo, 12, nil, {}, []].each do |input| - it "should fail sensible when given #{input.inspect}" do - expect { Puppet::Faces::Option.new(face, input) }. - should raise_error ArgumentError, /is not valid for an option argument/ - end - end - - ["-foo", "-foo=BAR", "-foo BAR"].each do |input| - it "should fail with a single dash for long option #{input.inspect}" do - expect { Puppet::Faces::Option.new(face, input) }. - should raise_error ArgumentError, /long options need two dashes \(--\)/ - end - end - end - - it "requires a face when created" do - expect { Puppet::Faces::Option.new }. - should raise_error ArgumentError, /wrong number of arguments/ - end - - it "also requires some declaration arguments when created" do - expect { Puppet::Faces::Option.new(face) }. - should raise_error ArgumentError, /No option declarations found/ - end - - it "should infer the name from an optparse string" do - option = Puppet::Faces::Option.new(face, "--foo") - option.name.should == :foo - end - - it "should infer the name when multiple optparse string are given" do - option = Puppet::Faces::Option.new(face, "--foo", "-f") - option.name.should == :foo - end - - it "should prefer the first long option name over a short option name" do - option = Puppet::Faces::Option.new(face, "-f", "--foo") - option.name.should == :foo - end - - it "should create an instance when given a face and name" do - Puppet::Faces::Option.new(face, "--foo"). - should be_instance_of Puppet::Faces::Option - end - - describe "#to_s" do - it "should transform a symbol into a string" do - option = Puppet::Faces::Option.new(face, "--foo") - option.name.should == :foo - option.to_s.should == "foo" - end - - it "should use - rather than _ to separate words in strings but not symbols" do - option = Puppet::Faces::Option.new(face, "--foo-bar") - option.name.should == :foo_bar - option.to_s.should == "foo-bar" - end - end -end diff --git a/spec/unit/faces/report_spec.rb b/spec/unit/faces/report_spec.rb index f7a67349c..30897d5e7 100755 --- a/spec/unit/faces/report_spec.rb +++ b/spec/unit/faces/report_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:report, '0.0.1'] do + it "should actually have some tests..." end diff --git a/spec/unit/faces/resource_spec.rb b/spec/unit/faces/resource_spec.rb index 0b4b24882..e3f2e1c62 100755 --- a/spec/unit/faces/resource_spec.rb +++ b/spec/unit/faces/resource_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:resource, '0.0.1'] do + it "should actually have some tests..." end diff --git a/spec/unit/faces/resource_type_spec.rb b/spec/unit/faces/resource_type_spec.rb index 157066f2d..fcbf07520 100755 --- a/spec/unit/faces/resource_type_spec.rb +++ b/spec/unit/faces/resource_type_spec.rb @@ -1,6 +1,3 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') - describe Puppet::Faces[:resource_type, '0.0.1'] do + it "should actually have some tests..." end diff --git a/spec/unit/faces_spec.rb b/spec/unit/faces_spec.rb deleted file mode 100755 index 586abd6b5..000000000 --- a/spec/unit/faces_spec.rb +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb') - -describe Puppet::Faces do - before :all do - @faces = Puppet::Faces::FaceCollection.instance_variable_get("@faces").dup - end - - before :each do - Puppet::Faces::FaceCollection.instance_variable_get("@faces").clear - end - - after :all do - Puppet::Faces::FaceCollection.instance_variable_set("@faces", @faces) - end - - describe "#define" do - it "should register the face" do - face = Puppet::Faces.define(:face_test_register, '0.0.1') - face.should == Puppet::Faces[:face_test_register, '0.0.1'] - end - - it "should load actions" do - Puppet::Faces.any_instance.expects(:load_actions) - Puppet::Faces.define(:face_test_load_actions, '0.0.1') - end - - it "should require a version number" do - expect { Puppet::Faces.define(:no_version) }.should raise_error ArgumentError - end - end - - describe "#initialize" do - it "should require a version number" do - expect { Puppet::Faces.new(:no_version) }.should raise_error ArgumentError - end - - it "should require a valid version number" do - expect { Puppet::Faces.new(:bad_version, 'Rasins') }. - should raise_error ArgumentError - end - - it "should instance-eval any provided block" do - face = Puppet::Faces.new(:face_test_block, '0.0.1') do - action(:something) do - when_invoked { "foo" } - end - end - - face.something.should == "foo" - end - end - - it "should have a name" do - Puppet::Faces.new(:me, '0.0.1').name.should == :me - end - - it "should stringify with its own name" do - Puppet::Faces.new(:me, '0.0.1').to_s.should =~ /\bme\b/ - end - - it "should allow overriding of the default format" do - face = Puppet::Faces.new(:me, '0.0.1') - face.set_default_format :foo - face.default_format.should == :foo - end - - it "should default to :pson for its format" do - Puppet::Faces.new(:me, '0.0.1').default_format.should == :pson - end - - # Why? - it "should create a class-level autoloader" do - Puppet::Faces.autoloader.should be_instance_of(Puppet::Util::Autoload) - end - - it "should try to require faces that are not known" do - Puppet::Faces::FaceCollection.expects(:require).with "puppet/faces/foo" - Puppet::Faces[:foo, '0.0.1'] - end - - it "should be able to load all actions in all search paths" - - - it_should_behave_like "things that declare options" do - def add_options_to(&block) - Puppet::Faces.new(:with_options, '0.0.1', &block) - end - end - - describe "with face-level options" do - it "should not return any action-level options" do - face = Puppet::Faces.new(:with_options, '0.0.1') do - option "--foo" - option "--bar" - action :baz do - option "--quux" - end - end - face.options.should =~ [:foo, :bar] - end - - it "should fail when a face option duplicates an action option" do - expect { - Puppet::Faces.new(:action_level_options, '0.0.1') do - action :bar do option "--foo" end - option "--foo" - end - }.should raise_error ArgumentError, /Option foo conflicts with existing option foo on/i - end - - it "should work when two actions have the same option" do - face = Puppet::Faces.new(:with_options, '0.0.1') do - action :foo do option "--quux" end - action :bar do option "--quux" end - end - - face.get_action(:foo).options.should =~ [:quux] - face.get_action(:bar).options.should =~ [:quux] - end - end - - describe "with inherited options" do - let :face do - parent = Class.new(Puppet::Faces) - parent.option("--inherited") - face = parent.new(:example, '0.2.1') - face.option("--local") - face - end - - describe "#options" do - it "should list inherited options" do - face.options.should =~ [:inherited, :local] - end - end - - describe "#get_option" do - it "should return an inherited option object" do - face.get_option(:inherited).should be_an_instance_of Puppet::Faces::Option - end - end - end -end diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb new file mode 100755 index 000000000..ae9cc83d4 --- /dev/null +++ b/spec/unit/interface/action_builder_spec.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/interface/action_builder' + +describe Puppet::Interface::ActionBuilder do + describe "::build" do + it "should build an action" do + action = Puppet::Interface::ActionBuilder.build(nil, :foo) do + end + action.should be_a(Puppet::Interface::Action) + action.name.should == :foo + end + + it "should define a method on the face which invokes the action" do + face = Puppet::Interface.new(:action_builder_test_interface, '0.0.1') + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + when_invoked do + "invoked the method" + end + end + + face.foo.should == "invoked the method" + end + + it "should require a block" do + expect { Puppet::Interface::ActionBuilder.build(nil, :foo) }. + should raise_error("Action :foo must specify a block") + end + + describe "when handling options" do + let :face do Puppet::Interface.new(:option_handling, '0.0.1') end + + it "should have a #option DSL function" do + method = nil + Puppet::Interface::ActionBuilder.build(face, :foo) do + method = self.method(:option) + end + method.should be + end + + it "should define an option without a block" do + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + option "--bar" + end + action.should be_option :bar + end + + it "should accept an empty block" do + action = Puppet::Interface::ActionBuilder.build(face, :foo) do + option "--bar" do + # This space left deliberately blank. + end + end + action.should be_option :bar + end + end + end +end diff --git a/spec/unit/interface/action_manager_spec.rb b/spec/unit/interface/action_manager_spec.rb new file mode 100755 index 000000000..50bea5f89 --- /dev/null +++ b/spec/unit/interface/action_manager_spec.rb @@ -0,0 +1,233 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') + +# This is entirely an internal class for Interface, so we have to load it instead of our class. +require 'puppet/interface' + +class ActionManagerTester + include Puppet::Interface::ActionManager +end + +describe Puppet::Interface::ActionManager do + subject { ActionManagerTester.new } + + describe "when included in a class" do + it "should be able to define an action" do + subject.action(:foo) do + when_invoked { "something "} + end + end + + it "should be able to define a 'script' style action" do + subject.script :bar do + "a bar is where beer is found" + end + end + + it "should be able to list defined actions" do + subject.action(:foo) do + when_invoked { "something" } + end + subject.action(:bar) do + when_invoked { "something" } + end + + subject.actions.should =~ [:foo, :bar] + end + + it "should list 'script' actions" do + subject.script :foo do "foo" end + subject.actions.should =~ [:foo] + end + + it "should list both script and normal actions" do + subject.action :foo do + when_invoked do "foo" end + end + subject.script :bar do "a bar is where beer is found" end + + subject.actions.should =~ [:foo, :bar] + end + + it "should be able to indicate when an action is defined" do + subject.action(:foo) do + when_invoked { "something" } + end + + subject.should be_action(:foo) + end + + it "should indicate an action is defined for script actions" do + subject.script :foo do "foo" end + subject.should be_action :foo + end + + it "should correctly treat action names specified as strings" do + subject.action(:foo) do + when_invoked { "something" } + end + + subject.should be_action("foo") + end + end + + describe "when used to extend a class" do + subject { Class.new.extend(Puppet::Interface::ActionManager) } + + it "should be able to define an action" do + subject.action(:foo) do + when_invoked { "something "} + end + end + + it "should be able to list defined actions" do + subject.action(:foo) do + when_invoked { "something" } + end + subject.action(:bar) do + when_invoked { "something" } + end + + subject.actions.should include(:bar) + subject.actions.should include(:foo) + end + + it "should be able to indicate when an action is defined" do + subject.action(:foo) { "something" } + subject.should be_action(:foo) + end + end + + describe "when used both at the class and instance level" do + before do + @klass = Class.new do + include Puppet::Interface::ActionManager + extend Puppet::Interface::ActionManager + end + @instance = @klass.new + end + + it "should be able to define an action at the class level" do + @klass.action(:foo) do + when_invoked { "something "} + end + end + + it "should create an instance method when an action is defined at the class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.foo.should == "something" + end + + it "should be able to define an action at the instance level" do + @instance.action(:foo) do + when_invoked { "something "} + end + end + + it "should create an instance method when an action is defined at the instance level" do + @instance.action(:foo) do + when_invoked { "something" } + end + @instance.foo.should == "something" + end + + it "should be able to list actions defined at the class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @klass.action(:bar) do + when_invoked { "something" } + end + + @klass.actions.should include(:bar) + @klass.actions.should include(:foo) + end + + it "should be able to list actions defined at the instance level" do + @instance.action(:foo) do + when_invoked { "something" } + end + @instance.action(:bar) do + when_invoked { "something" } + end + + @instance.actions.should include(:bar) + @instance.actions.should include(:foo) + end + + it "should be able to list actions defined at both instance and class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.action(:bar) do + when_invoked { "something" } + end + + @instance.actions.should include(:bar) + @instance.actions.should include(:foo) + end + + it "should be able to indicate when an action is defined at the class level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.should be_action(:foo) + end + + it "should be able to indicate when an action is defined at the instance level" do + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.should be_action(:foo) + end + + it "should list actions defined in superclasses" do + @subclass = Class.new(@klass) + @instance = @subclass.new + + @klass.action(:parent) do + when_invoked { "a" } + end + @subclass.action(:sub) do + when_invoked { "a" } + end + @instance.action(:instance) do + when_invoked { "a" } + end + + @instance.should be_action(:parent) + @instance.should be_action(:sub) + @instance.should be_action(:instance) + end + + it "should create an instance method when an action is defined in a superclass" do + @subclass = Class.new(@klass) + @instance = @subclass.new + + @klass.action(:foo) do + when_invoked { "something" } + end + @instance.foo.should == "something" + end + end + + describe "#get_action" do + let :parent_class do + parent_class = Class.new(Puppet::Interface) + parent_class.action(:foo) {} + parent_class + end + + it "should check that we can find inherited actions when we are a class" do + Class.new(parent_class).get_action(:foo).name.should == :foo + end + + it "should check that we can find inherited actions when we are an instance" do + instance = parent_class.new(:foo, '0.0.0') + instance.get_action(:foo).name.should == :foo + end + end +end diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb new file mode 100755 index 000000000..4801a3cc8 --- /dev/null +++ b/spec/unit/interface/action_spec.rb @@ -0,0 +1,173 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'puppet/interface/action' + +describe Puppet::Interface::Action do + describe "when validating the action name" do + [nil, '', 'foo bar', '-foobar'].each do |input| + it "should treat #{input.inspect} as an invalid name" do + expect { Puppet::Interface::Action.new(nil, input) }. + should raise_error(/is an invalid action name/) + end + end + end + + describe "when invoking" do + it "should be able to call other actions on the same object" do + face = Puppet::Interface.new(:my_face, '0.0.1') do + action(:foo) do + when_invoked { 25 } + end + + action(:bar) do + when_invoked { "the value of foo is '#{foo}'" } + end + end + face.foo.should == 25 + face.bar.should == "the value of foo is '25'" + end + + # bar is a class action calling a class action + # quux is a class action calling an instance action + # baz is an instance action calling a class action + # qux is an instance action calling an instance action + it "should be able to call other actions on the same object when defined on a class" do + class Puppet::Interface::MyInterfaceBaseClass < Puppet::Interface + action(:foo) do + when_invoked { 25 } + end + + action(:bar) do + when_invoked { "the value of foo is '#{foo}'" } + end + + action(:quux) do + when_invoked { "qux told me #{qux}" } + end + end + + face = Puppet::Interface::MyInterfaceBaseClass.new(:my_inherited_face, '0.0.1') do + action(:baz) do + when_invoked { "the value of foo in baz is '#{foo}'" } + end + + action(:qux) do + when_invoked { baz } + end + end + face.foo.should == 25 + face.bar.should == "the value of foo is '25'" + face.quux.should == "qux told me the value of foo in baz is '25'" + face.baz.should == "the value of foo in baz is '25'" + face.qux.should == "the value of foo in baz is '25'" + end + + context "when calling the Ruby API" do + let :face do + Puppet::Interface.new(:ruby_api, '1.0.0') do + action :bar do + when_invoked do |options| + options + end + end + end + end + + it "should work when no options are supplied" do + options = face.bar + options.should == {} + end + + it "should work when options are supplied" do + options = face.bar :bar => "beer" + options.should == { :bar => "beer" } + end + end + end + + describe "with action-level options" do + it "should support options with an empty block" do + face = Puppet::Interface.new(:action_level_options, '0.0.1') do + action :foo do + option "--bar" do + # this line left deliberately blank + end + end + end + + face.should_not be_option :bar + face.get_action(:foo).should be_option :bar + end + + it "should return only action level options when there are no face options" do + face = Puppet::Interface.new(:action_level_options, '0.0.1') do + action :foo do option "--bar" end + end + + face.get_action(:foo).options.should =~ [:bar] + end + + describe "with both face and action options" do + let :face do + Puppet::Interface.new(:action_level_options, '0.0.1') do + action :foo do option "--bar" end + action :baz do option "--bim" end + option "--quux" + end + end + + it "should return combined face and action options" do + face.get_action(:foo).options.should =~ [:bar, :quux] + end + + it "should fetch options that the face inherited" do + parent = Class.new(Puppet::Interface) + parent.option "--foo" + child = parent.new(:inherited_options, '0.0.1') do + option "--bar" + action :action do option "--baz" end + end + + action = child.get_action(:action) + action.should be + + [:baz, :bar, :foo].each do |name| + action.get_option(name).should be_an_instance_of Puppet::Interface::Option + end + end + + it "should get an action option when asked" do + face.get_action(:foo).get_option(:bar). + should be_an_instance_of Puppet::Interface::Option + end + + it "should get a face option when asked" do + face.get_action(:foo).get_option(:quux). + should be_an_instance_of Puppet::Interface::Option + end + + it "should return options only for this action" do + face.get_action(:baz).options.should =~ [:bim, :quux] + end + end + + it_should_behave_like "things that declare options" do + def add_options_to(&block) + face = Puppet::Interface.new(:with_options, '0.0.1') do + action(:foo, &block) + end + face.get_action(:foo) + end + end + + it "should fail when a face option duplicates an action option" do + expect { + Puppet::Interface.new(:action_level_options, '0.0.1') do + option "--foo" + action :bar do option "--foo" end + end + }.should raise_error ArgumentError, /Option foo conflicts with existing option foo/i + end + end +end diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb new file mode 100755 index 000000000..de6d29cee --- /dev/null +++ b/spec/unit/interface/face_collection_spec.rb @@ -0,0 +1,184 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') +require 'tmpdir' + +describe Puppet::Interface::FaceCollection do + # To avoid cross-pollution we have to save and restore both the hash + # containing all the interface data, and the array used by require. Restoring + # both means that we don't leak side-effects across the code. --daniel 2011-04-06 + before :each do + @original_faces = subject.instance_variable_get("@faces").dup + @original_required = $".dup + subject.instance_variable_get("@faces").clear + end + + after :each do + subject.instance_variable_set("@faces", @original_faces) + $".clear ; @original_required.each do |item| $" << item end + end + + describe "::faces" do + it "REVISIT: should have some tests here, if we describe it" + end + + describe "::validate_version" do + it 'should permit three number versions' do + subject.validate_version('10.10.10').should == true + end + + it 'should permit versions with appended descriptions' do + subject.validate_version('10.10.10beta').should == true + end + + it 'should not permit versions with more than three numbers' do + subject.validate_version('1.2.3.4').should == false + end + + it 'should not permit versions with only two numbers' do + subject.validate_version('10.10').should == false + end + + it 'should not permit versions with only one number' do + subject.validate_version('123').should == false + end + + it 'should not permit versions with text in any position but at the end' do + subject.validate_version('v1.1.1').should == false + end + end + + describe "::[]" do + before :each do + subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + end + + before :each do + @dir = Dir.mktmpdir + @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'faces') + $LOAD_PATH.push(@dir) + end + + after :each do + FileUtils.remove_entry_secure @dir + $LOAD_PATH.pop + end + + it "should return the faces with the given name" do + subject["foo", '0.0.1'].should == 10 + end + + it "should attempt to load the faces if it isn't found" do + subject.expects(:require).with('puppet/faces/bar') + subject["bar", '0.0.1'] + end + + it "should attempt to load the default faces for the specified version :current" do + subject.expects(:require).never # except... + subject.expects(:require).with('puppet/faces/fozzie') + subject['fozzie', :current] + end + end + + describe "::face?" do + before :each do + subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + end + + it "should return true if the faces specified is registered" do + subject.face?("foo", '0.0.1').should == true + end + + it "should attempt to require the faces if it is not registered" do + subject.expects(:require).with do |file| + subject.instance_variable_get("@faces")[:bar]['0.0.1'] = true + file == 'puppet/faces/bar' + end + subject.face?("bar", '0.0.1').should == true + end + + it "should return true if requiring the faces registered it" do + subject.stubs(:require).with do + subject.instance_variable_get("@faces")[:bar]['0.0.1'] = 20 + end + end + + it "should return false if the faces is not registered" do + subject.stubs(:require).returns(true) + subject.face?("bar", '0.0.1').should be_false + end + + it "should return false if the faces file itself is missing" do + subject.stubs(:require). + raises(LoadError, 'no such file to load -- puppet/faces/bar') + subject.face?("bar", '0.0.1').should be_false + end + + it "should register the version loaded by `:current` as `:current`" do + subject.expects(:require).with do |file| + subject.instance_variable_get("@faces")[:huzzah]['2.0.1'] = :huzzah_faces + file == 'puppet/faces/huzzah' + end + subject.face?("huzzah", :current) + subject.instance_variable_get("@faces")[:huzzah][:current].should == :huzzah_faces + end + + context "with something on disk" do + before :each do + write_scratch_faces :huzzah do |fh| + fh.puts < {'0.0.1' => faces}} + end + end + + describe "::underscorize" do + faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] + valid = { + "Foo" => :foo, + :Foo => :foo, + "foo_bar" => :foo_bar, + :foo_bar => :foo_bar, + "foo-bar" => :foo_bar, + :"foo-bar" => :foo_bar, + } + + valid.each do |input, expect| + it "should map #{input.inspect} to #{expect.inspect}" do + result = subject.underscorize(input) + result.should == expect + end + end + + faulty.each do |input| + it "should fail when presented with #{input.inspect} (#{input.class})" do + expect { subject.underscorize(input) }. + should raise_error ArgumentError, /not a valid face name/ + end + end + end +end diff --git a/spec/unit/interface/option_builder_spec.rb b/spec/unit/interface/option_builder_spec.rb new file mode 100644 index 000000000..fae48324e --- /dev/null +++ b/spec/unit/interface/option_builder_spec.rb @@ -0,0 +1,29 @@ +require 'puppet/interface/option_builder' + +describe Puppet::Interface::OptionBuilder do + let :face do Puppet::Interface.new(:option_builder_testing, '0.0.1') end + + it "should be able to construct an option without a block" do + Puppet::Interface::OptionBuilder.build(face, "--foo"). + should be_an_instance_of Puppet::Interface::Option + end + + describe "when using the DSL block" do + it "should work with an empty block" do + option = Puppet::Interface::OptionBuilder.build(face, "--foo") do + # This block deliberately left blank. + end + + option.should be_an_instance_of Puppet::Interface::Option + end + + it "should support documentation declarations" do + text = "this is the description" + option = Puppet::Interface::OptionBuilder.build(face, "--foo") do + desc text + end + option.should be_an_instance_of Puppet::Interface::Option + option.desc.should == text + end + end +end diff --git a/spec/unit/interface/option_spec.rb b/spec/unit/interface/option_spec.rb new file mode 100644 index 000000000..3bcd121e2 --- /dev/null +++ b/spec/unit/interface/option_spec.rb @@ -0,0 +1,75 @@ +require 'puppet/interface/option' + +describe Puppet::Interface::Option do + let :face do Puppet::Interface.new(:option_testing, '0.0.1') end + + describe "#optparse_to_name" do + ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix| + { "--foo" => :foo, "-f" => :f }.each do |base, expect| + input = base + postfix + it "should map #{input.inspect} to #{expect.inspect}" do + option = Puppet::Interface::Option.new(face, input) + option.name.should == expect + end + end + end + + [:foo, 12, nil, {}, []].each do |input| + it "should fail sensible when given #{input.inspect}" do + expect { Puppet::Interface::Option.new(face, input) }. + should raise_error ArgumentError, /is not valid for an option argument/ + end + end + + ["-foo", "-foo=BAR", "-foo BAR"].each do |input| + it "should fail with a single dash for long option #{input.inspect}" do + expect { Puppet::Interface::Option.new(face, input) }. + should raise_error ArgumentError, /long options need two dashes \(--\)/ + end + end + end + + it "requires a face when created" do + expect { Puppet::Interface::Option.new }. + should raise_error ArgumentError, /wrong number of arguments/ + end + + it "also requires some declaration arguments when created" do + expect { Puppet::Interface::Option.new(face) }. + should raise_error ArgumentError, /No option declarations found/ + end + + it "should infer the name from an optparse string" do + option = Puppet::Interface::Option.new(face, "--foo") + option.name.should == :foo + end + + it "should infer the name when multiple optparse string are given" do + option = Puppet::Interface::Option.new(face, "--foo", "-f") + option.name.should == :foo + end + + it "should prefer the first long option name over a short option name" do + option = Puppet::Interface::Option.new(face, "-f", "--foo") + option.name.should == :foo + end + + it "should create an instance when given a face and name" do + Puppet::Interface::Option.new(face, "--foo"). + should be_instance_of Puppet::Interface::Option + end + + describe "#to_s" do + it "should transform a symbol into a string" do + option = Puppet::Interface::Option.new(face, "--foo") + option.name.should == :foo + option.to_s.should == "foo" + end + + it "should use - rather than _ to separate words in strings but not symbols" do + option = Puppet::Interface::Option.new(face, "--foo-bar") + option.name.should == :foo_bar + option.to_s.should == "foo-bar" + end + end +end diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb new file mode 100755 index 000000000..afcf95dcf --- /dev/null +++ b/spec/unit/interface_spec.rb @@ -0,0 +1,146 @@ +require 'puppet/faces' +require 'puppet/interface' + +describe Puppet::Interface do + subject { Puppet::Interface } + + before :all do + @faces = Puppet::Interface::FaceCollection.instance_variable_get("@faces").dup + end + + before :each do + Puppet::Interface::FaceCollection.instance_variable_get("@faces").clear + end + + after :all do + Puppet::Interface::FaceCollection.instance_variable_set("@faces", @faces) + end + + describe "#define" do + it "should register the face" do + face = subject.define(:face_test_register, '0.0.1') + face.should == subject[:face_test_register, '0.0.1'] + end + + it "should load actions" do + subject.any_instance.expects(:load_actions) + subject.define(:face_test_load_actions, '0.0.1') + end + + it "should require a version number" do + expect { subject.define(:no_version) }.should raise_error ArgumentError + end + end + + describe "#initialize" do + it "should require a version number" do + expect { subject.new(:no_version) }.should raise_error ArgumentError + end + + it "should require a valid version number" do + expect { subject.new(:bad_version, 'Rasins') }. + should raise_error ArgumentError + end + + it "should instance-eval any provided block" do + face = subject.new(:face_test_block, '0.0.1') do + action(:something) do + when_invoked { "foo" } + end + end + + face.something.should == "foo" + end + end + + it "should have a name" do + subject.new(:me, '0.0.1').name.should == :me + end + + it "should stringify with its own name" do + subject.new(:me, '0.0.1').to_s.should =~ /\bme\b/ + end + + it "should allow overriding of the default format" do + face = subject.new(:me, '0.0.1') + face.set_default_format :foo + face.default_format.should == :foo + end + + it "should default to :pson for its format" do + subject.new(:me, '0.0.1').default_format.should == :pson + end + + # Why? + it "should create a class-level autoloader" do + subject.autoloader.should be_instance_of(Puppet::Util::Autoload) + end + + it "should try to require faces that are not known" do + subject::FaceCollection.expects(:require).with "puppet/faces/foo" + subject[:foo, '0.0.1'] + end + + it "should be able to load all actions in all search paths" + + + it_should_behave_like "things that declare options" do + def add_options_to(&block) + subject.new(:with_options, '0.0.1', &block) + end + end + + describe "with face-level options" do + it "should not return any action-level options" do + face = subject.new(:with_options, '0.0.1') do + option "--foo" + option "--bar" + action :baz do + option "--quux" + end + end + face.options.should =~ [:foo, :bar] + end + + it "should fail when a face option duplicates an action option" do + expect { + subject.new(:action_level_options, '0.0.1') do + action :bar do option "--foo" end + option "--foo" + end + }.should raise_error ArgumentError, /Option foo conflicts with existing option foo on/i + end + + it "should work when two actions have the same option" do + face = subject.new(:with_options, '0.0.1') do + action :foo do option "--quux" end + action :bar do option "--quux" end + end + + face.get_action(:foo).options.should =~ [:quux] + face.get_action(:bar).options.should =~ [:quux] + end + end + + describe "with inherited options" do + let :face do + parent = Class.new(subject) + parent.option("--inherited") + face = parent.new(:example, '0.2.1') + face.option("--local") + face + end + + describe "#options" do + it "should list inherited options" do + face.options.should =~ [:inherited, :local] + end + end + + describe "#get_option" do + it "should return an inherited option object" do + face.get_option(:inherited).should be_an_instance_of subject::Option + end + end + end +end -- cgit