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.
- data/CHANGELOG +1 -0
- data/LICENSE +21 -0
- data/Manifest +44 -0
- data/README.textile +124 -0
- data/RUBYFORGE +40 -0
- data/Rakefile +27 -0
- data/bin/zerg-xcode +7 -0
- data/lib/zerg_xcode/file_format/archiver.rb +87 -0
- data/lib/zerg_xcode/file_format/encoder.rb +40 -0
- data/lib/zerg_xcode/file_format/lexer.rb +60 -0
- data/lib/zerg_xcode/file_format/parser.rb +46 -0
- data/lib/zerg_xcode/file_format/paths.rb +44 -0
- data/lib/zerg_xcode/objects/pbx_build_file.rb +27 -0
- data/lib/zerg_xcode/objects/pbx_native_target.rb +19 -0
- data/lib/zerg_xcode/objects/pbx_project.rb +60 -0
- data/lib/zerg_xcode/objects/xcode_object.rb +144 -0
- data/lib/zerg_xcode/plugins/core/core.rb +36 -0
- data/lib/zerg_xcode/plugins/help.rb +30 -0
- data/lib/zerg_xcode/plugins/import.rb +19 -0
- data/lib/zerg_xcode/plugins/irb.rb +22 -0
- data/lib/zerg_xcode/plugins/ls.rb +27 -0
- data/lib/zerg_xcode/plugins/retarget.rb +90 -0
- data/lib/zerg_xcode/shortcuts.rb +22 -0
- data/lib/zerg_xcode.rb +17 -0
- data/test/file_format/archiver_test.rb +70 -0
- data/test/file_format/encoder_test.rb +10 -0
- data/test/file_format/lexer_test.rb +56 -0
- data/test/file_format/parser_test.rb +44 -0
- data/test/file_format/path_test.rb +40 -0
- data/test/objects/pbx_build_file_test.rb +38 -0
- data/test/objects/pbx_native_target_test.rb +31 -0
- data/test/objects/pbx_project_test.rb +62 -0
- data/test/objects/xcode_object_test.rb +117 -0
- data/test/plugins/core/core_test.rb +32 -0
- data/test/plugins/import_test.rb +22 -0
- data/test/plugins/irb_test.rb +30 -0
- data/test/plugins/ls_test.rb +43 -0
- data/test/plugins/retarget_test.rb +65 -0
- data/test/plugins/test_helper.rb +21 -0
- data/test/shortcuts_test.rb +18 -0
- data/testdata/TestApp/TestApp.xcodeproj/project.pbxproj +328 -0
- data/testdata/ZergSupport.xcodeproj/project.pbxproj +930 -0
- data/testdata/project.pbxproj +250 -0
- data/testdata/project.pbxproj.compat +320 -0
- data/zerg_xcode.gemspec +34 -0
- 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,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
|