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
@@ -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
|