Permalink
Browse files

Introduce Xcodeproj::Differ

  • Loading branch information...
fabiopelosin committed Feb 15, 2013
1 parent 47d91e8 commit 308941eeaa3bca817742c774fd584cc5ab1c8f84
View
@@ -10,11 +10,12 @@ def message
end
end
- autoload :Config, 'xcodeproj/config'
autoload :Command, 'xcodeproj/command'
+ autoload :Config, 'xcodeproj/config'
autoload :Constants, 'xcodeproj/constants'
+ autoload :Differ, 'xcodeproj/differ'
autoload :Helper, 'xcodeproj/helper'
autoload :Project, 'xcodeproj/project'
- autoload :Workspace, 'xcodeproj/workspace'
autoload :UI, 'xcodeproj/user_interface'
+ autoload :Workspace, 'xcodeproj/workspace'
end
@@ -32,20 +32,20 @@ def initialize(argv)
def run
- hash_1 = Project.new(@path_project1).to_tree_hash
- hash_2 = Project.new(@path_project2).to_tree_hash
- (@keys_to_ignore).each do |key|
- hash_1.recursive_delete(key)
- hash_2.recursive_delete(key)
+ hash_1 = Project.new(@path_project1).to_tree_hash.dup
+ hash_2 = Project.new(@path_project2).to_tree_hash.dup
+ @keys_to_ignore.each do |key|
+ Differ.clean_hash!(hash_1, key)
+ Differ.clean_hash!(hash_2, key)
end
- diff = hash_1.recursive_diff(hash_2, @path_project1, @path_project2)
- diff.recursive_delete('displayName')
+ diff = Differ.project_diff(hash_1, hash_2, @path_project1, @path_project2)
require 'yaml'
yaml = diff.to_yaml
yaml = yaml.gsub(@path_project1, @path_project1.cyan)
yaml = yaml.gsub(@path_project2, @path_project2.magenta)
+ yaml = yaml.gsub(":diff:", "diff:".yellow)
puts yaml
end
end
View
@@ -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
View
@@ -1,9 +1,7 @@
require 'fileutils'
require 'pathname'
require 'xcodeproj/xcodeproj_ext'
-
require 'xcodeproj/project/object'
-require 'xcodeproj/project/recursive_diff'
module Xcodeproj
Oops, something went wrong.

0 comments on commit 308941e

Please sign in to comment.