xcodeproj 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ext/xcodeproj/xcodeproj_ext.c +1 -1
- data/lib/xcodeproj.rb +9 -7
- data/lib/xcodeproj/command/project_diff.rb +7 -7
- data/lib/xcodeproj/command/show.rb +11 -2
- data/lib/xcodeproj/config.rb +102 -38
- data/lib/xcodeproj/constants.rb +16 -14
- data/lib/xcodeproj/differ.rb +243 -0
- data/lib/xcodeproj/helper.rb +1 -0
- data/lib/xcodeproj/project.rb +92 -68
- data/lib/xcodeproj/project/object.rb +32 -9
- data/lib/xcodeproj/project/object/build_configuration.rb +21 -1
- data/lib/xcodeproj/project/object/build_file.rb +40 -6
- data/lib/xcodeproj/project/object/build_phase.rb +96 -84
- data/lib/xcodeproj/project/object/build_rule.rb +14 -8
- data/lib/xcodeproj/project/object/configuration_list.rb +7 -3
- data/lib/xcodeproj/project/object/container_item_proxy.rb +31 -29
- data/lib/xcodeproj/project/object/file_reference.rb +33 -20
- data/lib/xcodeproj/project/object/group.rb +58 -35
- data/lib/xcodeproj/project/object/native_target.rb +132 -108
- data/lib/xcodeproj/project/object/reference_proxy.rb +7 -3
- data/lib/xcodeproj/project/object/root_object.rb +4 -4
- data/lib/xcodeproj/project/object_attributes.rb +9 -6
- data/lib/xcodeproj/user_interface.rb +26 -0
- data/lib/xcodeproj/workspace.rb +23 -19
- metadata +4 -3
- data/lib/xcodeproj/project/recursive_diff.rb +0 -116
@@ -168,7 +168,7 @@ str_to_url(VALUE path) {
|
|
168
168
|
#else
|
169
169
|
VALUE p = rb_String(path);
|
170
170
|
#endif
|
171
|
-
CFURLRef fileURL = CFURLCreateFromFileSystemRepresentation(NULL, RSTRING_PTR(p), RSTRING_LEN(p), false);
|
171
|
+
CFURLRef fileURL = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)RSTRING_PTR(p), RSTRING_LEN(p), false);
|
172
172
|
if (!fileURL) {
|
173
173
|
rb_raise(rb_eArgError, "Unable to create CFURL from `%s'.", RSTRING_PTR(rb_inspect(path)));
|
174
174
|
}
|
data/lib/xcodeproj.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Xcodeproj
|
2
|
-
VERSION = '0.
|
2
|
+
VERSION = '0.5.0' unless defined? Xcodeproj::VERSION
|
3
3
|
|
4
4
|
class PlainInformative < StandardError
|
5
5
|
end
|
@@ -10,10 +10,12 @@ module Xcodeproj
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
autoload :
|
14
|
-
autoload :
|
15
|
-
autoload :Constants,
|
16
|
-
autoload :
|
17
|
-
autoload :
|
18
|
-
autoload :
|
13
|
+
autoload :Command, 'xcodeproj/command'
|
14
|
+
autoload :Config, 'xcodeproj/config'
|
15
|
+
autoload :Constants, 'xcodeproj/constants'
|
16
|
+
autoload :Differ, 'xcodeproj/differ'
|
17
|
+
autoload :Helper, 'xcodeproj/helper'
|
18
|
+
autoload :Project, 'xcodeproj/project'
|
19
|
+
autoload :UI, 'xcodeproj/user_interface'
|
20
|
+
autoload :Workspace, 'xcodeproj/workspace'
|
19
21
|
end
|
@@ -32,20 +32,20 @@ module Xcodeproj
|
|
32
32
|
|
33
33
|
|
34
34
|
def run
|
35
|
-
hash_1 = Project.new(@path_project1).to_tree_hash
|
36
|
-
hash_2 = Project.new(@path_project2).to_tree_hash
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
hash_1 = Project.new(@path_project1).to_tree_hash.dup
|
36
|
+
hash_2 = Project.new(@path_project2).to_tree_hash.dup
|
37
|
+
@keys_to_ignore.each do |key|
|
38
|
+
Differ.clean_hash!(hash_1, key)
|
39
|
+
Differ.clean_hash!(hash_2, key)
|
40
40
|
end
|
41
41
|
|
42
|
-
diff =
|
43
|
-
diff.recursive_delete('displayName')
|
42
|
+
diff = Differ.project_diff(hash_1, hash_2, @path_project1, @path_project2)
|
44
43
|
|
45
44
|
require 'yaml'
|
46
45
|
yaml = diff.to_yaml
|
47
46
|
yaml = yaml.gsub(@path_project1, @path_project1.cyan)
|
48
47
|
yaml = yaml.gsub(@path_project2, @path_project2.magenta)
|
48
|
+
yaml = yaml.gsub(":diff:", "diff:".yellow)
|
49
49
|
puts yaml
|
50
50
|
end
|
51
51
|
end
|
@@ -18,8 +18,17 @@ module Xcodeproj
|
|
18
18
|
|
19
19
|
def run
|
20
20
|
require 'yaml'
|
21
|
-
|
22
|
-
|
21
|
+
pretty_print = xcodeproj.pretty_print
|
22
|
+
sections = []
|
23
|
+
pretty_print.each do |key, value|
|
24
|
+
section = key.green
|
25
|
+
yaml = value.to_yaml
|
26
|
+
yaml.gsub!(/^---$/,'')
|
27
|
+
yaml.gsub!(/^-/,"\n-")
|
28
|
+
section << yaml
|
29
|
+
sections << section
|
30
|
+
end
|
31
|
+
puts sections * "\n\n"
|
23
32
|
end
|
24
33
|
end
|
25
34
|
end
|
data/lib/xcodeproj/config.rb
CHANGED
@@ -1,40 +1,39 @@
|
|
1
1
|
module Xcodeproj
|
2
2
|
|
3
3
|
# This class holds the data for a Xcode build settings file (xcconfig) and
|
4
|
-
#
|
4
|
+
# provides support for serialization.
|
5
5
|
#
|
6
6
|
class Config
|
7
7
|
|
8
8
|
require 'set'
|
9
9
|
|
10
10
|
# @return [Hash{String => String}] The attributes of the settings file
|
11
|
-
#
|
11
|
+
# excluding frameworks, weak_framework and libraries.
|
12
12
|
#
|
13
13
|
attr_accessor :attributes
|
14
14
|
|
15
15
|
# @return [Set<String>] The list of the frameworks required by this
|
16
|
-
#
|
16
|
+
# settings file.
|
17
17
|
#
|
18
18
|
attr_accessor :frameworks
|
19
19
|
|
20
20
|
# @return [Set<String>] The list of the *weak* frameworks required by
|
21
|
-
#
|
21
|
+
# this settings file.
|
22
22
|
#
|
23
23
|
attr_accessor :weak_frameworks
|
24
24
|
|
25
25
|
# @return [Set<String>] The list of the libraries required by this
|
26
|
-
#
|
26
|
+
# settings file.
|
27
27
|
#
|
28
28
|
attr_accessor :libraries
|
29
29
|
|
30
30
|
# @return [Array] The list of the configuration files included by this
|
31
|
-
#
|
31
|
+
# configuration file (`#include "SomeConfig"`).
|
32
32
|
#
|
33
33
|
attr_accessor :includes
|
34
34
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# @param [Hash, File, String] xcconfig_hash_or_file Initial data.
|
35
|
+
# @param [Hash, File, String] xcconfig_hash_or_file
|
36
|
+
# The initial data.
|
38
37
|
#
|
39
38
|
def initialize(xcconfig_hash_or_file = {})
|
40
39
|
@attributes = {}
|
@@ -43,8 +42,18 @@ module Xcodeproj
|
|
43
42
|
merge!(extract_hash(xcconfig_hash_or_file))
|
44
43
|
end
|
45
44
|
|
45
|
+
def inspect
|
46
|
+
to_hash.inspect
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
other.respond_to?(:to_hash) && other.to_hash == self.to_hash
|
51
|
+
end
|
52
|
+
|
46
53
|
#-------------------------------------------------------------------------#
|
47
54
|
|
55
|
+
public
|
56
|
+
|
48
57
|
# @!group Serialization
|
49
58
|
|
50
59
|
# Sorts the internal data by setting name and serializes it in the xcconfig
|
@@ -55,23 +64,32 @@ module Xcodeproj
|
|
55
64
|
# config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
|
56
65
|
# config.to_s # => "OTHER_LDFLAGS = -lxml2\nPODS_ROOT = \"$(SRCROOT)/Pods\""
|
57
66
|
#
|
58
|
-
# @return [String]
|
67
|
+
# @return [String] The serialized internal data.
|
68
|
+
#
|
59
69
|
def to_s
|
60
70
|
to_hash.sort_by(&:first).map { |k, v| "#{k} = #{v}" }.join("\n")
|
61
71
|
end
|
62
72
|
|
63
|
-
#
|
64
|
-
#
|
73
|
+
# Writes the serialized representation of the internal data to the given
|
74
|
+
# path.
|
65
75
|
#
|
66
|
-
# @param
|
76
|
+
# @param [Pathname] pathname
|
77
|
+
# The file where the data should be written to.
|
78
|
+
#
|
79
|
+
# @return [void]
|
67
80
|
#
|
68
81
|
def save_as(pathname)
|
69
82
|
pathname.open('w') { |file| file << to_s }
|
70
83
|
end
|
71
84
|
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
85
|
+
# The hash representation of the xcconfig. The hash includes the
|
86
|
+
# frameworks, the weak frameworks and the libraries in the `Other Linker
|
87
|
+
# Flags` (`OTHER_LDFLAGS`).
|
88
|
+
#
|
89
|
+
# @note All the values are sorted to have a consistent output in Ruby
|
90
|
+
# 1.8.7.
|
91
|
+
#
|
92
|
+
# @return [Hash] The hash representation
|
75
93
|
#
|
76
94
|
def to_hash
|
77
95
|
hash = @attributes.dup
|
@@ -87,12 +105,11 @@ module Xcodeproj
|
|
87
105
|
|
88
106
|
#-------------------------------------------------------------------------#
|
89
107
|
|
108
|
+
public
|
109
|
+
|
90
110
|
# @!group Merging
|
91
111
|
|
92
|
-
# Merges the given xcconfig
|
93
|
-
#
|
94
|
-
# If a key in the given hash already exists in the internal data then its
|
95
|
-
# value is appended to the value in the internal data.
|
112
|
+
# Merges the given xcconfig representation in the receiver.
|
96
113
|
#
|
97
114
|
# @example
|
98
115
|
#
|
@@ -100,7 +117,16 @@ module Xcodeproj
|
|
100
117
|
# config.merge!('OTHER_LDFLAGS' => '-lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"')
|
101
118
|
# config.to_hash # => { 'PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2 -lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"' }
|
102
119
|
#
|
103
|
-
# @
|
120
|
+
# @note If a key in the given hash already exists in the internal data
|
121
|
+
# then its value is appended.
|
122
|
+
#
|
123
|
+
# @param [Hash, Config] config
|
124
|
+
# The xcconfig representation to merge.
|
125
|
+
#
|
126
|
+
# @todo The logic to normalize an hash should be extracted and the
|
127
|
+
# initializer should not call this method.
|
128
|
+
#
|
129
|
+
# @return [void]
|
104
130
|
#
|
105
131
|
def merge!(xcconfig)
|
106
132
|
if xcconfig.is_a? Config
|
@@ -110,7 +136,8 @@ module Xcodeproj
|
|
110
136
|
@weak_frameworks.merge(xcconfig.weak_frameworks)
|
111
137
|
else
|
112
138
|
@attributes.merge!(xcconfig.to_hash) { |key, v1, v2| "#{v1} #{v2}" }
|
113
|
-
# Parse frameworks and libraries. Then remove
|
139
|
+
# Parse frameworks and libraries. Then remove them from the linker
|
140
|
+
# flags
|
114
141
|
flags = @attributes['OTHER_LDFLAGS']
|
115
142
|
return unless flags
|
116
143
|
|
@@ -130,45 +157,57 @@ module Xcodeproj
|
|
130
157
|
end
|
131
158
|
alias_method :<<, :merge!
|
132
159
|
|
160
|
+
# Creates a new #{Config} with the data of the receiver merged with the
|
161
|
+
# given xcconfig representation.
|
162
|
+
#
|
163
|
+
# @param [Hash, Config] config
|
164
|
+
# The xcconfig representation to merge.
|
165
|
+
#
|
166
|
+
# @return [Config] the new xcconfig.
|
167
|
+
#
|
133
168
|
def merge(config)
|
134
169
|
self.dup.tap { |x| x.merge!(config) }
|
135
170
|
end
|
136
171
|
|
172
|
+
# @return [Config] A copy of the receiver.
|
173
|
+
#
|
137
174
|
def dup
|
138
175
|
Xcodeproj::Config.new(self.to_hash.dup)
|
139
176
|
end
|
140
177
|
|
141
178
|
#-------------------------------------------------------------------------#
|
142
179
|
|
143
|
-
|
144
|
-
|
145
|
-
def inspect
|
146
|
-
to_hash.inspect
|
147
|
-
end
|
148
|
-
|
149
|
-
def ==(other)
|
150
|
-
other.respond_to?(:to_hash) && other.to_hash == self.to_hash
|
151
|
-
end
|
152
|
-
|
153
|
-
#-------------------------------------------------------------------------#
|
180
|
+
private
|
154
181
|
|
155
182
|
# @!group Private Helpers
|
156
183
|
|
157
|
-
|
158
|
-
|
184
|
+
# Returns a hash from the given argument reading it from disk if necessary.
|
185
|
+
#
|
186
|
+
# @param [String, Pathname, Hash] argument
|
187
|
+
# The source from where the hash should be extracted.
|
188
|
+
#
|
189
|
+
# @return [Hash]
|
190
|
+
#
|
159
191
|
def extract_hash(argument)
|
160
192
|
if argument.respond_to? :read
|
161
193
|
hash_from_file_content(argument.read)
|
162
|
-
elsif File.readable?
|
194
|
+
elsif File.readable?(argument.to_s)
|
163
195
|
hash_from_file_content(File.read(argument))
|
164
196
|
else
|
165
197
|
argument
|
166
198
|
end
|
167
199
|
end
|
168
200
|
|
169
|
-
|
201
|
+
# Returns a hash from the string representation of an Xcconfig file.
|
202
|
+
#
|
203
|
+
# @param [String] string
|
204
|
+
# The string representation of an xcconfig file.
|
205
|
+
#
|
206
|
+
# @return [Hash] the hash containing the xcconfig data.
|
207
|
+
#
|
208
|
+
def hash_from_file_content(string)
|
170
209
|
hash = {}
|
171
|
-
|
210
|
+
string.split("\n").each do |line|
|
172
211
|
uncommented_line = strip_comment(line)
|
173
212
|
if include = extract_include(uncommented_line)
|
174
213
|
@includes.push include
|
@@ -180,16 +219,39 @@ module Xcodeproj
|
|
180
219
|
hash
|
181
220
|
end
|
182
221
|
|
222
|
+
# Strips the comments from a line of an xcconfig string.
|
223
|
+
#
|
224
|
+
# @param [String] line
|
225
|
+
# the line to process.
|
226
|
+
#
|
227
|
+
# @return [String] the uncommented line.
|
228
|
+
#
|
183
229
|
def strip_comment(line)
|
184
230
|
line.partition('//').first
|
185
231
|
end
|
186
232
|
|
233
|
+
# Returns the file included by a line of an xcconfig string if present.
|
234
|
+
#
|
235
|
+
# @param [String] line
|
236
|
+
# the line to process.
|
237
|
+
#
|
238
|
+
# @return [String] the included file.
|
239
|
+
# @return [Nil] if no include was found in the line.
|
240
|
+
#
|
187
241
|
def extract_include(line)
|
188
242
|
regexp = /#include\s*"(.+)"/
|
189
243
|
match = line.match(regexp)
|
190
244
|
match[1] if match
|
191
245
|
end
|
192
246
|
|
247
|
+
# Returns the key and the value described by the given line of an xcconfig.
|
248
|
+
#
|
249
|
+
# @param [String] line
|
250
|
+
# the line to process.
|
251
|
+
#
|
252
|
+
# @return [Array] A tuple where the first entry is the key and the second
|
253
|
+
# entry is the value.
|
254
|
+
#
|
193
255
|
def extract_key_value(line)
|
194
256
|
key, value = line.split('=', 2)
|
195
257
|
if key && value
|
@@ -199,5 +261,7 @@ module Xcodeproj
|
|
199
261
|
end
|
200
262
|
end
|
201
263
|
|
264
|
+
#-------------------------------------------------------------------------#
|
265
|
+
|
202
266
|
end
|
203
267
|
end
|
data/lib/xcodeproj/constants.rb
CHANGED
@@ -4,23 +4,23 @@ module Xcodeproj
|
|
4
4
|
#
|
5
5
|
module Constants
|
6
6
|
|
7
|
-
# The last known iOS SDK (stable).
|
7
|
+
# @return [String] The last known iOS SDK (stable).
|
8
8
|
#
|
9
|
-
LAST_KNOWN_IOS_SDK = '6.
|
9
|
+
LAST_KNOWN_IOS_SDK = '6.1'
|
10
10
|
|
11
|
-
# The last known OS X SDK (stable).
|
11
|
+
# @return [String] The last known OS X SDK (stable).
|
12
12
|
#
|
13
13
|
LAST_KNOWN_OSX_SDK = '10.8'
|
14
14
|
|
15
|
-
# The last known archive version to Xcodeproj.
|
15
|
+
# @return [String] The last known archive version to Xcodeproj.
|
16
16
|
#
|
17
17
|
LAST_KNOWN_ARCHIVE_VERSION = 1
|
18
18
|
|
19
|
-
# The last known object version to Xcodeproj.
|
19
|
+
# @return [String] The last known object version to Xcodeproj.
|
20
20
|
#
|
21
21
|
LAST_KNOWN_OBJECT_VERSION = 46
|
22
22
|
|
23
|
-
# The all the known ISAs grouped by superclass.
|
23
|
+
# @return [Hash] The all the known ISAs grouped by superclass.
|
24
24
|
#
|
25
25
|
KNOWN_ISAS = {
|
26
26
|
'AbstractObject' => %w[
|
@@ -58,11 +58,11 @@ module Xcodeproj
|
|
58
58
|
]
|
59
59
|
}.freeze
|
60
60
|
|
61
|
-
# The list of the super classes for each ISA.
|
61
|
+
# @return [Array] The list of the super classes for each ISA.
|
62
62
|
#
|
63
63
|
ISAS_SUPER_CLASSES = %w[ AbstractObject AbstractBuildPhase PBXGroup ]
|
64
64
|
|
65
|
-
# The known file types corresponding to each extension.
|
65
|
+
# @return [Hash] The known file types corresponding to each extension.
|
66
66
|
#
|
67
67
|
FILE_TYPES_BY_EXTENSION = {
|
68
68
|
'a' => 'archive.ar',
|
@@ -75,7 +75,7 @@ module Xcodeproj
|
|
75
75
|
'xcdatamodel' => 'wrapper.xcdatamodel',
|
76
76
|
}.freeze
|
77
77
|
|
78
|
-
# The uniform type identifier of various product types.
|
78
|
+
# @return [Hash] The uniform type identifier of various product types.
|
79
79
|
#
|
80
80
|
PRODUCT_TYPE_UTI = {
|
81
81
|
:application => 'com.apple.product-type.application',
|
@@ -83,8 +83,8 @@ module Xcodeproj
|
|
83
83
|
:static_library => 'com.apple.product-type.library.static',
|
84
84
|
}.freeze
|
85
85
|
|
86
|
-
# The common build settings grouped by platform, and build
|
87
|
-
# name.
|
86
|
+
# @return [Hash] The common build settings grouped by platform, and build
|
87
|
+
# configuration name.
|
88
88
|
#
|
89
89
|
COMMON_BUILD_SETTINGS = {
|
90
90
|
:all => {
|
@@ -107,7 +107,8 @@ module Xcodeproj
|
|
107
107
|
'COPY_PHASE_STRIP' => 'NO',
|
108
108
|
}.freeze,
|
109
109
|
:release => {
|
110
|
-
|
110
|
+
'OTHER_CFLAGS' => '-DNS_BLOCK_ASSERTIONS=1',
|
111
|
+
'OTHER_CPLUSPLUSFLAGS' => '-DNS_BLOCK_ASSERTIONS=1',
|
111
112
|
}.freeze,
|
112
113
|
:ios => {
|
113
114
|
'ARCHS' => "$(ARCHS_STANDARD_32_BIT)",
|
@@ -137,7 +138,8 @@ module Xcodeproj
|
|
137
138
|
}.freeze,
|
138
139
|
}.freeze
|
139
140
|
|
140
|
-
# The corresponding numeric value of each copy build phase
|
141
|
+
# @return [Hash] The corresponding numeric value of each copy build phase
|
142
|
+
# destination.
|
141
143
|
#
|
142
144
|
COPY_FILES_BUILD_PHASE_DESTINATIONS = {
|
143
145
|
:absolute_path => '0',
|
@@ -152,7 +154,7 @@ module Xcodeproj
|
|
152
154
|
:plug_ins => '13'
|
153
155
|
}.freeze
|
154
156
|
|
155
|
-
# The extensions which are associated with header files
|
157
|
+
# @return [Hash] The extensions which are associated with header files.
|
156
158
|
#
|
157
159
|
HEADER_FILES_EXTENSIONS = %w| .h .hh .hpp |.freeze
|
158
160
|
|
@@ -0,0 +1,243 @@
|
|
1
|
+
module Xcodeproj
|
2
|
+
|
3
|
+
# Computes the recursive diff of Hashes, Array and other objects.
|
4
|
+
#
|
5
|
+
# Useful to compare two projects. Inspired from
|
6
|
+
# 'active_support/core_ext/hash/diff'.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# h1 = { :common => 'value', :changed => 'v1' }
|
10
|
+
# h2 = { :common => 'value', :changed => 'v2', :addition => 'new_value' }
|
11
|
+
# h1.recursive_diff(h2) == {
|
12
|
+
# :changed => {
|
13
|
+
# :self => 'v1',
|
14
|
+
# :other => 'v2'
|
15
|
+
# },
|
16
|
+
# :addition => {
|
17
|
+
# :self => nil,
|
18
|
+
# :other => 'new_value'
|
19
|
+
# }
|
20
|
+
# } #=> true
|
21
|
+
#
|
22
|
+
#
|
23
|
+
#
|
24
|
+
#
|
25
|
+
module Differ
|
26
|
+
|
27
|
+
# Computes the recursive difference of two given values.
|
28
|
+
#
|
29
|
+
# @param [Object] value_1
|
30
|
+
# The first value to compare.
|
31
|
+
#
|
32
|
+
# @param [Object] value_2
|
33
|
+
# The second value to compare.
|
34
|
+
#
|
35
|
+
# @param [Object] key_1
|
36
|
+
# The key for the diff of value_1.
|
37
|
+
#
|
38
|
+
# @param [Object] key_2
|
39
|
+
# The key for the diff of value_2.
|
40
|
+
#
|
41
|
+
# @param [Object] id_key
|
42
|
+
# The key used to identify correspondent hashes in an array.
|
43
|
+
#
|
44
|
+
# @return [Hash] The diff
|
45
|
+
# @return [Nil] if the given values are equal.
|
46
|
+
#
|
47
|
+
def self.diff(value_1, value_2, options = {})
|
48
|
+
options[:key_1] ||= 'value_1'
|
49
|
+
options[:key_2] ||= 'value_2'
|
50
|
+
options[:id_key] ||= nil
|
51
|
+
|
52
|
+
if value_1.class == value_2.class
|
53
|
+
method = case value_1
|
54
|
+
when Hash then :hash_diff
|
55
|
+
when Array then :array_diff
|
56
|
+
else :generic_diff
|
57
|
+
end
|
58
|
+
else
|
59
|
+
method = :generic_diff
|
60
|
+
end
|
61
|
+
self.send(method, value_1, value_2, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Optimized for reducing the noise from the tree hash of projects
|
65
|
+
#
|
66
|
+
def self.project_diff(project_1, project_2, key_1 = 'project_1', key_2 = 'project_2')
|
67
|
+
project_1 = project_1.to_tree_hash unless project_1.is_a?(Hash)
|
68
|
+
project_2 = project_2.to_tree_hash unless project_2.is_a?(Hash)
|
69
|
+
options = {
|
70
|
+
:key_1 => key_1,
|
71
|
+
:key_2 => key_2,
|
72
|
+
:id_key => 'displayName',
|
73
|
+
}
|
74
|
+
diff(project_1, project_2, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
#-------------------------------------------------------------------------#
|
78
|
+
|
79
|
+
public
|
80
|
+
|
81
|
+
# @!group Type specific handlers
|
82
|
+
|
83
|
+
# Computes the recursive difference of two hashes.
|
84
|
+
#
|
85
|
+
# @see diff
|
86
|
+
#
|
87
|
+
def self.hash_diff(value_1, value_2, options)
|
88
|
+
ensure_class(value_1, Hash)
|
89
|
+
ensure_class(value_2, Hash)
|
90
|
+
return nil if value_1 == value_2
|
91
|
+
|
92
|
+
result = {}
|
93
|
+
all_keys = (value_1.keys + value_2.keys).uniq
|
94
|
+
all_keys.each do |key|
|
95
|
+
key_value_1 = value_1[key]
|
96
|
+
key_value_2 = value_2[key]
|
97
|
+
diff = diff(key_value_1, key_value_2, options)
|
98
|
+
if diff
|
99
|
+
result[key] = diff if diff
|
100
|
+
end
|
101
|
+
end
|
102
|
+
if result.empty?
|
103
|
+
nil
|
104
|
+
else
|
105
|
+
result
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the recursive diff of two arrays.
|
110
|
+
#
|
111
|
+
# @see diff
|
112
|
+
#
|
113
|
+
def self.array_diff(value_1, value_2, options)
|
114
|
+
ensure_class(value_1, Array)
|
115
|
+
ensure_class(value_2, Array)
|
116
|
+
return nil if value_1 == value_2
|
117
|
+
|
118
|
+
new_objects_value_1 = (value_1 - value_2)
|
119
|
+
new_objects_value_2 = (value_2 - value_1)
|
120
|
+
return nil if value_1.empty? && value_2.empty?
|
121
|
+
|
122
|
+
matched_diff = {}
|
123
|
+
if id_key = options[:id_key]
|
124
|
+
matched_value_1 = []
|
125
|
+
matched_value_2 = []
|
126
|
+
new_objects_value_1.each do |entry_value_1|
|
127
|
+
if entry_value_1.is_a?(Hash)
|
128
|
+
id_value = entry_value_1[id_key]
|
129
|
+
entry_value_2 = new_objects_value_2.find do |entry|
|
130
|
+
entry[id_key] == id_value
|
131
|
+
end
|
132
|
+
if entry_value_2
|
133
|
+
matched_value_1 << entry_value_1
|
134
|
+
matched_value_2 << entry_value_2
|
135
|
+
diff = diff(entry_value_1, entry_value_2, options)
|
136
|
+
matched_diff[id_value] = diff if diff
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
new_objects_value_1 = new_objects_value_1 - matched_value_1
|
142
|
+
new_objects_value_2 = new_objects_value_2 - matched_value_2
|
143
|
+
end
|
144
|
+
|
145
|
+
if new_objects_value_1.empty? && new_objects_value_2.empty?
|
146
|
+
if matched_diff.empty?
|
147
|
+
nil
|
148
|
+
else
|
149
|
+
matched_diff
|
150
|
+
end
|
151
|
+
else
|
152
|
+
result = {}
|
153
|
+
result[options[:key_1]] = new_objects_value_1 unless new_objects_value_1.empty?
|
154
|
+
result[options[:key_2]] = new_objects_value_2 unless new_objects_value_2.empty?
|
155
|
+
result[:diff] = matched_diff unless matched_diff.empty?
|
156
|
+
result
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns the diff of two generic objects.
|
161
|
+
#
|
162
|
+
# @see diff
|
163
|
+
#
|
164
|
+
def self.generic_diff(value_1, value_2, options)
|
165
|
+
return nil if value_1 == value_2
|
166
|
+
|
167
|
+
{
|
168
|
+
options[:key_1] => value_1,
|
169
|
+
options[:key_2] => value_2
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
#-------------------------------------------------------------------------#
|
174
|
+
|
175
|
+
public
|
176
|
+
|
177
|
+
# @!group Cleaning
|
178
|
+
|
179
|
+
# Returns a copy of the hash where the given key is removed recursively.
|
180
|
+
#
|
181
|
+
# @param [Hash] hash
|
182
|
+
# The hash to clean
|
183
|
+
#
|
184
|
+
# @param [Object] key
|
185
|
+
# The key to remove.
|
186
|
+
#
|
187
|
+
# @return [Hash] A copy of the hash without the key.
|
188
|
+
#
|
189
|
+
def self.clean_hash(hash, key)
|
190
|
+
new_hash = hash.dup
|
191
|
+
self.clean_hash!(new_hash, key)
|
192
|
+
new_hash
|
193
|
+
end
|
194
|
+
|
195
|
+
# Recursively cleans a key from the given hash.
|
196
|
+
#
|
197
|
+
# @param [Hash] hash
|
198
|
+
# The hash to clean
|
199
|
+
#
|
200
|
+
# @param [Object] key
|
201
|
+
# The key to remove.
|
202
|
+
#
|
203
|
+
# @return [void]
|
204
|
+
#
|
205
|
+
def self.clean_hash!(hash, key)
|
206
|
+
hash.delete(key)
|
207
|
+
hash.each do |_, value|
|
208
|
+
case value
|
209
|
+
when Hash
|
210
|
+
clean_hash!(value, key)
|
211
|
+
when Array
|
212
|
+
value.each { |entry| clean_hash!(entry, key) if entry.is_a?(Hash)}
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
#-------------------------------------------------------------------------#
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
# @! Helpers
|
222
|
+
|
223
|
+
# Ensures that the given object belongs to the given class.
|
224
|
+
#
|
225
|
+
# @param [Object] object
|
226
|
+
# The object to check.
|
227
|
+
#
|
228
|
+
# @param [Class] klass
|
229
|
+
# the expected class of the object.
|
230
|
+
#
|
231
|
+
# @raise If the object doesn't belong to the given class.
|
232
|
+
#
|
233
|
+
# @return [void]
|
234
|
+
#
|
235
|
+
def self.ensure_class(object, klass)
|
236
|
+
raise "Wrong type `#{object.inspect}`" unless object.is_a?(klass)
|
237
|
+
end
|
238
|
+
|
239
|
+
#-------------------------------------------------------------------------#
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|