diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb index ccad0850d..d4e59a4df 100644 --- a/lib/puppet/interface/option_builder.rb +++ b/lib/puppet/interface/option_builder.rb @@ -1,44 +1,50 @@ 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 =~ /^=/ dsl = setter.sub(/=$/, '') unless self.class.methods.include?(dsl) define_method(dsl) do |value| @option.send(setter, value) end end end # Override some methods that deal in blocks, not objects. def before_action(&block) block or raise ArgumentError, "#{@option} before_action requires a block" if @option.before_action raise ArgumentError, "#{@option} already has a before_action set" end + unless block.arity == 3 then + raise ArgumentError, "before_action takes three arguments, action, args, and options" + end @option.before_action = block end def after_action(&block) block or raise ArgumentError, "#{@option} after_action requires a block" if @option.after_action raise ArgumentError, "#{@option} already has a after_action set" end + unless block.arity == 3 then + raise ArgumentError, "after_action takes three arguments, action, args, and options" + end @option.after_action = block end end diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb index 51aa837dc..4be6a1c55 100755 --- a/spec/unit/interface/action_spec.rb +++ b/spec/unit/interface/action_spec.rb @@ -1,343 +1,351 @@ #!/usr/bin/env rspec require 'spec_helper' 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 context "with action decorators" do context "local only" do let :face do Puppet::Interface.new(:action_decorators, '0.0.1') do action :bar do when_invoked do true end end def report(arg) end end end it "should call action before decorators" do face.action(:baz) do option "--baz" do - before_action do + before_action do |action, args, options| report(:action_option) end end when_invoked do true end end face.expects(:report).with(:action_option) face.baz :baz => true end it "should call action after decorators" do face.action(:baz) do option "--baz" do - after_action do + after_action do |action, args, options| report(:action_option) end end when_invoked do true end end face.expects(:report).with(:action_option) face.baz :baz => true end it "should call local before decorators" do face.option "--foo FOO" do - before_action do + before_action do |action, args, options| report(:before) end end face.expects(:report).with(:before) face.bar({:foo => 12}) end it "should call local after decorators" do - face.option "--foo FOO" do after_action do report(:after) end end + face.option "--foo FOO" do + after_action do |action, args, options| report(:after) end + end face.expects(:report).with(:after) face.bar({:foo => 12}) end context "with inactive decorators" do it "should not invoke a decorator if the options are empty" do face.option "--foo FOO" do - before_action do report :before_action end + before_action do |action, args, options| + report :before_action + end end face.expects(:report).never # I am testing the negative. face.bar end context "with some decorators only" do before :each do - face.option "--foo" do before_action do report :foo end end - face.option "--bar" do before_action do report :bar end end + face.option "--foo" do + before_action do |action, args, options| report :foo end + end + face.option "--bar" do + before_action do |action, args, options| report :bar end + end end it "should work with the foo option" do face.expects(:report).with(:foo) face.bar(:foo => true) end it "should work with the bar option" do face.expects(:report).with(:bar) face.bar(:bar => true) end it "should work with both options" do face.expects(:report).with(:foo) face.expects(:report).with(:bar) face.bar(:foo => true, :bar => true) end end end end context "with inherited decorators" do let :parent do parent = Class.new(Puppet::Interface) parent.script :on_parent do :on_parent end parent.define_method :report do |arg| arg end parent end let :child do child = parent.new(:inherited_decorators, '0.0.1') do script :on_child do :on_child end end end context "with a child decorator" do subject do child.option "--foo FOO" do - before_action do + before_action do |action, args, options| report(:child_before) end end child.expects(:report).with(:child_before) child end it "child actions should invoke the decorator" do subject.on_child({:foo => true, :bar => true}).should == :on_child end it "parent actions should invoke the decorator" do subject.on_parent({:foo => true, :bar => true}).should == :on_parent end end context "with a parent decorator" do subject do parent.option "--foo FOO" do - before_action do + before_action do |action, args, options| report(:parent_before) end end child.expects(:report).with(:parent_before) child end it "child actions should invoke the decorator" do subject.on_child({:foo => true, :bar => true}).should == :on_child end it "parent actions should invoke the decorator" do subject.on_parent({:foo => true, :bar => true}).should == :on_parent end end context "with child and parent decorators" do subject do parent.option "--foo FOO" do - before_action { report(:parent_before) } - after_action { report(:parent_after) } + before_action { |action, args, options| report(:parent_before) } + after_action { |action, args, options| report(:parent_after) } end child.option "--bar BAR" do - before_action { report(:child_before) } - after_action { report(:child_after) } + before_action { |action, args, options| report(:child_before) } + after_action { |action, args, options| report(:child_after) } end child.expects(:report).with(:child_before) child.expects(:report).with(:parent_before) child.expects(:report).with(:parent_after) child.expects(:report).with(:child_after) child end it "child actions should invoke all the decorator" do subject.on_child({:foo => true, :bar => true}).should == :on_child end it "parent actions should invoke all the decorator" do subject.on_parent({:foo => true, :bar => true}).should == :on_parent end end end end end diff --git a/spec/unit/interface/option_builder_spec.rb b/spec/unit/interface/option_builder_spec.rb index 0bcbed82c..b32b316f6 100755 --- a/spec/unit/interface/option_builder_spec.rb +++ b/spec/unit/interface/option_builder_spec.rb @@ -1,34 +1,60 @@ 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 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 - it "should support a before_action hook" do - option = Puppet::Interface::OptionBuilder.build(face, "--foo") do - before_action do :whatever end + context "before_action hook" do + it "should support a before_action hook" do + option = Puppet::Interface::OptionBuilder.build(face, "--foo") do + before_action do |a,b,c| :whatever end + end + option.before_action.should be_an_instance_of UnboundMethod + end + + it "should fail if the hook block takes too few arguments" do + expect do + Puppet::Interface::OptionBuilder.build(face, "--foo") do + before_action do |one, two| true end + end + end.to raise_error ArgumentError, /takes three arguments/ + end + + it "should fail if the hook block takes too many arguments" do + expect do + Puppet::Interface::OptionBuilder.build(face, "--foo") do + before_action do |one, two, three, four| true end + end + end.to raise_error ArgumentError, /takes three arguments/ + end + + it "should fail if the hook block takes a variable number of arguments" do + expect do + Puppet::Interface::OptionBuilder.build(face, "--foo") do + before_action do |*blah| true end + end + end.to raise_error ArgumentError, /takes three arguments/ end - option.before_action.should be_an_instance_of UnboundMethod end end