diff --git a/lib/hiera/puppet_function.rb b/lib/hiera/puppet_function.rb index b167c15b1..a42821c2d 100644 --- a/lib/hiera/puppet_function.rb +++ b/lib/hiera/puppet_function.rb @@ -1,72 +1,71 @@ require 'hiera_puppet' # Provides the base class for the puppet functions hiera, hiera_array, hiera_hash, and hiera_include. # The actual function definitions will call init_dispatch and override the merge_type and post_lookup methods. # # @see hiera_array.rb, hiera_include.rb under lib/puppet/functions for sample usage # class Hiera::PuppetFunction < Puppet::Functions::InternalFunction def self.init_dispatch dispatch :hiera_splat do scope_param param 'Tuple[String, Any, Any, 1, 3]', :args end dispatch :hiera do scope_param param 'String',:key - param 'Any', :default - param 'Any', :override - arg_count(1,3) + optional_param 'Any', :default + optional_param 'Any', :override end dispatch :hiera_block1 do scope_param - param 'String', :key - required_block_param 'Callable[1,1]', :default_block + param 'String', :key + block_param 'Callable[1,1]', :default_block end dispatch :hiera_block2 do scope_param - param 'String', :key - param 'Any', :override - required_block_param 'Callable[1,1]', :default_block + param 'String', :key + param 'Any', :override + block_param 'Callable[1,1]', :default_block end end def hiera_splat(scope, args) hiera(scope, *args) end def hiera(scope, key, default = nil, override = nil) post_lookup(key, lookup(scope, key, default, override)) end def hiera_block1(scope, key, &default_block) common(scope, key, nil, default_block) end def hiera_block2(scope, key, override, &default_block) common(scope, key, override, default_block) end def common(scope, key, override, default_block) undefined = (@@undefined_value ||= Object.new) result = lookup(scope, key, undefined, override) post_lookup(key, result.equal?(undefined) ? default_block.call(key) : result) end private :common def lookup(scope, key, default, override) HieraPuppet.lookup(key, default,scope, override, merge_type) end def merge_type :priority end def post_lookup(key, result) result end end diff --git a/lib/puppet/functions.rb b/lib/puppet/functions.rb index f0029670c..1a888d743 100644 --- a/lib/puppet/functions.rb +++ b/lib/puppet/functions.rb @@ -1,571 +1,608 @@ # @note WARNING: This new function API is still under development and may change at any time # # Functions in the puppet language can be written in Ruby and distributed in # puppet modules. The function is written by creating a file in the module's # `lib/puppet/functions/` directory, where `` is # replaced with the module's name. The file should have the name of the function. # For example, to create a function named `min` in a module named `math` create # a file named `lib/puppet/functions/math/min.rb` in the module. # # A function is implemented by calling {Puppet::Functions.create_function}, and # passing it a block that defines the implementation of the function. # # Functions are namespaced inside the module that contains them. The name of # the function is prefixed with the name of the module. For example, # `math::min`. # # @example A simple function # Puppet::Functions.create_function('math::min') do # def min(a, b) # a <= b ? a : b # end # end # # Anatomy of a function # --- # # Functions are composed of four parts: the name, the implementation methods, # the signatures, and the dispatches. # # The name is the string given to the {Puppet::Functions.create_function} # method. It specifies the name to use when calling the function in the puppet # language, or from other functions. # # The implementation methods are ruby methods (there can be one or more) that # provide that actual implementation of the function's behavior. In the # simplest case the name of the function (excluding any namespace) and the name # of the method are the same. When that is done no other parts (signatures and # dispatches) need to be used. # # Signatures are a way of specifying the types of the function's parameters. # The types of any arguments will be checked against the types declared in the # signature and an error will be produced if they don't match. The types are # defined by using the same syntax for types as in the puppet language. # # Dispatches are how signatures and implementation methods are tied together. # When the function is called, puppet searches the signatures for one that # matches the supplied arguments. Each signature is part of a dispatch, which # specifies the method that should be called for that signature. When a # matching signature is found, the corrosponding method is called. # # Documentation for the function should be placed as comments to the # implementation method(s). # # @todo Documentation for individual instances of these new functions is not # yet tied into the puppet doc system. # # @example Dispatching to different methods by type # Puppet::Functions.create_function('math::min') do # dispatch :numeric_min do -# param 'Numeric', 'a' -# param 'Numeric', 'b' +# param 'Numeric', :a +# param 'Numeric', :b # end # # dispatch :string_min do -# param 'String', 'a' -# param 'String', 'b' +# param 'String', :a +# param 'String', :b # end # # def numeric_min(a, b) # a <= b ? a : b # end # # def string_min(a, b) # a.downcase <= b.downcase ? a : b # end # end # # Specifying Signatures # --- # # If nothing is specified, the number of arguments given to the function must # be the same as the number of parameters, and all of the parameters are of # type 'Any'. # -# To express that the last parameter captures the rest, the method -# `last_captures_rest` can be called. This indicates that the last parameter is -# a varargs parameter and will be passed to the implementing method as an array -# of the given type. +# The following methods can be used to define a parameter # -# When defining a dispatch for a function, the resulting dispatch matches -# against the specified argument types and min/max occurrence of optional -# entries. When the dispatch makes the call to the implementation method the -# arguments are simply passed and it is the responsibility of the method's -# implementor to ensure it can handle those arguments (i.e. there is no check -# that what was declared as optional actually has a default value, and that -# a "captures rest" is declared using a `*`). +# - _param_ - the argument must be given in the call. +# - _optional_param_ - the argument may be missing in the call. May not be followed by a required parameter +# - _repeated_param_ - the type specifies a repeating type that occurs 0 to "infinite" number of times. It may only appear last or just before a block parameter. +# - _block_param_ - a block must be given in the call. May only appear last. +# - _optional_block_param_ - a block may be given in the call. May only appear last. # -# @example Varargs +# The method name _required_param_ is an alias for _param_ and _required_block_param_ is an alias for _block_param_ +# +# A parameter definition takes 2 arguments: +# - _type_ A string that must conform to a type in the puppet language +# - _name_ A symbol denoting the parameter name +# +# Both arguments are optional when defining a block parameter. The _type_ defaults to "Callable" +# and the _name_ to :block. +# +# Note that the dispatch definition is used to match arguments given in a call to the function with the defined +# parameters. It then dispatches the call to the implementation method simply passing the given arguments on to +# that method without any further processing and it is the responsibility of that method's implementor to ensure +# that it can handle those arguments. +# +# @example Variable number of arguments # Puppet::Functions.create_function('foo') do # dispatch :foo do -# param 'Numeric', 'first' -# param 'Numeric', 'values' -# last_captures_rest +# param 'Numeric', :first +# repeated_param 'Numeric', :values # end # # def foo(first, *values) # # do something # end # end # +# There is no requirement for direct mapping between parameter definitions and the parameters in the +# receiving implementation method so the following example is also legal. Here the dispatch will ensure +# that `*values` in the receiver will be an array with at least one entry of type String and that any +# remaining entries are of type Numeric: +# +# @example Inexact mapping or parameters +# Puppet::Functions.create_function('foo') do +# dispatch :foo do +# param 'String', :first +# repeated_param 'Numeric', :values +# end +# +# def foo(*values) +# # do something +# end +# end +# # Access to Scope # --- # In general, functions should not need access to scope; they should be # written to act on their given input only. If they absolutely must look up # variable values, they should do so via the closure scope (the scope where # they are defined) - this is done by calling `closure_scope()`. # # Calling other Functions # --- # Calling other functions by name is directly supported via # {Puppet::Pops::Functions::Function#call_function}. This allows a function to # call other functions visible from its loader. # # @api public module Puppet::Functions # @param func_name [String, Symbol] a simple or qualified function name # @param block [Proc] the block that defines the methods and dispatch of the # Function to create # @return [Class] the newly created Function class # # @api public def self.create_function(func_name, function_base = Function, &block) if function_base.ancestors.none? { |s| s == Puppet::Pops::Functions::Function } raise ArgumentError, "Functions must be based on Puppet::Pops::Functions::Function. Got #{function_base}" end func_name = func_name.to_s # Creates an anonymous class to represent the function # The idea being that it is garbage collected when there are no more # references to it. # the_class = Class.new(function_base, &block) # Make the anonymous class appear to have the class-name # Even if this class is not bound to such a symbol in a global ruby scope and # must be resolved via the loader. # This also overrides any attempt to define a name method in the given block # (Since it redefines it) # # TODO, enforce name in lower case (to further make it stand out since Ruby # class names are upper case) # the_class.instance_eval do @func_name = func_name def name @func_name end end # Automatically create an object dispatcher based on introspection if the # loaded user code did not define any dispatchers. Fail if function name # does not match a given method name in user code. # if the_class.dispatcher.empty? simple_name = func_name.split(/::/)[-1] type, names = default_dispatcher(the_class, simple_name) last_captures_rest = (type.size_range[1] == Puppet::Pops::Types::INFINITY) the_class.dispatcher.add_dispatch(type, simple_name, names, nil, nil, nil, last_captures_rest) end # The function class is returned as the result of the create function method the_class end # Creates a default dispatcher configured from a method with the same name as the function # # @api private def self.default_dispatcher(the_class, func_name) unless the_class.method_defined?(func_name) raise ArgumentError, "Function Creation Error, cannot create a default dispatcher for function '#{func_name}', no method with this name found" end any_signature(*min_max_param(the_class.instance_method(func_name))) end # @api private def self.min_max_param(method) # Ruby 1.8.7 does not have support for details about parameters if method.respond_to?(:parameters) result = {:req => 0, :opt => 0, :rest => 0 } # TODO: Optimize into one map iteration that produces names map, and sets # count as side effect method.parameters.each { |p| result[p[0]] += 1 } from = result[:req] to = result[:rest] > 0 ? :default : from + result[:opt] names = method.parameters.map {|p| p[1].to_s } else # Cannot correctly compute the signature in Ruby 1.8.7 because arity for # optional values is screwed up (there is no way to get the upper limit), # an optional looks the same as a varargs In this case - the failure will # simply come later when the call fails # arity = method.arity from = arity >= 0 ? arity : -arity -1 to = arity >= 0 ? arity : :default # i.e. infinite (which is wrong when there are optional - flaw in 1.8.7) names = [] # no names available end [from, to, names] end # Construct a signature consisting of Object type, with min, and max, and given names. # (there is only one type entry). # # @api private def self.any_signature(from, to, names) # Construct the type for the signature # Tuple[Object, from, to] factory = Puppet::Pops::Types::TypeFactory [factory.callable(factory.any, from, to), names] end # Function # === # This class is the base class for all Puppet 4x Function API functions. A # specialized class is created for each puppet function. # # @api public class Function < Puppet::Pops::Functions::Function # @api private def self.builder @type_parser ||= Puppet::Pops::Types::TypeParser.new @all_callables ||= Puppet::Pops::Types::TypeFactory.all_callables DispatcherBuilder.new(dispatcher, @type_parser, @all_callables) end # Dispatch any calls that match the signature to the provided method name. # # @param meth_name [Symbol] The name of the implementation method to call # when the signature defined in the block matches the arguments to a call # to the function. # @return [Void] # # @api public def self.dispatch(meth_name, &block) builder().instance_eval do dispatch(meth_name, &block) end end end # Public api methods of the DispatcherBuilder are available within dispatch() # blocks declared in a Puppet::Function.create_function() call. # # @api public class DispatcherBuilder # @api private def initialize(dispatcher, type_parser, all_callables) @type_parser = type_parser @all_callables = all_callables @dispatcher = dispatcher end - # Defines a positional parameter with type and name + # Defines a required positional parameter with _type_ and _name_. # # @param type [String] The type specification for the parameter. - # @param name [String] The name of the parameter. This is primarily used - # for error message output and does not have to match the name of the - # parameter on the implementation method. + # @param name [Symbol] The name of the parameter. This is primarily used + # for error message output and does not have to match an implementation + # method parameter. # @return [Void] # # @api public def param(type, name) - raise ArgumentError, 'Parameters cannot be added after a block_param' unless @block_type.nil? - if type.is_a?(String) - @types << type - @names << name - # mark what should be picked for this position when dispatching - @weaving << @names.size()-1 - else - raise ArgumentError, "Type signature argument must be a String reference to a Puppet Data Type. Got #{type.class}" - end + internal_param(type, name) + raise ArgumentError, 'A required parameter cannot be added after an optional parameter' if @min != @max + @min += 1 + @max += 1 + end + alias required_param param + + # Defines an optional positional parameter with _type_ and _name_. + # May not be followed by a required parameter. + # + # @param type [String] The type specification for the parameter. + # @param name [Symbol] The name of the parameter. This is primarily used + # for error message output and does not have to match an implementation + # method parameter. + # @return [Void] + # + # @api public + def optional_param(type, name) + internal_param(type, name) + @max += 1 + end + + # Defines a repeated positional parameter with _type_ and _name_ that may occur 0 to "infinite" number of times. + # It may only appear last or just before a block parameter. + # + # @param type [String] The type specification for the parameter. + # @param name [Symbol] The name of the parameter. This is primarily used + # for error message output and does not have to match an implementation + # method parameter. + # @return [Void] + # + # @api public + def repeated_param(type, name) + internal_param(type, name) + @max = :default end # Defines one required block parameter that may appear last. If type and name is missing the # default type is "Callable", and the name is "block". If only one # parameter is given, then that is the name and the type is "Callable". # # @api public - def required_block_param(*type_and_name) + def block_param(*type_and_name) case type_and_name.size when 0 # the type must be an independent instance since it will be contained in another type type = @all_callables.copy name = 'block' when 1 # the type must be an independent instance since it will be contained in another type type = @all_callables.copy name = type_and_name[0] when 2 type_string, name = type_and_name type = @type_parser.parse(type_string) else raise ArgumentError, "block_param accepts max 2 arguments (type, name), got #{type_and_name.size}." end unless Puppet::Pops::Types::TypeCalculator.is_kind_of_callable?(type, false) raise ArgumentError, "Expected PCallableType or PVariantType thereof, got #{type.class}" end unless name.is_a?(String) || name.is_a?(Symbol) raise ArgumentError, "Expected block_param name to be a String or Symbol, got #{name.class}" end if @block_type.nil? @block_type = type @block_name = name else raise ArgumentError, 'Attempt to redefine block' end end + alias required_block_param block_param # Defines one optional block parameter that may appear last. If type or name is missing the # defaults are "any callable", and the name is "block". The implementor of the dispatch target # must use block = nil when it is optional (or an error is raised when the call is made). # # @api public def optional_block_param(*type_and_name) # same as required, only wrap the result in an optional type required_block_param(*type_and_name) @block_type = Puppet::Pops::Types::TypeFactory.optional(@block_type) end - # Specifies the min and max occurrence of arguments (of the specified types) - # if something other than the exact count from the number of specified - # types). The max value may be specified as :default if an infinite number of - # arguments are supported. When max is > than the number of specified - # types, the last specified type repeats. - # - # @api public - def arg_count(min_occurs, max_occurs) - @min = min_occurs - @max = max_occurs - unless min_occurs.is_a?(Integer) && min_occurs >= 0 - raise ArgumentError, "min arg_count of function parameter must be an Integer >=0, got #{min_occurs.class} '#{min_occurs}'" - end - if max_occurs == :default - @last_captures = true + private + + # @api private + def internal_param(type, name) + raise ArgumentError, 'Parameters cannot be added after a block parameter' unless @block_type.nil? + raise ArgumentError, 'Parameters cannot be added after a repeated parameter' if @max == :default + if type.is_a?(String) + @types << type + @names << name + # mark what should be picked for this position when dispatching + @weaving << @names.size()-1 else - unless max_occurs.is_a?(Integer) && max_occurs >= min_occurs - raise ArgumentError, "max arg_count of function parameter must be :default (infinite) or an Integer >= min arg_count, got min: '#{min_occurs}, max: '#{max_occurs}'" - end + raise ArgumentError, "Parameter 'type' must be a String reference to a Puppet Data Type. Got #{type.class}" end end - # Specifies that the last argument captures the rest. - # - # @api public - def last_captures_rest - @last_captures = true - end - - private - # @api private def dispatch(meth_name, &block) # an array of either an index into names/types, or an array with # injection information [type, name, injection_name] used when the call # is being made to weave injections into the given arguments. # @types = [] @names = [] @weaving = [] @injections = [] - @min = nil - @max = nil - @last_captures = false + @min = 0 + @max = 0 @block_type = nil @block_name = nil self.instance_eval &block callable_t = create_callable(@types, @block_type, @min, @max) - @dispatcher.add_dispatch(callable_t, meth_name, @names, @block_name, @injections, @weaving, @last_captures) + @dispatcher.add_dispatch(callable_t, meth_name, @names, @block_name, @injections, @weaving, @max == :default) end # Handles creation of a callable type from strings specifications of puppet # types and allows the min/max occurs of the given types to be given as one # or two integer values at the end. The given block_type should be # Optional[Callable], Callable, or nil. # # @api private def create_callable(types, block_type, from, to) mapped_types = types.map do |t| @type_parser.parse(t) end - if !(from.nil? && to.nil?) + if from != to + # :optional and/or :repeated parameters are present. mapped_types << from mapped_types << to end if block_type mapped_types << block_type end Puppet::Pops::Types::TypeFactory.callable(*mapped_types) end end private # @note WARNING: This style of creating functions is not public. It is a system # under development that will be used for creating "system" functions. # # This is a private, internal, system for creating functions. It supports # everything that the public function definition system supports as well as a # few extra features. # # Injection Support # === # The Function API supports injection of data and services. It is possible to # make injection that takes effect when the function is loaded (for services # and runtime configuration that does not change depending on how/from where # in what context the function is called. It is also possible to inject and # weave argument values into a call. # # Injection of attributes # --- # Injection of attributes is performed by one of the methods `attr_injected`, # and `attr_injected_producer`. The injected attributes are available via # accessor method calls. # # @example using injected attributes # Puppet::Functions.create_function('test') do # attr_injected String, :larger, 'message_larger' # attr_injected String, :smaller, 'message_smaller' # def test(a, b) # a > b ? larger() : smaller() # end # end # # @api private class InternalFunction < Function # @api private def self.builder @type_parser ||= Puppet::Pops::Types::TypeParser.new @all_callables ||= Puppet::Pops::Types::TypeFactory.all_callables InternalDispatchBuilder.new(dispatcher, @type_parser, @all_callables) end # Defines class level injected attribute with reader method # # @api private def self.attr_injected(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup(closure_scope, type, injection_name)) end instance_variable_get(ivar) end end # Defines class level injected producer attribute with reader method # # @api private def self.attr_injected_producer(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup_producer(closure_scope, type, injection_name)) end instance_variable_get(ivar) end end # Allows the implementation of a function to call other functions by name and pass the caller # scope. The callable functions are those visible to the same loader that loaded this function # (the calling function). # # @param scope [Puppet::Parser::Scope] The caller scope # @param function_name [String] The name of the function # @param *args [Object] splat of arguments # @return [Object] The result returned by the called function # # @api public def call_function_with_scope(scope, function_name, *args) internal_call_function(scope, function_name, args) end end # @note WARNING: This style of creating functions is not public. It is a system # under development that will be used for creating "system" functions. # # Injection and Weaving of parameters # --- # It is possible to inject and weave parameters into a call. These extra # parameters are not part of the parameters passed from the Puppet logic, and # they can not be overridden by parameters given as arguments in the call. # They are invisible to the Puppet Language. # # @example using injected parameters # Puppet::Functions.create_function('test') do # dispatch :test do # param 'Scalar', 'a' # param 'Scalar', 'b' # injected_param 'String', 'larger', 'message_larger' # injected_param 'String', 'smaller', 'message_smaller' # end # def test(a, b, larger, smaller) # a > b ? larger : smaller # end # end # # The function in the example above is called like this: # # test(10, 20) # # Using injected value as default # --- # Default value assignment is handled by using the regular Ruby mechanism (a # value is assigned to the variable). The dispatch simply indicates that the # value is optional. If the default value should be injected, it can be # handled different ways depending on what is desired: # # * by calling the accessor method for an injected Function class attribute. # This is suitable if the value is constant across all instantiations of the # function, and across all calls. # * by injecting a parameter into the call # to the left of the parameter, and then assigning that as the default value. # * One of the above forms, but using an injected producer instead of a # directly injected value. # # @example method with injected default values # Puppet::Functions.create_function('test') do # dispatch :test do # injected_param String, 'b_default', 'b_default_value_key' # param 'Scalar', 'a' # param 'Scalar', 'b' # end # def test(b_default, a, b = b_default) # # ... # end # end # # @api private class InternalDispatchBuilder < DispatcherBuilder def scope_param() @injections << [:scope, 'scope', '', :dispatcher_internal] # mark what should be picked for this position when dispatching @weaving << [@injections.size()-1] end # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # # @api private def injected_param(type, name, injection_name = '') @injections << [type, name, injection_name] # mark what should be picked for this position when dispatching @weaving << [@injections.size() -1] end # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # # @api private def injected_producer_param(type, name, injection_name = '') @injections << [type, name, injection_name, :producer] # mark what should be picked for this position when dispatching @weaving << [@injections.size()-1] end end end diff --git a/lib/puppet/functions/each.rb b/lib/puppet/functions/each.rb index a9c9caf7b..c52aeaafb 100644 --- a/lib/puppet/functions/each.rb +++ b/lib/puppet/functions/each.rb @@ -1,111 +1,111 @@ # Applies a parameterized block to each element in a sequence of selected entries from the first # argument and returns the first argument. # # This function takes two mandatory arguments: the first should be an Array or a Hash or something that is # of enumerable type (integer, Integer range, or String), and the second # a parameterized block as produced by the puppet syntax: # # $a.each |$x| { ... } # each($a) |$x| { ... } # # When the first argument is an Array (or of enumerable type other than Hash), the parameterized block # should define one or two block parameters. # For each application of the block, the next element from the array is selected, and it is passed to # the block if the block has one parameter. If the block has two parameters, the first is the elements # index, and the second the value. The index starts from 0. # # $a.each |$index, $value| { ... } # each($a) |$index, $value| { ... } # # When the first argument is a Hash, the parameterized block should define one or two parameters. # When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, # and when two parameters are defined the iteration is performed with key and value. # # $a.each |$entry| { ..."key ${$entry[0]}, value ${$entry[1]}" } # $a.each |$key, $value| { ..."key ${key}, value ${value}" } # # @example using each # # [1,2,3].each |$val| { ... } # 1, 2, 3 # [5,6,7].each |$index, $val| { ... } # (0, 5), (1, 6), (2, 7) # {a=>1, b=>2, c=>3}].each |$val| { ... } # ['a', 1], ['b', 2], ['c', 3] # {a=>1, b=>2, c=>3}.each |$key, $val| { ... } # ('a', 1), ('b', 2), ('c', 3) # Integer[ 10, 20 ].each |$index, $value| { ... } # (0, 10), (1, 11) ... # "hello".each |$char| { ... } # 'h', 'e', 'l', 'l', 'o' # 3.each |$number| { ... } # 0, 1, 2 # # @since 3.2 for Array and Hash # @since 3.5 for other enumerables # @note requires `parser = future` # Puppet::Functions.create_function(:each) do dispatch :foreach_Hash_2 do param 'Hash[Any, Any]', :hash - required_block_param 'Callable[2,2]', :block + block_param 'Callable[2,2]', :block end dispatch :foreach_Hash_1 do param 'Hash[Any, Any]', :hash - required_block_param 'Callable[1,1]', :block + block_param 'Callable[1,1]', :block end dispatch :foreach_Enumerable_2 do param 'Any', :enumerable - required_block_param 'Callable[2,2]', :block + block_param 'Callable[2,2]', :block end dispatch :foreach_Enumerable_1 do param 'Any', :enumerable - required_block_param 'Callable[1,1]', :block + block_param 'Callable[1,1]', :block end def foreach_Hash_1(hash) enumerator = hash.each_pair hash.size.times do yield(enumerator.next) end # produces the receiver hash end def foreach_Hash_2(hash) enumerator = hash.each_pair hash.size.times do yield(*enumerator.next) end # produces the receiver hash end def foreach_Enumerable_1(enumerable) enum = asserted_enumerable(enumerable) begin loop { yield(enum.next) } rescue StopIteration end # produces the receiver enumerable end def foreach_Enumerable_2(enumerable) enum = asserted_enumerable(enumerable) index = 0 begin loop do yield(index, enum.next) index += 1 end rescue StopIteration end # produces the receiver enumerable end def asserted_enumerable(obj) unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj) raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.") end enum end end diff --git a/lib/puppet/functions/epp.rb b/lib/puppet/functions/epp.rb index baa0c1bed..2477a1e2e 100644 --- a/lib/puppet/functions/epp.rb +++ b/lib/puppet/functions/epp.rb @@ -1,54 +1,53 @@ # Evaluates an Embedded Puppet Template (EPP) file and returns the rendered text result as a String. # # The first argument to this function should be a `/