zerg_xcode 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.
Files changed (46) hide show
  1. data/CHANGELOG +1 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +44 -0
  4. data/README.textile +124 -0
  5. data/RUBYFORGE +40 -0
  6. data/Rakefile +27 -0
  7. data/bin/zerg-xcode +7 -0
  8. data/lib/zerg_xcode/file_format/archiver.rb +87 -0
  9. data/lib/zerg_xcode/file_format/encoder.rb +40 -0
  10. data/lib/zerg_xcode/file_format/lexer.rb +60 -0
  11. data/lib/zerg_xcode/file_format/parser.rb +46 -0
  12. data/lib/zerg_xcode/file_format/paths.rb +44 -0
  13. data/lib/zerg_xcode/objects/pbx_build_file.rb +27 -0
  14. data/lib/zerg_xcode/objects/pbx_native_target.rb +19 -0
  15. data/lib/zerg_xcode/objects/pbx_project.rb +60 -0
  16. data/lib/zerg_xcode/objects/xcode_object.rb +144 -0
  17. data/lib/zerg_xcode/plugins/core/core.rb +36 -0
  18. data/lib/zerg_xcode/plugins/help.rb +30 -0
  19. data/lib/zerg_xcode/plugins/import.rb +19 -0
  20. data/lib/zerg_xcode/plugins/irb.rb +22 -0
  21. data/lib/zerg_xcode/plugins/ls.rb +27 -0
  22. data/lib/zerg_xcode/plugins/retarget.rb +90 -0
  23. data/lib/zerg_xcode/shortcuts.rb +22 -0
  24. data/lib/zerg_xcode.rb +17 -0
  25. data/test/file_format/archiver_test.rb +70 -0
  26. data/test/file_format/encoder_test.rb +10 -0
  27. data/test/file_format/lexer_test.rb +56 -0
  28. data/test/file_format/parser_test.rb +44 -0
  29. data/test/file_format/path_test.rb +40 -0
  30. data/test/objects/pbx_build_file_test.rb +38 -0
  31. data/test/objects/pbx_native_target_test.rb +31 -0
  32. data/test/objects/pbx_project_test.rb +62 -0
  33. data/test/objects/xcode_object_test.rb +117 -0
  34. data/test/plugins/core/core_test.rb +32 -0
  35. data/test/plugins/import_test.rb +22 -0
  36. data/test/plugins/irb_test.rb +30 -0
  37. data/test/plugins/ls_test.rb +43 -0
  38. data/test/plugins/retarget_test.rb +65 -0
  39. data/test/plugins/test_helper.rb +21 -0
  40. data/test/shortcuts_test.rb +18 -0
  41. data/testdata/TestApp/TestApp.xcodeproj/project.pbxproj +328 -0
  42. data/testdata/ZergSupport.xcodeproj/project.pbxproj +930 -0
  43. data/testdata/project.pbxproj +250 -0
  44. data/testdata/project.pbxproj.compat +320 -0
  45. data/zerg_xcode.gemspec +34 -0
  46. metadata +137 -0
data/CHANGELOG ADDED
@@ -0,0 +1 @@
1
+ v0.1. Initial release. Knows help, irb, ls, retarget.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2008 Victor Costan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,44 @@
1
+ bin/zerg-xcode
2
+ CHANGELOG
3
+ lib/zerg_xcode/file_format/archiver.rb
4
+ lib/zerg_xcode/file_format/encoder.rb
5
+ lib/zerg_xcode/file_format/lexer.rb
6
+ lib/zerg_xcode/file_format/parser.rb
7
+ lib/zerg_xcode/file_format/paths.rb
8
+ lib/zerg_xcode/objects/pbx_build_file.rb
9
+ lib/zerg_xcode/objects/pbx_native_target.rb
10
+ lib/zerg_xcode/objects/pbx_project.rb
11
+ lib/zerg_xcode/objects/xcode_object.rb
12
+ lib/zerg_xcode/plugins/core/core.rb
13
+ lib/zerg_xcode/plugins/help.rb
14
+ lib/zerg_xcode/plugins/import.rb
15
+ lib/zerg_xcode/plugins/irb.rb
16
+ lib/zerg_xcode/plugins/ls.rb
17
+ lib/zerg_xcode/plugins/retarget.rb
18
+ lib/zerg_xcode/shortcuts.rb
19
+ lib/zerg_xcode.rb
20
+ LICENSE
21
+ Manifest
22
+ Rakefile
23
+ README.textile
24
+ RUBYFORGE
25
+ test/file_format/archiver_test.rb
26
+ test/file_format/encoder_test.rb
27
+ test/file_format/lexer_test.rb
28
+ test/file_format/parser_test.rb
29
+ test/file_format/path_test.rb
30
+ test/objects/pbx_build_file_test.rb
31
+ test/objects/pbx_native_target_test.rb
32
+ test/objects/pbx_project_test.rb
33
+ test/objects/xcode_object_test.rb
34
+ test/plugins/core/core_test.rb
35
+ test/plugins/import_test.rb
36
+ test/plugins/irb_test.rb
37
+ test/plugins/ls_test.rb
38
+ test/plugins/retarget_test.rb
39
+ test/plugins/test_helper.rb
40
+ test/shortcuts_test.rb
41
+ testdata/project.pbxproj
42
+ testdata/project.pbxproj.compat
43
+ testdata/TestApp/TestApp.xcodeproj/project.pbxproj
44
+ testdata/ZergSupport.xcodeproj/project.pbxproj
data/README.textile ADDED
@@ -0,0 +1,124 @@
1
+ h1. ZergXcode
2
+
3
+ ZergXcode is a tool and library for performing automated modifications to Xcode
4
+ project files. It can be used to work around Xcode's limitations, such as
5
+ * the cumbersome UI for assigning files to the main target vs the test target
6
+ * the impossibility to easily drop other people's code in your project
7
+ * a problem that annoys you enough to learn the API and write a ruby script
8
+
9
+ h2. User Instructions
10
+
11
+ If you're on OSX Leopard, you already have most of the infrastructure in place.
12
+ Install the gem by typing the following in Terminal.
13
+
14
+ <pre>sudo gem install zerg_xcode</pre>
15
+
16
+ Stay in Terminal and take a look at the available commands.
17
+ <pre>zerg-xcode help</pre>
18
+
19
+ Then start playing with your code project.
20
+ <pre>
21
+ zerg-xcode help
22
+ zerg-xcode help ls
23
+ zerg-xcode ls ProjectName
24
+ </pre>
25
+
26
+ h2. Developer Notes
27
+
28
+ The rest of the file is useful if you're considering tweaking the code. Here are
29
+ a few reasons to use this library:
30
+ * ruby and rubygems come pre-installed on every Mac
31
+ * full test coverage, so it's high quality and easy to refactor
32
+ * MIT license, so your boss will not hate you
33
+ * all commands are plug-ins, so all the infrastructure is in place
34
+
35
+ h3. Getting started.
36
+
37
+ It's recommended to start by playing with the API in
38
+ <code>lib/zerg_xcode/shortcuts.rb</code>
39
+
40
+ <pre>
41
+ moonstone:ZergSupport victor$ zerg-xcode irb ZergSupport
42
+ >> $p
43
+ *snip*
44
+ >> $p.attrs # shows the names of all the attributes of the Xcode object
45
+ => ["isa", "buildConfigurationList", "hasScannedForEncodings", "targets", "projectDirPath", "compatibilityVersion", "projectRoot", "mainGroup"]
46
+ >> $p['targets'].first.attrs # navigates a list
47
+ => ["name", "isa", "productType", "buildConfigurationList", "productReference", "productName", "buildRules", "dependencies", "buildPhases"]
48
+ >> $p['targets'].first['name'] # inspects attributes
49
+ => "ZergSupport"
50
+ >> $p['targets'].map { |target| target['name'] } # more attribute inspection
51
+ => ["ZergSupport", "ZergTestSupport", "ZergSupportTests"]
52
+ >> $p.all_files.length # call method in PBXProject
53
+ => 116
54
+ </pre>
55
+
56
+ You can up load your favorite Xcode project in irb, and understand the object
57
+ graph. Once you feel you have a decent grasp, you can start experimenting with
58
+ changing the graph, as shown below.
59
+
60
+ <pre>
61
+ moonstone:ZergSupport victor$ zerg-xcode irb ZergSupport
62
+ >> $p['targets'][2]['name']
63
+ => "ZergSupportTests"
64
+ >> $p['targets'][2]['name'] = 'ZergSupportTestz'
65
+ => "ZergSupportTestz"
66
+ >> $p.save! # the project remembers where it was loaded from
67
+ => 54809
68
+ >> quit # now load the project in Xcode and see the change
69
+ </pre>
70
+
71
+ h3. Plug-ins Extend the Command Set
72
+
73
+ The set of commands accepted by the <code>zerg-xcode</code> tool can be extended
74
+ by adding a plug-in, which is nothing but a ruby file that exists in
75
+ <code>lib/zerg_xcode/plugins</code>. An easy example to get you started can be
76
+ found at <code>lib/zerg_xcode/plugins/ls.rb</code>. The code in there should
77
+ help you if you want to write your own command-line tool as well.
78
+
79
+ Plug-ins must implement the methods <code>run(args)</code> (called when the
80
+ command is executed) and <code>help</code> (called when the user needs help on
81
+ your plug-in), which must return a Hash with the keys <code>:short</code> and
82
+ <code>:long</code>.
83
+
84
+ If you write a plug-in that seems even remotely useful, please don't be shy and
85
+ send a Pull Request on Github.
86
+
87
+ h3. Encoding and Decoding Files
88
+
89
+ The code is in <code>lib/zerg_xcode/file_format</code>. You can safely ignore it
90
+ unless you're handling a new file format, or you want to make the decoding
91
+ better.
92
+
93
+ A .pbxproj file is decoded by the chain Lexer -> Parser -> Archiver.unarchive
94
+ into an object graph. An object graph is encoded back into a .pbxproj by the
95
+ chain Archiver.archive -> Encoder.
96
+
97
+ The decoding process discards comments and the order of objects in the graph, so
98
+ an encoded .pbxproj will not be the same as the original. However, Xcode will
99
+ happily open an encoded file, and that's all that matters.
100
+
101
+ h3. Xcode Object Graph
102
+
103
+ Objects in the Xcode object graph are represented by XcodeObject instances. The
104
+ code for XcodeObject is in <code>lib/zerg_xcode/objects/xcode_object.rb</code>.
105
+ Xcode objects use the 'isa' attribute to indicate their class.
106
+ <code>XcodeObject.new</code> implements isa-based polymorphism as follows: if
107
+ <code>ZergXcode::Objects</code> has a class with the same name as the 'isa'
108
+ property, that class is instantiated instead of <code>XcodeObject.new</code>.
109
+
110
+ So, you can add magic methods to Xcode objects by implementing them in
111
+ XcodeObject subclasses contained in the <code>ZergXcode::Objects</code> module.
112
+ By convention, these classes are implemented in
113
+ <code>lib/zerg_xcode/objects</code>.
114
+
115
+ XcodeObject stores the attributes of the original object, and also keeps track
116
+ of subtle meta-data from the original objects, such as the object version and
117
+ archive identifier. This is done so modified Xcode files resemble the originals
118
+ as much as possible.
119
+
120
+ h3. Testing
121
+
122
+ The <code>test</code> directory contains a parallel tree to
123
+ <code>lib/zerg_xcode</code>. Coverage must remain excellent, because this is a
124
+ tool that deals with people's work.
data/RUBYFORGE ADDED
@@ -0,0 +1,40 @@
1
+ Quickstart for Rubyforge:
2
+
3
+ 1) Checkout the zergxcode code
4
+ git clone git@github.com:costan/zerg_xcode.git
5
+
6
+ 2) Install the rubyforge gem
7
+ gem install rubyforge
8
+
9
+ 3) Save your rubyforge.org login information
10
+ rubyforge setup
11
+
12
+ 4) Get a login cookie
13
+ rubyforge login
14
+
15
+ 5) Get project configuration from rubyforge
16
+ rubyforge config zerglings
17
+
18
+ 6) Create a package to release under
19
+ rubyforge create_package zerglings zerg_xcode
20
+
21
+ 7) Install the echoe gem (required for building this gem)
22
+ gem install echoe
23
+
24
+ 8) Release the gem (finally!)
25
+ rake release
26
+
27
+ Releasing a new gemspec to Github
28
+
29
+ 1) Build the gem
30
+ rake package
31
+
32
+ 2) Copy the spec
33
+ cp pkg/zerg_xcode-0.1/zerg_xcode.gemspec .
34
+
35
+ 3) Commit the spec
36
+ git add zerg_xcode.gemspec
37
+ git commit -m "New gemspec, for Github distribution."
38
+
39
+ 4) Push to Github
40
+ git push
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'echoe'
3
+
4
+ Echoe.new('zerg_xcode') do |p|
5
+ p.project = 'zerglings' # rubyforge project
6
+
7
+ p.author = 'Victor Costan'
8
+ p.email = 'victor@zergling.net'
9
+ p.summary = 'Automated modifications for Xcode project files'
10
+ p.url = 'http://www.zergling.net/'
11
+ p.runtime_dependencies = []
12
+ p.dependencies = []
13
+ # remove echoe, because it becomes a runtime dependency for rubygems < 1.2
14
+ p.development_dependencies = []
15
+ p.eval = proc do |p|
16
+ p.default_executable = 'bin/zerg-xcode'
17
+ end
18
+
19
+ p.need_tar_gz = true
20
+ p.need_zip = true
21
+ p.rdoc_pattern = /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^LICENSE$/
22
+ end
23
+
24
+ if $0 == __FILE__
25
+ Rake.application = Rake::Application.new
26
+ Rake.application.run
27
+ end
data/bin/zerg-xcode ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'zerg_xcode'
4
+
5
+ plugin = ARGV.shift
6
+ plugin = 'help' unless ZergXcode::Plugins.all.include? plugin
7
+ ZergXcode::Plugins.run(plugin, ARGV)
@@ -0,0 +1,87 @@
1
+ module ZergXcode::Archiver
2
+ # Unarchives an Xcode object graph from the contents of a file.
3
+ def self.unarchive(string)
4
+ proj_hash = ZergXcode::Parser.parse string
5
+ unarchive_hash proj_hash
6
+ end
7
+
8
+ # Archives an Xcode object graph to a string to be written to a .pbxproj file.
9
+ def self.archive(project)
10
+ proj_hash = archive_to_hash project
11
+ ZergXcode::Encoder.encode proj_hash
12
+ end
13
+
14
+ # Unarchives an Xcode object graph serialized to a hash.
15
+ def self.unarchive_hash(hash)
16
+ raise 'Uknown archive version' unless hash['archiveVersion'] == '1'
17
+ raise 'Classes not implemented' unless hash['classes'] = {}
18
+ version = hash['objectVersion'].to_i
19
+
20
+ objects = {}
21
+ hash['objects'].each do |archive_id, object_hash|
22
+ object = ZergXcode::XcodeObject.from object_hash
23
+ object.version, object.archive_id = version, archive_id
24
+ objects[archive_id] = object
25
+ end
26
+ objects.each do |object_id, object|
27
+ object.visit do |object, parent, key, value|
28
+ parent[key] = objects[value] if objects.has_key? value
29
+ next true
30
+ end
31
+ end
32
+ return objects[hash['rootObject']]
33
+ end
34
+
35
+ def self.archive_to_hash(root_object)
36
+ archived = ZergXcode::XcodeObject.from root_object
37
+ id_generator = IdGenerator.new
38
+ visited = Set.new([archived])
39
+
40
+ archived.visit do |object, parent, key, value|
41
+ next true unless value.kind_of? ZergXcode::XcodeObject
42
+
43
+ if visited.include? value
44
+ parent[key] = value.archive_id
45
+ next false
46
+ else
47
+ visited << value
48
+ value.archive_id = id_generator.id_for(value)
49
+ parent[key] = value.archive_id
50
+ next true
51
+ end
52
+ end
53
+ root_object_id = archived.archive_id
54
+
55
+ objects = {}
56
+ visited.each { |object| objects[object.archive_id] = object._attr_hash }
57
+ return { 'archiveVersion' => '1',
58
+ 'objectVersion' => root_object.version.to_s,
59
+ 'classes' => Hash.new, 'rootObject' => root_object_id,
60
+ 'objects' => objects }
61
+ end
62
+
63
+ # Generates archive IDs for objects.
64
+ class IdGenerator
65
+ def initialize
66
+ @assigned_ids = Set.new
67
+ end
68
+
69
+ def new_id
70
+ loop do
71
+ id = (0...24).map { '%02X' % rand(256) }.join
72
+ next if @assigned_ids.include? id
73
+ @assigned_ids << id
74
+ return id
75
+ end
76
+ end
77
+
78
+ def id_for(object)
79
+ if object.archive_id && !@assigned_ids.include?(object.archive_id)
80
+ @assigned_ids << object.archive_id
81
+ return object.archive_id
82
+ else
83
+ return new_id
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,40 @@
1
+ module ZergXcode::Encoder
2
+ def self.encode(project)
3
+ "// !$*UTF8*$!\n" + encode_hash(project, 0) + "\n"
4
+ end
5
+
6
+ def self.encode_hash(hash, indentation)
7
+ "{\n" + hash.map { |key, value|
8
+ encode_indentation(indentation + 1) +
9
+ encode_value(key, indentation + 1) + " = " +
10
+ encode_object(value, indentation + 1) + ";\n"
11
+ }.join + encode_indentation(indentation) + "}"
12
+ end
13
+
14
+ def self.encode_object(object, indentation)
15
+ case object
16
+ when Hash
17
+ encode_hash object, indentation
18
+ when Array
19
+ encode_array object, indentation
20
+ when String
21
+ encode_value object, indentation
22
+ end
23
+ end
24
+
25
+ def self.encode_array(array, indentation)
26
+ "(\n" + array.map { |value|
27
+ encode_indentation(indentation + 1) +
28
+ encode_object(value, indentation + 1) + ",\n"
29
+ }.join + encode_indentation(indentation) + ")"
30
+ end
31
+
32
+ def self.encode_value(value, indentation)
33
+ # This escapes what needs to be escaped.
34
+ value.to_s.inspect
35
+ end
36
+
37
+ def self.encode_indentation(indentation)
38
+ "\t" * indentation
39
+ end
40
+ end
@@ -0,0 +1,60 @@
1
+ module ZergXcode::Lexer
2
+ def self.tokenize(string)
3
+
4
+ encoding_match = string.match /^\/\/ \!\$\*(.*?)\*\$\!/
5
+ raise "No encoding - #{string[0, 20]}" unless encoding_match
6
+
7
+ i = encoding_match[0].length
8
+ tokens = [[:encoding, encoding_match[1]]]
9
+ while i < string.length
10
+ # skip comments
11
+ if string[i, 2] == '/*'
12
+ i += 2
13
+ i += 1 while string[i, 2] != '*/'
14
+ i += 2
15
+ next
16
+ end
17
+
18
+ case string[i, 1]
19
+ when /\s/
20
+ i += 1
21
+ when '(', ')', '{', '}', '=', ';', ','
22
+ tokens << {'(' => :begin_array, ')' => :end_array,
23
+ '{' => :begin_hash, '}' => :end_hash,
24
+ '=' => :assign, ';' => :stop, ',' => :comma}[string[i, 1]]
25
+ i += 1
26
+ when '"'
27
+ # string
28
+ i += 1
29
+ token = ''
30
+ while string[i, 1] != '"'
31
+ if string[i, 1] == '\\'
32
+ i += 1
33
+ case string[i, 1]
34
+ when 'n', 'r', 't'
35
+ token << { 'n' => "\n", 't' => "\t", 'r' => "\r" }[string[i, 1]]
36
+ i += 1
37
+ when '"', "'", '\\'
38
+ token << string[i]
39
+ i += 1
40
+ else
41
+ raise "Uknown escape sequence \\#{string[i, 20]}"
42
+ end
43
+ else
44
+ token << string[i]
45
+ i += 1
46
+ end
47
+ end
48
+ tokens << [:string, token]
49
+ i += 1
50
+ else
51
+ # something
52
+ len = 0
53
+ len += 1 while /[^\s\t\r\n\f(){}=;,]/ =~ string[i + len, 1]
54
+ tokens << [:symbol, string[i, len]]
55
+ i += len
56
+ end
57
+ end
58
+ return tokens
59
+ end
60
+ end
@@ -0,0 +1,46 @@
1
+ module ZergXcode::Parser
2
+ def self.parse(project_string)
3
+ tokens = ZergXcode::Lexer.tokenize project_string
4
+
5
+ context = [[]]
6
+ last_token = nil
7
+ tokens.each do |token|
8
+ case token
9
+ when :begin_array
10
+ context << Array.new
11
+ when :begin_hash
12
+ context << Hash.new
13
+ when :end_array, :end_hash
14
+ last_object = context.pop
15
+ if context.last.kind_of? Array
16
+ context.last << last_object
17
+ elsif context.last.kind_of? String
18
+ hash_key = context.pop
19
+ context.last[hash_key] = last_object
20
+ end
21
+ when :assign, :stop, :comma
22
+
23
+ when Array
24
+ case token.first
25
+ when :encoding
26
+ when :string, :symbol
27
+ token_string = token.last
28
+ if context.last.kind_of? Hash
29
+ context << token_string
30
+ elsif context.last.kind_of? Array
31
+ context.last << token_string
32
+ elsif context.last.kind_of? String
33
+ key = context.pop
34
+ context.last[key] = token_string
35
+ else
36
+ p context
37
+ raise 'WTFed'
38
+ end
39
+ end
40
+ else
41
+ raise "Unknown token #{token}"
42
+ end
43
+ end
44
+ return context[0][0]
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ module ZergXcode::Paths
2
+ # The most likely project file name for the given path.
3
+ def self.project_file_at(base_path)
4
+ return base_path if File.exist?(base_path) and File.file?(base_path)
5
+ pbxfile = 'project.pbxproj'
6
+
7
+ # naively assume the user gave the right name
8
+ path = base_path
9
+ path = path[0...-1] if path[-1, 1] == '/' || path[-1, 1] == '\\'
10
+ path = path + '.xcodeproj' unless /\.xcodeproj$/ =~ path
11
+ if File.exist? path
12
+ file = File.join(path, pbxfile)
13
+ return file
14
+ end
15
+
16
+ # didn't work, perhaps user gave us a path into their root
17
+ entries = Dir.entries(base_path).sort_by do |entry|
18
+ File.file?(File.join(base_path, entry)) ? 0 : 1
19
+ end
20
+
21
+ entries.each do |entry|
22
+ next if entry == '..'
23
+ path = File.join(base_path, entry)
24
+ case entry
25
+ when /\.pbxproj$/
26
+ return path
27
+ when /\.xcodeproj$/
28
+ return File.join(path, pbxfile)
29
+ else
30
+ if File.directory?(path) && File.exist?(File.join(path, pbxfile))
31
+ return File.join(path, pbxfile)
32
+ end
33
+ end
34
+ end
35
+
36
+ raise "Could not find Xcode project at #{base_path}"
37
+ end
38
+
39
+ # The most likely project root dir for the given path.
40
+ def self.project_root_at(base_path)
41
+ file = project_file_at base_path
42
+ File.dirname File.dirname(file)
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ # A file used for building. Points to a PBXFileRef.
2
+ class ZergXcode::Objects::PBXBuildFile < ZergXcode::XcodeObject
3
+ # The name of the referenced file.
4
+ def filename
5
+ self['fileRef']['path']
6
+ end
7
+
8
+ # The type of the referenced file.
9
+ def file_type
10
+ self['fileRef']['explicitFileType'] || self['fileRef']['lastKnownFileType']
11
+ end
12
+
13
+ # Guesses the type of the build phase that this file should belong to.
14
+ # This can be useful when figuring out which build phase to add a file to.
15
+ def guessed_build_phase_type
16
+ case file_type
17
+ when /\.h$/
18
+ return 'PBXHeadersBuildPhase'
19
+ when /^sourcecode/
20
+ return 'PBXSourcesBuildPhase'
21
+ when /\.framework$/, /\.ar$/
22
+ return 'PBXFrameworksBuildPhase'
23
+ else
24
+ return 'PBXResourcesBuildPhase'
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # An Xcode target.
2
+ class ZergXcode::Objects::PBXNativeTarget < ZergXcode::XcodeObject
3
+ # All the files referenced by this target.
4
+ # Returns:
5
+ # an array containing a hash for each file with
6
+ # :phase - the build phase referencing the file
7
+ # :build_object - the build object referencing the file
8
+ # :object - the file object
9
+ def all_files
10
+ files = []
11
+ self['buildPhases'].each do |phase|
12
+ phase['files'].each do |build_file|
13
+ files << { :phase => phase, :build_object => build_file,
14
+ :object => build_file['fileRef'] }
15
+ end
16
+ end
17
+ files
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ # The root object in the Xcode object graph.
2
+ class ZergXcode::Objects::PBXProject < ZergXcode::XcodeObject
3
+ # Used to implement save!
4
+ attr_accessor :source_filename
5
+
6
+ # Saves a project that was loaded by ZergXcode.load
7
+ def save!
8
+ raise 'Project not loaded by ZergXcode.load' unless @source_filename
9
+ ZergXcode.dump self, @source_filename
10
+ end
11
+
12
+ # All the files referenced by the project.
13
+ def all_files
14
+ files = []
15
+ project_root = self['projectRoot'].empty? ? '.' : self['projectRoot']
16
+ FileVisitor.visit self['mainGroup'], project_root, files
17
+ files
18
+ end
19
+
20
+ # Container for the visitor that lists all files in a project.
21
+ module FileVisitor
22
+ def self.visit(object, root_path, files)
23
+ case object.isa
24
+ when :PBXGroup
25
+ visit_group(object, root_path, files)
26
+ when :PBXFileReference
27
+ visit_file(object, root_path, files)
28
+ end
29
+ end
30
+
31
+ def self.visit_group(group, root_path, files)
32
+ path = merge_path(root_path, group['sourceTree'], group)
33
+
34
+ group['children'].each do |child|
35
+ visit child, path, files
36
+ end
37
+ end
38
+
39
+ def self.visit_file(file, root_path, files)
40
+ path = merge_path(root_path, file['sourceTree'], file)
41
+
42
+ files << { :path => path, :object => file }
43
+ end
44
+
45
+ def self.merge_path(old_path, source_tree, object)
46
+ case source_tree
47
+ when '<group>'
48
+ base_path = old_path
49
+ else
50
+ base_path = source_tree
51
+ end
52
+ if object['path']
53
+ path = File.join(base_path, object['path'])
54
+ else
55
+ path = old_path
56
+ end
57
+ return path
58
+ end
59
+ end
60
+ end