Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
47d91e8
commit 308941e
Showing
8 changed files
with
534 additions
and
160 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,268 @@ | |||
module Xcodeproj | |||
|
|||
# Computes the recursive diff of Hashes, Array and other objects. | |||
# | |||
# Useful to compare two projects. Inspired from | |||
# 'active_support/core_ext/hash/diff'. | |||
# | |||
# @example | |||
# h1 = { :common => 'value', :changed => 'v1' } | |||
# h2 = { :common => 'value', :changed => 'v2', :addition => 'new_value' } | |||
# h1.recursive_diff(h2) == { | |||
# :changed => { | |||
# :self => 'v1', | |||
# :other => 'v2' | |||
# }, | |||
# :addition => { | |||
# :self => nil, | |||
# :other => 'new_value' | |||
# } | |||
# } #=> true | |||
# | |||
# | |||
# | |||
# | |||
module Differ | |||
|
|||
# Computes the recursive difference of two given values. | |||
# | |||
# @param [Object] value_1 | |||
# The first value to compare. | |||
# | |||
# @param [Object] value_2 | |||
# The second value to compare. | |||
# | |||
# @param [Object] key_1 | |||
# The key for the diff of value_1. | |||
# | |||
# @param [Object] key_2 | |||
# The key for the diff of value_2. | |||
# | |||
# @param [Object] id_key | |||
# The key used to identify correspondent hashes in an array. | |||
# | |||
# @return [Hash] The diff | |||
# @return [Nil] if the given values are equal. | |||
# | |||
def self.diff(value_1, value_2, options = {}) | |||
options[:key_1] ||= 'value_1' | |||
options[:key_2] ||= 'value_2' | |||
options[:id_key] ||= nil | |||
|
|||
if value_1.class == value_2.class | |||
method = case value_1 | |||
when Hash then :hash_diff | |||
when Array then :array_diff | |||
else :generic_diff | |||
end | |||
else | |||
method = :generic_diff | |||
end | |||
self.send(method, value_1, value_2, options) | |||
end | |||
|
|||
# Optimized for reducing the noise from the tree hash of projects | |||
# | |||
def self.project_diff(project_1, project_2, key_1 = 'project_1', key_2 = 'project_2') | |||
project_1 = project_1.to_tree_hash unless project_1.is_a?(Hash) | |||
project_2 = project_2.to_tree_hash unless project_2.is_a?(Hash) | |||
options = { | |||
:key_1 => key_1, | |||
:key_2 => key_2, | |||
:id_key => 'displayName', | |||
} | |||
diff(project_1, project_2, options) | |||
end | |||
|
|||
#-------------------------------------------------------------------------# | |||
|
|||
public | |||
|
|||
# @!group Type specific handlers | |||
|
|||
# Computes the recursive difference of two hashes. | |||
# | |||
# @see diff | |||
# | |||
def self.hash_diff(value_1, value_2, options) | |||
ensure_class(value_1, Hash) | |||
ensure_class(value_2, Hash) | |||
return nil if value_1 == value_2 | |||
|
|||
result = {} | |||
all_keys = (value_1.keys + value_2.keys).uniq | |||
all_keys.each do |key| | |||
key_value_1 = value_1[key] | |||
key_value_2 = value_2[key] | |||
diff = diff(key_value_1, key_value_2, options) | |||
if diff | |||
result[key] = diff if diff | |||
end | |||
end | |||
if result.empty? | |||
nil | |||
else | |||
result | |||
end | |||
end | |||
|
|||
# Returns the recursive diff of two arrays. | |||
# | |||
# @see diff | |||
# | |||
def self.array_diff(value_1, value_2, options) | |||
ensure_class(value_1, Array) | |||
ensure_class(value_2, Array) | |||
return nil if value_1 == value_2 | |||
|
|||
new_objects_value_1 = (value_1 - value_2) | |||
new_objects_value_2 = (value_2 - value_1) | |||
return nil if value_1.empty? && value_2.empty? | |||
|
|||
matched_diff = {} | |||
if id_key = options[:id_key] | |||
matched_value_1 = [] | |||
matched_value_2 = [] | |||
new_objects_value_1.each do |entry_value_1| | |||
if entry_value_1.is_a?(Hash) | |||
id_value = entry_value_1[id_key] | |||
entry_value_2 = new_objects_value_2.find do |entry| | |||
entry[id_key] == id_value | |||
end | |||
if entry_value_2 | |||
matched_value_1 << entry_value_1 | |||
matched_value_2 << entry_value_2 | |||
diff = diff(entry_value_1, entry_value_2, options) | |||
matched_diff[id_value] = diff if diff | |||
end | |||
end | |||
end | |||
|
|||
new_objects_value_1 = new_objects_value_1 - matched_value_1 | |||
new_objects_value_2 = new_objects_value_2 - matched_value_2 | |||
end | |||
|
|||
if new_objects_value_1.empty? && new_objects_value_2.empty? | |||
if matched_diff.empty? | |||
nil | |||
else | |||
matched_diff | |||
end | |||
else | |||
result = {} | |||
result[options[:key_1]] = new_objects_value_1 unless new_objects_value_1.empty? | |||
result[options[:key_2]] = new_objects_value_2 unless new_objects_value_2.empty? | |||
result[:diff] = matched_diff unless matched_diff.empty? | |||
result | |||
end | |||
end | |||
|
|||
# Returns the diff of two generic objects. | |||
# | |||
# @see diff | |||
# | |||
def self.generic_diff(value_1, value_2, options) | |||
return nil if value_1 == value_2 | |||
|
|||
{ | |||
options[:key_1] => value_1, | |||
options[:key_2] => value_2 | |||
} | |||
end | |||
|
|||
#-------------------------------------------------------------------------# | |||
|
|||
public | |||
|
|||
# @!group Cleaning | |||
|
|||
# Returns a copy of the hash where the given key is removed recursively. | |||
# | |||
# @param [Hash] hash | |||
# The hash to clean | |||
# | |||
# @param [Object] key | |||
# The key to remove. | |||
# | |||
# @return [Hash] A copy of the hash without the key. | |||
# | |||
def self.clean_hash(hash, key) | |||
new_hash = hash.dup | |||
self.clean_hash!(new_hash, key) | |||
new_hash | |||
end | |||
|
|||
# Recursively cleans a key from the given hash. | |||
# | |||
# @param [Hash] hash | |||
# The hash to clean | |||
# | |||
# @param [Object] key | |||
# The key to remove. | |||
# | |||
# @return [void] | |||
# | |||
def self.clean_hash!(hash, key) | |||
hash.delete(key) | |||
hash.each do |_, value| | |||
case value | |||
when Hash | |||
clean_hash!(value, key) | |||
when Array | |||
value.each { |entry| clean_hash!(entry, key) if entry.is_a?(Hash)} | |||
end | |||
end | |||
end | |||
|
|||
#-------------------------------------------------------------------------# | |||
|
|||
private | |||
|
|||
# @! Helpers | |||
|
|||
# Ensures that the given object belongs to the given class. | |||
# | |||
# @param [Object] object | |||
# The object to check. | |||
# | |||
# @param [Class] klass | |||
# the expected class of the object. | |||
# | |||
# @raise If the object doesn't belong to the given class. | |||
# | |||
# @return [void] | |||
# | |||
def self.ensure_class(object, klass) | |||
raise "Wrong type `#{object.inspect}`" unless object.is_a?(klass) | |||
end | |||
|
|||
#-------------------------------------------------------------------------# | |||
|
|||
end | |||
end | |||
|
|||
#-----------------------------------------------------------------------------# | |||
|
|||
# TODO: Remove monkey patching | |||
|
|||
class Hash | |||
def recursive_diff(other, self_key = 'self', other_key = 'other') | |||
Xcodeproj::Differ.project_diff(self, other, self_key, other_key) | |||
end | |||
|
|||
def recursive_delete(key_to_delete) | |||
Xcodeproj::Differ.project_diff!(self, key_to_delete) | |||
end | |||
end | |||
|
|||
class Array | |||
def recursive_diff(other, self_key = 'self', other_key = 'other') | |||
Xcodeproj::Differ.project_diff(self, other, self_key, other_key) | |||
end | |||
end | |||
|
|||
class Object | |||
def recursive_diff(other, self_key = 'self', other_key = 'other') | |||
Xcodeproj::Differ.project_diff(self, other, self_key, other_key) | |||
end | |||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.