1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Steve Midgley (http://www.misuse.org/science)
# Copyright:: Copyright 2009-2016, Chef Software Inc.
# Copyright:: Copyright 2008-2016, Steve Midgley
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class Chef
module Mixin
# == Chef::Mixin::DeepMerge
# Implements a deep merging algorithm for nested data structures.
# ==== Notice:
# This code was originally imported from deep_merge by Steve Midgley.
# deep_merge is available under the MIT license from
# http://trac.misuse.org/science/wiki/DeepMerge
module DeepMerge
extend self
def merge(first, second)
first = Mash.new(first) unless first.kind_of?(Mash)
second = Mash.new(second) unless second.kind_of?(Mash)
DeepMerge.deep_merge(second, first)
end
class InvalidParameter < StandardError; end
# Deep Merge core documentation.
# deep_merge! method permits merging of arbitrary child elements. The two top level
# elements must be hashes. These hashes can contain unlimited (to stack limit) levels
# of child elements. These child elements to not have to be of the same types.
# Where child elements are of the same type, deep_merge will attempt to merge them together.
# Where child elements are not of the same type, deep_merge will skip or optionally overwrite
# the destination element with the contents of the source element at that level.
# So if you have two hashes like this:
# source = {:x => [1,2,3], :y => 2}
# dest = {:x => [4,5,'6'], :y => [7,8,9]}
# dest.deep_merge!(source)
# Results: {:x => [1,2,3,4,5,'6'], :y => 2}
# By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
# To avoid this, use "deep_merge" (no bang/exclamation mark)
def deep_merge!(source, dest)
# if dest doesn't exist, then simply copy source to it
if dest.nil?
dest = source; return dest
end
case source
when nil
dest
when Hash
if dest.kind_of?(Hash)
source.each do |src_key, src_value|
if dest[src_key]
dest[src_key] = deep_merge!(src_value, dest[src_key])
else # dest[src_key] doesn't exist so we take whatever source has
dest[src_key] = src_value
end
end
else # dest isn't a hash, so we overwrite it completely
dest = source
end
when Array
if dest.kind_of?(Array)
dest = dest | source
else
dest = source
end
when String
dest = source
else # src_hash is not an array or hash, so we'll have to overwrite dest
dest = source
end
dest
end # deep_merge!
def hash_only_merge(merge_onto, merge_with)
hash_only_merge!(safe_dup(merge_onto), safe_dup(merge_with))
end
def safe_dup(thing)
thing.dup
rescue TypeError
thing
end
# Deep merge without Array merge.
# `merge_onto` is the object that will "lose" in case of conflict.
# `merge_with` is the object whose values will replace `merge_onto`s
# values when there is a conflict.
def hash_only_merge!(merge_onto, merge_with)
# If there are two Hashes, recursively merge.
if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash)
merge_with.each do |key, merge_with_value|
value =
if merge_onto.has_key?(key)
hash_only_merge(merge_onto[key], merge_with_value)
else
merge_with_value
end
if merge_onto.respond_to?(:public_method_that_only_deep_merge_should_use)
# we can't call ImmutableMash#[]= because its immutable, but we need to mutate it to build it in-place
merge_onto.public_method_that_only_deep_merge_should_use(key, value)
else
merge_onto[key] = value
end
end
merge_onto
# If merge_with is nil, don't replace merge_onto
elsif merge_with.nil?
merge_onto
# In all other cases, replace merge_onto with merge_with
else
merge_with
end
end
def deep_merge(source, dest)
deep_merge!(safe_dup(source), safe_dup(dest))
end
end
end
end
|