# module Chef::Mixin::DeepMerge

## 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

### Constants

OLD_KNOCKOUT_MATCH

  Regex to match the “knockout prefix” that was used to indicate subtractive merging in Chef 10.x and previous. Subtractive merging is removed as of Chef 11, but we detect attempted use of it and raise an error (see: raise_if_knockout_used!)

OLD_KNOCKOUT_PREFIX

### Public Instance Methods

deep_merge(source, dest) click to toggle source

    # File lib/chef/mixin/deep_merge.rb, line 170
    def deep_merge(source, dest)
      deep_merge!(safe_dup(source), safe_dup(dest))
    end

deep_merge!(source, dest) click to toggle source

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)

    # File lib/chef/mixin/deep_merge.rb, line 75
    def deep_merge!(source, dest)
      # if dest doesn't exist, then simply copy source to it
      if dest.nil?
        dest = source; return dest
      end

      raise_if_knockout_used!(source)
      raise_if_knockout_used!(dest)
      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
              raise_if_knockout_used!(src_value)
              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

hash_only_merge(merge_onto, merge_with) click to toggle source

    # File lib/chef/mixin/deep_merge.rb, line 113
    def hash_only_merge(merge_onto, merge_with)
      hash_only_merge!(safe_dup(merge_onto), safe_dup(merge_with))
    end

hash_only_merge!(merge_onto, merge_with) click to toggle source

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.

    # File lib/chef/mixin/deep_merge.rb, line 127
    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|
          merge_onto[key] = if merge_onto.has_key?(key)
                              hash_only_merge(merge_onto[key], merge_with_value)
                            else
                              merge_with_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

merge(first, second) click to toggle source

    # File lib/chef/mixin/deep_merge.rb, line 43
    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

raise_if_knockout_used!(obj) click to toggle source

Checks for attempted use of subtractive merge, which was removed for Chef 11.0. If subtractive merge use is detected, will raise an InvalidSubtractiveMerge exception.

    # File lib/chef/mixin/deep_merge.rb, line 152
    def raise_if_knockout_used!(obj)
      if uses_knockout?(obj)
        raise InvalidSubtractiveMerge, "subtractive merge with !merge is no longer supported"
      end
    end

role_merge(first, second) click to toggle source

Inherited roles use the knockout_prefix array subtraction functionality This is likely to go away in Chef >= 0.11

    # File lib/chef/mixin/deep_merge.rb, line 52
    def role_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

safe_dup(thing) click to toggle source

    # File lib/chef/mixin/deep_merge.rb, line 117
    def safe_dup(thing)
      thing.dup
    rescue TypeError
      thing
    end

uses_knockout?(obj) click to toggle source

Checks for attempted use of subtractive merge in `obj`.

    # File lib/chef/mixin/deep_merge.rb, line 159
    def uses_knockout?(obj)
      case obj
      when String
        obj =~ OLD_KNOCKOUT_MATCH
      when Array
        obj.any? {|element| element.respond_to?(:gsub) && element =~ OLD_KNOCKOUT_MATCH }
      else
        false
      end
    end