diff --git a/lib/puppet/functions/reduce.rb b/lib/puppet/functions/reduce.rb index 83850dfe8..b23c94dde 100644 --- a/lib/puppet/functions/reduce.rb +++ b/lib/puppet/functions/reduce.rb @@ -1,102 +1,102 @@ # Applies a parameterized block to each element in a sequence of entries from the first # argument (_the enumerable_) and returns the last result of the invocation of the parameterized block. # # This function takes two mandatory arguments: the first should be an Array, Hash, or something of # enumerable type, and the last a parameterized block as produced by the puppet syntax: # # $a.reduce |$memo, $x| { ... } # reduce($a) |$memo, $x| { ... } # # When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn. # When the first argument is a hash each entry is converted to an array with `[key, value]` before being # fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash # and mandatory block. # # $a.reduce(start) |$memo, $x| { ... } # reduce($a, start) |$memo, $x| { ... } # # If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second # elements of the enumeration, and if the enumerable has fewer than 2 elements, the first # element is produced as the result of the reduction without invocation of the block. # # On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the # next invocation. # # @example Using reduce # # # Reduce an array # $a = [1,2,3] # $a.reduce |$memo, $entry| { $memo + $entry } # #=> 6 # # # Reduce hash values # $a = {a => 1, b => 2, c => 3} # $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] } # #=> [sum, 6] # # # reverse a string # "abc".reduce |$memo, $char| { "$char$memo" } # #=>"cbe" # # It is possible to provide a starting 'memo' as an argument. # # @example Using reduce with given start 'memo' # # # Reduce an array # $a = [1,2,3] # $a.reduce(4) |$memo, $entry| { $memo + $entry } # #=> 10 # # # Reduce hash values # $a = {a => 1, b => 2, c => 3} # $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] } # #=> [sum, 10] # # @example Using reduce with an Integer range # # Integer[1,4].reduce |$memo, $x| { $memo + $x } # #=> 10 # # @since 3.2 for Array and Hash # @since 3.5 for additional enumerable types # @note requires `parser = future`. # Puppet::Functions.create_function(:reduce) do dispatch :reduce_without_memo do param 'Object', :enumerable required_block_param end dispatch :reduce_with_memo do param 'Object', :enumerable param 'Object', :memo required_block_param end require 'puppet/util/functions/iterative_support' include Puppet::Util::Functions::IterativeSupport def reduce_without_memo(enumerable, pblock) assert_serving_size(pblock) enum = asserted_enumerable(enumerable) enum.reduce {|memo, x| pblock.call(nil, memo, x) } end def reduce_with_memo(enumerable, given_memo, pblock) assert_serving_size(pblock) enum = asserted_enumerable(enumerable) enum.reduce(given_memo) {|memo, x| pblock.call(nil, memo, x) } end # Asserts number of lambda parameters with more specific error message than the generic # mis-matched arguments message that is produced by the dispatcher's type checking. # def assert_serving_size(pblock) serving_size = pblock.parameter_count - if serving_size != 2 + unless serving_size == 2 || pblock.last_captures_rest? && serving_size <= 2 raise ArgumentError, "reduce(): block must define 2 parameters; memo, value. Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end end end diff --git a/lib/puppet/functions/slice.rb b/lib/puppet/functions/slice.rb index e0fe8aaba..e103043da 100644 --- a/lib/puppet/functions/slice.rb +++ b/lib/puppet/functions/slice.rb @@ -1,121 +1,121 @@ # Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first # argument and returns the first argument, or if no block is given returns a new array with a concatenation of # the slices. # # This function takes two mandatory arguments: the first, `$a`, should be an Array, Hash, or something of # enumerable type (integer, Integer range, or String), and the second, `$n`, the number of elements to include # in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax: # # $a.slice($n) |$x| { ... } # slice($a) |$x| { ... } # # The parameterized block should have either one parameter (receiving an array with the slice), or the same number # of parameters as specified by the slice size (each parameter receiving its part of the slice). # In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining # elements. When the block has multiple parameters, excess parameters are set to :undef for an array or # enumerable type, and to empty arrays for a Hash. # # $a.slice(2) |$first, $second| { ... } # # When the first argument is a Hash, each `key,value` entry is counted as one, e.g, a slice size of 2 will produce # an array of two arrays with key, and value. # # @example Using slice with Hash # # $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } # $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } # # When called without a block, the function produces a concatenated result of the slices. # # @example Using slice without a block # # slice([1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]] # slice(Integer[1,6], 2) # produces [[1,2], [3,4], [5,6]] # slice(4,2) # produces [[0,1], [2,3]] # slice('hello',2) # produces [[h, e], [l, l], [o]] # # @since 3.2 for Array and Hash # @since 3.5 for additional enumerable types # @note requires `parser = future`. # Puppet::Functions.create_function(:slice) do dispatch :slice_Hash do param 'Hash[Object, Object]', :hash param 'Integer[1, default]', :slize_size optional_block_param end dispatch :slice_Enumerable do param 'Object', :enumerable param 'Integer[1, default]', :slize_size optional_block_param end require 'puppet/util/functions/iterative_support' include Puppet::Util::Functions::IterativeSupport def slice_Hash(hash, slice_size, pblock = nil) result = slice_Common(hash, slice_size, [], pblock) pblock ? hash : result end def slice_Enumerable(enumerable, slice_size, pblock = nil) enum = asserted_enumerable(enumerable) result = slice_Common(enum, slice_size, :undef, pblock) pblock ? enumerable : result end def slice_Common(o, slice_size, filler, pblock) serving_size = asserted_slice_serving_size(pblock, slice_size) enumerator = o.each_slice(slice_size) result = [] if serving_size == 1 begin if pblock loop do pblock.call(nil, enumerator.next) end else loop do result << enumerator.next end end rescue StopIteration end else begin loop do a = enumerator.next if a.size < serving_size a = a.dup.fill(filler, a.length...serving_size) end pblock.call(nil, *a) end rescue StopIteration end end if pblock o else result end end def asserted_slice_serving_size(pblock, slice_size) if pblock - serving_size = pblock.captures_rest? ? slice_size : pblock.parameter_count + serving_size = pblock.last_captures_rest? ? slice_size : pblock.parameter_count else serving_size = 1 end if serving_size == 0 raise ArgumentError, "slice(): block must define at least one parameter. Block has 0." end unless serving_size == 1 || serving_size == slice_size raise ArgumentError, "slice(): block must define one parameter, or " + "the same number of parameters as the given size of the slice (#{slice_size}). Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end serving_size end end