xcodeproj 0.1.0 → 0.2.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/README.md +3 -9
- data/ext/xcodeproj/extconf.rb +1 -0
- data/ext/xcodeproj/xcodeproj_ext.c +81 -13
- data/lib/xcodeproj/config.rb +142 -10
- data/lib/xcodeproj/inflector.rb +1 -1
- data/lib/xcodeproj/project/association/has_many.rb +51 -0
- data/lib/xcodeproj/project/association/has_one.rb +39 -0
- data/lib/xcodeproj/project/association/reflection.rb +88 -0
- data/lib/xcodeproj/project/association.rb +54 -0
- data/lib/xcodeproj/project/object/build_phase.rb +89 -0
- data/lib/xcodeproj/project/object/configuration.rb +100 -0
- data/lib/xcodeproj/project/object/file_reference.rb +71 -0
- data/lib/xcodeproj/project/object/group.rb +102 -0
- data/lib/xcodeproj/project/object/native_target.rb +158 -0
- data/lib/xcodeproj/project/object.rb +207 -0
- data/lib/xcodeproj/project/object_list.rb +146 -0
- data/lib/xcodeproj/project.rb +154 -566
- data/lib/xcodeproj.rb +1 -1
- metadata +17 -5
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Xcodeproj
|
|
2
2
|
|
|
3
|
-
[](https://secure.travis-ci.org/CocoaPods/Xcodeproj)
|
|
3
|
+
[](https://secure.travis-ci.org/CocoaPods/Xcodeproj)
|
|
4
|
+
[](https://secure.travis-ci.org/CocoaPods/Xcodeproj)
|
|
4
5
|
|
|
5
6
|
Xcodeproj lets you create and modify Xcode projects from [Ruby][ruby].
|
|
6
7
|
Script boring management tasks or build Xcode-friendly libraries. Also includes
|
|
@@ -15,7 +16,7 @@ static library from scratch, for both iOS and OSX.
|
|
|
15
16
|
Xcodeproj itself installs through RubyGems, the Ruby package manager. Install it
|
|
16
17
|
by performing the following command:
|
|
17
18
|
|
|
18
|
-
$ sudo gem install xcodeproj
|
|
19
|
+
$ [sudo] gem install xcodeproj --pre
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
## Colaborate
|
|
@@ -31,13 +32,6 @@ If you're really oldschool and you want to discuss Xcodeproj development you
|
|
|
31
32
|
can join #cocoapods on irc.freenode.net.
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
## Authors
|
|
35
|
-
|
|
36
|
-
* [Nolan Waite](https://github.com/nolanw)
|
|
37
|
-
* [Luke Redpath](https://github.com/lukeredpath)
|
|
38
|
-
* [Eloy Durán](https://github.com/alloy)
|
|
39
|
-
|
|
40
|
-
|
|
41
35
|
## LICENSE
|
|
42
36
|
|
|
43
37
|
These works are available under the MIT license. See the [LICENSE][license] file
|
data/ext/xcodeproj/extconf.rb
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
#include "extconf.h"
|
|
5
5
|
|
|
6
6
|
#include "ruby.h"
|
|
7
|
+
#if HAVE_RUBY_ST_H
|
|
7
8
|
#include "ruby/st.h"
|
|
9
|
+
#endif
|
|
10
|
+
|
|
8
11
|
#include "CoreFoundation/CoreFoundation.h"
|
|
9
12
|
#include "CoreFoundation/CFStream.h"
|
|
10
13
|
#include "CoreFoundation/CFPropertyList.h"
|
|
@@ -14,12 +17,22 @@ VALUE Xcodeproj = Qnil;
|
|
|
14
17
|
|
|
15
18
|
static VALUE
|
|
16
19
|
cfstr_to_str(const void *cfstr) {
|
|
17
|
-
|
|
20
|
+
CFDataRef data = CFStringCreateExternalRepresentation(NULL, cfstr, kCFStringEncodingUTF8, 0);
|
|
21
|
+
assert(data != NULL);
|
|
22
|
+
long len = (long)CFDataGetLength(data);
|
|
18
23
|
char *buf = (char *)malloc(len+1);
|
|
19
24
|
assert(buf != NULL);
|
|
20
|
-
|
|
25
|
+
CFDataGetBytes(data, CFRangeMake(0, len), buf);
|
|
26
|
+
|
|
21
27
|
register VALUE str = rb_str_new(buf, len);
|
|
22
28
|
free(buf);
|
|
29
|
+
|
|
30
|
+
// force UTF-8 encoding in Ruby 1.9+
|
|
31
|
+
ID forceEncodingId = rb_intern("force_encoding");
|
|
32
|
+
if (rb_respond_to(str, forceEncodingId)) {
|
|
33
|
+
rb_funcall(str, forceEncodingId, 1, rb_str_new("UTF-8", 5));
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
return str;
|
|
24
37
|
}
|
|
25
38
|
|
|
@@ -29,7 +42,14 @@ str_to_cfstr(VALUE str) {
|
|
|
29
42
|
return CFStringCreateWithCString(NULL, RSTRING_PTR(rb_String(str)), kCFStringEncodingUTF8);
|
|
30
43
|
}
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
/* Generates a UUID. The original version is truncated, so this is not 100%
|
|
46
|
+
* guaranteed to be unique. However, the `PBXObject#generate_uuid` method
|
|
47
|
+
* checks that the UUID does not exist yet, in the project, before using it.
|
|
48
|
+
*
|
|
49
|
+
* @note Meant for internal use only.
|
|
50
|
+
*
|
|
51
|
+
* @return [String] A 24 characters long UUID.
|
|
52
|
+
*/
|
|
33
53
|
static VALUE
|
|
34
54
|
generate_uuid(void) {
|
|
35
55
|
CFUUIDRef uuid = CFUUIDCreate(NULL);
|
|
@@ -69,11 +89,22 @@ hash_set(const void *keyRef, const void *valueRef, void *hash) {
|
|
|
69
89
|
value = rb_ary_new();
|
|
70
90
|
CFIndex i, count = CFArrayGetCount(valueRef);
|
|
71
91
|
for (i = 0; i < count; i++) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
CFTypeRef elementRef = CFArrayGetValueAtIndex(valueRef, i);
|
|
93
|
+
CFTypeID elementType = CFGetTypeID(elementRef);
|
|
94
|
+
if (elementType == CFStringGetTypeID()) {
|
|
95
|
+
rb_ary_push(value, cfstr_to_str(elementRef));
|
|
96
|
+
|
|
97
|
+
} else if (elementType == CFDictionaryGetTypeID()) {
|
|
98
|
+
VALUE hashElement = rb_hash_new();
|
|
99
|
+
CFDictionaryApplyFunction(elementRef, hash_set, (void *)hashElement);
|
|
100
|
+
rb_ary_push(value, hashElement);
|
|
101
|
+
|
|
75
102
|
} else {
|
|
76
|
-
|
|
103
|
+
CFStringRef descriptionRef = CFCopyDescription(elementRef);
|
|
104
|
+
// obviously not optimial, but we're raising here, so it doesn't really matter
|
|
105
|
+
VALUE description = cfstr_to_str(descriptionRef);
|
|
106
|
+
rb_raise(rb_eTypeError, "Plist array value contains a object type unsupported by Xcodeproj. In: `%s'", RSTRING_PTR(description));
|
|
107
|
+
CFRelease(descriptionRef);
|
|
77
108
|
}
|
|
78
109
|
}
|
|
79
110
|
|
|
@@ -94,15 +125,26 @@ dictionary_set(st_data_t key, st_data_t value, CFMutableDictionaryRef dict) {
|
|
|
94
125
|
0,
|
|
95
126
|
&kCFTypeDictionaryKeyCallBacks,
|
|
96
127
|
&kCFTypeDictionaryValueCallBacks);
|
|
97
|
-
|
|
128
|
+
rb_hash_foreach(value, dictionary_set, (st_data_t)valueRef);
|
|
98
129
|
|
|
99
130
|
} else if (TYPE(value) == T_ARRAY) {
|
|
100
131
|
long i, count = RARRAY_LEN(value);
|
|
101
132
|
valueRef = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks);
|
|
102
133
|
for (i = 0; i < count; i++) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
134
|
+
VALUE element = RARRAY_PTR(value)[i];
|
|
135
|
+
CFTypeRef elementRef = NULL;
|
|
136
|
+
if (TYPE(element) == T_HASH) {
|
|
137
|
+
elementRef = CFDictionaryCreateMutable(NULL,
|
|
138
|
+
0,
|
|
139
|
+
&kCFTypeDictionaryKeyCallBacks,
|
|
140
|
+
&kCFTypeDictionaryValueCallBacks);
|
|
141
|
+
rb_hash_foreach(element, dictionary_set, (st_data_t)elementRef);
|
|
142
|
+
} else {
|
|
143
|
+
// otherwise coerce to string
|
|
144
|
+
elementRef = str_to_cfstr(element);
|
|
145
|
+
}
|
|
146
|
+
CFArrayAppendValue((CFMutableArrayRef)valueRef, elementRef);
|
|
147
|
+
CFRelease(elementRef);
|
|
106
148
|
}
|
|
107
149
|
|
|
108
150
|
} else {
|
|
@@ -130,7 +172,19 @@ str_to_url(VALUE path) {
|
|
|
130
172
|
}
|
|
131
173
|
|
|
132
174
|
|
|
133
|
-
|
|
175
|
+
/* @overload read_plist(path)
|
|
176
|
+
*
|
|
177
|
+
* Reads from the specified path and de-serializes the property list.
|
|
178
|
+
*
|
|
179
|
+
* @note This does not yet support all possible types that can exist in a valid property list.
|
|
180
|
+
*
|
|
181
|
+
* @note This currently only assumes to be given an Xcode project document.
|
|
182
|
+
* This means that it only accepts dictionaries, arrays, and strings in
|
|
183
|
+
* the document.
|
|
184
|
+
*
|
|
185
|
+
* @param [String] path The path to the property list file.
|
|
186
|
+
* @return [Hash] The dictionary contents of the document.
|
|
187
|
+
*/
|
|
134
188
|
static VALUE
|
|
135
189
|
read_plist(VALUE self, VALUE path) {
|
|
136
190
|
CFPropertyListRef dict;
|
|
@@ -159,6 +213,20 @@ read_plist(VALUE self, VALUE path) {
|
|
|
159
213
|
return hash;
|
|
160
214
|
}
|
|
161
215
|
|
|
216
|
+
/* @overload write_plist(hash, path)
|
|
217
|
+
*
|
|
218
|
+
* Writes the serialized contents of a property list to the specified path.
|
|
219
|
+
*
|
|
220
|
+
* @note This does not yet support all possible types that can exist in a valid property list.
|
|
221
|
+
*
|
|
222
|
+
* @note This currently only assumes to be given an Xcode project document.
|
|
223
|
+
* This means that it only accepts dictionaries, arrays, and strings in
|
|
224
|
+
* the document.
|
|
225
|
+
*
|
|
226
|
+
* @param [Hash] hash The property list to serialize.
|
|
227
|
+
* @param [String] path The path to the property list file.
|
|
228
|
+
* @return [true, false] Wether or not saving was successful.
|
|
229
|
+
*/
|
|
162
230
|
static VALUE
|
|
163
231
|
write_plist(VALUE self, VALUE hash, VALUE path) {
|
|
164
232
|
VALUE h = rb_check_convert_type(hash, T_HASH, "Hash", "to_hash");
|
|
@@ -171,7 +239,7 @@ write_plist(VALUE self, VALUE hash, VALUE path) {
|
|
|
171
239
|
&kCFTypeDictionaryKeyCallBacks,
|
|
172
240
|
&kCFTypeDictionaryValueCallBacks);
|
|
173
241
|
|
|
174
|
-
|
|
242
|
+
rb_hash_foreach(h, dictionary_set, (st_data_t)dict);
|
|
175
243
|
|
|
176
244
|
CFURLRef fileURL = str_to_url(path);
|
|
177
245
|
CFWriteStreamRef stream = CFWriteStreamCreateWithFile(NULL, fileURL);
|
data/lib/xcodeproj/config.rb
CHANGED
|
@@ -1,31 +1,163 @@
|
|
|
1
1
|
module Xcodeproj
|
|
2
|
+
# This class holds the data for a Xcode build settings file (xcconfig) and
|
|
3
|
+
# serializes it.
|
|
2
4
|
class Config
|
|
3
|
-
|
|
5
|
+
# Returns a new instance of Config
|
|
6
|
+
#
|
|
7
|
+
# @param [Hash, File, String] xcconfig_hash_or_file Initial data.
|
|
8
|
+
require 'set'
|
|
9
|
+
|
|
10
|
+
attr_accessor :attributes, :frameworks ,:libraries
|
|
11
|
+
|
|
12
|
+
def initialize(xcconfig_hash_or_file = {})
|
|
4
13
|
@attributes = {}
|
|
5
|
-
|
|
14
|
+
@includes = []
|
|
15
|
+
@frameworks, @libraries = Set.new, Set.new
|
|
16
|
+
merge!(extract_hash(xcconfig_hash_or_file))
|
|
6
17
|
end
|
|
7
18
|
|
|
19
|
+
# @return [Hash] The internal data.
|
|
8
20
|
def to_hash
|
|
9
|
-
@attributes
|
|
21
|
+
hash = @attributes.dup
|
|
22
|
+
flags = hash['OTHER_LDFLAGS'] || ''
|
|
23
|
+
flags = flags.dup.strip
|
|
24
|
+
flags << libraries.to_a.sort.reduce('') {| memo, l | memo << " -l#{l}" }
|
|
25
|
+
flags << frameworks.to_a.sort.reduce('') {| memo, f | memo << " -framework #{f}" }
|
|
26
|
+
hash['OTHER_LDFLAGS'] = flags.strip
|
|
27
|
+
hash.delete('OTHER_LDFLAGS') if flags.strip.empty?
|
|
28
|
+
hash
|
|
10
29
|
end
|
|
11
30
|
|
|
31
|
+
def ==(other)
|
|
32
|
+
other.respond_to?(:to_hash) && other.to_hash == self.to_hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Array] Config's include file list
|
|
36
|
+
# @example
|
|
37
|
+
#
|
|
38
|
+
# Consider following xcconfig file:
|
|
39
|
+
#
|
|
40
|
+
# #include "SomeConfig"
|
|
41
|
+
# Key1 = Value1
|
|
42
|
+
# Key2 = Value2
|
|
43
|
+
#
|
|
44
|
+
# config.includes # => [ "SomeConfig" ]
|
|
45
|
+
def includes
|
|
46
|
+
@includes
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Merges the given xcconfig hash or Config into the internal data.
|
|
50
|
+
#
|
|
51
|
+
# If a key in the given hash already exists, in the internal data, then its
|
|
52
|
+
# value is appended to the value in the internal data.
|
|
53
|
+
#
|
|
54
|
+
# @example
|
|
55
|
+
#
|
|
56
|
+
# config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
|
|
57
|
+
# config.merge!('OTHER_LDFLAGS' => '-lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"')
|
|
58
|
+
# config.to_hash # => { 'PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2 -lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"' }
|
|
59
|
+
#
|
|
60
|
+
# @param [Hash, Config] xcconfig The data to merge into the internal data.
|
|
12
61
|
def merge!(xcconfig)
|
|
13
|
-
xcconfig.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
62
|
+
if xcconfig.is_a? Config
|
|
63
|
+
@attributes.merge!(xcconfig.attributes) { |key, v1, v2| "#{v1} #{v2}" }
|
|
64
|
+
@libraries.merge xcconfig.libraries
|
|
65
|
+
@frameworks.merge xcconfig.frameworks
|
|
66
|
+
else
|
|
67
|
+
@attributes.merge!(xcconfig.to_hash) { |key, v1, v2| "#{v1} #{v2}" }
|
|
68
|
+
# Parse frameworks and libraries. Then remove the from the linker flags
|
|
69
|
+
flags = @attributes['OTHER_LDFLAGS']
|
|
70
|
+
return unless flags
|
|
71
|
+
|
|
72
|
+
frameworks = flags.scan(/-framework\s+([^\s]+)/).map { |m| m[0] }
|
|
73
|
+
libraries = flags.scan(/-l ?([^\s]+)/).map { |m| m[0] }
|
|
74
|
+
@frameworks.merge frameworks
|
|
75
|
+
@libraries.merge libraries
|
|
76
|
+
|
|
77
|
+
new_flags = flags.dup
|
|
78
|
+
frameworks.each {|f| new_flags.gsub!("-framework #{f}", "") }
|
|
79
|
+
libraries.each {|l| new_flags.gsub!("-l#{l}", ""); new_flags.gsub!("-l #{l}", "") }
|
|
80
|
+
@attributes['OTHER_LDFLAGS'] = new_flags.gsub("\w*", ' ').strip
|
|
19
81
|
end
|
|
20
82
|
end
|
|
21
83
|
alias_method :<<, :merge!
|
|
22
84
|
|
|
85
|
+
def merge(config)
|
|
86
|
+
self.dup.tap { |x|x.merge!(config) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def dup
|
|
90
|
+
Xcodeproj::Config.new(self.to_hash.dup)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Serializes the internal data in the xcconfig format.
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
#
|
|
97
|
+
# config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
|
|
98
|
+
# config.to_s # => "PODS_ROOT = \"$(SRCROOT)/Pods\"\nOTHER_LDFLAGS = -lxml2"
|
|
99
|
+
#
|
|
100
|
+
# @return [String] The serialized internal data.
|
|
23
101
|
def to_s
|
|
24
|
-
|
|
102
|
+
to_hash.map { |key, value| "#{key} = #{value}" }.join("\n")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def inspect
|
|
106
|
+
to_hash.inspect
|
|
25
107
|
end
|
|
26
108
|
|
|
109
|
+
# Writes the serialized representation of the internal data to the given
|
|
110
|
+
# path.
|
|
111
|
+
#
|
|
112
|
+
# @param [Pathname] pathname The file that the data should be written to.
|
|
27
113
|
def save_as(pathname)
|
|
28
114
|
pathname.open('w') { |file| file << to_s }
|
|
29
115
|
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def extract_hash(argument)
|
|
120
|
+
if argument.respond_to? :read
|
|
121
|
+
hash_from_file_content(argument.read)
|
|
122
|
+
elsif File.readable? argument.to_s
|
|
123
|
+
hash_from_file_content(File.read(argument))
|
|
124
|
+
else
|
|
125
|
+
argument
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def hash_from_file_content(raw_string)
|
|
130
|
+
hash = {}
|
|
131
|
+
raw_string.split("\n").each do |line|
|
|
132
|
+
uncommented_line = strip_comment(line)
|
|
133
|
+
if include = extract_include(uncommented_line)
|
|
134
|
+
@includes.push include
|
|
135
|
+
else
|
|
136
|
+
key, value = extract_key_value(uncommented_line)
|
|
137
|
+
hash[key] = value if key
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
hash
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def strip_comment(line)
|
|
144
|
+
line.partition('//').first
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def extract_include(line)
|
|
148
|
+
regexp = /#include\s*"(.+)"/
|
|
149
|
+
match = line.match(regexp)
|
|
150
|
+
match[1] if match
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def extract_key_value(line)
|
|
154
|
+
key, value = line.split('=', 2)
|
|
155
|
+
if key && value
|
|
156
|
+
[key.strip, value.strip]
|
|
157
|
+
else
|
|
158
|
+
[]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
30
162
|
end
|
|
31
163
|
end
|
data/lib/xcodeproj/inflector.rb
CHANGED
|
@@ -189,7 +189,7 @@ module Xcodeproj
|
|
|
189
189
|
if first_letter_in_uppercase
|
|
190
190
|
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
|
191
191
|
else
|
|
192
|
-
lower_case_and_underscored_word.
|
|
192
|
+
lower_case_and_underscored_word[0,1].downcase + camelize(lower_case_and_underscored_word)[1..-1]
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Xcodeproj
|
|
2
|
+
class Project
|
|
3
|
+
module Object
|
|
4
|
+
class Association
|
|
5
|
+
|
|
6
|
+
class HasMany < Association
|
|
7
|
+
def direct_get
|
|
8
|
+
uuids = @owner.send(@reflection.attribute_getter)
|
|
9
|
+
if @block
|
|
10
|
+
# Evaluate the block, which was specified at the class level, in
|
|
11
|
+
# the instance’s context.
|
|
12
|
+
@owner.list_by_class(uuids, @reflection.klass) do |list|
|
|
13
|
+
list.let(:push) do |new_object|
|
|
14
|
+
@owner.instance_exec(new_object, &@block)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
else
|
|
18
|
+
@owner.list_by_class(uuids, @reflection.klass)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def inverse_get
|
|
23
|
+
PBXObjectList.new(@reflection.klass, @owner.project) do |list|
|
|
24
|
+
list.let(:uuid_scope) do
|
|
25
|
+
@owner.project.objects.list_by_class(@reflection.klass).select do |object|
|
|
26
|
+
object.send(@reflection.inverse.attribute_getter) == @owner.uuid
|
|
27
|
+
end.map(&:uuid)
|
|
28
|
+
end
|
|
29
|
+
list.let(:push) do |new_object|
|
|
30
|
+
new_object.send(@reflection.inverse.attribute_setter, @owner.uuid)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @todo Currently this does not call the @block, which means that
|
|
36
|
+
# in theory (like with a group's children) the object stays
|
|
37
|
+
# asociated with its old group.
|
|
38
|
+
def direct_set(list)
|
|
39
|
+
@owner.send(@reflection.attribute_setter, list.map(&:uuid))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inverse_set(list)
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Xcodeproj
|
|
2
|
+
class Project
|
|
3
|
+
module Object
|
|
4
|
+
class Association
|
|
5
|
+
|
|
6
|
+
# @todo Does this need 'new object'-callback support too?
|
|
7
|
+
class HasOne < Association
|
|
8
|
+
def direct_get
|
|
9
|
+
@owner.project.objects[@owner.send(@reflection.attribute_getter)]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def inverse_get
|
|
13
|
+
# Loop over all objects of the class and find the one that includes
|
|
14
|
+
# this object in the specified uuid list.
|
|
15
|
+
@owner.project.objects.list_by_class(@reflection.klass).find do |object|
|
|
16
|
+
object.send(@reflection.inverse.attribute_getter).include?(@owner.uuid)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def direct_set(object)
|
|
21
|
+
@owner.send(@reflection.attribute_setter, object.uuid)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inverse_set(object)
|
|
25
|
+
# Remove this object from the uuid list of the target
|
|
26
|
+
# that this object was associated to.
|
|
27
|
+
if previous = @owner.send(@reflection.name)
|
|
28
|
+
previous.send(@reflection.inverse.attribute_getter).delete(@owner.uuid)
|
|
29
|
+
end
|
|
30
|
+
# Now assign this object to the new object
|
|
31
|
+
object.send(@reflection.inverse.attribute_getter) << @owner.uuid if object
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require 'xcodeproj/inflector'
|
|
2
|
+
|
|
3
|
+
module Xcodeproj
|
|
4
|
+
class Project
|
|
5
|
+
module Object
|
|
6
|
+
|
|
7
|
+
class AbstractPBXObject
|
|
8
|
+
def self.reflections
|
|
9
|
+
@reflections ||= []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.create_reflection(type, name, options)
|
|
13
|
+
(reflections << Association::Reflection.new(type, name, options)).last
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.reflection(name)
|
|
17
|
+
reflections.find { |r| r.name.to_s == name.to_s }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Association
|
|
22
|
+
class Reflection
|
|
23
|
+
def initialize(type, name, options)
|
|
24
|
+
@type, @name, @options = type, name.to_s, options
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_reader :type, :name, :options
|
|
28
|
+
|
|
29
|
+
def klass
|
|
30
|
+
@options[:class] ||= begin
|
|
31
|
+
name = "PBX#{@name.classify}"
|
|
32
|
+
name = "XC#{@name.classify}" unless Xcodeproj::Project::Object.const_defined?(name)
|
|
33
|
+
Xcodeproj::Project::Object.const_get(name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def inverse
|
|
38
|
+
klass.reflection(@options[:inverse_of])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inverse?
|
|
42
|
+
!!@options[:inverse_of]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def attribute_name
|
|
46
|
+
(@options[:uuid] || @options[:uuids] || @name).to_sym
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def attribute_getter
|
|
50
|
+
case type
|
|
51
|
+
when :has_many
|
|
52
|
+
uuid_method_name.pluralize
|
|
53
|
+
when :has_one
|
|
54
|
+
uuid_method_name
|
|
55
|
+
end.to_sym
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def attribute_setter
|
|
59
|
+
"#{attribute_getter}=".to_sym
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def getter
|
|
63
|
+
@name.to_sym
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def setter
|
|
67
|
+
"#{@name}=".to_sym
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def association_for(owner, &block)
|
|
71
|
+
case type
|
|
72
|
+
when :has_many then Association::HasMany
|
|
73
|
+
when :has_one then Association::HasOne
|
|
74
|
+
end.new(owner, self, &block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def uuid_method_name
|
|
80
|
+
(@options[:uuids_as] || @options[:uuid] || @options[:uuids] || "#{@name.singularize}_reference").to_s.singularize
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'xcodeproj/project/association/has_many'
|
|
2
|
+
require 'xcodeproj/project/association/has_one'
|
|
3
|
+
require 'xcodeproj/project/association/reflection'
|
|
4
|
+
|
|
5
|
+
module Xcodeproj
|
|
6
|
+
class Project
|
|
7
|
+
module Object
|
|
8
|
+
|
|
9
|
+
class AbstractPBXObject
|
|
10
|
+
class << self
|
|
11
|
+
def has_many(plural_attr_name, options = {}, &block)
|
|
12
|
+
create_association(:has_many, plural_attr_name, options, &block)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def has_one(singular_attr_name, options = {})
|
|
16
|
+
create_association(:has_one, singular_attr_name, options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def create_association(type, name, options, &block)
|
|
22
|
+
reflection = create_reflection(type, name, options)
|
|
23
|
+
unless reflection.inverse?
|
|
24
|
+
attribute(reflection.attribute_name, :as => reflection.attribute_getter)
|
|
25
|
+
end
|
|
26
|
+
define_method(reflection.getter) do
|
|
27
|
+
reflection.association_for(self, &block).get
|
|
28
|
+
end
|
|
29
|
+
define_method(reflection.setter) do |new_value|
|
|
30
|
+
reflection.association_for(self, &block).set(new_value)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Association
|
|
37
|
+
attr_reader :owner, :reflection
|
|
38
|
+
|
|
39
|
+
def initialize(owner, reflection, &block)
|
|
40
|
+
@owner, @reflection, @block = owner, reflection, block
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get
|
|
44
|
+
@reflection.inverse? ? inverse_get : direct_get
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def set(value)
|
|
48
|
+
@reflection.inverse? ? inverse_set(value) : direct_set(value)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|