diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb index 07e27efa8..27b3584b9 100644 --- a/lib/puppet/interface.rb +++ b/lib/puppet/interface.rb @@ -1,115 +1,120 @@ 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 def [](name, version) unless face = Puppet::Interface::FaceCollection[name, version] if current = Puppet::Interface::FaceCollection[name, :current] raise Puppet::Error, "Could not find version #{version} of #{current}" else raise Puppet::Error, "Could not find Puppet Face #{name.inspect}" end end face end 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 + attr_accessor :summary + def summary(value = nil) + @summary = value unless value.nil? + @summary + end + + attr_reader :name, :version 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/spec/unit/application/indirection_base_spec.rb b/spec/unit/application/indirection_base_spec.rb index 10ebe8e3d..98eb3a118 100755 --- a/spec/unit/application/indirection_base_spec.rb +++ b/spec/unit/application/indirection_base_spec.rb @@ -1,39 +1,39 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/application/indirection_base' require 'puppet/faces/indirector' ######################################################################## # Stub for testing; the names are critical, sadly. --daniel 2011-03-30 class Puppet::Application::TestIndirection < Puppet::Application::IndirectionBase end 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 -face.version = :current +face.instance_variable_set('@version', :current) Puppet::Faces.register(face) ######################################################################## describe Puppet::Application::IndirectionBase do subject { Puppet::Application::TestIndirection.new } it "should accept a terminus command line option" do # It would be nice not to have to stub this, but whatever... writing an # entire indirection stack would cause us more grief. --daniel 2011-03-31 terminus = mock("test indirection terminus") Puppet::Indirector::Indirection.expects(:instance). with(:testindirection).twice.returns() subject.command_line. instance_variable_set('@args', %w{--terminus foo save}) # Not a very nice thing. :( $stderr.stubs(:puts) expect { subject.run }.should raise_error SystemExit end end diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb index b25d06f51..7e6b7de77 100755 --- a/spec/unit/interface_spec.rb +++ b/spec/unit/interface_spec.rb @@ -1,162 +1,185 @@ require 'spec_helper' 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 "#[]" do it "should fail when no version is requested" do expect { subject[:huzzah] }.should raise_error ArgumentError end it "should raise an exception when the requested version is unavailable" do expect { subject[:huzzah, '17.0.0'] }.should raise_error, Puppet::Error end it "should raise an exception when the requested face doesn't exist" do expect { subject[:burrble_toot, :current] }.should raise_error, Puppet::Error end 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 + expect { subject.define(:no_version) }.to raise_error ArgumentError + end + + it "should support summary builder and accessor methods" do + subject.new(:foo, '1.0.0').should respond_to(:summary).with(0).arguments + subject.new(:foo, '1.0.0').should respond_to(:summary=).with(1).arguments + end + + it "should set the summary text" do + text = "hello, freddy, my little pal" + subject.define(:face_test_summary, '1.0.0') do + summary text + end + subject[:face_test_summary, '1.0.0'].summary.should == text + end + + it "should support mutating the summary" do + text = "hello, freddy, my little pal" + subject.define(:face_test_summary, '1.0.0') do + summary text + end + subject[:face_test_summary, '1.0.0'].summary.should == text + subject[:face_test_summary, '1.0.0'].summary = text + text + subject[:face_test_summary, '1.0.0'].summary.should == text + text end end describe "#initialize" do it "should require a version number" do - expect { subject.new(:no_version) }.should raise_error ArgumentError + expect { subject.new(:no_version) }.to 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 pending "mocking require causes random stack overflow" 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