xcodeproj 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Eloy Durán <eloy.de.enige@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Xcodeproj
2
+
3
+ Xcodeproj lets you create and modify Xcode projects from [MacRuby][macruby].
4
+ Script boring management tasks or build Xcode-friendly libraries. Also includes
5
+ support for Xcode workspaces (.xcworkspace) and configuration files (.xcconfig).
6
+
7
+ It is used in [CocoaPods](https://github.com/cocoapods/cocoapods) to create a
8
+ static library from scratch, for both iOS and OSX.
9
+
10
+
11
+ ## Installing Xcodeproj
12
+
13
+ You’ll need MacRuby. xcodeproj itself installs through RubyGems, the Ruby
14
+ package manager. Download and install [version 0.10][macruby-dl] and then
15
+ perform the following command:
16
+
17
+ $ sudo macgem install xcodeproj
18
+
19
+ The load time can be improved a bit by compiling the Ruby source files:
20
+
21
+ $ sudo macgem install rubygems-compile
22
+ $ sudo macgem compile xcodeproj
23
+
24
+
25
+ ## Colaborate
26
+
27
+ All Xcodeproj development happens on [GitHub][xcodeproj]. Contributing patches
28
+ is really easy and gratifying. You even get push access when one of your patches
29
+ is accepted.
30
+
31
+ Follow [@CocoaPodsOrg][twitter] to get up to date information about what's
32
+ going on in the CocoaPods world.
33
+
34
+ If you're really oldschool and you want to discuss Xcodeproj development you
35
+ can join #cocoapods on irc.freenode.net.
36
+
37
+
38
+ ## Contributors
39
+
40
+ * [Nolan Waite](https://github.com/nolanw)
41
+
42
+
43
+ ## LICENSE
44
+
45
+ These works are available under the MIT license. See the [LICENSE][license] file
46
+ for more info.
47
+
48
+ Included in this package is the [inflector part of ActiveSupport][activesupport]
49
+ which is also available under the MIT license.
50
+
51
+ [twitter]: http://twitter.com/CocoaPodsOrg
52
+ [macruby]: http://www.macruby.org
53
+ [macruby-dl]: http://www.macruby.org/files
54
+ [xcodeproj]: https://github.com/cocoapods/xcodeproj
55
+ [tickets]: https://github.com/cocoapods/xcodeproj/issues
56
+ [license]: xcodeproj/blob/master/LICENSE
57
+ [activesupport]: https://github.com/rails/rails/tree/2-3-stable/activesupport
data/lib/xcodeproj.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Xcodeproj
2
+ VERSION = '0.0.1'
3
+
4
+ autoload :Config, 'xcodeproj/config'
5
+ autoload :Project, 'xcodeproj/project'
6
+ autoload :Workspace, 'xcodeproj/workspace'
7
+ end
@@ -0,0 +1,31 @@
1
+ module Xcodeproj
2
+ class Config
3
+ def initialize(xcconfig = {})
4
+ @attributes = {}
5
+ merge!(xcconfig)
6
+ end
7
+
8
+ def to_hash
9
+ @attributes
10
+ end
11
+
12
+ def merge!(xcconfig)
13
+ xcconfig.to_hash.each do |key, value|
14
+ if existing_value = @attributes[key]
15
+ @attributes[key] = "#{existing_value} #{value}"
16
+ else
17
+ @attributes[key] = value
18
+ end
19
+ end
20
+ end
21
+ alias_method :<<, :merge!
22
+
23
+ def to_s
24
+ @attributes.map { |key, value| "#{key} = #{value}" }.join("\n")
25
+ end
26
+
27
+ def save_as(pathname)
28
+ pathname.open('w') { |file| file << to_s }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,453 @@
1
+ # encoding: utf-8
2
+
3
+ # This is part of ActiveSupport, which is available under the MIT license>
4
+ #
5
+ # Copyright (c) 2005-2010 David Heinemeier Hansson
6
+ #
7
+ # From files:
8
+ # * https://raw.github.com/rails/rails/2-3-stable/activesupport/lib/active_support/inflector.rb
9
+ # * https://raw.github.com/rails/rails/2-3-stable/activesupport/lib/active_support/inflections.rb
10
+ # * https://raw.github.com/rails/rails/2-3-stable/activesupport/lib/active_support/core_ext/string/inflections.rb
11
+
12
+ require 'singleton'
13
+
14
+ module Xcodeproj
15
+ module ActiveSupport
16
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
17
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
18
+ # in inflections.rb.
19
+ #
20
+ # The Rails core team has stated patches for the inflections library will not be accepted
21
+ # in order to avoid breaking legacy applications which may be relying on errant inflections.
22
+ # If you discover an incorrect inflection and require it for your application, you'll need
23
+ # to correct it yourself (explained below).
24
+ module Inflector
25
+ extend self
26
+
27
+ # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
28
+ # inflection rules. Examples:
29
+ #
30
+ # ActiveSupport::Inflector.inflections do |inflect|
31
+ # inflect.plural /^(ox)$/i, '\1\2en'
32
+ # inflect.singular /^(ox)en/i, '\1'
33
+ #
34
+ # inflect.irregular 'octopus', 'octopi'
35
+ #
36
+ # inflect.uncountable "equipment"
37
+ # end
38
+ #
39
+ # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
40
+ # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
41
+ # already have been loaded.
42
+ class Inflections
43
+ include Singleton
44
+
45
+ attr_reader :plurals, :singulars, :uncountables, :humans
46
+
47
+ def initialize
48
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
49
+ end
50
+
51
+ # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
52
+ # The replacement should always be a string that may include references to the matched data from the rule.
53
+ def plural(rule, replacement)
54
+ @uncountables.delete(rule) if rule.is_a?(String)
55
+ @uncountables.delete(replacement)
56
+ @plurals.insert(0, [rule, replacement])
57
+ end
58
+
59
+ # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
60
+ # The replacement should always be a string that may include references to the matched data from the rule.
61
+ def singular(rule, replacement)
62
+ @uncountables.delete(rule) if rule.is_a?(String)
63
+ @uncountables.delete(replacement)
64
+ @singulars.insert(0, [rule, replacement])
65
+ end
66
+
67
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
68
+ # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
69
+ #
70
+ # Examples:
71
+ # irregular 'octopus', 'octopi'
72
+ # irregular 'person', 'people'
73
+ def irregular(singular, plural)
74
+ @uncountables.delete(singular)
75
+ @uncountables.delete(plural)
76
+ if singular[0,1].upcase == plural[0,1].upcase
77
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
78
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
79
+ else
80
+ plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
81
+ plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
82
+ singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
83
+ singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
84
+ end
85
+ end
86
+
87
+ # Add uncountable words that shouldn't be attempted inflected.
88
+ #
89
+ # Examples:
90
+ # uncountable "money"
91
+ # uncountable "money", "information"
92
+ # uncountable %w( money information rice )
93
+ def uncountable(*words)
94
+ (@uncountables << words).flatten!
95
+ end
96
+
97
+ # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
98
+ # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
99
+ # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
100
+ #
101
+ # Examples:
102
+ # human /_cnt$/i, '\1_count'
103
+ # human "legacy_col_person_name", "Name"
104
+ def human(rule, replacement)
105
+ @humans.insert(0, [rule, replacement])
106
+ end
107
+
108
+ # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
109
+ # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
110
+ # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
111
+ #
112
+ # Examples:
113
+ # clear :all
114
+ # clear :plurals
115
+ def clear(scope = :all)
116
+ case scope
117
+ when :all
118
+ @plurals, @singulars, @uncountables = [], [], []
119
+ else
120
+ instance_variable_set "@#{scope}", []
121
+ end
122
+ end
123
+ end
124
+
125
+ # Yields a singleton instance of Inflector::Inflections so you can specify additional
126
+ # inflector rules.
127
+ #
128
+ # Example:
129
+ # ActiveSupport::Inflector.inflections do |inflect|
130
+ # inflect.uncountable "rails"
131
+ # end
132
+ def inflections
133
+ if block_given?
134
+ yield Inflections.instance
135
+ else
136
+ Inflections.instance
137
+ end
138
+ end
139
+
140
+ # Returns the plural form of the word in the string.
141
+ #
142
+ # Examples:
143
+ # "post".pluralize # => "posts"
144
+ # "octopus".pluralize # => "octopi"
145
+ # "sheep".pluralize # => "sheep"
146
+ # "words".pluralize # => "words"
147
+ # "CamelOctopus".pluralize # => "CamelOctopi"
148
+ def pluralize(word)
149
+ result = word.to_s.dup
150
+
151
+ if word.empty? || inflections.uncountables.include?(result.downcase)
152
+ result
153
+ else
154
+ inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
155
+ result
156
+ end
157
+ end
158
+
159
+ # The reverse of +pluralize+, returns the singular form of a word in a string.
160
+ #
161
+ # Examples:
162
+ # "posts".singularize # => "post"
163
+ # "octopi".singularize # => "octopus"
164
+ # "sheep".singluarize # => "sheep"
165
+ # "word".singularize # => "word"
166
+ # "CamelOctopi".singularize # => "CamelOctopus"
167
+ def singularize(word)
168
+ result = word.to_s.dup
169
+
170
+ if inflections.uncountables.any? { |inflection| result =~ /#{inflection}\Z/i }
171
+ result
172
+ else
173
+ inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
174
+ result
175
+ end
176
+ end
177
+
178
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
179
+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
180
+ #
181
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
182
+ #
183
+ # Examples:
184
+ # "active_record".camelize # => "ActiveRecord"
185
+ # "active_record".camelize(:lower) # => "activeRecord"
186
+ # "active_record/errors".camelize # => "ActiveRecord::Errors"
187
+ # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
188
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
189
+ if first_letter_in_uppercase
190
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
191
+ else
192
+ lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
193
+ end
194
+ end
195
+
196
+ # Capitalizes all the words and replaces some characters in the string to create
197
+ # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
198
+ # used in the Rails internals.
199
+ #
200
+ # +titleize+ is also aliased as as +titlecase+.
201
+ #
202
+ # Examples:
203
+ # "man from the boondocks".titleize # => "Man From The Boondocks"
204
+ # "x-men: the last stand".titleize # => "X Men: The Last Stand"
205
+ def titleize(word)
206
+ humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
207
+ end
208
+
209
+ # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
210
+ #
211
+ # Changes '::' to '/' to convert namespaces to paths.
212
+ #
213
+ # Examples:
214
+ # "ActiveRecord".underscore # => "active_record"
215
+ # "ActiveRecord::Errors".underscore # => active_record/errors
216
+ def underscore(camel_cased_word)
217
+ camel_cased_word.to_s.gsub(/::/, '/').
218
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
219
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
220
+ tr("-", "_").
221
+ downcase
222
+ end
223
+
224
+ # Replaces underscores with dashes in the string.
225
+ #
226
+ # Example:
227
+ # "puni_puni" # => "puni-puni"
228
+ def dasherize(underscored_word)
229
+ underscored_word.gsub(/_/, '-')
230
+ end
231
+
232
+ # Capitalizes the first word and turns underscores into spaces and strips a
233
+ # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
234
+ #
235
+ # Examples:
236
+ # "employee_salary" # => "Employee salary"
237
+ # "author_id" # => "Author"
238
+ def humanize(lower_case_and_underscored_word)
239
+ result = lower_case_and_underscored_word.to_s.dup
240
+
241
+ inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
242
+ result.gsub(/_id$/, "").gsub(/_/, " ").capitalize
243
+ end
244
+
245
+ # Removes the module part from the expression in the string.
246
+ #
247
+ # Examples:
248
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
249
+ # "Inflections".demodulize # => "Inflections"
250
+ def demodulize(class_name_in_module)
251
+ class_name_in_module.to_s.gsub(/^.*::/, '')
252
+ end
253
+
254
+ # Create a class name from a plural table name like Rails does for table names to models.
255
+ # Note that this returns a string and not a Class. (To convert to an actual class
256
+ # follow +classify+ with +constantize+.)
257
+ #
258
+ # Examples:
259
+ # "egg_and_hams".classify # => "EggAndHam"
260
+ # "posts".classify # => "Post"
261
+ #
262
+ # Singular names are not handled correctly:
263
+ # "business".classify # => "Busines"
264
+ def classify(table_name)
265
+ # strip out any leading schema name
266
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
267
+ end
268
+
269
+ # Turns a number into an ordinal string used to denote the position in an
270
+ # ordered sequence such as 1st, 2nd, 3rd, 4th.
271
+ #
272
+ # Examples:
273
+ # ordinalize(1) # => "1st"
274
+ # ordinalize(2) # => "2nd"
275
+ # ordinalize(1002) # => "1002nd"
276
+ # ordinalize(1003) # => "1003rd"
277
+ def ordinalize(number)
278
+ if (11..13).include?(number.to_i % 100)
279
+ "#{number}th"
280
+ else
281
+ case number.to_i % 10
282
+ when 1; "#{number}st"
283
+ when 2; "#{number}nd"
284
+ when 3; "#{number}rd"
285
+ else "#{number}th"
286
+ end
287
+ end
288
+ end
289
+ end
290
+
291
+ Inflector.inflections do |inflect|
292
+ inflect.plural(/$/, 's')
293
+ inflect.plural(/s$/i, 's')
294
+ inflect.plural(/(ax|test)is$/i, '\1es')
295
+ inflect.plural(/(octop|vir)us$/i, '\1i')
296
+ inflect.plural(/(alias|status)$/i, '\1es')
297
+ inflect.plural(/(bu)s$/i, '\1ses')
298
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
299
+ inflect.plural(/([ti])um$/i, '\1a')
300
+ inflect.plural(/sis$/i, 'ses')
301
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
302
+ inflect.plural(/(hive)$/i, '\1s')
303
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
304
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
305
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
306
+ inflect.plural(/([m|l])ouse$/i, '\1ice')
307
+ inflect.plural(/^(ox)$/i, '\1en')
308
+ inflect.plural(/(quiz)$/i, '\1zes')
309
+
310
+ inflect.singular(/s$/i, '')
311
+ inflect.singular(/(n)ews$/i, '\1ews')
312
+ inflect.singular(/([ti])a$/i, '\1um')
313
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
314
+ inflect.singular(/(^analy)ses$/i, '\1sis')
315
+ inflect.singular(/([^f])ves$/i, '\1fe')
316
+ inflect.singular(/(hive)s$/i, '\1')
317
+ inflect.singular(/(tive)s$/i, '\1')
318
+ inflect.singular(/([lr])ves$/i, '\1f')
319
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
320
+ inflect.singular(/(s)eries$/i, '\1eries')
321
+ inflect.singular(/(m)ovies$/i, '\1ovie')
322
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
323
+ inflect.singular(/([m|l])ice$/i, '\1ouse')
324
+ inflect.singular(/(bus)es$/i, '\1')
325
+ inflect.singular(/(o)es$/i, '\1')
326
+ inflect.singular(/(shoe)s$/i, '\1')
327
+ inflect.singular(/(cris|ax|test)es$/i, '\1is')
328
+ inflect.singular(/(octop|vir)i$/i, '\1us')
329
+ inflect.singular(/(alias|status)es$/i, '\1')
330
+ inflect.singular(/^(ox)en/i, '\1')
331
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
332
+ inflect.singular(/(matr)ices$/i, '\1ix')
333
+ inflect.singular(/(quiz)zes$/i, '\1')
334
+ inflect.singular(/(database)s$/i, '\1')
335
+
336
+ inflect.irregular('person', 'people')
337
+ inflect.irregular('man', 'men')
338
+ inflect.irregular('child', 'children')
339
+ inflect.irregular('sex', 'sexes')
340
+ inflect.irregular('move', 'moves')
341
+ inflect.irregular('cow', 'kine')
342
+
343
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
344
+ end
345
+ end
346
+ end
347
+
348
+ # String inflections define new methods on the String class to transform names for different purposes.
349
+ class NSString
350
+ include Xcodeproj::ActiveSupport
351
+
352
+ # Returns the plural form of the word in the string.
353
+ #
354
+ # "post".pluralize # => "posts"
355
+ # "octopus".pluralize # => "octopi"
356
+ # "sheep".pluralize # => "sheep"
357
+ # "words".pluralize # => "words"
358
+ # "the blue mailman".pluralize # => "the blue mailmen"
359
+ # "CamelOctopus".pluralize # => "CamelOctopi"
360
+ def pluralize
361
+ Inflector.pluralize(self)
362
+ end
363
+
364
+ # The reverse of +pluralize+, returns the singular form of a word in a string.
365
+ #
366
+ # "posts".singularize # => "post"
367
+ # "octopi".singularize # => "octopus"
368
+ # "sheep".singularize # => "sheep"
369
+ # "word".singularize # => "word"
370
+ # "the blue mailmen".singularize # => "the blue mailman"
371
+ # "CamelOctopi".singularize # => "CamelOctopus"
372
+ def singularize
373
+ Inflector.singularize(self)
374
+ end
375
+
376
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
377
+ # is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
378
+ #
379
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
380
+ #
381
+ # "active_record".camelize # => "ActiveRecord"
382
+ # "active_record".camelize(:lower) # => "activeRecord"
383
+ # "active_record/errors".camelize # => "ActiveRecord::Errors"
384
+ # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
385
+ def camelize(first_letter = :upper)
386
+ case first_letter
387
+ when :upper then Inflector.camelize(self, true)
388
+ when :lower then Inflector.camelize(self, false)
389
+ end
390
+ end
391
+ alias_method :camelcase, :camelize
392
+
393
+ # Capitalizes all the words and replaces some characters in the string to create
394
+ # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
395
+ # used in the Rails internals.
396
+ #
397
+ # +titleize+ is also aliased as +titlecase+.
398
+ #
399
+ # "man from the boondocks".titleize # => "Man From The Boondocks"
400
+ # "x-men: the last stand".titleize # => "X Men: The Last Stand"
401
+ def titleize
402
+ Inflector.titleize(self)
403
+ end
404
+ alias_method :titlecase, :titleize
405
+
406
+ # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
407
+ #
408
+ # +underscore+ will also change '::' to '/' to convert namespaces to paths.
409
+ #
410
+ # "ActiveRecord".underscore # => "active_record"
411
+ # "ActiveRecord::Errors".underscore # => active_record/errors
412
+ def underscore
413
+ Inflector.underscore(self)
414
+ end
415
+
416
+ # Replaces underscores with dashes in the string.
417
+ #
418
+ # "puni_puni" # => "puni-puni"
419
+ def dasherize
420
+ Inflector.dasherize(self)
421
+ end
422
+
423
+ # Removes the module part from the constant expression in the string.
424
+ #
425
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
426
+ # "Inflections".demodulize # => "Inflections"
427
+ def demodulize
428
+ Inflector.demodulize(self)
429
+ end
430
+
431
+ # Create a class name from a plural table name like Rails does for table names to models.
432
+ # Note that this returns a string and not a class. (To convert to an actual class
433
+ # follow +classify+ with +constantize+.)
434
+ #
435
+ # "egg_and_hams".classify # => "EggAndHam"
436
+ # "posts".classify # => "Post"
437
+ #
438
+ # Singular names are not handled correctly.
439
+ #
440
+ # "business".classify # => "Busines"
441
+ def classify
442
+ Inflector.classify(self)
443
+ end
444
+
445
+ # Capitalizes the first word, turns underscores into spaces, and strips '_id'.
446
+ # Like +titleize+, this is meant for creating pretty output.
447
+ #
448
+ # "employee_salary" # => "Employee salary"
449
+ # "author_id" # => "Author"
450
+ def humanize
451
+ Inflector.humanize(self)
452
+ end
453
+ end
@@ -0,0 +1,664 @@
1
+ framework 'Foundation'
2
+ require 'fileutils'
3
+ require 'xcodeproj/inflector'
4
+
5
+ module Xcodeproj
6
+ class Project
7
+ class PBXObject
8
+ class AssociationReflection
9
+ def initialize(name, options)
10
+ @name, @options = name.to_s, options
11
+ end
12
+
13
+ attr_reader :name, :options
14
+
15
+ def klass
16
+ @options[:class] ||= begin
17
+ name = "PBX#{@name.classify}"
18
+ name = "XC#{@name.classify}" unless Project.const_defined?(name)
19
+ Project.const_get(name)
20
+ end
21
+ end
22
+
23
+ def inverse
24
+ klass.reflection(@options[:inverse_of])
25
+ end
26
+
27
+ def inverse?
28
+ !!@options[:inverse_of]
29
+ end
30
+
31
+ def singular_name
32
+ @options[:singular_name] || @name.singularize
33
+ end
34
+
35
+ def singular_getter
36
+ singular_name
37
+ end
38
+
39
+ def singular_setter
40
+ "#{singular_name}="
41
+ end
42
+
43
+ def plural_name
44
+ @name.pluralize
45
+ end
46
+
47
+ def plural_getter
48
+ plural_name
49
+ end
50
+
51
+ def plural_setter
52
+ "#{plural_name}="
53
+ end
54
+
55
+ def uuid_attribute
56
+ @options[:uuid] || @name
57
+ end
58
+
59
+ def uuid_method_name
60
+ (@options[:uuid] || @options[:uuids] || "#{singular_name}Reference").to_s.singularize
61
+ end
62
+
63
+ def uuid_getter
64
+ uuid_method_name
65
+ end
66
+
67
+ def uuid_setter
68
+ "#{uuid_method_name}="
69
+ end
70
+
71
+ def uuids_method_name
72
+ uuid_method_name.pluralize
73
+ end
74
+
75
+ def uuids_getter
76
+ uuids_method_name
77
+ end
78
+
79
+ def uuids_setter
80
+ "#{uuids_method_name}="
81
+ end
82
+ end
83
+
84
+ def self.reflections
85
+ @reflections ||= []
86
+ end
87
+
88
+ def self.create_reflection(name, options)
89
+ (reflections << AssociationReflection.new(name, options)).last
90
+ end
91
+
92
+ def self.reflection(name)
93
+ reflections.find { |r| r.name.to_s == name.to_s }
94
+ end
95
+
96
+ def self.attribute(attribute_name, accessor_name = nil)
97
+ attribute_name = attribute_name.to_s
98
+ name = (accessor_name || attribute_name).to_s
99
+ define_method(name) { @attributes[attribute_name] }
100
+ define_method("#{name}=") { |value| @attributes[attribute_name] = value }
101
+ end
102
+
103
+ def self.attributes(*names)
104
+ names.each { |name| attribute(name) }
105
+ end
106
+
107
+ def self.has_many(plural_attr_name, options = {}, &block)
108
+ reflection = create_reflection(plural_attr_name, options)
109
+ if reflection.inverse?
110
+ define_method(reflection.name) do
111
+ scoped = @project.objects.select_by_class(reflection.klass).select do |object|
112
+ object.send(reflection.inverse.uuid_getter) == self.uuid
113
+ end
114
+ PBXObjectList.new(reflection.klass, @project, scoped) do |object|
115
+ object.send(reflection.inverse.uuid_setter, self.uuid)
116
+ end
117
+ end
118
+ else
119
+ attribute(reflection.name, reflection.uuids_getter)
120
+ define_method(reflection.name) do
121
+ uuids = send(reflection.uuids_getter)
122
+ if block
123
+ # Evaluate the block, which was specified at the class level, in
124
+ # the instance’s context.
125
+ list_by_class(uuids, reflection.klass) do |object|
126
+ instance_exec(object, &block)
127
+ end
128
+ else
129
+ list_by_class(uuids, reflection.klass)
130
+ end
131
+ end
132
+ define_method(reflection.plural_setter) do |objects|
133
+ send(reflection.uuids_setter, objects.map(&:uuid))
134
+ end
135
+ end
136
+ end
137
+
138
+ def self.has_one(singular_attr_name, options = {})
139
+ reflection = create_reflection(singular_attr_name, options)
140
+ if reflection.inverse?
141
+ define_method(reflection.name) do
142
+ # Loop over all objects of the class and find the one that includes
143
+ # this object in the specified uuid list.
144
+ @project.objects.select_by_class(reflection.klass).find do |object|
145
+ object.send(reflection.inverse.uuids_getter).include?(self.uuid)
146
+ end
147
+ end
148
+ define_method(reflection.singular_setter) do |object|
149
+ # Remove this object from the uuid list of the target
150
+ # that this object was associated to.
151
+ if previous = send(reflection.name)
152
+ previous.send(reflection.inverse.uuids_getter).delete(self.uuid)
153
+ end
154
+ # Now assign this object to the new object
155
+ object.send(reflection.inverse.uuids_getter) << self.uuid if object
156
+ end
157
+ else
158
+ attribute(reflection.uuid_attribute, reflection.uuid_getter)
159
+ define_method(reflection.name) do
160
+ @project.objects[send(reflection.uuid_getter)]
161
+ end
162
+ define_method(reflection.singular_setter) do |object|
163
+ send(reflection.uuid_setter, object.uuid)
164
+ end
165
+ end
166
+ end
167
+
168
+ def self.isa
169
+ @isa ||= name.split('::').last
170
+ end
171
+
172
+ attr_reader :uuid, :attributes
173
+ attributes :isa, :name
174
+
175
+ def initialize(project, uuid, attributes)
176
+ @project, @attributes = project, attributes
177
+ unless uuid
178
+ # Add new objects to the main hash with a unique UUID
179
+ begin; uuid = generate_uuid; end while @project.objects_hash.has_key?(uuid)
180
+ @project.objects_hash[uuid] = @attributes
181
+ end
182
+ @uuid = uuid
183
+ self.isa ||= self.class.isa
184
+ end
185
+
186
+ def ==(other)
187
+ other.is_a?(PBXObject) && self.uuid == other.uuid
188
+ end
189
+
190
+ def inspect
191
+ "#<#{isa} UUID: `#{uuid}', name: `#{name}'>"
192
+ end
193
+
194
+ private
195
+
196
+ def generate_uuid
197
+ _uuid = CFUUIDCreate(nil)
198
+ uuid = CFUUIDCreateString(nil, _uuid)
199
+ CFRelease(_uuid)
200
+ CFMakeCollectable(uuid)
201
+ # Xcode's version is actually shorter, not worrying about collisions too much right now.
202
+ uuid.gsub('-', '')[0..23]
203
+ end
204
+
205
+ def list_by_class(uuids, klass, scoped = nil, &block)
206
+ unless scoped
207
+ scoped = uuids.map { |uuid| @project.objects[uuid] }.select { |o| o.is_a?(klass) }
208
+ end
209
+ if block
210
+ PBXObjectList.new(klass, @project, scoped, &block)
211
+ else
212
+ PBXObjectList.new(klass, @project, scoped) do |object|
213
+ # Add the uuid of a newly created object to the uuids list
214
+ uuids << object.uuid
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ # Missing constants that begin with either `PBX' or `XC' are assumed to be
221
+ # valid classes in a Xcode project. A new PBXObject subclass is created
222
+ # for the constant and returned.
223
+ def self.const_missing(name)
224
+ if name =~ /^(PBX|XC)/
225
+ klass = Class.new(PBXObject)
226
+ const_set(name, klass)
227
+ klass
228
+ else
229
+ super
230
+ end
231
+ end
232
+
233
+ class PBXFileReference < PBXObject
234
+ attributes :path, :sourceTree, :explicitFileType, :lastKnownFileType, :includeInIndex
235
+ has_many :buildFiles, :inverse_of => :file
236
+ has_one :group, :inverse_of => :children
237
+
238
+ def self.new_static_library(project, productName)
239
+ new(project, nil, {
240
+ "path" => "lib#{productName}.a",
241
+ "includeInIndex" => "0", # no idea what this is
242
+ "sourceTree" => "BUILT_PRODUCTS_DIR",
243
+ })
244
+ end
245
+
246
+ def initialize(project, uuid, attributes)
247
+ is_new = uuid.nil?
248
+ super
249
+ self.path = path if path # sets default name
250
+ self.sourceTree ||= 'SOURCE_ROOT'
251
+ if is_new
252
+ @project.main_group.children << self
253
+ end
254
+ set_default_file_type!
255
+ end
256
+
257
+ alias_method :_path=, :path=
258
+ def path=(path)
259
+ self._path = path
260
+ self.name ||= pathname.basename.to_s
261
+ path
262
+ end
263
+
264
+ def pathname
265
+ Pathname.new(path)
266
+ end
267
+
268
+ def set_default_file_type!
269
+ return if explicitFileType || lastKnownFileType
270
+ case path
271
+ when /\.a$/
272
+ self.explicitFileType = 'archive.ar'
273
+ when /\.framework$/
274
+ self.lastKnownFileType = 'wrapper.framework'
275
+ when /\.xcconfig$/
276
+ self.lastKnownFileType = 'text.xcconfig'
277
+ end
278
+ end
279
+ end
280
+
281
+ class PBXGroup < PBXObject
282
+ attributes :sourceTree
283
+
284
+ has_many :children, :class => PBXFileReference do |object|
285
+ if object.is_a?(Xcodeproj::Project::PBXFileReference)
286
+ # Associating the file to this group through the inverse
287
+ # association will also remove it from the group it was in.
288
+ object.group = self
289
+ else
290
+ # TODO What objects can actually be in a group and don't they
291
+ # all need the above treatment.
292
+ childReferences << object.uuid
293
+ end
294
+ end
295
+
296
+ def initialize(*)
297
+ super
298
+ self.sourceTree ||= '<group>'
299
+ self.childReferences ||= []
300
+ end
301
+
302
+ def files
303
+ list_by_class(childReferences, Xcodeproj::Project::PBXFileReference) do |file|
304
+ file.group = self
305
+ end
306
+ end
307
+
308
+ def source_files
309
+ files = self.files.reject { |file| file.buildFiles.empty? }
310
+ list_by_class(childReferences, Xcodeproj::Project::PBXFileReference, files) do |file|
311
+ file.group = self
312
+ end
313
+ end
314
+
315
+ def groups
316
+ list_by_class(childReferences, Xcodeproj::Project::PBXGroup)
317
+ end
318
+
319
+ def <<(child)
320
+ children << child
321
+ end
322
+ end
323
+
324
+ class PBXBuildFile < PBXObject
325
+ attributes :settings
326
+ has_one :file, :uuid => :fileRef
327
+ end
328
+
329
+ class PBXBuildPhase < PBXObject
330
+ # TODO rename this to buildFiles and add a files :through => :buildFiles shortcut
331
+ has_many :files, :class => PBXBuildFile
332
+
333
+ attributes :buildActionMask, :runOnlyForDeploymentPostprocessing
334
+
335
+ def initialize(*)
336
+ super
337
+ self.fileReferences ||= []
338
+ # These are always the same, no idea what they are.
339
+ self.buildActionMask ||= "2147483647"
340
+ self.runOnlyForDeploymentPostprocessing ||= "0"
341
+ end
342
+ end
343
+
344
+ class PBXCopyFilesBuildPhase < PBXBuildPhase
345
+ attributes :dstPath, :dstSubfolderSpec
346
+
347
+ def initialize(*)
348
+ super
349
+ self.dstSubfolderSpec ||= "16"
350
+ end
351
+ end
352
+
353
+ class PBXSourcesBuildPhase < PBXBuildPhase; end
354
+ class PBXFrameworksBuildPhase < PBXBuildPhase; end
355
+ class PBXShellScriptBuildPhase < PBXBuildPhase
356
+ attribute :shellScript
357
+ end
358
+
359
+ class PBXNativeTarget < PBXObject
360
+ STATIC_LIBRARY = 'com.apple.product-type.library.static'
361
+
362
+ attributes :productName, :productType
363
+
364
+ has_many :buildPhases
365
+ has_many :dependencies # TODO :class => ?
366
+ has_many :buildRules # TODO :class => ?
367
+ has_one :buildConfigurationList
368
+ has_one :product, :uuid => :productReference
369
+
370
+ def self.new_static_library(project, productName)
371
+ # TODO should probably switch the uuid and attributes argument
372
+ target = new(project, nil, 'productType' => STATIC_LIBRARY, 'productName' => productName)
373
+ target.product = project.files.new_static_library(productName)
374
+ products = project.groups.find { |g| g.name == 'Products' }
375
+ products ||= project.groups.new({ 'name' => 'Products'})
376
+ products.children << target.product
377
+ target.buildPhases.add(PBXSourcesBuildPhase)
378
+
379
+ buildPhase = target.buildPhases.add(PBXFrameworksBuildPhase)
380
+ frameworks = project.groups.find { |g| g.name == 'Frameworks' }
381
+ frameworks ||= project.groups.new({ 'name' => 'Frameworks'})
382
+ frameworks.files.each do |framework|
383
+ buildPhase.files << framework.buildFiles.new
384
+ end
385
+
386
+ target.buildPhases.add(PBXCopyFilesBuildPhase, 'dstPath' => '$(PUBLIC_HEADERS_FOLDER_PATH)')
387
+ target
388
+ end
389
+
390
+ # You need to specify a product. For a static library you can use
391
+ # PBXFileReference.new_static_library.
392
+ def initialize(project, *)
393
+ super
394
+ self.name ||= productName
395
+ self.buildRuleReferences ||= []
396
+ self.dependencyReferences ||= []
397
+ self.buildPhaseReferences ||= []
398
+
399
+ unless buildConfigurationList
400
+ self.buildConfigurationList = project.objects.add(XCConfigurationList)
401
+ # TODO or should this happen in buildConfigurationList?
402
+ buildConfigurationList.buildConfigurations.new('name' => 'Debug')
403
+ buildConfigurationList.buildConfigurations.new('name' => 'Release')
404
+ end
405
+ end
406
+
407
+ alias_method :_product=, :product=
408
+ def product=(product)
409
+ self._product = product
410
+ product.group = @project.products
411
+ end
412
+
413
+ def buildConfigurations
414
+ buildConfigurationList.buildConfigurations
415
+ end
416
+
417
+ def source_build_phases
418
+ buildPhases.select_by_class(PBXSourcesBuildPhase)
419
+ end
420
+
421
+ def copy_files_build_phases
422
+ buildPhases.select_by_class(PBXCopyFilesBuildPhase)
423
+ end
424
+
425
+ def frameworks_build_phases
426
+ buildPhases.select_by_class(PBXFrameworksBuildPhase)
427
+ end
428
+
429
+ # Finds an existing file reference or creates a new one.
430
+ def add_source_file(path, copy_header_phase = nil, compiler_flags = nil)
431
+ file = @project.files.find { |file| file.path == path.to_s } || @project.files.new('path' => path.to_s)
432
+ buildFile = file.buildFiles.new
433
+ if path.extname == '.h'
434
+ buildFile.settings = { 'ATTRIBUTES' => ["Public"] }
435
+ # Working around a bug in Xcode 4.2 betas, remove this once the Xcode bug is fixed:
436
+ # https://github.com/alloy/cocoapods/issues/13
437
+ #phase = copy_header_phase || headers_build_phases.first
438
+ phase = copy_header_phase || copy_files_build_phases.first
439
+ phase.files << buildFile
440
+ else
441
+ buildFile.settings = { 'COMPILER_FLAGS' => compiler_flags } if compiler_flags
442
+ source_build_phases.first.files << buildFile
443
+ end
444
+ file
445
+ end
446
+ end
447
+
448
+ class XCBuildConfiguration < PBXObject
449
+ attribute :buildSettings
450
+ has_one :baseConfiguration, :uuid => :baseConfigurationReference
451
+
452
+ def initialize(*)
453
+ super
454
+ # TODO These are from an iOS static library, need to check if it works for any product type
455
+ self.buildSettings = {
456
+ 'DSTROOT' => '/tmp/xcodeproj.dst',
457
+ 'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES',
458
+ 'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
459
+ 'PRODUCT_NAME' => '$(TARGET_NAME)',
460
+ 'SKIP_INSTALL' => 'YES',
461
+ }.merge(buildSettings || {})
462
+ end
463
+ end
464
+
465
+ class XCConfigurationList < PBXObject
466
+ has_many :buildConfigurations
467
+
468
+ def initialize(*)
469
+ super
470
+ self.buildConfigurationReferences ||= []
471
+ end
472
+ end
473
+
474
+ class PBXProject < PBXObject
475
+ has_many :targets, :class => PBXNativeTarget
476
+ has_one :products, :singular_name => :products, :uuid => :productRefGroup, :class => PBXGroup
477
+ end
478
+
479
+ class PBXObjectList
480
+ include Enumerable
481
+
482
+ def initialize(represented_class, project, scoped, &new_object_callback)
483
+ @represented_class = represented_class
484
+ @project = project
485
+ @scoped_hash = scoped.is_a?(Array) ? scoped.inject({}) { |h, o| h[o.uuid] = o.attributes; h } : scoped
486
+ @callback = new_object_callback
487
+ end
488
+
489
+ def empty?
490
+ @scoped_hash.empty?
491
+ end
492
+
493
+ def [](uuid)
494
+ if hash = @scoped_hash[uuid]
495
+ Project.const_get(hash['isa']).new(@project, uuid, hash)
496
+ end
497
+ end
498
+
499
+ def add(klass, hash = {})
500
+ object = klass.new(@project, nil, hash)
501
+ @callback.call(object) if @callback
502
+ object
503
+ end
504
+
505
+ def new(hash = {})
506
+ add(@represented_class, hash)
507
+ end
508
+
509
+ def <<(object)
510
+ @callback.call(object) if @callback
511
+ end
512
+
513
+ def each
514
+ @scoped_hash.keys.each do |uuid|
515
+ yield self[uuid]
516
+ end
517
+ end
518
+
519
+ def ==(other)
520
+ self.to_a == other.to_a
521
+ end
522
+
523
+ def first
524
+ to_a.first
525
+ end
526
+
527
+ def last
528
+ to_a.last
529
+ end
530
+
531
+ def inspect
532
+ "<PBXObjectList: #{map(&:inspect)}>"
533
+ end
534
+
535
+ # Only makes sense on lists that contain mixed classes.
536
+ def select_by_class(klass)
537
+ scoped = @scoped_hash.select { |_, attr| attr['isa'] == klass.isa }
538
+ PBXObjectList.new(klass, @project, scoped) do |object|
539
+ # Objects added to the subselection should still use the same
540
+ # callback as this list.
541
+ self << object
542
+ end
543
+ end
544
+
545
+ def method_missing(name, *args, &block)
546
+ if @represented_class.respond_to?(name)
547
+ object = @represented_class.send(name, @project, *args)
548
+ # The callbacks are only for PBXObject instances instantiated
549
+ # from the class method that we forwarded the message to.
550
+ @callback.call(object) if object.is_a?(PBXObject)
551
+ object
552
+ else
553
+ super
554
+ end
555
+ end
556
+ end
557
+
558
+ def initialize(xcodeproj = nil)
559
+ if xcodeproj
560
+ file = File.join(xcodeproj, 'project.pbxproj')
561
+ @plist = NSMutableDictionary.dictionaryWithContentsOfFile(file.to_s)
562
+ else
563
+ @plist = {
564
+ 'archiveVersion' => '1',
565
+ 'classes' => {},
566
+ 'objectVersion' => '46',
567
+ 'objects' => {}
568
+ }
569
+ self.root_object = objects.add(Xcodeproj::Project::PBXProject, {
570
+ 'attributes' => { 'LastUpgradeCheck' => '0420' },
571
+ 'compatibilityVersion' => 'Xcode 3.2',
572
+ 'developmentRegion' => 'English',
573
+ 'hasScannedForEncodings' => '0',
574
+ 'knownRegions' => ['en'],
575
+ 'mainGroup' => groups.new.uuid,
576
+ 'projectDirPath' => '',
577
+ 'projectRoot' => '',
578
+ 'targets' => []
579
+ })
580
+ end
581
+ end
582
+
583
+ def to_hash
584
+ @plist
585
+ end
586
+
587
+ def objects_hash
588
+ @plist['objects']
589
+ end
590
+
591
+ def objects
592
+ @objects ||= PBXObjectList.new(PBXObject, self, objects_hash)
593
+ end
594
+
595
+ def root_object
596
+ objects[@plist['rootObject']]
597
+ end
598
+
599
+ def root_object=(object)
600
+ @plist['rootObject'] = object.uuid
601
+ end
602
+
603
+ def groups
604
+ objects.select_by_class(PBXGroup)
605
+ end
606
+
607
+ def main_group
608
+ objects[root_object.attributes['mainGroup']]
609
+ end
610
+
611
+ def files
612
+ objects.select_by_class(PBXFileReference)
613
+ end
614
+
615
+ def add_system_framework(name)
616
+ files.new({
617
+ 'name' => "#{name}.framework",
618
+ 'path' => "System/Library/Frameworks/#{name}.framework",
619
+ 'sourceTree' => 'SDKROOT'
620
+ })
621
+ end
622
+
623
+ def add_shell_script_build_phase(name, script_path)
624
+ objects.add(Xcodeproj::Project::PBXShellScriptBuildPhase, {
625
+ 'name' => name,
626
+ 'files' => [],
627
+ 'inputPaths' => [],
628
+ 'outputPaths' => [],
629
+ 'shellPath' => '/bin/sh',
630
+ 'shellScript' => script_path
631
+ })
632
+ end
633
+
634
+ def build_files
635
+ objects.select_by_class(PBXBuildFile)
636
+ end
637
+
638
+ def targets
639
+ # Better to check the project object for targets to ensure they are
640
+ # actually there so the project will work
641
+ root_object.targets
642
+ end
643
+
644
+ def products
645
+ root_object.products
646
+ end
647
+
648
+ IGNORE_GROUPS = ['Frameworks', 'Products', 'Supporting Files']
649
+ def source_files
650
+ source_files = {}
651
+ groups.each do |group|
652
+ next if group.name.nil? || IGNORE_GROUPS.include?(group.name)
653
+ source_files[group.name] = group.source_files.map(&:pathname)
654
+ end
655
+ source_files
656
+ end
657
+
658
+ def save_as(projpath)
659
+ projpath = projpath.to_s
660
+ FileUtils.mkdir_p(projpath)
661
+ @plist.writeToFile(File.join(projpath, 'project.pbxproj'), atomically:true)
662
+ end
663
+ end
664
+ end
@@ -0,0 +1,54 @@
1
+ framework 'Foundation'
2
+ require 'fileutils'
3
+
4
+ module Xcodeproj
5
+ class Workspace
6
+ def initialize(*projpaths)
7
+ @projpaths = projpaths
8
+ end
9
+
10
+ def self.new_from_xcworkspace(path)
11
+ begin
12
+ from_s(File.read(File.join(path, 'contents.xcworkspacedata')))
13
+ rescue Errno::ENOENT
14
+ new
15
+ end
16
+ end
17
+
18
+ def self.from_s(xml)
19
+ doc = NSXMLDocument.alloc.initWithXMLString(xml, options:0, error:nil)
20
+ projpaths = doc.nodesForXPath("/Workspace/FileRef", error:nil).map do |node|
21
+ node.attributeForName("location").stringValue.sub(/^group:/, '')
22
+ end
23
+ new(*projpaths)
24
+ end
25
+
26
+ attr_reader :projpaths
27
+
28
+ def <<(projpath)
29
+ @projpaths << projpath
30
+ end
31
+
32
+ def include?(projpath)
33
+ @projpaths.include?(projpath)
34
+ end
35
+
36
+ TEMPLATE = %q[<?xml version="1.0" encoding="UTF-8"?><Workspace version="1.0"></Workspace>]
37
+ def to_s
38
+ doc = NSXMLDocument.alloc.initWithXMLString(TEMPLATE, options:0, error:nil)
39
+ @projpaths.each do |projpath|
40
+ el = NSXMLNode.elementWithName("FileRef")
41
+ el.addAttribute(NSXMLNode.attributeWithName("location", stringValue:"group:#{projpath}"))
42
+ doc.rootElement.addChild(el)
43
+ end
44
+ NSString.alloc.initWithData(doc.XMLData, encoding:NSUTF8StringEncoding)
45
+ end
46
+
47
+ def save_as(path)
48
+ FileUtils.mkdir_p(path)
49
+ File.open(File.join(path, 'contents.xcworkspacedata'), 'w') do |out|
50
+ out << to_s
51
+ end
52
+ end
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xcodeproj
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Eloy Duran
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-11-10 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Xcodeproj lets you create and modify Xcode projects from MacRuby. Script boring management tasks or build Xcode-friendly libraries. Also includes support for Xcode workspaces (.xcworkspace) and configuration files (.xcconfig).
22
+ email: eloy.de.enige@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/xcodeproj/config.rb
31
+ - lib/xcodeproj/inflector.rb
32
+ - lib/xcodeproj/project.rb
33
+ - lib/xcodeproj/workspace.rb
34
+ - lib/xcodeproj.rb
35
+ - README.md
36
+ - LICENSE
37
+ has_rdoc: true
38
+ homepage: https://github.com/cocoapods/xcodeproj
39
+ licenses:
40
+ - MIT
41
+ post_install_message:
42
+ rdoc_options: []
43
+
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ segments:
51
+ - 0
52
+ version: "0"
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.6
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Create and modify Xcode projects from MacRuby.
67
+ test_files: []
68
+