diff --git a/lib/puppet/functions/filter.rb b/lib/puppet/functions/filter.rb index e268a3609..0654d9c9c 100644 --- a/lib/puppet/functions/filter.rb +++ b/lib/puppet/functions/filter.rb @@ -1,92 +1,113 @@ # Applies a parameterized block to each element in a sequence of entries from the first # argument and returns an array or hash (same type as left operand for array/hash, and array for # other enumerable types) with the entries for which the block evaluates to `true`. # # This function takes two mandatory arguments: the first should be an Array, a Hash, or an # Enumerable object (integer, Integer range, or String), # and the second a parameterized block as produced by the puppet syntax: # # $a.filter |$x| { ... } # filter($a) |$x| { ... } # # When the first argument is something other than a Hash, the block is called with each entry in turn. # When the first argument is a Hash the entry is an array with `[key, value]`. # # @example Using filter with one parameter # # # selects all that end with berry # $a = ["raspberry", "blueberry", "orange"] # $a.filter |$x| { $x =~ /berry$/ } # rasberry, blueberry # # If the block defines two parameters, they will be set to `index, value` (with index starting at 0) for all # enumerables except Hash, and to `key, value` for a Hash. # # @example Using filter with two parameters # # # selects all that end with 'berry' at an even numbered index # $a = ["raspberry", "blueberry", "orange"] # $a.filter |$index, $x| { $index % 2 == 0 and $x =~ /berry$/ } # raspberry # # # selects all that end with 'berry' and value >= 1 # $a = {"raspberry"=>0, "blueberry"=>1, "orange"=>1} # $a.filter |$key, $x| { $x =~ /berry$/ and $x >= 1 } # blueberry # # @since 3.4 for Array and Hash # @since 3.5 for other enumerables # @note requires `parser = future` # Puppet::Functions.create_function(:filter) do - dispatch :filter_Hash do + dispatch :filter_Hash_2 do param 'Hash[Any, Any]', :hash - required_block_param + required_block_param 'Callable[2,2]', :block end - dispatch :filter_Enumerable do + dispatch :filter_Hash_1 do + param 'Hash[Any, Any]', :hash + required_block_param 'Callable[1,1]', :block + end + + dispatch :filter_Enumerable_2 do param 'Any', :enumerable - required_block_param + required_block_param 'Callable[2,2]', :block end - require 'puppet/util/functions/iterative_support' - include Puppet::Util::Functions::IterativeSupport + dispatch :filter_Enumerable_1 do + param 'Any', :enumerable + required_block_param 'Callable[1,1]', :block + end - def filter_Hash(hash, pblock) - if asserted_serving_size(pblock, 'key') == 1 - result = hash.select {|x, y| pblock.call(self, [x, y]) } - else - result = hash.select {|x, y| pblock.call(self, x, y) } - end + def filter_Hash_1(hash, pblock) + result = hash.select {|x, y| pblock.call(self, [x, y]) } # Ruby 1.8.7 returns Array result = Hash[result] unless result.is_a? Hash result end - def filter_Enumerable(enumerable, pblock) + def filter_Hash_2(hash, pblock) + result = hash.select {|x, y| pblock.call(self, x, y) } + # Ruby 1.8.7 returns Array + result = Hash[result] unless result.is_a? Hash + result + end + + def filter_Enumerable_1(enumerable, pblock) result = [] index = 0 enum = asserted_enumerable(enumerable) - - if asserted_serving_size(pblock, 'index') == 1 - begin - loop do - it = enum.next - if pblock.call(nil, it) == true - result << it - end + begin + loop do + it = enum.next + if pblock.call(nil, it) == true + result << it end - rescue StopIteration end - else - begin - loop do - it = enum.next - if pblock.call(nil, index, it) == true - result << it - end - index += 1 + rescue StopIteration + end + result + end + + def filter_Enumerable_2(enumerable, pblock) + result = [] + index = 0 + enum = asserted_enumerable(enumerable) + begin + loop do + it = enum.next + if pblock.call(nil, index, it) == true + result << it end - rescue StopIteration + index += 1 end + rescue StopIteration end result 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/spec/unit/functions/filter_spec.rb b/spec/unit/functions/filter_spec.rb index 909837602..d33320149 100644 --- a/spec/unit/functions/filter_spec.rb +++ b/spec/unit/functions/filter_spec.rb @@ -1,149 +1,131 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'unit/functions/shared' describe 'the filter method' do include PuppetSpec::Compiler include Matchers::Resource before :each do Puppet[:parser] = 'future' end it 'should filter on an array (all berries)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $a.filter |$x|{ $x =~ /berry$/}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer)' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,10] $a.filter |$x|{ $x % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_9]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer) using two args index/value' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[10,18] $a.filter |$i, $x|{ $i % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_13]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_16]").with_parameter(:ensure, 'present') end it 'should produce an array when acting on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$x|{ $x =~ /berry$/} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'can filter array using index and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$index, $x|{ $index == 0 or $index ==2} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'can filter array using index and value (using captures-rest)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |*$ix|{ $ix[0] == 0 or $ix[0] ==2} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by key' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $a.filter |$x|{ $x[0] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should produce a hash when acting on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $b = $a.filter |$x|{ $x[0] =~ /berry$/} file { "/file_${b['strawberry']}": ensure => present } file { "/file_${b['blueberry']}": ensure => present } file { "/file_${b['orange']}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_red]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blue]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} $a.filter |$x|{ $x[1] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawb]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueb]").with_parameter(:ensure, 'present') end - context 'filter checks arguments and' do - it 'raises an error when block has more than 2 argument' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].filter |$indexm, $x, $yikes|{ } - MANIFEST - end.to raise_error(Puppet::Error, /block must define at most two parameters/) - end - - it 'raises an error when block has fewer than 1 argument' do - expect do - compile_to_catalog(<<-MANIFEST) - [1].filter || { } - MANIFEST - end.to raise_error(Puppet::Error, /block must define at least one parameter/) - end - end - it_should_behave_like 'all iterative functions argument checks', 'filter' it_should_behave_like 'all iterative functions hash handling', 'filter' end