diff options
| author | Josh Cooper <josh@puppetlabs.com> | 2011-04-21 14:37:50 -0700 |
|---|---|---|
| committer | Josh Cooper <josh@puppetlabs.com> | 2011-04-21 14:37:50 -0700 |
| commit | 01f610bb223b435dc52f491260af3ea002930102 (patch) | |
| tree | 93565136d5ef2b4156fdd64476792e441bcfbb4e /lib/puppet/interface | |
| parent | ac428b9557e2da251e4b51e48de844833ca0aa2a (diff) | |
| parent | fc66e98b84b9a16728af054485883334a5887cca (diff) | |
Merge branch 'next'
Diffstat (limited to 'lib/puppet/interface')
| -rw-r--r-- | lib/puppet/interface/action.rb | 124 | ||||
| -rw-r--r-- | lib/puppet/interface/action_builder.rb | 44 | ||||
| -rw-r--r-- | lib/puppet/interface/action_manager.rb | 9 | ||||
| -rw-r--r-- | lib/puppet/interface/option.rb | 56 | ||||
| -rw-r--r-- | lib/puppet/interface/option_builder.rb | 37 | ||||
| -rw-r--r-- | lib/puppet/interface/option_manager.rb | 4 |
6 files changed, 237 insertions, 37 deletions
diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb index db338e39e..23366b407 100644 --- a/lib/puppet/interface/action.rb +++ b/lib/puppet/interface/action.rb @@ -7,8 +7,10 @@ class Puppet::Interface::Action raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ @face = face @name = name.to_sym - @options = {} attrs.each do |k, v| send("#{k}=", v) end + + @options = {} + @when_rendering = {} end # This is not nice, but it is the easiest way to make us behave like the @@ -20,11 +22,79 @@ 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 + end + 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 + return unless @when_rendering.has_key? type + return @when_rendering[type].bind(@face) + 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 + + + ######################################################################## + # Documentation stuff, whee! + attr_accessor :summary, :description + def summary=(value) + value = value.to_s + value =~ /\n/ and + raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead." + + @summary = value + 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 @@ -82,13 +152,24 @@ class Puppet::Interface::Action # idea how motivated we were to make this cleaner. Sorry. --daniel 2011-03-31 internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym - file = __FILE__ + "+eval" - line = __LINE__ + 1 - wrapper = "def #{@name}(*args, &block) - args << {} unless args.last.is_a? Hash - args << block if block_given? - self.__send__(#{internal_name.inspect}, *args) - end" + file = __FILE__ + "+eval" + line = __LINE__ + 1 + wrapper = <<WRAPPER +def #{@name}(*args) + if args.last.is_a? Hash then + options = args.last + else + args << (options = {}) + end + + action = get_action(#{name.inspect}) + action.validate_args(args) + __invoke_decorations(:before, action, args, options) + rval = self.__send__(#{internal_name.inspect}, *args) + __invoke_decorations(:after, action, args, options) + return rval +end +WRAPPER if @face.is_a?(Class) @face.class_eval do eval wrapper, nil, file, line end @@ -123,7 +204,28 @@ class Puppet::Interface::Action (@options.keys + @face.options).sort end - def get_option(name) - @options[name.to_sym] || @face.get_option(name) + def get_option(name, with_inherited_options = true) + option = @options[name.to_sym] + if option.nil? and with_inherited_options + option = @face.get_option(name) + end + option + end + + def validate_args(args) + required = options.map do |name| + get_option(name) + end.select(&:required?).collect(&:name) - args.last.keys + + return if required.empty? + raise ArgumentError, "missing required options (#{required.join(', ')})" + end + + ######################################################################## + # 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 __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 34bb3fa44..2ffa38709 100644 --- a/lib/puppet/interface/action_builder.rb +++ b/lib/puppet/interface/action_builder.rb @@ -20,16 +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 summary(text) - @action.summary = text + 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 + + # 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/action_manager.rb b/lib/puppet/interface/action_manager.rb index d75697afa..440be2d1b 100644 --- a/lib/puppet/interface/action_manager.rb +++ b/lib/puppet/interface/action_manager.rb @@ -5,8 +5,13 @@ module Puppet::Interface::ActionManager # the code to do so. def action(name, &block) @actions ||= {} + @default_action ||= nil raise "Action #{name} already defined for #{self}" if action?(name) action = Puppet::Interface::ActionBuilder.build(self, name, &block) + if action.default + raise "Actions #{@default_action.name} and #{name} cannot both be default" if @default_action + @default_action = action + end @actions[action.name] = action end @@ -50,6 +55,10 @@ module Puppet::Interface::ActionManager return result end + def get_default_action + @default_action + end + def action?(name) actions.include?(name.to_sym) end diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb index ccc2fbba7..f4c56cb2c 100644 --- a/lib/puppet/interface/option.rb +++ b/lib/puppet/interface/option.rb @@ -1,19 +1,4 @@ -require 'puppet/interface' - class Puppet::Interface::Option - attr_reader :parent - attr_reader :name - attr_reader :aliases - attr_reader :optparse - attr_accessor :desc - - def takes_argument? - !!@argument - end - def optional_argument? - !!@optional_argument - end - def initialize(parent, *declaration, &block) @parent = parent @optparse = [] @@ -58,8 +43,9 @@ class Puppet::Interface::Option # Is our argument optional? The rules about consistency apply here, also, # just like they do to taking arguments at all. --daniel 2011-03-30 - @optional_argument = @optparse.any? { |o| o.include? "[" } - if @optional_argument and not @optparse.all? { |o| o.include? "[" } then + @optional_argument = @optparse.any? { |o| o=~/[ =]\[/ } + @optional_argument and raise ArgumentError, "Options with optional arguments are not supported" + if @optional_argument and not @optparse.all? { |o| o=~/[ =]\[/ } then raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional" end end @@ -79,4 +65,40 @@ class Puppet::Interface::Option raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/ name.to_sym end + + + def takes_argument? + !!@argument + end + def optional_argument? + !!@optional_argument + end + def required? + !!@required + end + + attr_reader :parent, :name, :aliases, :optparse + attr_accessor :required, :desc + + attr_accessor :before_action + 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__(:__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__(:__add_method, __decoration_name(:after), proc) + end + + def __decoration_name(type) + if @parent.is_a? Puppet::Interface::Action then + :"option #{name} from #{parent.name} #{type} decoration" + else + :"option #{name} #{type} decoration" + end + end end diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb index 2240b3e4a..8f358c222 100644 --- a/lib/puppet/interface/option_builder.rb +++ b/lib/puppet/interface/option_builder.rb @@ -11,15 +11,44 @@ class Puppet::Interface::OptionBuilder def initialize(face, *declaration, &block) @face = face @option = Puppet::Interface::Option.new(face, *declaration) - block and instance_eval(&block) + instance_eval(&block) if block_given? @option end # Metaprogram the simple DSL from the option class. Puppet::Interface::Option.instance_methods.grep(/=$/).each do |setter| - next if setter =~ /^=/ # special case, darn it... + next if setter =~ /^=/ + dsl = setter.sub(/=$/, '') - dsl = setter.to_s.sub(/=$/, '') - define_method(dsl) do |value| @option.send(setter, value) end + unless private_instance_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 + + def required(value = true) + @option.required = value end end diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb index 56df9760f..d42359c07 100644 --- a/lib/puppet/interface/option_manager.rb +++ b/lib/puppet/interface/option_manager.rb @@ -37,10 +37,10 @@ module Puppet::Interface::OptionManager result.sort end - def get_option(name) + def get_option(name, with_inherited_options = true) @options ||= {} result = @options[name.to_sym] - unless result then + if result.nil? and with_inherited_options then if self.is_a?(Class) and superclass.respond_to?(:get_option) result = superclass.get_option(name) elsif self.class.respond_to?(:get_option) |
