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