"Fossies" - the Fresh Open Source Software Archive

Member "puppet-7.10.0/lib/puppet/interface/action.rb" (16 Aug 2021, 14203 Bytes) of package /linux/misc/puppet-7.10.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 # coding: utf-8
    2 require 'prettyprint'
    3 
    4 # This represents an action that is attached to a face. Actions should
    5 # be constructed by calling {Puppet::Interface::ActionManager#action},
    6 # which is available on {Puppet::Interface}, and then calling methods of
    7 # {Puppet::Interface::ActionBuilder} in the supplied block.
    8 # @api private
    9 class Puppet::Interface::Action
   10   extend  Puppet::Interface::DocGen
   11   include Puppet::Interface::FullDocs
   12 
   13   # @api private
   14   def initialize(face, name)
   15     raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/
   16     @face    = face
   17     @name    = name.to_sym
   18 
   19     # The few bits of documentation we actually demand.  The default license
   20     # is a favour to our end users; if you happen to get that in a core face
   21     # report it as a bug, please. --daniel 2011-04-26
   22     @authors = []
   23     @license  = 'All Rights Reserved'
   24 
   25     # @options collects the added options in the order they're declared.
   26     # @options_hash collects the options keyed by alias for quick lookups.
   27     @options        = []
   28     @display_global_options = []
   29     @options_hash   = {}
   30     @when_rendering = {}
   31   end
   32 
   33   # This is not nice, but it is the easiest way to make us behave like the
   34   # Ruby Method object rather than UnboundMethod.  Duplication is vaguely
   35   # annoying, but at least we are a shallow clone. --daniel 2011-04-12
   36 
   37   # @return [void]
   38   # @api private
   39   def __dup_and_rebind_to(to)
   40     bound_version = self.dup
   41     bound_version.instance_variable_set(:@face, to)
   42     return bound_version
   43   end
   44 
   45   def to_s() "#{@face}##{@name}" end
   46 
   47   # The name of this action
   48   # @return [Symbol]
   49   attr_reader   :name
   50 
   51   # The face this action is attached to
   52   # @return [Puppet::Interface]
   53   attr_reader   :face
   54 
   55   # Whether this is the default action for the face
   56   # @return [Boolean]
   57   # @api private
   58   attr_accessor :default
   59   def default?
   60     !!@default
   61   end
   62 
   63   ########################################################################
   64   # Documentation...
   65   attr_doc :returns
   66   attr_doc :arguments
   67   def synopsis
   68     build_synopsis(@face.name, default? ? nil : name, arguments)
   69   end
   70 
   71   ########################################################################
   72   # Support for rendering formats and all.
   73 
   74 
   75   # @api private
   76   def when_rendering(type)
   77     unless type.is_a? Symbol
   78       raise ArgumentError, _("The rendering format must be a symbol, not %{class_name}") % { class_name: type.class.name }
   79     end
   80     # Do we have a rendering hook for this name?
   81     return @when_rendering[type].bind(@face) if @when_rendering.has_key? type
   82 
   83     # How about by another name?
   84     alt = type.to_s.sub(/^to_/, '').to_sym
   85     return @when_rendering[alt].bind(@face) if @when_rendering.has_key? alt
   86 
   87     # Guess not, nothing to run.
   88     return nil
   89   end
   90 
   91   # @api private
   92   def set_rendering_method_for(type, proc)
   93     unless proc.is_a? Proc
   94       msg = if proc.nil?
   95               #TRANSLATORS 'set_rendering_method_for' and 'Proc' should not be translated
   96               _("The second argument to set_rendering_method_for must be a Proc")
   97             else
   98               #TRANSLATORS 'set_rendering_method_for' and 'Proc' should not be translated
   99               _("The second argument to set_rendering_method_for must be a Proc, not %{class_name}") %
  100                   { class_name: proc.class.name }
  101             end
  102       raise ArgumentError, msg
  103     end
  104 
  105     if proc.arity != 1 and proc.arity != (@positional_arg_count + 1)
  106       msg = if proc.arity < 0 then
  107               #TRANSLATORS 'when_rendering', 'when_invoked' are method names and should not be translated
  108               _("The when_rendering method for the %{face} face %{name} action takes either just one argument,"\
  109                   " the result of when_invoked, or the result plus the %{arg_count} arguments passed to the"\
  110                   " when_invoked block, not a variable number") %
  111                   { face: @face.name, name: name, arg_count: @positional_arg_count }
  112             else
  113               #TRANSLATORS 'when_rendering', 'when_invoked' are method names and should not be translated
  114               _("The when_rendering method for the %{face} face %{name} action takes either just one argument,"\
  115                   " the result of when_invoked, or the result plus the %{arg_count} arguments passed to the"\
  116                   " when_invoked block, not %{string}") %
  117                   { face: @face.name, name: name, arg_count: @positional_arg_count, string: proc.arity.to_s }
  118             end
  119       raise ArgumentError, msg
  120     end
  121     unless type.is_a? Symbol
  122       raise ArgumentError, _("The rendering format must be a symbol, not %{class_name}") % { class_name: type.class.name }
  123     end
  124     if @when_rendering.has_key? type then
  125       raise ArgumentError, _("You can't define a rendering method for %{type} twice") % { type: type }
  126     end
  127     # Now, the ugly bit.  We add the method to our interface object, and
  128     # retrieve it, to rotate through the dance of getting a suitable method
  129     # object out of the whole process. --daniel 2011-04-18
  130     @when_rendering[type] =
  131       @face.__send__( :__add_method, __render_method_name_for(type), proc)
  132   end
  133 
  134   # @return [void]
  135   # @api private
  136   def __render_method_name_for(type)
  137     :"#{name}_when_rendering_#{type}"
  138   end
  139   private :__render_method_name_for
  140 
  141 
  142   # @api private
  143   # @return [Symbol]
  144   attr_accessor :render_as
  145   def render_as=(value)
  146     @render_as = value.to_sym
  147   end
  148 
  149   # @api private
  150   # @return [void]
  151   def deprecate
  152     @deprecated = true
  153   end
  154 
  155   # @api private
  156   # @return [Boolean]
  157   def deprecated?
  158     @deprecated
  159   end
  160 
  161   ########################################################################
  162   # Initially, this was defined to allow the @action.invoke pattern, which is
  163   # a very natural way to invoke behaviour given our introspection
  164   # capabilities.   Heck, our initial plan was to have the faces delegate to
  165   # the action object for invocation and all.
  166   #
  167   # It turns out that we have a binding problem to solve: @face was bound to
  168   # the parent class, not the subclass instance, and we don't pass the
  169   # appropriate context or change the binding enough to make this work.
  170   #
  171   # We could hack around it, by either mandating that you pass the context in
  172   # to invoke, or try to get the binding right, but that has probably got
  173   # subtleties that we don't instantly think of – especially around threads.
  174   #
  175   # So, we are pulling this method for now, and will return it to life when we
  176   # have the time to resolve the problem.  For now, you should replace...
  177   #
  178   #     @action = @face.get_action(name)
  179   #     @action.invoke(arg1, arg2, arg3)
  180   #
  181   # ...with...
  182   #
  183   #     @action = @face.get_action(name)
  184   #     @face.send(@action.name, arg1, arg2, arg3)
  185   #
  186   # I understand that is somewhat cumbersome, but it functions as desired.
  187   # --daniel 2011-03-31
  188   #
  189   # PS: This code is left present, but commented, to support this chunk of
  190   # documentation, for the benefit of the reader.
  191   #
  192   # def invoke(*args, &block)
  193   #   @face.send(name, *args, &block)
  194   # end
  195 
  196 
  197   # We need to build an instance method as a wrapper, using normal code, to be
  198   # able to expose argument defaulting between the caller and definer in the
  199   # Ruby API.  An extra method is, sadly, required for Ruby 1.8 to work since
  200   # it doesn't expose bind on a block.
  201   #
  202   # Hopefully we can improve this when we finally shuffle off the last of Ruby
  203   # 1.8 support, but that looks to be a few "enterprise" release eras away, so
  204   # we are pretty stuck with this for now.
  205   #
  206   # Patches to make this work more nicely with Ruby 1.9 using runtime version
  207   # checking and all are welcome, provided that they don't change anything
  208   # outside this little ol' bit of code and all.
  209   #
  210   # Incidentally, we though about vendoring evil-ruby and actually adjusting
  211   # the internal C structure implementation details under the hood to make
  212   # this stuff work, because it would have been cleaner.  Which gives you an
  213   # idea how motivated we were to make this cleaner.  Sorry.
  214   # --daniel 2011-03-31
  215 
  216 
  217   # The arity of the action
  218   # @return [Integer]
  219   attr_reader   :positional_arg_count
  220 
  221   # The block that is executed when the action is invoked
  222   # @return [block]
  223   attr_accessor :when_invoked
  224   def when_invoked=(block)
  225 
  226     internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym
  227 
  228     arity = @positional_arg_count = block.arity
  229     if arity == 0 then
  230       # This will never fire on 1.8.7, which treats no arguments as "*args",
  231       # but will on 1.9.2, which treats it as "no arguments".  Which bites,
  232       # because this just begs for us to wind up in the horrible situation
  233       # where a 1.8 vs 1.9 error bites our end users. --daniel 2011-04-19
  234       #TRANSLATORS 'when_invoked' should not be translated
  235       raise ArgumentError, _("when_invoked requires at least one argument (options) for action %{name}") % { name: @name }
  236     elsif arity > 0 then
  237       range = Range.new(1, arity - 1)
  238       decl = range.map { |x| "arg#{x}" } << "options = {}"
  239       optn = ""
  240       args = "[" + (range.map { |x| "arg#{x}" } << "options").join(", ") + "]"
  241     else
  242       range = Range.new(1, arity.abs - 1)
  243       decl = range.map { |x| "arg#{x}" } << "*rest"
  244       optn = "rest << {} unless rest.last.is_a?(Hash)"
  245       if arity == -1 then
  246         args = "rest"
  247       else
  248         args = "[" + range.map { |x| "arg#{x}" }.join(", ") + "] + rest"
  249       end
  250     end
  251 
  252     file    = __FILE__ + "+eval[wrapper]"
  253     line    = __LINE__ + 2 # <== points to the same line as 'def' in the wrapper.
  254     wrapper = <<WRAPPER
  255 def #{@name}(#{decl.join(", ")})
  256   #{optn}
  257   args    = #{args}
  258   action  = get_action(#{name.inspect})
  259   args   << action.validate_and_clean(args.pop)
  260   __invoke_decorations(:before, action, args, args.last)
  261   rval = self.__send__(#{internal_name.inspect}, *args)
  262   __invoke_decorations(:after, action, args, args.last)
  263   return rval
  264 end
  265 WRAPPER
  266 
  267     if @face.is_a?(Class)
  268       @face.class_eval do eval wrapper, nil, file, line end
  269       @face.send(:define_method, internal_name, &block)
  270       @when_invoked = @face.instance_method(name)
  271     else
  272       @face.instance_eval do eval wrapper, nil, file, line end
  273       @face.meta_def(internal_name, &block)
  274       @when_invoked = @face.method(name).unbind
  275     end
  276   end
  277 
  278   def add_option(option)
  279     option.aliases.each do |name|
  280       conflict = get_option(name)
  281       if conflict
  282         raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict}") %
  283             { option: option, conflict: conflict }
  284       else
  285         conflict = @face.get_option(name)
  286         if conflict
  287           raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict} on %{face}") %
  288               { option: option, conflict: conflict, face: @face }
  289         end
  290       end
  291     end
  292 
  293     @options << option.name
  294 
  295     option.aliases.each do |name|
  296       @options_hash[name] = option
  297     end
  298 
  299     option
  300   end
  301 
  302   def option?(name)
  303     @options_hash.include? name.to_sym
  304   end
  305 
  306   def options
  307     @face.options + @options
  308   end
  309 
  310   def add_display_global_options(*args)
  311     @display_global_options ||= []
  312     [args].flatten.each do |refopt|
  313       unless Puppet.settings.include? refopt
  314         #TRANSLATORS 'Puppet.settings' should not be translated
  315         raise ArgumentError, _("Global option %{option} does not exist in Puppet.settings") % { option: refopt }
  316       end
  317       @display_global_options << refopt
  318     end
  319     @display_global_options.uniq!
  320     @display_global_options
  321   end
  322 
  323   def display_global_options(*args)
  324     args ? add_display_global_options(args) : @display_global_options + @face.display_global_options
  325   end
  326   alias :display_global_option :display_global_options
  327 
  328   def get_option(name, with_inherited_options = true)
  329     option = @options_hash[name.to_sym]
  330     if option.nil? and with_inherited_options
  331       option = @face.get_option(name)
  332     end
  333     option
  334   end
  335 
  336   def validate_and_clean(original)
  337     # The final set of arguments; effectively a hand-rolled shallow copy of
  338     # the original, which protects the caller from the surprises they might
  339     # get if they passed us a hash and we mutated it...
  340     result = {}
  341 
  342     # Check for multiple aliases for the same option, and canonicalize the
  343     # name of the argument while we are about it.
  344     overlap = Hash.new do |h, k| h[k] = [] end
  345     unknown = []
  346     original.keys.each do |name|
  347       option = get_option(name)
  348       if option
  349         canonical = option.name
  350         if result.has_key? canonical
  351           overlap[canonical] << name
  352         else
  353           result[canonical] = original[name]
  354         end
  355       elsif Puppet.settings.include? name
  356         result[name] = original[name]
  357       else
  358         unknown << name
  359       end
  360     end
  361 
  362     unless overlap.empty?
  363       overlap_list = overlap.map {|k, v| "(#{k}, #{v.sort.join(', ')})" }.join(", ")
  364       raise ArgumentError, _("Multiple aliases for the same option passed: %{overlap_list}") %
  365           { overlap_list: overlap_list }
  366     end
  367 
  368     unless unknown.empty?
  369       unknown_list = unknown.sort.join(", ")
  370       raise ArgumentError, _("Unknown options passed: %{unknown_list}") % { unknown_list: unknown_list }
  371     end
  372 
  373     # Inject default arguments and check for missing mandating options.
  374     missing = []
  375     options.map {|x| get_option(x) }.each do |option|
  376       name = option.name
  377       next if result.has_key? name
  378 
  379       if option.has_default?
  380         result[name] = option.default
  381       elsif option.required?
  382         missing << name
  383       end
  384     end
  385 
  386     unless missing.empty?
  387       missing_list = missing.sort.join(', ')
  388       raise ArgumentError, _("The following options are required: %{missing_list}") % { missing_list: missing_list }
  389     end
  390 
  391     # All done.
  392     return result
  393   end
  394 
  395   ########################################################################
  396   # Support code for action decoration; see puppet/interface.rb for the gory
  397   # details of why this is hidden away behind private. --daniel 2011-04-15
  398   private
  399   # @return [void]
  400   # @api private
  401   def __add_method(name, proc)
  402     @face.__send__ :__add_method, name, proc
  403   end
  404 end