summaryrefslogtreecommitdiffstats
path: root/lib/puppet/interface
diff options
context:
space:
mode:
authorJosh Cooper <josh@puppetlabs.com>2011-04-21 14:37:50 -0700
committerJosh Cooper <josh@puppetlabs.com>2011-04-21 14:37:50 -0700
commit01f610bb223b435dc52f491260af3ea002930102 (patch)
tree93565136d5ef2b4156fdd64476792e441bcfbb4e /lib/puppet/interface
parentac428b9557e2da251e4b51e48de844833ca0aa2a (diff)
parentfc66e98b84b9a16728af054485883334a5887cca (diff)
Merge branch 'next'
Diffstat (limited to 'lib/puppet/interface')
-rw-r--r--lib/puppet/interface/action.rb124
-rw-r--r--lib/puppet/interface/action_builder.rb44
-rw-r--r--lib/puppet/interface/action_manager.rb9
-rw-r--r--lib/puppet/interface/option.rb56
-rw-r--r--lib/puppet/interface/option_builder.rb37
-rw-r--r--lib/puppet/interface/option_manager.rb4
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)