xcodeproj 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+