def self.deep_merge!(source, dest, options = {})
merge_debug = options[:merge_debug] || false
overwrite_unmergeable = !options[:preserve_unmergeables]
knockout_prefix = options[:knockout_prefix] || nil
raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!" if knockout_prefix == ""
raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!" if knockout_prefix && !overwrite_unmergeable
array_split_char = options[:unpack_arrays] || false
sort_merged_arrays = options[:sort_merged_arrays] || false
di = options[:debug_indent] || ''
return dest if source.nil?
if dest.nil? && overwrite_unmergeable
dest = source; return dest
end
puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug
if source.kind_of?(Hash)
puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug
source.each do |src_key, src_value|
if dest.kind_of?(Hash)
puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
if dest[src_key]
puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + ' '))
else
puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
begin
src_dup = src_value.dup
rescue TypeError
src_dup = src_value
end
dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + ' '))
end
else
if overwrite_unmergeable
puts "#{di} overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over-> #{dest.inspect}" if merge_debug
dest = overwrite_unmergeables(source, dest, options)
end
end
end
elsif source.kind_of?(Array)
puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
if array_split_char
puts "#{di} split/join on source: #{source.inspect}" if merge_debug
source = source.join(array_split_char).split(array_split_char)
if dest.kind_of?(Array)
dest = dest.join(array_split_char).split(array_split_char)
end
end
if source.index(knockout_prefix)
dest = clear_or_nil(dest); source.delete(knockout_prefix)
end
if dest.kind_of?(Array)
if knockout_prefix
print "#{di} knocking out: " if merge_debug
source.delete_if do |ko_item|
retval = false
item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}}, "") : ko_item
if item != ko_item
print "#{ko_item} - " if merge_debug
dest.delete(item)
dest.delete(ko_item)
retval = true
end
retval
end
puts if merge_debug
end
puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
dest = dest | source
dest.sort! if sort_merged_arrays
elsif overwrite_unmergeable
puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug
dest = overwrite_unmergeables(source, dest, options)
end
else
puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug
dest = overwrite_unmergeables(source, dest, options)
end
puts "#{di}Returning #{dest.inspect}" if merge_debug
dest
end