This implement support for options with default values, allowing faces to set
those values when not invoked.  This can eliminate substantial duplicate code
from actions, especially when there are face-level options in use.

Reviewed-By: Pieter van de Bruggen <[email protected]>
---
 lib/puppet/interface/action.rb                     |    9 ++
 lib/puppet/interface/option.rb                     |   19 ++++
 lib/puppet/interface/option_builder.rb             |   13 +++
 .../things_that_declare_options.rb                 |  113 ++++++++++++++++++++
 spec/unit/interface/action_spec.rb                 |   45 ++++++++
 spec/unit/interface/option_spec.rb                 |   44 ++++++++
 6 files changed, 243 insertions(+), 0 deletions(-)

diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb
index fe77a96..fc1121e 100644
--- a/lib/puppet/interface/action.rb
+++ b/lib/puppet/interface/action.rb
@@ -199,6 +199,7 @@ def #{@name}(#{decl.join(", ")})
   options = args.last
 
   action = get_action(#{name.inspect})
+  action.add_default_args(args)
   action.validate_args(args)
   __invoke_decorations(:before, action, args, options)
   rval = self.__send__(#{internal_name.inspect}, *args)
@@ -252,6 +253,14 @@ WRAPPER
     option
   end
 
+  def add_default_args(args)
+    options.map {|x| get_option(x) }.each do |option|
+      if option.has_default? and not option.aliases.any? {|x| 
args.last.has_key? x}
+        args.last[option.name] = option.default
+      end
+    end
+  end
+
   def validate_args(args)
     # Check for multiple aliases for the same option...
     args.last.keys.each do |name|
diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb
index 3cd930a..01f6f23 100644
--- a/lib/puppet/interface/option.rb
+++ b/lib/puppet/interface/option.rb
@@ -6,6 +6,7 @@ class Puppet::Interface::Option
   def initialize(parent, *declaration, &block)
     @parent   = parent
     @optparse = []
+    @default  = nil
 
     # Collect and sort the arguments in the declaration.
     dups = {}
@@ -81,8 +82,26 @@ class Puppet::Interface::Option
     !!@required
   end
 
+  def has_default?
+    !!@default
+  end
+
+  def default=(proc)
+    required and raise ArgumentError, "#{self} can't be optional and have a 
default value"
+    proc.is_a? Proc or raise ArgumentError, "default value for #{self} is a 
#{proc.class.name.inspect}, not a proc"
+    @default = proc
+  end
+
+  def default
+    @default and @default.call
+  end
+
   attr_reader   :parent, :name, :aliases, :optparse
   attr_accessor :required
+  def required=(value)
+    has_default? and raise ArgumentError, "#{self} can't be optional and have 
a default value"
+    @required = value
+  end
 
   attr_accessor :before_action
   def before_action=(proc)
diff --git a/lib/puppet/interface/option_builder.rb 
b/lib/puppet/interface/option_builder.rb
index 5676ec9..c87adc2 100644
--- a/lib/puppet/interface/option_builder.rb
+++ b/lib/puppet/interface/option_builder.rb
@@ -51,4 +51,17 @@ class Puppet::Interface::OptionBuilder
   def required(value = true)
     @option.required = value
   end
+
+  def default_to(&block)
+    block or raise ArgumentError, "#{@option} default_to requires a block"
+    if @option.has_default?
+      raise ArgumentError, "#{@option} already has a default value"
+    end
+    # Ruby 1.8 treats a block without arguments as accepting any number; 1.9
+    # gets this right, so we work around it for now... --daniel 2011-07-20
+    unless block.arity == 0 or (RUBY_VERSION =~ /^1\.8/ and block.arity == -1)
+      raise ArgumentError, "#{@option} default_to block should not take any 
arguments"
+    end
+    @option.default = block
+  end
 end
diff --git a/spec/shared_behaviours/things_that_declare_options.rb 
b/spec/shared_behaviours/things_that_declare_options.rb
index 19bba66..ecdbfca 100755
--- a/spec/shared_behaviours/things_that_declare_options.rb
+++ b/spec/shared_behaviours/things_that_declare_options.rb
@@ -146,4 +146,117 @@ shared_examples_for "things that declare options" do
       end
     end
   end
+
+  describe "#default_to" do
+    it "should not have a default value by default" do
+      option = add_options_to do option "--foo" end.get_option(:foo)
+      option.should_not be_has_default
+    end
+
+    it "should accept a block for the default value" do
+      option = add_options_to do
+        option "--foo" do
+          default_to do
+            12
+          end
+        end
+      end.get_option(:foo)
+
+      option.should be_has_default
+    end
+
+    it "should invoke the block when asked for the default value" do
+      invoked = false
+      option = add_options_to do
+        option "--foo" do
+          default_to do
+            invoked = true
+          end
+        end
+      end.get_option(:foo)
+
+      option.should be_has_default
+      option.default.should be_true
+      invoked.should be_true
+    end
+
+    it "should return the value of the block when asked for the default" do
+      option = add_options_to do
+        option "--foo" do
+          default_to do
+            12
+          end
+        end
+      end.get_option(:foo)
+
+      option.should be_has_default
+      option.default.should == 12
+    end
+
+    it "should invoke the block every time the default is requested" do
+      option = add_options_to do
+        option "--foo" do
+          default_to do
+            {}
+          end
+        end
+      end.get_option(:foo)
+
+      first  = option.default.object_id
+      second = option.default.object_id
+      third  = option.default.object_id
+
+      first.should_not == second
+      first.should_not == third
+      second.should_not == third
+    end
+
+    it "should fail if the option has a default and is required" do
+      expect {
+        add_options_to do
+          option "--foo" do
+            required
+            default_to do 12 end
+          end
+        end
+      }.to raise_error ArgumentError, /can't be optional and have a default 
value/
+
+      expect {
+        add_options_to do
+          option "--foo" do
+            default_to do 12 end
+            required
+          end
+        end
+      }.to raise_error ArgumentError, /can't be optional and have a default 
value/
+    end
+
+    it "should fail if default_to has no block" do
+      expect { add_options_to do option "--foo" do default_to end end }.
+        to raise_error ArgumentError, /default_to requires a block/
+    end
+
+    it "should fail if default_to is invoked twice" do
+      expect {
+        add_options_to do
+          option "--foo" do
+            default_to do 12 end
+            default_to do "fun" end
+          end
+        end
+      }.to raise_error ArgumentError, /already has a default value/
+    end
+
+    [ "one", "one, two", "one, *two" ].each do |input|
+      it "should fail if the block has the wrong arity (#{input})" do
+        expect {
+          add_options_to do
+            option "--foo" do
+              eval "default_to do |#{input}| 12 end"
+            end
+          end
+        }.to raise_error ArgumentError, /should not take any arguments/
+      end
+    end
+  end
 end
diff --git a/spec/unit/interface/action_spec.rb 
b/spec/unit/interface/action_spec.rb
index 23216e7..dbbf847 100755
--- a/spec/unit/interface/action_spec.rb
+++ b/spec/unit/interface/action_spec.rb
@@ -552,4 +552,49 @@ describe Puppet::Interface::Action do
         to raise_error ArgumentError, /Multiple aliases for the same option/
     end
   end
+
+  context "default option values" do
+    subject do
+      Puppet::Interface.new(:default_option_values, '1.0.0') do
+        action :foo do
+          option "--foo" do end
+          option "--bar" do end
+          when_invoked do |options| options end
+        end
+      end
+    end
+
+    let :action do subject.get_action :foo end
+    let :option do action.get_option :foo end
+
+    it "should not add options without defaults" do
+      subject.foo.should == {}
+    end
+
+    it "should not add options without defaults, if options are given" do
+      subject.foo(:bar => 1).should == { :bar => 1 }
+    end
+
+    it "should add the option default value when set" do
+      option.default = proc { 12 }
+      subject.foo.should == { :foo => 12 }
+    end
+
+    it "should add the option default value when set, if other options are 
given" do
+      option.default = proc { 12 }
+      subject.foo(:bar => 1).should == { :foo => 12, :bar => 1 }
+    end
+
+    it "should invoke the same default proc every time called" do
+      option.default = proc { @foo ||= {} }
+      subject.foo[:foo].object_id.should == subject.foo[:foo].object_id
+    end
+
+    [nil, 0, 1, true, false, {}, []].each do |input|
+      it "should not override a passed option (#{input.inspect})" do
+        option.default = proc { :fail }
+        subject.foo(:foo => input).should == { :foo => input }
+      end
+    end
+  end
 end
diff --git a/spec/unit/interface/option_spec.rb 
b/spec/unit/interface/option_spec.rb
index e77b46e..e73561f 100755
--- a/spec/unit/interface/option_spec.rb
+++ b/spec/unit/interface/option_spec.rb
@@ -97,4 +97,48 @@ describe Puppet::Interface::Option do
       end
     end
   end
+
+  context "defaults" do
+    subject { Puppet::Interface::Option.new(face, "--foo") }
+
+    it "should work sanely if member variables are used for state" do
+      subject.default = proc { @foo ||= 0; @foo += 1 }
+      subject.default.should == 1
+      subject.default.should == 2
+      subject.default.should == 3
+    end
+
+    context "with no default" do
+      it { should_not be_has_default }
+      its :default do should be_nil end
+
+      it "should set a proc as default" do
+        expect { subject.default = proc { 12 } }.should_not raise_error
+      end
+
+      [1, {}, [], Object.new, "foo"].each do |input|
+        it "should reject anything but a proc (#{input.class})" do
+          expect { subject.default = input }.to raise_error ArgumentError, 
/not a proc/
+        end
+      end
+    end
+
+    context "with a default" do
+      before :each do subject.default = proc { [:foo] } end
+
+      it { should be_has_default }
+      its :default do should == [:foo] end
+
+      it "should invoke the block every time" do
+        subject.default.object_id.should_not == subject.default.object_id
+        subject.default.should == subject.default
+      end
+
+      it "should allow replacing the default proc" do
+        subject.default.should == [:foo]
+        subject.default = proc { :bar }
+        subject.default.should == :bar
+      end
+    end
+  end
 end
-- 
1.7.6

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to