[Pkg-puppet-devel] [SCM] Puppet packaging for Debian branch, experimental, updated. debian/2.6.8-1-844-g7ec39d5
Daniel Pittman
daniel at puppetlabs.com
Tue May 10 08:16:42 UTC 2011
The following commit has been merged in the experimental branch:
commit 86801b580101315706b1b02a00a36840eabd75cd
Author: Daniel Pittman <daniel at puppetlabs.com>
Date: Mon Apr 18 13:29:47 2011 -0700
(#7013) Support 'when_rendering' and 'render_as' in actions.
These define the API used by folks writing actions that supports their
rendering hooks. 'when_rendering' defines a helper method on the interface,
which runs the users code in their expected context.
'render_as' just sets the default rendering format; by default this is
:for_humans.
Reviewed-By: Max Martin <max at puppetlabs.com>
diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb
index 5e93550..51ae0cd 100644
--- a/lib/puppet/interface.rb
+++ b/lib/puppet/interface.rb
@@ -148,11 +148,11 @@ class Puppet::Interface
end
end
- def __decorate(type, name, proc)
+ def __add_method(name, proc)
meta_def(name, &proc)
method(name).unbind
end
- def self.__decorate(type, name, proc)
+ def self.__add_method(name, proc)
define_method(name, proc)
instance_method(name)
end
diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb
index efe7b1f..bdd42b1 100644
--- a/lib/puppet/interface/action.rb
+++ b/lib/puppet/interface/action.rb
@@ -10,6 +10,8 @@ class Puppet::Interface::Action
attrs.each do |k, v| send("#{k}=", v) end
@options = {}
+ @when_rendering = {}
+ @render_as = :for_humans
end
# This is not nice, but it is the easiest way to make us behave like the
@@ -21,9 +23,9 @@ class Puppet::Interface::Action
return bound_version
end
- attr_reader :name
def to_s() "#{@face}##{@name}" end
+ attr_reader :name
attr_accessor :default
def default?
!!@default
@@ -31,6 +33,55 @@ class Puppet::Interface::Action
attr_accessor :summary
+
+ ########################################################################
+ # Support for rendering formats and all.
+ def when_rendering(type)
+ unless type.is_a? Symbol
+ raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}"
+ end
+ @when_rendering[type]
+ end
+ def set_rendering_method_for(type, proc)
+ unless proc.is_a? Proc
+ msg = "The second argument to set_rendering_method_for must be a Proc"
+ msg += ", not #{proc.class.name}" unless proc.nil?
+ raise ArgumentError, msg
+ end
+ if proc.arity != 1 then
+ msg = "when_rendering methods take one argument, the result, not "
+ if proc.arity < 0 then
+ msg += "a variable number"
+ else
+ msg += proc.arity.to_s
+ end
+ raise ArgumentError, msg
+ end
+ unless type.is_a? Symbol
+ raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}"
+ end
+ if @when_rendering.has_key? type then
+ raise ArgumentError, "You can't define a rendering method for #{type} twice"
+ end
+ # Now, the ugly bit. We add the method to our interface object, and
+ # retrieve it, to rotate through the dance of getting a suitable method
+ # object out of the whole process. --daniel 2011-04-18
+ @when_rendering[type] =
+ @face.__send__( :__add_method, __render_method_name_for(type), proc)
+ end
+
+ def __render_method_name_for(type)
+ :"#{name}_when_rendering_#{type}"
+ end
+ private :__render_method_name_for
+
+
+ attr_accessor :render_as
+ def render_as=(value)
+ @render_as = value.to_sym
+ 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
@@ -161,7 +212,7 @@ WRAPPER
# Support code for action decoration; see puppet/interface.rb for the gory
# details of why this is hidden away behind private. --daniel 2011-04-15
private
- def __decorate(type, name, proc)
- @face.__send__ :__decorate, type, name, proc
+ def __add_method(name, proc)
+ @face.__send__ :__add_method, name, proc
end
end
diff --git a/lib/puppet/interface/action_builder.rb b/lib/puppet/interface/action_builder.rb
index 639d8fc..2ffa387 100644
--- a/lib/puppet/interface/action_builder.rb
+++ b/lib/puppet/interface/action_builder.rb
@@ -20,20 +20,54 @@ class Puppet::Interface::ActionBuilder
# 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 when_rendering(type = nil, &block)
+ if type.nil? then # the default error message sucks --daniel 2011-04-18
+ raise ArgumentError, 'You must give a rendering format to when_rendering'
+ end
+ if block.nil? then
+ raise ArgumentError, 'You must give a block to when_rendering'
+ end
+ @action.set_rendering_method_for(type, block)
+ end
+
def option(*declaration, &block)
option = Puppet::Interface::OptionBuilder.build(@action, *declaration, &block)
@action.add_option(option)
end
- def default
- @action.default = true
+ def default(value = true)
+ @action.default = !!value
+ end
+
+ def render_as(value = nil)
+ value.nil? and raise ArgumentError, "You must give a rendering format to render_as"
+
+ formats = Puppet::Network::FormatHandler.formats << :for_humans
+ unless formats.include? value
+ raise ArgumentError, "#{value.inspect} is not a valid rendering format: #{formats.sort.join(", ")}"
+ end
+
+ @action.render_as = value
end
- def summary(text)
- @action.summary = text
+ # Metaprogram the simple DSL from the target class.
+ Puppet::Interface::Action.instance_methods.grep(/=$/).each do |setter|
+ next if setter =~ /^=/
+ dsl = setter.sub(/=$/, '')
+
+ unless private_instance_methods.include? dsl
+ # Using eval because the argument handling semantics are less awful than
+ # when we use the define_method/block version. The later warns on older
+ # Ruby versions if you pass the wrong number of arguments, but carries
+ # on, which is totally not what we want. --daniel 2011-04-18
+ eval <<METHOD
+def #{dsl}(value)
+ @action.#{dsl} = value
+end
+METHOD
+ end
end
end
diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb
index 1971926..f4c56cb 100644
--- a/lib/puppet/interface/option.rb
+++ b/lib/puppet/interface/option.rb
@@ -84,14 +84,14 @@ class Puppet::Interface::Option
def before_action=(proc)
proc.is_a? Proc or raise ArgumentError, "before action hook for #{self} is a #{proc.class.name.inspect}, not a proc"
@before_action =
- @parent.__send__(:__decorate, :before, __decoration_name(:before), proc)
+ @parent.__send__(:__add_method, __decoration_name(:before), proc)
end
attr_accessor :after_action
def after_action=(proc)
proc.is_a? Proc or raise ArgumentError, "after action hook for #{self} is a #{proc.class.name.inspect}, not a proc"
@after_action =
- @parent.__send__(:__decorate, :after, __decoration_name(:after), proc)
+ @parent.__send__(:__add_method, __decoration_name(:after), proc)
end
def __decoration_name(type)
diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb
index 7c2ab89..8f358c2 100644
--- a/lib/puppet/interface/option_builder.rb
+++ b/lib/puppet/interface/option_builder.rb
@@ -20,7 +20,7 @@ class Puppet::Interface::OptionBuilder
next if setter =~ /^=/
dsl = setter.sub(/=$/, '')
- unless self.class.methods.include?(dsl)
+ unless private_instance_methods.include? dsl
define_method(dsl) do |value| @option.send(setter, value) end
end
end
diff --git a/spec/unit/interface/action_builder_spec.rb b/spec/unit/interface/action_builder_spec.rb
index 8f8e8d1..38a23a6 100755
--- a/spec/unit/interface/action_builder_spec.rb
+++ b/spec/unit/interface/action_builder_spec.rb
@@ -1,81 +1,185 @@
#!/usr/bin/env rspec
require 'spec_helper'
require 'puppet/interface/action_builder'
+require 'puppet/network/format_handler'
describe Puppet::Interface::ActionBuilder do
- describe "::build" do
- it "should build an action" do
- action = Puppet::Interface::ActionBuilder.build(nil, :foo) do
+ let :face do Puppet::Interface.new(:puppet_interface_actionbuilder, '0.0.1') end
+
+ 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') do
+ action(:foo) { when_invoked { "invoked the method" } }
+ 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
+ it "should have a #option DSL function" do
+ method = nil
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ method = self.method(:option)
end
- action.should be_a(Puppet::Interface::Action)
- action.name.should == :foo
+ method.should be
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') do
- action(:foo) { when_invoked { "invoked the method" } }
+ 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
- face.foo.should == "invoked the method"
+ context "inline documentation" do
+ it "should set the summary" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ summary "this is some text"
+ end
+ action.summary.should == "this is some text"
end
+ end
- it "should require a block" do
- expect { Puppet::Interface::ActionBuilder.build(nil, :foo) }.
- should raise_error("Action :foo must specify a block")
+ context "action defaulting" do
+ it "should set the default to true" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ default
+ end
+ action.default.should be_true
end
- describe "when handling options" do
- let :face do Puppet::Interface.new(:option_handling, '0.0.1') end
+ it "should not be default by, er, default. *cough*" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do end
+ action.default.should be_false
+ end
+ end
- it "should have a #option DSL function" do
- method = nil
+ context "#when_rendering" do
+ it "should fail if no rendering format is given" do
+ expect {
Puppet::Interface::ActionBuilder.build(face, :foo) do
- method = self.method(:option)
+ when_rendering do true end
end
- method.should be
- end
+ }.to raise_error ArgumentError, /must give a rendering format to when_rendering/
+ end
- it "should define an option without a block" do
- action = Puppet::Interface::ActionBuilder.build(face, :foo) do
- option "--bar"
+ it "should fail if no block is given" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json
end
- action.should be_option :bar
- end
+ }.to raise_error ArgumentError, /must give a block to when_rendering/
+ 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
+ it "should fail if the block takes no arguments" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do true end
end
- action.should be_option :bar
- end
+ }.to raise_error ArgumentError, /when_rendering methods take one argument, the result, not/
end
- context "inline documentation" do
- let :face do Puppet::Interface.new(:inline_action_docs, '0.0.1') end
+ it "should fail if the block takes more than one argument" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a, b, c| true end
+ end
+ }.to raise_error ArgumentError, /when_rendering methods take one argument, the result, not/
+ end
- it "should set the summary" do
- action = Puppet::Interface::ActionBuilder.build(face, :foo) do
- summary "this is some text"
+ it "should fail if the block takes a variable number of arguments" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |*args| true end
+ end
+ }.to raise_error(ArgumentError,
+ /when_rendering methods take one argument, the result, not/)
+ end
+
+ it "should stash a rendering block" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a| true end
+ end
+ action.when_rendering(:json).should be_an_instance_of UnboundMethod
+ end
+
+ it "should fail if you try to set the same rendering twice" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a| true end
+ when_rendering :json do |a| true end
end
- action.summary.should == "this is some text"
+ }.to raise_error ArgumentError, /You can't define a rendering method for json twice/
+ end
+
+ it "should work if you set two different renderings" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ when_rendering :json do |a| true end
+ when_rendering :yaml do |a| true end
end
+ action.when_rendering(:json).should be_an_instance_of UnboundMethod
+ action.when_rendering(:yaml).should be_an_instance_of UnboundMethod
end
+ end
- context "action defaulting" do
- let :face do Puppet::Interface.new(:default_action, '0.0.1') end
+ context "#render_as" do
+ it "should default to :for_humans" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do end
+ action.render_as.should == :for_humans
+ end
+
+ it "should fail if not rendering format is given" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ render_as
+ end
+ }.to raise_error ArgumentError, /must give a rendering format to render_as/
+ end
- it "should set the default to true" do
+ Puppet::Network::FormatHandler.formats.each do |name|
+ it "should accept #{name.inspect} format" do
action = Puppet::Interface::ActionBuilder.build(face, :foo) do
- default
+ render_as name
end
- action.default.should be_true
+ action.render_as.should == name
end
+ end
- it "should not be default by, er, default. *cough*" do
- action = Puppet::Interface::ActionBuilder.build(face, :foo) do end
- action.default.should be_false
+ it "should accept :for_humans format" do
+ action = Puppet::Interface::ActionBuilder.build(face, :foo) do
+ render_as :for_humans
+ end
+ action.render_as.should == :for_humans
+ end
+
+ [:if_you_define_this_format_you_frighten_me, "json", 12].each do |input|
+ it "should fail if given #{input.inspect}" do
+ expect {
+ Puppet::Interface::ActionBuilder.build(face, :foo) do
+ render_as input
+ end
+ }.to raise_error ArgumentError, /#{input.inspect} is not a valid rendering format/
end
end
end
diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb
index fe2409b..07853e7 100755
--- a/spec/unit/interface/action_spec.rb
+++ b/spec/unit/interface/action_spec.rb
@@ -371,4 +371,13 @@ describe Puppet::Interface::Action do
end
end
end
+
+ context "#when_rendering" do
+ it "should fail if no type is given when_rendering"
+ it "should accept a when_rendering block"
+ it "should accept multiple when_rendering blocks"
+ it "should fail if when_rendering gets a non-symbol identifier"
+ it "should fail if a second block is given for the same type"
+ it "should return the block if asked"
+ end
end
--
Puppet packaging for Debian
More information about the Pkg-puppet-devel
mailing list