Alexandros Kosiaris has uploaded a new change for review. https://gerrit.wikimedia.org/r/278241
Change subject: stdlib: import deep_merge function ...................................................................... stdlib: import deep_merge function Import the #189 PR of puppetlabs-stdlib to add the deep_merge function https://github.com/puppetlabs/puppetlabs-stdlib/pull/189. It is a variant of the merge function that we already have in our repo. It is done as a solo commit, instead of fully upgrading our stdlib version to the one by puppetlabs in order to avoid any unforeseen consequences to other users of stdlib in our repo. Change-Id: Iddd0b869b1c55197d5e7436bdab8bac67cbf5d06 --- A modules/stdlib/189.patch A modules/stdlib/lib/puppet/parser/functions/deep_merge.rb A modules/stdlib/spec/unit/puppet/parser/functions/deep_merge_spec.rb 3 files changed, 287 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/operations/puppet refs/changes/41/278241/1 diff --git a/modules/stdlib/189.patch b/modules/stdlib/189.patch new file mode 100644 index 0000000..c58ddf0 --- /dev/null +++ b/modules/stdlib/189.patch @@ -0,0 +1,158 @@ +From a25afe1965d0fdea01aff86117fd6f9c50322280 Mon Sep 17 00:00:00 2001 +From: Justin Burnham <[email protected]> +Date: Tue, 24 Sep 2013 10:57:15 -0700 +Subject: [PATCH] (#20200) Add a recursive merge function. + +Issue #20200 notes that the merge function does not +support nested hashes. + +To prevent unintended side effects with changing merge, +add a deep_merge function instead. +--- + lib/puppet/parser/functions/deep_merge.rb | 52 +++++++++++++++ + .../puppet/parser/functions/deep_merge_spec.rb | 77 ++++++++++++++++++++++ + 2 files changed, 129 insertions(+) + create mode 100644 lib/puppet/parser/functions/deep_merge.rb + create mode 100644 spec/unit/puppet/parser/functions/deep_merge_spec.rb + +diff --git a/lib/puppet/parser/functions/deep_merge.rb b/lib/puppet/parser/functions/deep_merge.rb +new file mode 100644 +index 0000000..3021d77 +--- /dev/null ++++ b/lib/puppet/parser/functions/deep_merge.rb +@@ -0,0 +1,52 @@ ++module Puppet::Parser::Functions ++ newfunction(:deep_merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| ++ Recursively merges two or more hashes together and returns the resulting hash. ++ ++ For example: ++ ++ $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } ++ $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } ++ $merged_hash = deep_merge($hash1, $hash2) ++ # The resulting hash is equivalent to: ++ # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } ++ ++ When there is a duplicate key that is a hash, they are recursively merged. ++ When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." ++ ++ ENDHEREDOC ++ ++ if args.length < 2 ++ raise Puppet::ParseError, ("deep_merge(): wrong number of arguments (#{args.length}; must be at least 2)") ++ end ++ ++ result = Hash.new ++ args.each do |arg| ++ next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef ++ # If the argument was not a hash, skip it. ++ unless arg.is_a?(Hash) ++ raise Puppet::ParseError, "deep_merge: unexpected argument type #{arg.class}, only expects hash arguments" ++ end ++ ++ # Now we have to traverse our hash assigning our non-hash values ++ # to the matching keys in our result while following our hash values ++ # and repeating the process. ++ overlay( result, arg ) ++ end ++ return( result ) ++ end ++end ++ ++def overlay( hash1, hash2 ) ++ hash2.each do |key, value| ++ if( value.is_a?(Hash) ) ++ if( ! hash1.has_key?( key ) or ! hash1[key].is_a?(Hash)) ++ hash1[key] = value ++ else ++ overlay( hash1[key], value ) ++ end ++ else ++ hash1[key] = value ++ end ++ end ++end ++ +diff --git a/spec/unit/puppet/parser/functions/deep_merge_spec.rb b/spec/unit/puppet/parser/functions/deep_merge_spec.rb +new file mode 100644 +index 0000000..fffb7f7 +--- /dev/null ++++ b/spec/unit/puppet/parser/functions/deep_merge_spec.rb +@@ -0,0 +1,77 @@ ++#! /usr/bin/env ruby -S rspec ++ ++require 'spec_helper' ++ ++describe Puppet::Parser::Functions.function(:deep_merge) do ++ let(:scope) { PuppetlabsSpec::PuppetInternals.scope } ++ ++ describe 'when calling deep_merge from puppet' do ++ it "should not compile when no arguments are passed" do ++ pending("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ ++ Puppet[:code] = '$x = deep_merge()' ++ expect { ++ scope.compiler.compile ++ }.to raise_error(Puppet::ParseError, /wrong number of arguments/) ++ end ++ ++ it "should not compile when 1 argument is passed" do ++ pending("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ ++ Puppet[:code] = "$my_hash={'one' => 1}\n$x = deep_merge($my_hash)" ++ expect { ++ scope.compiler.compile ++ }.to raise_error(Puppet::ParseError, /wrong number of arguments/) ++ end ++ end ++ ++ describe 'when calling deep_merge on the scope instance' do ++ it 'should require all parameters are hashes' do ++ expect { new_hash = scope.function_deep_merge([{}, '2'])}.to raise_error(Puppet::ParseError, /unexpected argument type String/) ++ expect { new_hash = scope.function_deep_merge([{}, 2])}.to raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) ++ end ++ ++ it 'should accept empty strings as puppet undef' do ++ expect { new_hash = scope.function_deep_merge([{}, ''])}.not_to raise_error(Puppet::ParseError, /unexpected argument type String/) ++ end ++ ++ it 'should be able to deep_merge two hashes' do ++ new_hash = scope.function_deep_merge([{'one' => '1', 'two' => '1'}, {'two' => '2', 'three' => '2'}]) ++ new_hash['one'].should == '1' ++ new_hash['two'].should == '2' ++ new_hash['three'].should == '2' ++ end ++ ++ it 'should deep_merge multiple hashes' do ++ hash = scope.function_deep_merge([{'one' => 1}, {'one' => '2'}, {'one' => '3'}]) ++ hash['one'].should == '3' ++ end ++ ++ it 'should accept empty hashes' do ++ scope.function_deep_merge([{},{},{}]).should == {} ++ end ++ ++ it 'should deep_merge subhashes' do ++ hash = scope.function_deep_merge([{'one' => 1}, {'two' => 2, 'three' => { 'four' => 4 } }]) ++ hash['one'].should == 1 ++ hash['two'].should == 2 ++ hash['three'].should == { 'four' => 4 } ++ end ++ ++ it 'should append to subhashes' do ++ hash = scope.function_deep_merge([{'one' => { 'two' => 2 } }, { 'one' => { 'three' => 3 } }]) ++ hash['one'].should == { 'two' => 2, 'three' => 3 } ++ end ++ ++ it 'should append to subhashes 2' do ++ hash = scope.function_deep_merge([{'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, {'two' => 'dos', 'three' => { 'five' => 5 } }]) ++ hash['one'].should == 1 ++ hash['two'].should == 'dos' ++ hash['three'].should == { 'four' => 4, 'five' => 5 } ++ end ++ ++ it 'should append to subhashes 3' do ++ hash = scope.function_deep_merge([{ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, { 'key1' => { 'b' => 99 } }]) ++ hash['key1'].should == { 'a' => 1, 'b' => 99 } ++ hash['key2'].should == { 'c' => 3 } ++ end ++ end ++end diff --git a/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb b/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb new file mode 100644 index 0000000..3021d77 --- /dev/null +++ b/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb @@ -0,0 +1,52 @@ +module Puppet::Parser::Functions + newfunction(:deep_merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Recursively merges two or more hashes together and returns the resulting hash. + + For example: + + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = deep_merge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + + When there is a duplicate key that is a hash, they are recursively merged. + When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." + + ENDHEREDOC + + if args.length < 2 + raise Puppet::ParseError, ("deep_merge(): wrong number of arguments (#{args.length}; must be at least 2)") + end + + result = Hash.new + args.each do |arg| + next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef + # If the argument was not a hash, skip it. + unless arg.is_a?(Hash) + raise Puppet::ParseError, "deep_merge: unexpected argument type #{arg.class}, only expects hash arguments" + end + + # Now we have to traverse our hash assigning our non-hash values + # to the matching keys in our result while following our hash values + # and repeating the process. + overlay( result, arg ) + end + return( result ) + end +end + +def overlay( hash1, hash2 ) + hash2.each do |key, value| + if( value.is_a?(Hash) ) + if( ! hash1.has_key?( key ) or ! hash1[key].is_a?(Hash)) + hash1[key] = value + else + overlay( hash1[key], value ) + end + else + hash1[key] = value + end + end +end + diff --git a/modules/stdlib/spec/unit/puppet/parser/functions/deep_merge_spec.rb b/modules/stdlib/spec/unit/puppet/parser/functions/deep_merge_spec.rb new file mode 100644 index 0000000..fffb7f7 --- /dev/null +++ b/modules/stdlib/spec/unit/puppet/parser/functions/deep_merge_spec.rb @@ -0,0 +1,77 @@ +#! /usr/bin/env ruby -S rspec + +require 'spec_helper' + +describe Puppet::Parser::Functions.function(:deep_merge) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + describe 'when calling deep_merge from puppet' do + it "should not compile when no arguments are passed" do + pending("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = '$x = deep_merge()' + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + + it "should not compile when 1 argument is passed" do + pending("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = "$my_hash={'one' => 1}\n$x = deep_merge($my_hash)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + end + + describe 'when calling deep_merge on the scope instance' do + it 'should require all parameters are hashes' do + expect { new_hash = scope.function_deep_merge([{}, '2'])}.to raise_error(Puppet::ParseError, /unexpected argument type String/) + expect { new_hash = scope.function_deep_merge([{}, 2])}.to raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) + end + + it 'should accept empty strings as puppet undef' do + expect { new_hash = scope.function_deep_merge([{}, ''])}.not_to raise_error(Puppet::ParseError, /unexpected argument type String/) + end + + it 'should be able to deep_merge two hashes' do + new_hash = scope.function_deep_merge([{'one' => '1', 'two' => '1'}, {'two' => '2', 'three' => '2'}]) + new_hash['one'].should == '1' + new_hash['two'].should == '2' + new_hash['three'].should == '2' + end + + it 'should deep_merge multiple hashes' do + hash = scope.function_deep_merge([{'one' => 1}, {'one' => '2'}, {'one' => '3'}]) + hash['one'].should == '3' + end + + it 'should accept empty hashes' do + scope.function_deep_merge([{},{},{}]).should == {} + end + + it 'should deep_merge subhashes' do + hash = scope.function_deep_merge([{'one' => 1}, {'two' => 2, 'three' => { 'four' => 4 } }]) + hash['one'].should == 1 + hash['two'].should == 2 + hash['three'].should == { 'four' => 4 } + end + + it 'should append to subhashes' do + hash = scope.function_deep_merge([{'one' => { 'two' => 2 } }, { 'one' => { 'three' => 3 } }]) + hash['one'].should == { 'two' => 2, 'three' => 3 } + end + + it 'should append to subhashes 2' do + hash = scope.function_deep_merge([{'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, {'two' => 'dos', 'three' => { 'five' => 5 } }]) + hash['one'].should == 1 + hash['two'].should == 'dos' + hash['three'].should == { 'four' => 4, 'five' => 5 } + end + + it 'should append to subhashes 3' do + hash = scope.function_deep_merge([{ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, { 'key1' => { 'b' => 99 } }]) + hash['key1'].should == { 'a' => 1, 'b' => 99 } + hash['key2'].should == { 'c' => 3 } + end + end +end -- To view, visit https://gerrit.wikimedia.org/r/278241 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Iddd0b869b1c55197d5e7436bdab8bac67cbf5d06 Gerrit-PatchSet: 1 Gerrit-Project: operations/puppet Gerrit-Branch: production Gerrit-Owner: Alexandros Kosiaris <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
