Skip to content

Commit

Permalink
Introduce Xcodeproj::Differ
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiopelosin committed Feb 15, 2013
1 parent 47d91e8 commit 308941e
Show file tree
Hide file tree
Showing 8 changed files with 534 additions and 160 deletions.
5 changes: 3 additions & 2 deletions lib/xcodeproj.rb
Expand Up @@ -10,11 +10,12 @@ def message
end end
end end


autoload :Config, 'xcodeproj/config'
autoload :Command, 'xcodeproj/command' autoload :Command, 'xcodeproj/command'
autoload :Config, 'xcodeproj/config'
autoload :Constants, 'xcodeproj/constants' autoload :Constants, 'xcodeproj/constants'
autoload :Differ, 'xcodeproj/differ'
autoload :Helper, 'xcodeproj/helper' autoload :Helper, 'xcodeproj/helper'
autoload :Project, 'xcodeproj/project' autoload :Project, 'xcodeproj/project'
autoload :Workspace, 'xcodeproj/workspace'
autoload :UI, 'xcodeproj/user_interface' autoload :UI, 'xcodeproj/user_interface'
autoload :Workspace, 'xcodeproj/workspace'
end end
14 changes: 7 additions & 7 deletions lib/xcodeproj/command/project_diff.rb
Expand Up @@ -32,20 +32,20 @@ def initialize(argv)




def run def run
hash_1 = Project.new(@path_project1).to_tree_hash hash_1 = Project.new(@path_project1).to_tree_hash.dup
hash_2 = Project.new(@path_project2).to_tree_hash hash_2 = Project.new(@path_project2).to_tree_hash.dup
(@keys_to_ignore).each do |key| @keys_to_ignore.each do |key|
hash_1.recursive_delete(key) Differ.clean_hash!(hash_1, key)
hash_2.recursive_delete(key) Differ.clean_hash!(hash_2, key)
end end


diff = hash_1.recursive_diff(hash_2, @path_project1, @path_project2) diff = Differ.project_diff(hash_1, hash_2, @path_project1, @path_project2)
diff.recursive_delete('displayName')


require 'yaml' require 'yaml'
yaml = diff.to_yaml yaml = diff.to_yaml
yaml = yaml.gsub(@path_project1, @path_project1.cyan) yaml = yaml.gsub(@path_project1, @path_project1.cyan)
yaml = yaml.gsub(@path_project2, @path_project2.magenta) yaml = yaml.gsub(@path_project2, @path_project2.magenta)
yaml = yaml.gsub(":diff:", "diff:".yellow)
puts yaml puts yaml
end end
end end
Expand Down
268 changes: 268 additions & 0 deletions lib/xcodeproj/differ.rb
@@ -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
2 changes: 0 additions & 2 deletions lib/xcodeproj/project.rb
@@ -1,9 +1,7 @@
require 'fileutils' require 'fileutils'
require 'pathname' require 'pathname'
require 'xcodeproj/xcodeproj_ext' require 'xcodeproj/xcodeproj_ext'

require 'xcodeproj/project/object' require 'xcodeproj/project/object'
require 'xcodeproj/project/recursive_diff'


module Xcodeproj module Xcodeproj


Expand Down

0 comments on commit 308941e

Please sign in to comment.