zerg_xcode 0.1

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