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
@@ -0,0 +1,144 @@
|
|
1
|
+
# Xcode objects
|
2
|
+
class ZergXcode::XcodeObject
|
3
|
+
attr_accessor :version
|
4
|
+
attr_accessor :archive_id
|
5
|
+
|
6
|
+
# Hash-like behavior
|
7
|
+
|
8
|
+
def [](key)
|
9
|
+
@attrs[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(key, value)
|
13
|
+
@attrs[key] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
# Inheritance based on isa
|
17
|
+
|
18
|
+
def initialize(hash)
|
19
|
+
@attrs = hash
|
20
|
+
if self.class != ZergXcode::XcodeObject
|
21
|
+
class_name = self.class.name
|
22
|
+
@attrs['isa'] ||= class_name[(class_name.rindex(':') + 1)..-1]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.new(*args)
|
27
|
+
return super unless self == ZergXcode::XcodeObject
|
28
|
+
if hash_isa = args.first['isa']
|
29
|
+
classes = ZergXcode::Objects.constants
|
30
|
+
if classes.include? hash_isa
|
31
|
+
return ZergXcode::Objects.const_get(hash_isa).new(*args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
# Inspecting the object
|
38
|
+
|
39
|
+
# The names of the object's attributes.
|
40
|
+
def attrs
|
41
|
+
@attrs.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
# The (internal) hash holding the file's attributes.
|
45
|
+
def _attr_hash
|
46
|
+
@attrs
|
47
|
+
end
|
48
|
+
|
49
|
+
# Object type helpers
|
50
|
+
def isa
|
51
|
+
return @attrs['isa'].to_sym
|
52
|
+
end
|
53
|
+
|
54
|
+
# Visitor pattern
|
55
|
+
|
56
|
+
# Visits an object's internal structure.
|
57
|
+
#
|
58
|
+
# The given block is called like this:
|
59
|
+
# yield object, parent, key, value
|
60
|
+
# Where
|
61
|
+
# object: the object currently visited (can be a sub-object)
|
62
|
+
# parent: the collection currently visited (an object, hash, or array)
|
63
|
+
# key:
|
64
|
+
# value:
|
65
|
+
# The block can return
|
66
|
+
# false: no recursive visiting for the given value
|
67
|
+
# true: normal recursive visiting for the given value
|
68
|
+
# something else: replace the given value with the return, the recursive
|
69
|
+
# visiting is done on the new value
|
70
|
+
def visit(&accept)
|
71
|
+
visit_hash(@attrs, &accept)
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_hash(hash, &accept)
|
76
|
+
hash.each_key do |key|
|
77
|
+
visit_value(hash, key, hash[key], &accept)
|
78
|
+
end
|
79
|
+
hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def visit_array(array, &accept)
|
83
|
+
array.each_with_index do |value, index|
|
84
|
+
visit_value(array, index, value, &accept)
|
85
|
+
end
|
86
|
+
array
|
87
|
+
end
|
88
|
+
|
89
|
+
def visit_value(parent, key, value, &accept)
|
90
|
+
visit_parent = (parent == @attrs) ? self : parent
|
91
|
+
recurse = yield self, visit_parent, key, value
|
92
|
+
return if recurse == false
|
93
|
+
|
94
|
+
if recurse != true
|
95
|
+
value = recurse
|
96
|
+
parent[key] = recurse
|
97
|
+
end
|
98
|
+
|
99
|
+
case value
|
100
|
+
when ZergXcode::XcodeObject
|
101
|
+
value.visit(&accept)
|
102
|
+
when Hash
|
103
|
+
visit_hash(value, &accept)
|
104
|
+
when Array
|
105
|
+
visit_array(value, &accept)
|
106
|
+
end
|
107
|
+
value
|
108
|
+
end
|
109
|
+
|
110
|
+
# Deep copy
|
111
|
+
def self.from(object_or_hash)
|
112
|
+
new_object = case object_or_hash
|
113
|
+
when ZergXcode::XcodeObject
|
114
|
+
object_or_hash.shallow_copy
|
115
|
+
else
|
116
|
+
self.new object_or_hash.dup
|
117
|
+
end
|
118
|
+
object_map = { object_or_hash => new_object }
|
119
|
+
|
120
|
+
new_object.visit do |object, parent, key, value|
|
121
|
+
case value
|
122
|
+
when Hash, Array
|
123
|
+
next value.dup
|
124
|
+
when ZergXcode::XcodeObject
|
125
|
+
if object_map[value]
|
126
|
+
parent[key] = object_map[value]
|
127
|
+
next false
|
128
|
+
else
|
129
|
+
object_map[value] = value.shallow_copy
|
130
|
+
next object_map[value]
|
131
|
+
end
|
132
|
+
else
|
133
|
+
next true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def shallow_copy
|
139
|
+
new_object = self.class.new @attrs.dup
|
140
|
+
new_object.version, new_object.archive_id = version, archive_id
|
141
|
+
return new_object
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ZergXcode::Plugins
|
4
|
+
def self.all
|
5
|
+
plugin_dir = File.join(File.dirname(__FILE__), '..')
|
6
|
+
plugins = Dir.entries(plugin_dir).select { |entry|
|
7
|
+
/^[^_].*\.rb$/ =~ entry
|
8
|
+
}.map { |entry| entry[0..-4] }
|
9
|
+
return Set.new(plugins)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.require_all
|
13
|
+
all.each { |plugin| self.require plugin }
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.require(plugin_name)
|
17
|
+
Kernel.require "zerg_xcode/plugins/#{plugin_name}.rb"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get(plugin_name)
|
21
|
+
self.require plugin_name
|
22
|
+
ZergXcode::Plugins.const_get(plugin_name.capitalize).new
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run(plugin_name, args)
|
26
|
+
self.get(plugin_name).run args
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.help(plugin_name)
|
30
|
+
self.get(plugin_name).help
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# :nodoc:
|
35
|
+
module ZergXcode
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class ZergXcode::Plugins::Help
|
2
|
+
def help
|
3
|
+
{:short => 'command-line usage instructions',
|
4
|
+
:long => <<"END" }
|
5
|
+
Usage: help [command]
|
6
|
+
|
7
|
+
Shows the usage for the given command. If no command is given, shows a list of
|
8
|
+
commands with a short description for each command.
|
9
|
+
END
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(args)
|
13
|
+
helpstr = "Xcode Project Modifier brought to you by Zergling.Net.\n"
|
14
|
+
|
15
|
+
plugin = args.shift
|
16
|
+
if ZergXcode::Plugins.all.include? plugin
|
17
|
+
help = ZergXcode::Plugins.get(plugin).help
|
18
|
+
helpstr << "#{plugin} - #{help[:short]}\n#{help[:long]}"
|
19
|
+
else
|
20
|
+
helpstr << "Available commands:\n"
|
21
|
+
ZergXcode::Plugins.all.each do |plugin|
|
22
|
+
short_help = ZergXcode::Plugins.get(plugin).help[:short]
|
23
|
+
helpstr << " #{plugin}: #{short_help}\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
print helpstr
|
28
|
+
helpstr
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class ZergXcode::Plugins::Import
|
2
|
+
def help
|
3
|
+
{:short => 'imports the objects from a project into another project',
|
4
|
+
:long => <<"END" }
|
5
|
+
Usage: import source_path [target_path]
|
6
|
+
|
7
|
+
Imports the objects (build targets, files) from a source project into another
|
8
|
+
target project. Useful when the source project wraps code that can be used as a
|
9
|
+
library.
|
10
|
+
END
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(args)
|
14
|
+
source = ZergXcode.load args.shift
|
15
|
+
target = ZergXcode.load(args.shift || '.')
|
16
|
+
|
17
|
+
print "Import is not yet implemented."
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'irb'
|
2
|
+
|
3
|
+
class ZergXcode::Plugins::Irb
|
4
|
+
def help
|
5
|
+
{:short => 'opens up a project in an interactive ruby shell',
|
6
|
+
:long => <<"END" }
|
7
|
+
Usage: irb [path]
|
8
|
+
|
9
|
+
Loads the object graph in the Xcode project at the given path. If no path is
|
10
|
+
given, looks for a project in the current directory. The project object is
|
11
|
+
available as the global variable $p.
|
12
|
+
END
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(args)
|
16
|
+
$p = ZergXcode.load(args.shift || '.')
|
17
|
+
|
18
|
+
print "This is an IRB shell. Your project is available as $p.\n" +
|
19
|
+
"Use the 'quit' command if you're here by mistake.\n"
|
20
|
+
IRB.start __FILE__
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class ZergXcode::Plugins::Ls
|
2
|
+
def help
|
3
|
+
{:short => 'shows the files in a project',
|
4
|
+
:long => <<"END" }
|
5
|
+
Usage: ls [path]
|
6
|
+
|
7
|
+
Lists all the files in the project at the given path. If no path is given, looks
|
8
|
+
for a project in the current directory.
|
9
|
+
END
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(args)
|
13
|
+
list = list_for(args.shift || '.')
|
14
|
+
output = ""
|
15
|
+
list.each do |entry|
|
16
|
+
output << "%-20s %s\n" % [entry.last, entry.first]
|
17
|
+
end
|
18
|
+
print output
|
19
|
+
output
|
20
|
+
end
|
21
|
+
|
22
|
+
def list_for(project_name)
|
23
|
+
ZergXcode.load(project_name).all_files.map do |file|
|
24
|
+
[file[:path], file[:object]['lastKnownFileType']]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class ZergXcode::Plugins::Retarget
|
2
|
+
include ZergXcode::Objects
|
3
|
+
XcodeObject = ZergXcode::XcodeObject
|
4
|
+
|
5
|
+
def help
|
6
|
+
{:short => 'reassign files to a target or set of targets',
|
7
|
+
:long => <<"END" }
|
8
|
+
Usage: retarget project_path pattern [target target..]
|
9
|
+
|
10
|
+
Reassigns all the files matching a pattern to a target or set of targets. The
|
11
|
+
files matching the pattern will be removed from all other targets. If no target
|
12
|
+
is specified, the files are removed from all targets.
|
13
|
+
END
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(args)
|
17
|
+
path = args.shift
|
18
|
+
pattern = args.shift
|
19
|
+
regexp = Regexp.compile pattern
|
20
|
+
project = ZergXcode.load path
|
21
|
+
retarget! project, regexp, args
|
22
|
+
project.save!
|
23
|
+
end
|
24
|
+
|
25
|
+
def retarget!(project, regexp, targets)
|
26
|
+
# maps each file type to the type of the phase it's in
|
27
|
+
file_type_phases = {}
|
28
|
+
# maps each file to the phase it's in
|
29
|
+
file_phases = {}
|
30
|
+
# maps each PBXFileRef to the PBXBuildFile pointing to it
|
31
|
+
build_files = {}
|
32
|
+
|
33
|
+
# populate the maps
|
34
|
+
project['targets'].each do |target|
|
35
|
+
target.all_files.each do |file|
|
36
|
+
build_file = file[:build_object]
|
37
|
+
phase_type = file[:phase]['isa']
|
38
|
+
build_files[file[:object]] = build_file
|
39
|
+
file_type_phases[build_file.file_type] = phase_type
|
40
|
+
file_phases[file[:object]] = phase_type
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# compute target sets
|
45
|
+
in_targets = project['targets'].select do |target|
|
46
|
+
targets.include? target['name']
|
47
|
+
end
|
48
|
+
out_targets = project['targets'] - in_targets
|
49
|
+
|
50
|
+
# clean up targets outside the args
|
51
|
+
out_targets.each do |target|
|
52
|
+
target['buildPhases'].each do |phase|
|
53
|
+
phase['files'].reject! { |build_file| regexp =~ build_file.filename }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# build a list of the files in the project matching the pattern
|
58
|
+
new_files = project.all_files.map { |file| file[:object] }.select do |file|
|
59
|
+
regexp =~ file['path']
|
60
|
+
end
|
61
|
+
# build PBXBuildFile wrappers around files that don't have them
|
62
|
+
new_files.each do |file|
|
63
|
+
next if build_files[file]
|
64
|
+
|
65
|
+
build_file = PBXBuildFile.new 'fileRef' => file
|
66
|
+
build_files[file] = build_file
|
67
|
+
file_phases[file] = file_type_phases[build_file.file_type] ||
|
68
|
+
build_file.guessed_build_phase_type
|
69
|
+
end
|
70
|
+
|
71
|
+
# add files to targets matching the args
|
72
|
+
in_targets.each do |target|
|
73
|
+
already_in = Set.new(target.all_files.map { |file| file[:object] })
|
74
|
+
new_files.each do |file|
|
75
|
+
file_ref = file[:object]
|
76
|
+
next if already_in.include? file
|
77
|
+
phase_type = file_phases[file]
|
78
|
+
phase = target['buildPhases'].find { |p| p['isa'] == phase_type }
|
79
|
+
unless phase
|
80
|
+
phase = XcodeObject.new 'isa' => phase_type, 'dependencies' => [],
|
81
|
+
'files' => [],
|
82
|
+
'buildActionMask' => '2147483647',
|
83
|
+
'runOnlyForDeploymentPostprocessing' => '0'
|
84
|
+
target['buildPhases'] << phase
|
85
|
+
end
|
86
|
+
phase['files'] << build_files[file]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ZergXcode
|
2
|
+
# Reads an Xcode project from the filesystem.
|
3
|
+
def self.load(path)
|
4
|
+
file = ZergXcode::Paths.project_file_at path
|
5
|
+
pbx_contents = File.read file
|
6
|
+
project = Archiver.unarchive pbx_contents
|
7
|
+
project.source_filename = file
|
8
|
+
return project
|
9
|
+
end
|
10
|
+
|
11
|
+
# Dumps an Xcode project to the filesystem.
|
12
|
+
def self.dump(project, path)
|
13
|
+
file = ZergXcode::Paths.project_file_at path
|
14
|
+
pbx_contents = Archiver.archive project
|
15
|
+
File.open(file, 'w') { |f| f.write pbx_contents }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Instantiate a plug-in.
|
19
|
+
def self.plugin(plugin_name)
|
20
|
+
ZergXcode::Plugins.get(plugin_name)
|
21
|
+
end
|
22
|
+
end
|
data/lib/zerg_xcode.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module ZergXcode; end
|
2
|
+
module ZergXcode::Objects; end
|
3
|
+
|
4
|
+
|
5
|
+
require 'zerg_xcode/file_format/archiver.rb'
|
6
|
+
require 'zerg_xcode/file_format/encoder.rb'
|
7
|
+
require 'zerg_xcode/file_format/lexer.rb'
|
8
|
+
require 'zerg_xcode/file_format/parser.rb'
|
9
|
+
require 'zerg_xcode/file_format/paths.rb'
|
10
|
+
|
11
|
+
require 'zerg_xcode/objects/xcode_object.rb'
|
12
|
+
require 'zerg_xcode/objects/pbx_build_file.rb'
|
13
|
+
require 'zerg_xcode/objects/pbx_native_target.rb'
|
14
|
+
require 'zerg_xcode/objects/pbx_project.rb'
|
15
|
+
require 'zerg_xcode/plugins/core/core.rb'
|
16
|
+
|
17
|
+
require 'zerg_xcode/shortcuts.rb'
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'zerg_xcode'
|
3
|
+
|
4
|
+
class ArchiverTest < Test::Unit::TestCase
|
5
|
+
Parser = ZergXcode::Parser
|
6
|
+
Archiver = ZergXcode::Archiver
|
7
|
+
XcodeObject = ZergXcode::XcodeObject
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@sub1 = XcodeObject.new :array => ['a', 'b', 'c'], :string => 's'
|
11
|
+
@sub1.archive_id = '39'
|
12
|
+
@sub2 = XcodeObject.new :hash => { :k => 'v', :k2 => 'v2' }, :sub1 => @sub1
|
13
|
+
@sub2.archive_id = '42'
|
14
|
+
@root = XcodeObject.new :sub1 => @sub1, :sub2 => @sub2
|
15
|
+
@root.archive_id = '49'
|
16
|
+
@root.version = 45
|
17
|
+
|
18
|
+
@archived = {
|
19
|
+
"archiveVersion" => '1',
|
20
|
+
"rootObject" => '49',
|
21
|
+
"classes" => {},
|
22
|
+
"objectVersion" => '45',
|
23
|
+
"objects" => {
|
24
|
+
"49" => { :sub1 => "39", :sub2 => "42" },
|
25
|
+
"39" => { :array => ["a", "b", "c"], :string => "s" },
|
26
|
+
"42" => { :hash => { :k => "v", :k2 => "v2" }, :sub1 => "39" }
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
@pbxdata = File.read 'testdata/project.pbxproj'
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_archive_to_hash
|
34
|
+
hash = Archiver.archive_to_hash @root
|
35
|
+
assert_equal @archived, hash
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_unarchive_hash
|
39
|
+
root = Archiver.unarchive_hash @archived
|
40
|
+
assert_equal @sub1[:s], root[:sub1][:s]
|
41
|
+
assert_equal @sub1[:array], root[:sub1][:array]
|
42
|
+
assert_equal @sub2[:hash], root[:sub2][:hash]
|
43
|
+
assert_equal root[:sub1], root[:sub2][:sub1], 'Object graph split'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_generator
|
47
|
+
generator = Archiver::IdGenerator.new
|
48
|
+
assert_equal '49', generator.id_for(@root)
|
49
|
+
assert_equal '42', generator.id_for(@sub2)
|
50
|
+
new_id = generator.id_for @root
|
51
|
+
assert '49' != new_id, 'Initial ID generated twice'
|
52
|
+
newer_id = generator.id_for @root
|
53
|
+
assert !['49', new_id].include?(newer_id), 'New ID generated twice'
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_archive_passthrough
|
57
|
+
golden_hash = Parser.parse @pbxdata
|
58
|
+
project = Archiver.unarchive @pbxdata
|
59
|
+
dupdata = Archiver.archive project
|
60
|
+
|
61
|
+
assert_equal golden_hash, Parser.parse(dupdata)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_unarchive
|
65
|
+
project = Archiver.unarchive @pbxdata
|
66
|
+
|
67
|
+
assert_equal 'TestApp.app',
|
68
|
+
project['targets'][0]['productReference']['path']
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
class EncoderTest < Test::Unit::TestCase
|
4
|
+
def test_encoder
|
5
|
+
pbxdata = File.read 'testdata/project.pbxproj'
|
6
|
+
golden_encoded_proj = File.read 'testdata/project.pbxproj.compat'
|
7
|
+
proj = ZergXcode::Parser.parse pbxdata
|
8
|
+
assert_equal golden_encoded_proj, ZergXcode::Encoder.encode(proj)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'zerg_xcode'
|
3
|
+
|
4
|
+
class LexerTest < Test::Unit::TestCase
|
5
|
+
def test_lexer
|
6
|
+
pbxdata = File.read 'testdata/project.pbxproj'
|
7
|
+
golden_starts = [[:encoding, "UTF8"],
|
8
|
+
:begin_hash,
|
9
|
+
[:symbol, "archiveVersion"], :assign, [:symbol, "1"],
|
10
|
+
:stop,
|
11
|
+
[:symbol, "classes"], :assign, :begin_hash, :end_hash,
|
12
|
+
:stop,
|
13
|
+
[:symbol, "objectVersion"], :assign, [:symbol, "45"],
|
14
|
+
:stop,
|
15
|
+
[:symbol, "objects"], :assign, :begin_hash,
|
16
|
+
[:symbol, "1D3623260D0F684500981E51"], :assign,
|
17
|
+
:begin_hash,
|
18
|
+
[:symbol, "isa"], :assign, [:symbol, "PBXBuildFile"],
|
19
|
+
:stop,
|
20
|
+
[:symbol, "fileRef"], :assign,
|
21
|
+
[:symbol, "1D3623250D0F684500981E51"],
|
22
|
+
:stop,
|
23
|
+
:end_hash,
|
24
|
+
:stop,
|
25
|
+
[:symbol, "1D60589B0D05DD56006BFB54"], :assign,
|
26
|
+
:begin_hash,
|
27
|
+
[:symbol, "isa"], :assign, [:symbol, "PBXBuildFile"],
|
28
|
+
:stop,
|
29
|
+
[:symbol, "fileRef"], :assign,
|
30
|
+
[:symbol, "29B97316FDCFA39411CA2CEA"],
|
31
|
+
:stop,
|
32
|
+
:end_hash,
|
33
|
+
:stop,
|
34
|
+
[:symbol, "1D60589F0D05DD5A006BFB54"], :assign,
|
35
|
+
:begin_hash,
|
36
|
+
[:symbol, "isa"], :assign, [:symbol, "PBXBuildFile"],
|
37
|
+
:stop,
|
38
|
+
[:symbol, "fileRef"], :assign,
|
39
|
+
[:symbol, "1D30AB110D05D00D00671497"],
|
40
|
+
:stop,
|
41
|
+
:end_hash,
|
42
|
+
:stop]
|
43
|
+
|
44
|
+
tokens = ZergXcode::Lexer.tokenize pbxdata
|
45
|
+
assert_equal golden_starts, tokens[0, golden_starts.length]
|
46
|
+
#p tokens.length
|
47
|
+
#p tokens[197, 50]
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_escaped_string
|
51
|
+
pbxdata = File.read 'testdata/ZergSupport.xcodeproj/project.pbxproj'
|
52
|
+
tokens = ZergXcode::Lexer.tokenize pbxdata
|
53
|
+
assert tokens.include?([:string,
|
54
|
+
"\"$(SRCROOT)/build/Debug-iphonesimulator\""])
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
class ParserTest < Test::Unit::TestCase
|
4
|
+
def test_parser
|
5
|
+
pbxdata = File.read 'testdata/project.pbxproj'
|
6
|
+
proj = ZergXcode::Parser.parse pbxdata
|
7
|
+
|
8
|
+
assert proj.kind_of?(Hash), 'Project structure should be a hash'
|
9
|
+
assert_equal '1', proj['archiveVersion'], 'Archive version'
|
10
|
+
assert_equal '45', proj['objectVersion'], 'Object version'
|
11
|
+
assert_equal '29B97313FDCFA39411CA2CEA', proj['rootObject'], 'Root object'
|
12
|
+
|
13
|
+
golden_file_ref = {
|
14
|
+
'isa' => 'PBXBuildFile',
|
15
|
+
'fileRef' => '28AD733E0D9D9553002E5188'
|
16
|
+
}
|
17
|
+
assert_equal golden_file_ref, proj['objects']['28AD733F0D9D9553002E5188']
|
18
|
+
|
19
|
+
golden_file = {
|
20
|
+
'isa' => 'PBXFileReference',
|
21
|
+
'fileEncoding' => '4',
|
22
|
+
'lastKnownFileType' => 'sourcecode.c.h',
|
23
|
+
'path' => 'TestAppViewController.h',
|
24
|
+
'sourceTree' => "<group>"
|
25
|
+
}
|
26
|
+
assert_equal golden_file, proj['objects']['28D7ACF60DDB3853001CB0EB']
|
27
|
+
|
28
|
+
golden_config = {
|
29
|
+
'isa' => 'XCBuildConfiguration',
|
30
|
+
'buildSettings' => {
|
31
|
+
'ARCHS' => "$(ARCHS_STANDARD_32_BIT)",
|
32
|
+
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" => "iPhone Developer",
|
33
|
+
'GCC_C_LANGUAGE_STANDARD' => 'c99',
|
34
|
+
'GCC_WARN_ABOUT_RETURN_TYPE' => 'YES',
|
35
|
+
'GCC_WARN_UNUSED_VARIABLE' => 'YES',
|
36
|
+
'ONLY_ACTIVE_ARCH' => 'YES',
|
37
|
+
'PREBINDING' => 'NO',
|
38
|
+
'SDKROOT' => 'iphoneos2.2.1'
|
39
|
+
},
|
40
|
+
'name' => 'Debug'
|
41
|
+
}
|
42
|
+
assert_equal golden_config, proj['objects']['C01FCF4F08A954540054247B']
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
class PathTest < Test::Unit::TestCase
|
4
|
+
Paths = ZergXcode::Paths
|
5
|
+
|
6
|
+
def test_project_file_at
|
7
|
+
assert_equal 'testdata/ZergSupport.xcodeproj/project.pbxproj',
|
8
|
+
Paths.project_file_at('testdata/ZergSupport'), 'short'
|
9
|
+
assert_equal 'testdata/ZergSupport.xcodeproj/project.pbxproj',
|
10
|
+
Paths.project_file_at('testdata/ZergSupport.xcodeproj'),
|
11
|
+
'full project name'
|
12
|
+
assert_equal 'testdata/project.pbxproj',
|
13
|
+
Paths.project_file_at('testdata'),
|
14
|
+
'enclosing dir with weird name'
|
15
|
+
assert_equal './testdata/project.pbxproj',
|
16
|
+
Paths.project_file_at('.'),
|
17
|
+
'subdir with weird name'
|
18
|
+
assert_equal 'testdata/TestApp/TestApp.xcodeproj/project.pbxproj',
|
19
|
+
Paths.project_file_at('testdata/TestApp'), 'project in subdir'
|
20
|
+
assert_equal 'testdata/TestApp/TestApp.xcodeproj/project.pbxproj',
|
21
|
+
Paths.project_file_at('testdata/TestApp/TestApp.xcodeproj'),
|
22
|
+
'full project name in subdir'
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_project_root_at
|
26
|
+
assert_equal 'testdata', Paths.project_root_at('testdata/ZergSupport'),
|
27
|
+
'short project name'
|
28
|
+
assert_equal 'testdata',
|
29
|
+
Paths.project_root_at('testdata/ZergSupport.xcodeproj'),
|
30
|
+
'full project name'
|
31
|
+
assert_equal '.', Paths.project_root_at('testdata'),
|
32
|
+
'enclosing dir with weird name'
|
33
|
+
assert_equal '.', Paths.project_root_at('.'), 'subdir with weird name'
|
34
|
+
assert_equal 'testdata/TestApp',
|
35
|
+
Paths.project_root_at('testdata/TestApp'), 'project dir'
|
36
|
+
assert_equal 'testdata/TestApp',
|
37
|
+
Paths.project_root_at('testdata/TestApp/TestApp.xcodeproj'),
|
38
|
+
'full project name in subdir'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'zerg_xcode'
|
4
|
+
|
5
|
+
class PBXBuildFileTest < Test::Unit::TestCase
|
6
|
+
PBXBuildFile = ZergXcode::Objects::PBXBuildFile
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@target = ZergXcode.load('testdata/project.pbxproj')['targets'].first
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_attributes
|
13
|
+
sources_phase = @target['buildPhases'][1]
|
14
|
+
build_file = sources_phase['files'].first
|
15
|
+
assert_equal PBXBuildFile, build_file.class
|
16
|
+
assert_equal 'main.m', build_file.filename
|
17
|
+
assert_equal 'sourcecode.c.objc', build_file.file_type
|
18
|
+
|
19
|
+
big_project = ZergXcode.load('testdata/ZergSupport')
|
20
|
+
big_project['targets'].map { |t| t.all_files }.flatten.each do |file|
|
21
|
+
assert_not_nil file[:build_object].file_type,
|
22
|
+
"No file type for #{file[:build_object].inspect}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_guessed_build_phase_type
|
27
|
+
big_project = ZergXcode.load('testdata/ZergSupport')
|
28
|
+
|
29
|
+
phases = @target['buildPhases'] +
|
30
|
+
big_project['targets'].map { |t| t['buildPhases'] }.flatten
|
31
|
+
phases.each do |phase|
|
32
|
+
phase['files'].each do |file|
|
33
|
+
assert_equal phase['isa'], file.guessed_build_phase_type,
|
34
|
+
"Wrong phase for #{file.inspect}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|