pbxproject 0.1.0

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