pbxproject 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pbxproject.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'pbxproject/pbxtypes'
2
+ require 'pbxproject/pbxproject'
@@ -0,0 +1,22 @@
1
+ require 'pbxproject'
2
+ require 'yaml'
3
+ begin
4
+ require 'thor'
5
+ rescue LoadError
6
+ $stderr.puts "To use CLI, please install Thor"
7
+ exit
8
+ end
9
+
10
+ module PBXProject
11
+ class CLI < Thor
12
+ desc "parse", "Parses pbxproject file and outputs its"
13
+ method_option :input
14
+ method_option :output
15
+ def parse
16
+ pbx = PBXProject.new :file => options[:input]
17
+ pbx.parse
18
+
19
+ puts pbx.to_pbx
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,258 @@
1
+ module PBXProject
2
+ class PBXProject
3
+ attr_reader :filename, :state
4
+ attr_accessor :archiveVersion, :objectVersion, :sections
5
+
6
+ def initialize(args = {})
7
+ return unless args[:file]
8
+ raise "Project file cannot be read" unless File.file?(args[:file])
9
+
10
+ @filename = args[:file]
11
+
12
+ # initialize our class
13
+ @sections = {}
14
+
15
+ # and set that we're ready for parsing
16
+ @state = :ready
17
+
18
+ end
19
+
20
+ # This is one big parser
21
+ def parse
22
+ raise "Not ready for parsing" unless @state == :ready
23
+ pbx = File.open(@filename, 'r')
24
+
25
+ line_num = 0
26
+ group_name = []
27
+ # group = []
28
+ section_type = nil
29
+ section = nil
30
+ item = nil
31
+ list_name = nil
32
+ list = nil
33
+ grouplist_name = nil
34
+ grouplist = nil
35
+
36
+ # Read our file
37
+ pbx.each_line do |line|
38
+ if (line_num == 0 && !line.match(Regexp.new(Regexp.escape('// !$*UTF8*$!'))))
39
+ raise "Unkown file format"
40
+ end
41
+
42
+ # Main level Attributes
43
+ if (group_name.count == 0 && !section_type && m = line.match(/\s+(.*?) = (.*?)( \/\* (.*) \*\/)?;/))
44
+ # d = { :value => m[2], :comment => m[4] }
45
+ self.instance_variable_set("@#{m[1]}", PBXTypes::BasicValue.new(:value => m[2], :comment => m[4]))
46
+
47
+ next
48
+ end
49
+
50
+ # Begin object group
51
+ if (m = line.match(/\s+(.*) = \{/))
52
+ group_name.push m[1]
53
+ # group.push {}
54
+ end
55
+
56
+ # End our object group
57
+ if (line.match(/\s+\};/))
58
+ group_name.pop
59
+ # group.pop
60
+
61
+ if (item && group_name.count < 2)
62
+ @sections[section_type].push item
63
+ item = nil
64
+ end
65
+ end
66
+
67
+ # Begin section
68
+ if (m = line.match(/\/\* Begin (.*) section \*\//))
69
+ section_type = m[1]
70
+ @sections[section_type] = []
71
+
72
+ next
73
+ end
74
+
75
+ # One line section data, simple. huh?
76
+ if (section_type && group_name.count < 3 && m = line.match(/\s+(.*?) (\/\* (.*?) \*\/ )?= \{(.*?)\};/))
77
+ begin
78
+ # cls = PBXProject::PBXProject::const_get(section_type)
79
+ # item = cls.new
80
+ item = eval("PBXTypes::#{section_type}").new
81
+
82
+ item.guid = m[1]
83
+ item.comment = m[3]
84
+ m[4].scan(/(.*?) = (.*?)( \/\* (.*) \*\/)?; ?/).each do |v|
85
+ if (v[3])
86
+ # d = { :value => v[1], :comment => v[3]}
87
+ item.instance_variable_set("@#{v[0]}", PBXTypes::BasicValue.new(:value => v[1], :comment => v[3]))
88
+ else
89
+ item.instance_variable_set("@#{v[0]}", v[1])
90
+ end
91
+ end
92
+
93
+ @sections[section_type].push item
94
+ item = nil
95
+ rescue NameError => e
96
+ puts e.inspect
97
+ end
98
+
99
+ next
100
+ end
101
+
102
+ # Multiline with lists
103
+ if (section_type && group_name.count < 3 && m = line.match(/\s+(.*?) (\/\* (.*?) \*\/ )?= \{/))
104
+ begin
105
+ # cls = PBXProject::PBXProject::const_get(section_type)
106
+ # item = cls.new
107
+ item = eval("PBXTypes::#{section_type}").new
108
+
109
+ item.guid = m[1]
110
+ item.comment = m[3]
111
+
112
+ # puts item.inspect
113
+ rescue NameError => e
114
+ puts e.inspect
115
+ end
116
+
117
+ next
118
+ end
119
+
120
+ # Next line in multiline
121
+ if (item && m = line.match(/\s+(.*?) = (.*)( \/\* (.*) \*\/)?;/))
122
+ if (group_name.count < 3)
123
+ # i = { :value => m[2], :comment => m[4] }
124
+ item.instance_variable_set("@#{m[1]}", PBXTypes::BasicValue.new(:value => m[2], :comment => m[4]))
125
+ else
126
+ grp = item.instance_variable_get("@#{group_name.last}")
127
+ if (!grp.kind_of?(Hash))
128
+ grp = {}
129
+ end
130
+ # grp[m[1]] = { :value => m[2], :comment => m[4] }
131
+ grp[m[1]] = PBXTypes::BasicValue.new :value => m[2], :comment => m[4]
132
+ item.instance_variable_set("@#{group_name.last}", grp)
133
+ end
134
+
135
+ next
136
+ end
137
+
138
+ # And the multiline list begin
139
+ if (item && m = line.match(/\s+(.*?) = \(/))
140
+ if (group_name.count < 3)
141
+ list_name = m[1]
142
+ list = []
143
+ else
144
+ grouplist_name = m[1]
145
+ grouplist = []
146
+ end
147
+
148
+ next
149
+ end
150
+
151
+ # And list items
152
+ if (item && m = line.match(/\s+(.*?)( \/\* (.*?) \*\/)?,/))
153
+ if (group_name.count < 3)
154
+ # i = { :item => m[1], :comment => m[3] }
155
+ list.push PBXTypes::BasicValue.new :value => m[1], :comment => m[3]
156
+ else
157
+ # i = { :item => m[1], :comment => m[3] }
158
+ grouplist.push PBXTypes::BasicValue.new :value => m[1], :comment => m[3]
159
+ end
160
+
161
+ next
162
+ end
163
+
164
+ if (item && line.match(/\s+\);/))
165
+ if (group_name.count < 3)
166
+ item.instance_variable_set("@#{list_name}", list)
167
+ list = nil
168
+ list_name = nil
169
+ else
170
+ grp = item.instance_variable_get("@#{group_name.last}")
171
+ if (!grp.kind_of?(Hash))
172
+ grp = {}
173
+ end
174
+ grp[grouplist_name] = grouplist
175
+ item.instance_variable_set("@#{group_name.last}", grp)
176
+ grouplist_name = nil
177
+ grouplist = nil
178
+ end
179
+
180
+ next
181
+ end
182
+
183
+ # End section
184
+ if (m = line.match(/\/\* End (.*) section \*\//))
185
+ section_type = nil
186
+ section = nil
187
+ end
188
+
189
+ # Increse our line counter
190
+ line_num += 1
191
+ end
192
+
193
+ @state = :parsed
194
+
195
+ true
196
+ end
197
+
198
+ def find_item args = {}
199
+ type = args[:type]
200
+ type_name = type.name.split('::').last || ''
201
+ args.delete(:type)
202
+ @sections.each do |t, arr|
203
+ next unless t == type_name
204
+
205
+ arr.each do |item|
206
+ args.each do |k,v|
207
+ key = item.instance_variable_get("@#{k}")
208
+ if (key && (key == v || (key.kind_of?(PBXTypes::BasicValue) && key.value == v)))
209
+ return item
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ nil
216
+ end
217
+
218
+ def add_item item, position = -1
219
+ type = item.class.name.split('::').last || ''
220
+
221
+ # Ensure that we have array
222
+ if (@sections[type].nil?)
223
+ @sections[type] = []
224
+ end
225
+
226
+ @sections[type].insert(position, item)
227
+
228
+ item.guid
229
+ end
230
+
231
+ def to_pbx ind = 0
232
+ ind = 2
233
+ pbx = ''
234
+
235
+ pbx += "// !$*UTF8*$!\n{\n"
236
+ pbx += "\tarchiveVersion = %s;\n" % @archiveVersion.to_pbx
237
+ pbx += "\tclasses = {\n\t};\n"
238
+ pbx += "\tobjectVersion = %s;\n" % @objectVersion.to_pbx
239
+ pbx += "\tobjects = {\n"
240
+
241
+ @sections.each do |type, val|
242
+ pbx += "\n/* Begin %s section */\n" % type
243
+
244
+ val.each do |item|
245
+ pbx += item.to_pbx ind
246
+ end
247
+
248
+ pbx += "/* End %s section */\n" % type
249
+ end
250
+
251
+ pbx += "\t};\n"
252
+ pbx += "\trootObject = %s;\n" % @rootObject.to_pbx
253
+ pbx += "}"
254
+
255
+ pbx
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,326 @@
1
+ require 'digest/sha1'
2
+
3
+ module PBXProject
4
+ module PBXTypes
5
+
6
+ class BasicValue
7
+ attr_accessor :value, :comment
8
+
9
+ def initialize(args = {})
10
+ @value = args[:value]
11
+ @comment = args[:comment]
12
+ end
13
+
14
+ def to_pbx ind = 0
15
+ pbx = ''
16
+ pbx += "#{@value}"
17
+ pbx += " /* #{@comment} */" if @comment
18
+
19
+ pbx
20
+ end
21
+ end
22
+
23
+ class ISAType
24
+ attr_accessor :guid, :isa, :comment
25
+
26
+ def initialize args = {}
27
+ @isa = basic_value(self.class.name)
28
+ @guid = hashify(self)
29
+
30
+ args.each do |k,v|
31
+ instance_variable_set("@#{k}", basic_value(v)) unless v.nil?
32
+ end
33
+ end
34
+
35
+ def basic_value(value = nil, comment = nil)
36
+ # { :value => value, :comment => comment }
37
+ BasicValue.new :value => value, :comment => comment
38
+ end
39
+
40
+ def hashify to_hash
41
+ # example
42
+ ex = 'C01713D713462F35007665FA'
43
+ "OEF" + (Digest::SHA1.hexdigest to_hash.to_s).upcase[4..ex.length]
44
+ end
45
+
46
+ def self.has_fields(*fields)
47
+ _fields = []
48
+ fields.each do |f|
49
+ _fields.push f.id2name
50
+
51
+ define_method(f) do
52
+ instance_variable_get("@#{f}")
53
+ end
54
+ end
55
+
56
+ define_method("pbxfields") do
57
+ _fields
58
+ end
59
+ end
60
+
61
+ def self.has_format(format)
62
+ _format = format
63
+
64
+ define_method("format") do
65
+ _format
66
+ end
67
+ end
68
+
69
+ def _pbx_indent(ind = 0)
70
+ "\t" * ind
71
+ end
72
+
73
+ def _pbx_format
74
+ return nil unless defined? format
75
+ format
76
+ end
77
+
78
+ def _pbx_newline
79
+ case _pbx_format
80
+ when :oneline
81
+ nil
82
+ else
83
+ "\n"
84
+ end
85
+ end
86
+
87
+ def to_pbx(ind = 0)
88
+ pbx = ''
89
+ pbx += _pbx_indent(ind) + "#{@guid}"
90
+ pbx += " /* #{@comment} */" if @comment
91
+ pbx += " = {%s" % _pbx_newline
92
+
93
+ ind += 1
94
+
95
+ # PBX fields
96
+ pbxfields.each do |fld|
97
+ field = self.instance_variable_get("@#{fld}")
98
+ case field.class.name
99
+ when "String"
100
+ pbx += (_pbx_format == :multiline ? _pbx_indent(ind) : "") + "%s = %s;%s%s" % [fld, field, (_pbx_newline == nil ? " " : ""),_pbx_newline]
101
+ when 'PBXProject::PBXTypes::BasicValue'
102
+ pbx += (_pbx_format == :multiline ? _pbx_indent(ind) : "") + "%s = %s;%s%s" % [fld, field.to_pbx, (_pbx_newline == nil ? " " : ""), _pbx_newline]
103
+ when "Array"
104
+ pbx += _pbx_indent(ind) + "%s = (%s" % [fld, _pbx_newline]
105
+
106
+ ind += 1
107
+ field.each do |item|
108
+ pbx += _pbx_indent(ind) + "%s,%s" % [item.to_pbx, _pbx_newline]
109
+ end
110
+ ind -= 1
111
+ pbx += _pbx_indent(ind) + ");%s" % _pbx_newline
112
+ when "NilClass"
113
+ when "Hash"
114
+ pbx += _pbx_indent(ind) + "%s = {%s" % [fld, _pbx_newline]
115
+
116
+ ind += 1
117
+ field.each do |name, d|
118
+ case d.class.name
119
+ when "PBXProject::PBXTypes::BasicValue"
120
+ pbx += _pbx_indent(ind) + "%s = %s;%s%s" % [name, d.to_pbx, (_pbx_newline == nil ? " " : ""), _pbx_newline]
121
+ when "Array"
122
+ pbx += _pbx_indent(ind) + "%s = (%s" % [name, _pbx_newline]
123
+
124
+ ind += 1
125
+ d.each do |item|
126
+ pbx += _pbx_indent(ind) + "%s,%s" % [item.to_pbx, _pbx_newline]
127
+ end
128
+ ind -= 1
129
+
130
+ pbx += _pbx_indent(ind) + ");%s" % _pbx_newline
131
+ end
132
+ end
133
+ ind -= 1
134
+
135
+ pbx += _pbx_indent(ind) + "};%s" % _pbx_newline
136
+ else
137
+ puts "WHAT? #{field.class}"
138
+ puts "#{field}"
139
+ end
140
+
141
+ # case self.instance_variable_get("@#{field}").class.name
142
+ # when "Hash"
143
+ # h = self.instance_variable_get("@#{field}")
144
+ # if (h.value)
145
+ # # We have value-comment hash
146
+ # h.comment = " /* #{h.comment} */" if h.comment != nil
147
+ #
148
+ # ind.times{print"\t"};
149
+ # pbx += sprintf "%s = %s%s;", field, h[:value], h[:comment]
150
+ # pbx += "\n" unless @format == :oneline
151
+ # else
152
+ # # We have dictionary
153
+ # ind.times{print"\t"};
154
+ # pbx += sprintf "%s = {", field
155
+ # pbx += "\n" unless @format == :oneline
156
+ #
157
+ # ind += 1
158
+ # h.each do |name, d|
159
+ # case d.class.name
160
+ # when "Hash"
161
+ # ind.times{print"\t"}
162
+ # d[:comment] = " /* #{d[:comment]} */" if d[:comment] != nil
163
+ # pbx += sprintf "%s = %s%s;", name, d[:value], d[:comment]
164
+ # pbx += "\n" unless @format == :oneline
165
+ #
166
+ # when "Array"
167
+ # ind.times{print"\t"}
168
+ # pbx += "#{name} = ("
169
+ # pbx += "\n" unless @format == :oneline
170
+ #
171
+ # ind += 1
172
+ # d.each do |r|
173
+ # ind.times{print"\t"}
174
+ # r[:comment] = " /* #{r[:comment]} */" if r[:comment] != nil
175
+ # pbx += sprintf "%s%s,", r[:name], r[:item], r[:comment]
176
+ # pbx += "\n" unless @format == :oneline
177
+ #
178
+ # end
179
+ # ind -= 1
180
+ # ind.times{print"\t"}
181
+ # pbx += ");"
182
+ # pbx += "\n" unless @format == :oneline
183
+ #
184
+ # end
185
+ # end
186
+ # ind -= 1
187
+ # ind.times{print"\t"}; puts "};"
188
+ # end
189
+ # when "Array"
190
+ # a = self.instance_variable_get("@#{field}")
191
+ # ind.times{print"\t"};
192
+ # pbx += sprintf "%s = (%s", field, pbxformat
193
+ # ind += 1
194
+ # a.each do |r|
195
+ # ind.times{print"\t"}
196
+ # r[:comment] = " /* #{r[:comment]} */" if r[:comment] != nil
197
+ #
198
+ # printf "%s%s,\n", r[:item], r[:comment]
199
+ # end
200
+ # ind -= 1
201
+ # ind.times{print"\t"}; print ");\n"
202
+ # end
203
+ end
204
+ ind -= 1
205
+
206
+ # ind.times{print"\t"}; pbx += "};%s" % _pbx_newline
207
+ if (_pbx_newline)
208
+ pbx += "\t" * ind + "};\n"
209
+ else
210
+ pbx += "};\n"
211
+ end
212
+
213
+ pbx
214
+ end
215
+ end
216
+
217
+ class PBXBuildFile < ISAType
218
+ has_fields :isa, :fileRef
219
+ has_format :oneline
220
+
221
+ # def to_pbx(ind)
222
+ # print "#{@guid}"
223
+ # print " /* #{@comment} */" if @comment
224
+ # print " = {isa = #{@isa}; "
225
+ # if (@fileRef.kind_of?(Hash))
226
+ # print "fileRef = #{@fileRef[:value]} /* #{@fileRef[:comment]} */"
227
+ # else
228
+ # print "fileRef = #{@fileRef}"
229
+ # end
230
+ # puts "; };"
231
+ # end
232
+ end
233
+
234
+ class PBXFileReference < ISAType
235
+ has_fields :isa, :fileEncoding, :explicitFileType, :lastKnownFileType, :includeInIndex, :name, :path, :sourceTree
236
+ has_format :oneline
237
+
238
+ # def to_pbx(ind)
239
+ # print "#{@guid}"
240
+ # print " /* #{@comment} */" if @comment
241
+ # print " = {"
242
+ # ["isa", "fileEncoding", "explicitFileType", "lastKnownFileType", "includeInIndex", "name", "path", "sourceTree"].each do |var|
243
+ # printf "%s = %s; ", var, self.instance_variable_get("@#{var}") if self.instance_variable_get("@#{var}") != nil
244
+ # end
245
+ # puts "};"
246
+ # end
247
+ end
248
+
249
+ class PBXFrameworksBuildPhase < ISAType
250
+ has_fields :isa, :buildActionMask, :files, :runOnlyForDeploymentPostprocessing
251
+ has_format :multiline
252
+ end
253
+
254
+ class PBXGroup < ISAType
255
+ has_fields :isa, :children, :name, :path, :sourceTree
256
+ has_format :multiline
257
+
258
+ def add_children(fileref)
259
+ @children.push BasicValue.new(:value => fileref.guid, :comment => fileref.comment)
260
+
261
+ fileref.guid
262
+ end
263
+ end
264
+
265
+ class PBXNativeTarget < ISAType
266
+ has_fields :isa, :buildConfigurationList, :buildPhases, :buildRules, :dependencies, :name,
267
+ :productName, :productReference, :productType
268
+ has_format :multiline
269
+
270
+ def add_build_phase build_phase, position = -1
271
+ @buildPhases.insert(position, BasicValue.new(:value => build_phase.guid, :comment => build_phase.comment))
272
+ end
273
+ end
274
+
275
+ class PBXProject < ISAType
276
+ has_fields :isa, :attributes, :buildConfigurationList, :compatibilityVersion, :developmentRegion,
277
+ :hasScannedForEncodings, :knownRegions, :mainGroup, :productRefGroup, :projectDirPath, :projectRoot,
278
+ :targets
279
+ has_format :multiline
280
+ end
281
+
282
+ class PBXResourcesBuildPhase < ISAType
283
+ has_fields :isa, :buildActionMask, :files, :runOnlyForDeploymentPostprocessing
284
+ has_format :multiline
285
+ end
286
+
287
+ class PBXShellScriptBuildPhase < ISAType
288
+ has_fields :isa, :buildActionMask, :files, :inputPaths, :outputPaths, :runOnlyForDeploymentPostprocessing,
289
+ :shellPath, :shellScript, :showEnvVarsInLog
290
+ has_format :multiline
291
+
292
+ def initialize args = {}
293
+ super
294
+
295
+ # Defaults
296
+ @comment = "ShellScript"
297
+ @buildActionMask = basic_value(2147483647)
298
+ @files = []
299
+ @inputPaths = []
300
+ @outputPaths = []
301
+ @runOnlyForDeploymentPostprocessing = basic_value(0)
302
+ end
303
+ end
304
+
305
+ class PBXSourcesBuildPhase < ISAType
306
+ has_fields :isa, :buildActionMask, :files, :runOnlyForDeploymentPostprocessing
307
+ has_format :multiline
308
+ end
309
+
310
+ class PBXVariantGroup < ISAType
311
+ has_fields :isa, :children, :name, :sourceTree
312
+ has_format :multiline
313
+ end
314
+
315
+ class XCBuildConfiguration < ISAType
316
+ has_fields :isa, :buildSettings, :name
317
+ has_format :multiline
318
+ end
319
+
320
+ class XCConfigurationList < ISAType
321
+ has_fields :isa, :buildConfigurations, :defaultConfigurationIsVisible, :defaultConfigurationName
322
+ has_format :multiline
323
+ end
324
+
325
+ end
326
+ end