module EasyDiff module Core def self.easy_diff(original, modified) removed = nil added = nil if original.nil? added = modified.safe_dup elsif modified.nil? removed = original.safe_dup elsif original.is_a?(Hash) && modified.is_a?(Hash) removed = {} added = {} keys_in_common = original.keys & modified.keys keys_removed = original.keys - modified.keys keys_added = modified.keys - original.keys keys_removed.each{ |key| removed[key] = original[key].safe_dup } keys_added.each{ |key| added[key] = modified[key].safe_dup } keys_in_common.each do |key| r, a = easy_diff original[key], modified[key] unless _blank?(r) && _blank?(a) removed[key] = r added[key] = a end end elsif original.is_a?(Array) && modified.is_a?(Array) removed = subtract_arrays(original, modified) added = subtract_arrays(modified, original) elsif original != modified removed = original added = modified end return removed, added end def self.easy_unmerge!(original, removed) if original.is_a?(Hash) && removed.is_a?(Hash) keys_in_common = original.keys & removed.keys keys_in_common.each do |key| if original[key] == removed[key] original.delete(key) else easy_unmerge!(original[key], removed[key]) end end elsif original.is_a?(Array) && removed.is_a?(Array) subtract_arrays!(original, removed) end original end def self.easy_merge!(original, added) if original.is_a?(Hash) && added.is_a?(Hash) added_keys = added.keys added_keys.each{ |key| original[key] = easy_merge!(original[key], added[key])} elsif original.is_a?(Array) && added.is_a?(Array) original += added else original = added.safe_dup end original end def self.easy_clone(original) Marshal::load(Marshal.dump(original)) end # Can't use regular empty? because that affects strings. def self._blank?(obj) if obj.is_a?(Hash) || obj.is_a?(Array) obj.empty? else obj.nil? end end # Regular array difference does not handle duplicate values in the way that is needed for this library. # Examples: # subtract_arrays([1, 1, 2, 3], [1, 2]) => [1, 3] # subtract_arrays([3, 3, 3, 4], [3, 4, 5]) => [3, 3] # Shamelessly stolen from http://stackoverflow.com/questions/3852755/ruby-array-subtraction-without-removing-items-more-than-once def self.subtract_arrays! arr1, arr2 counts = arr2.inject(Hash.new(0)) { |h, v| h[v] += 1; h } arr1.reject! { |e| counts[e] -= 1 unless counts[e].zero? } end # Non-destructive version of above method. def self.subtract_arrays arr1, arr2 cloned_arr1 = easy_clone(arr1) subtract_arrays!(cloned_arr1, arr2) cloned_arr1 end end end