From 86801b580101315706b1b02a00a36840eabd75cd Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Mon, 18 Apr 2011 13:29:47 -0700 Subject: (#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 --- lib/puppet/interface.rb | 4 +- lib/puppet/interface/action.rb | 57 ++++++++- lib/puppet/interface/action_builder.rb | 44 ++++++- lib/puppet/interface/option.rb | 4 +- lib/puppet/interface/option_builder.rb | 2 +- spec/unit/interface/action_builder_spec.rb | 194 ++++++++++++++++++++++------- spec/unit/interface/action_spec.rb | 9 ++ 7 files changed, 256 insertions(+), 58 deletions(-) diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb index 5e9355061..51ae0cdc1 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 efe7b1f50..bdd42b197 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 639d8fc7f..2ffa38709 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 <