Mercurial > projects > dstep
comparison sciprts/dstepgen.rb @ 0:c7db221de6e8
First upload of dgen.rb and dstepgen.rb
author | Jacob Carlborg <doob@me.com> |
---|---|
date | Sat, 31 Jan 2009 17:22:44 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c7db221de6e8 |
---|---|
1 #!/usr/bin/env ruby | |
2 | |
3 # Copyright (c) 2008-2009, Jacob Carlborg. All rights reserved. | |
4 # Copyright (c) 2006-2007, Apple Inc. All rights reserved. | |
5 # Copyright (c) 2005-2006 FUJIMOTO Hisakuni | |
6 # | |
7 # Redistribution and use in source and binary forms, with or without | |
8 # modification, are permitted provided that the following conditions | |
9 # are met: | |
10 # 1. Redistributions of source code must retain the above copyright | |
11 # notice, this list of conditions and the following disclaimer. | |
12 # 2. Redistributions in binary form must reproduce the above copyright | |
13 # notice, this list of conditions and the following disclaimer in the | |
14 # documentation and/or other materials provided with the distribution. | |
15 # 3. Neither the name of Apple Inc. ("Apple") nor the names of | |
16 # its contributors may be used to endorse or promote products derived | |
17 # from this software without specific prior written permission. | |
18 # | |
19 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND | |
20 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
21 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
22 # ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR | |
23 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
24 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
25 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
26 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
27 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
28 # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
29 # POSSIBILITY OF SUCH DAMAGE. | |
30 | |
31 require "rubygems" | |
32 gem "builder", "~> 2.0" | |
33 require "builder" | |
34 require "tmpdir" | |
35 require "optparse" | |
36 include Builder | |
37 | |
38 $KCODE = "UTF8" | |
39 | |
40 # Extensions that adds support for member access syntax | |
41 class Hash | |
42 def type | |
43 result = self["type"] | |
44 return result unless result.nil? | |
45 self[:type] | |
46 end | |
47 | |
48 def type= (type) | |
49 self[:type] = type | |
50 end | |
51 | |
52 def id | |
53 result = self["id"] | |
54 return result unless result.nil? | |
55 self[:id] | |
56 end | |
57 | |
58 def id= (id) | |
59 self[:id] = id | |
60 end | |
61 | |
62 def methods | |
63 result = self["methods"] | |
64 return result unless result.nil? | |
65 self[:methods] | |
66 end | |
67 | |
68 def methods= (methods) | |
69 self[:methods] = methods | |
70 end | |
71 | |
72 def method_missing (method, *args) | |
73 self.class.instance_eval do | |
74 define_method(method) do |*args| | |
75 if args.length > 0 | |
76 self[method[0 ... -1]] = args[0] | |
77 self[eval(":#{method}"[0 ... -1])] = args[0] | |
78 else | |
79 result = self[method] | |
80 return result unless result.nil? | |
81 self[eval(":#{method}")] | |
82 end | |
83 end | |
84 end | |
85 | |
86 if (method = method.id2name) =~ /=/ | |
87 eval("self.#{method} (args.length < 2 ? args[0] : args)") | |
88 else | |
89 eval("self.#{method}") | |
90 end | |
91 end | |
92 end | |
93 | |
94 # This Struct represents an Objective-C Framework | |
95 Framework = Struct.new(:name, :headers) do | |
96 def initialize | |
97 self.headers = [] | |
98 self.name = "" | |
99 end | |
100 end | |
101 | |
102 # This Struct represents a C/Objective-C header | |
103 HeaderFile = Struct.new(:name, :framework, :cftypes, :constants, :defines, :enums, :externs, :functions, | |
104 :imports, :inline_functions, :opaques, :path, :structs, :typedefs) do | |
105 def initialize | |
106 self.name = "" | |
107 self.cftypes = [] | |
108 self.constants = [] | |
109 self.defines = [] | |
110 self.enums = [] | |
111 self.externs = [] | |
112 self.framework = "" | |
113 self.functions = [] | |
114 self.imports = [] | |
115 self.inline_functions = [] | |
116 self.opaques = [] | |
117 self.path = "" | |
118 self.structs = [] | |
119 self.typedefs = [] | |
120 end | |
121 end | |
122 | |
123 # This class scans the headers | |
124 class HeaderScaner | |
125 CPP = ['/usr/bin/cpp-4.0', '/usr/bin/cpp-3.3', '/usr/bin/cpp3'].find { |x| File.exist?(x) } | |
126 raise "cpp not found" if CPP.nil? | |
127 CPPFLAGS = "-D__APPLE_CPP__ -include /usr/include/AvailabilityMacros.h" | |
128 CPPFLAGS << "-D__GNUC__" unless /\Acpp-4/.match(File.basename(CPP)) | |
129 | |
130 attr_accessor :frameworks, :headers, :do_64bit | |
131 | |
132 def initialize | |
133 @extern_name = 'extern' | |
134 @frameworks = [] | |
135 @file_content = nil | |
136 @headers = [] | |
137 @classes = {} | |
138 @informal_protocols = {} | |
139 @function_pointer_types = {} | |
140 @do_64bit = false | |
141 end | |
142 | |
143 def classes | |
144 @classes | |
145 end | |
146 | |
147 def protocols | |
148 @informal_protocols | |
149 end | |
150 | |
151 def cftypes (header) | |
152 re = /typedef\s+(const\s+)?(struct\s*\w+\s*\*\s*)([^\s]+Ref)\s*;/ | |
153 @cpp_result.scan(re).each do |m| | |
154 header.cftypes << { :name => m[2], :type => m[1], :const => m[0] =~ /const/ ? true : false} | |
155 end | |
156 end | |
157 | |
158 def constants (header) | |
159 tmp = header.externs.map do |i| | |
160 constant?(i, true) | |
161 end | |
162 | |
163 header.constants = tmp.flatten.compact | |
164 end | |
165 | |
166 def defines (header) | |
167 re = /#define\s+([^\s]+)\s+(\([^)]+\)|[^\s]+)\s*$/ | |
168 @file_content.scan(re).each do |m| | |
169 next unless !m[0].include?('(') && m[1] != '\\' | |
170 header.defines << { :name => m[0], :value => m[1] } | |
171 end | |
172 end | |
173 | |
174 def enums (header) | |
175 re = /\benum\b\s*(\w+\s+)?\{([^}]*)\}/ | |
176 @cpp_result.scan(re).each do |m| | |
177 enum = { :name => m[0], :members => [] } | |
178 | |
179 m[1].split(",").map do |i| | |
180 name, value = i.split("=", 2).map do |x| | |
181 x.strip | |
182 end | |
183 | |
184 enum[:members] << { :name => name, :value => value } unless name.empty? || name[0] == ?# | |
185 end | |
186 | |
187 header.enums << enum | |
188 end | |
189 end | |
190 | |
191 def function_pointer_types (header) | |
192 re = /typedef\s+([\w\s]+)\s*\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*;/ | |
193 data = @cpp_result.scan(re) | |
194 re = /typedef\s+([\w\s]+)\s*\(([^)]+)\)\s*;/ | |
195 data |= @cpp_result.scan(re).map do |m| | |
196 ary = m[0].split(/(\w+)$/) | |
197 ary[1] << " *" | |
198 ary << m[1] | |
199 ary | |
200 end | |
201 | |
202 data.each do |m| | |
203 name = m[1] | |
204 args = m[2].split(",").map do |x| | |
205 if x.include?(" ") | |
206 ptr = x.sub!(/\[\]\s*$/, "") | |
207 x = x.sub(/\w+\s*$/, "").strip | |
208 ptr ? x + "*" : x | |
209 else | |
210 x.strip | |
211 end | |
212 end | |
213 | |
214 type = "#{m[0]}(*)(#{args.join(', ')})" | |
215 @function_pointer_types[name] = type | |
216 end | |
217 end | |
218 | |
219 def typedefs (header) | |
220 re = /^\s*typedef\s+(.+)\s+([\w\*]+)\s*;$/ | |
221 data = @cpp_result | |
222 data.scan(re).each do |m| | |
223 var = get_var_info(m[0] + " " + m[1]) | |
224 header.typedefs << get_var_info(m[0] + " " + m[1]) if var | |
225 end | |
226 end | |
227 | |
228 def externs (header) | |
229 re = /^\s*#{@extern_name}\s+\b(.*)\s*;.*$/ | |
230 header.externs = @cpp_result.scan(re).map do |m| | |
231 m[0].strip | |
232 end | |
233 end | |
234 | |
235 def imports (header) | |
236 tmp = [] | |
237 | |
238 @file_content.each do |line| | |
239 if line =~ /#(include|import) <(.+)\.h>/ | |
240 next if $2 == header.name | |
241 tmp << $2 | |
242 elsif line =~ /@class(\s+)(\w+)(,|;)/ | |
243 next if $2 == header.name | |
244 | |
245 if $3 == ";" | |
246 tmp << header.framework + "/" + $2 unless header.framework == "" | |
247 tmp << $2 if header.framework == "" | |
248 elsif | |
249 str = line[6 + $1.length ... -2] | |
250 str.gsub!(" ", "") | |
251 arr = str.split(",") | |
252 | |
253 arr.each do |s| | |
254 tmp << header.framework + "/" + s unless header.framework == "" | |
255 tmp << s if header.framework == "" | |
256 end | |
257 end | |
258 end | |
259 end | |
260 | |
261 header.imports = tmp.compact.uniq | |
262 end | |
263 | |
264 def informal_protocols (header) | |
265 self.methods(header) | |
266 end | |
267 | |
268 def methods (header) | |
269 interface_re = /^@(interface|protocol)\s+(\w+)\s*:?\s*(\w*)\s*(\([^)]+\))?/ | |
270 | |
271 end_re = /^@end/ | |
272 body_re = /^[-+]\s*(\([^)]+\))?\s*([^:\s;]+)/ | |
273 args_re = /\w+\s*:/ | |
274 prop_re = /^@property\s*(\([^)]+\))?\s*([^;]+);$/ | |
275 current_interface = current_category = nil | |
276 i = 0 | |
277 parent = nil | |
278 | |
279 @cpp_result.each_line do |line| | |
280 size = line.size | |
281 line.strip! | |
282 | |
283 if md = interface_re.match(line) | |
284 parent = nil | |
285 current_interface = md[1] == "protocol" ? "NSObject" : md[2] | |
286 current_category = md[4].delete("()").strip if md[4] | |
287 parent = md[3] unless md[3] == "" | |
288 | |
289 elsif end_re.match(line) | |
290 current_interface = current_category = nil | |
291 | |
292 elsif current_interface && md = prop_re.match(line) | |
293 # Parsing Objective-C 2.0 properties | |
294 if (a = md[2].split(/\s/)).length >= 2 && /^\w+$/.match(name = a[-1]) && (type = a[0 .. -2].join(" ")).index(",").nil? | |
295 getter, setter = name, "set#{name[0].chr.upcase + name[1 .. -1]}" | |
296 readonly = false | |
297 | |
298 if attributes = md[1] | |
299 if md = /getter\s*=\s*(\w+)/.match(attributes) | |
300 getter = md[1] | |
301 end | |
302 | |
303 if md = /setter\s*=\s*(\w+)/.match(attributes) | |
304 setter = md[1] | |
305 end | |
306 | |
307 readonly = true if attributes.index("readonly") | |
308 end | |
309 | |
310 typeinfo = VarInfo.new(type, "", "") | |
311 | |
312 @classes[current_interface] ||= {} | |
313 methods = (@classes[current_interface].methods ||= []) | |
314 methods << MethodInfo.new(typeinfo, getter, false, [], line) | |
315 | |
316 unless readonly | |
317 methods << MethodInfo.new(VarInfo.new("void", "", ""), setter + ":", false, [typeinfo], line) | |
318 end | |
319 end | |
320 | |
321 elsif current_interface && (line[0] == ?+ || line[0] == ?-) | |
322 mtype = line[0] | |
323 data = @cpp_result[i .. -1] | |
324 body_md = body_re.match(data) | |
325 | |
326 next if body_md.nil? | |
327 | |
328 rettype = body_md[1] ? body_md[1].delete("()") : "id" | |
329 retval = VarInfo.new(rettype, "", "") | |
330 args = [] | |
331 selector = "" | |
332 data = data[0 .. data.index(";")] | |
333 args_data = [] | |
334 | |
335 data.scan(args_re) do |x| | |
336 args_data << [$`, x, $'] | |
337 end | |
338 | |
339 variadic = false | |
340 args_data.each_with_index do |ary, n| | |
341 before, argname, argtype = ary | |
342 arg_nameless = (n > 0 && /\)\s*$/.match(before)) | |
343 argname = ":" if arg_nameless | |
344 realargname = nil | |
345 | |
346 if n < args_data.length - 1 | |
347 argtype.sub!(args_data[n + 1][2], "") | |
348 | |
349 if arg_nameless | |
350 argtype.sub!(/(\w+\s*)?\w+\s*:\s*$/, "") | |
351 else | |
352 unless argtype.sub!(/(\w+)\s+\w+:\s*$/) { |s| realargname = $1; "" } | |
353 # maybe the next argument is nameless | |
354 argtype.sub!(/\w+\s*:\s*$/, "") | |
355 end | |
356 end | |
357 else | |
358 argtype.sub!(/\s+__attribute__\(\(.+\)\)/, "") | |
359 | |
360 if arg_nameless | |
361 argtype.sub!(/\w+\s*;$/, "") | |
362 else | |
363 unless argtype.sub!(/(\w+)\s*;$/) { |s| realargname = $1; "" } | |
364 variadic = argtype.sub!(/,\s*\.\.\.\s*;/, "") != nil | |
365 argtype.sub!(/\w+\s*$/, "") if variadic | |
366 end | |
367 end | |
368 end | |
369 | |
370 selector << argname | |
371 realargname ||= argname.sub(/:/, "") | |
372 args << VarInfo.new(argtype, realargname, "") unless argtype.empty? | |
373 end | |
374 | |
375 selector = body_md[2] if selector.empty? | |
376 args << VarInfo.new("...", "vararg", "") if variadic | |
377 method = MethodInfo.new(retval, selector, line[0] == ?+, args, data) | |
378 | |
379 if current_category && current_interface == "NSObject" | |
380 (@informal_protocols[current_category] ||= []) << method | |
381 end | |
382 | |
383 @classes[current_interface] ||= {} | |
384 | |
385 if header.name == current_interface | |
386 @classes[current_interface].file = header.name | |
387 else | |
388 @classes[current_interface].file ||= header.name | |
389 end | |
390 | |
391 unless parent == current_interface || parent =~ /\s+/ || parent.nil? | |
392 @classes[current_interface].parent = parent | |
393 end | |
394 | |
395 (@classes[current_interface].methods ||= []) << method | |
396 end | |
397 i += size | |
398 end | |
399 end | |
400 | |
401 def structs (header) | |
402 re = /typedef\s+struct\s*\w*\s*((\w+)|\{([^{}]*(\{[^}]+\})?)*\}\s*([^\s]+))\s*(__attribute__\(.+\))?\s*;/ | |
403 i = 0 | |
404 body = nil | |
405 @cpp_result.scan(re).each do |m| | |
406 struct = { :name => m[4], :members => [] } | |
407 | |
408 unless struct[:name].nil? | |
409 if struct[:name][0, 1] == "*" | |
410 struct[:name].sub!("*", "") | |
411 end | |
412 end | |
413 | |
414 return_type = nil | |
415 stripped_return_type = nil | |
416 body = m[2] | |
417 | |
418 if m[2] | |
419 m[2].split(/,|;/).map do |i| | |
420 str, bytes = i.split(":", 2).map do |x| | |
421 x.strip | |
422 end | |
423 | |
424 var = get_var_info(str, true) | |
425 | |
426 if var | |
427 if var.return_type == "***dummy***" | |
428 var.return_type = return_type | |
429 var.stripped_return_type = stripped_return_type | |
430 else | |
431 return_type = var.return_type | |
432 stripped_return_type = var.stripped_return_type | |
433 end | |
434 | |
435 struct[:members] << { :name => var.name, :bytes => bytes, :declaredType => var.return_type, :type => "", :type64 => "" } unless str.empty? || str[0] == ?# | |
436 | |
437 names = [] | |
438 | |
439 tmp = struct[:members].collect do |member| | |
440 unless names.include?(member[:name]) | |
441 names << member[:name] | |
442 member | |
443 end | |
444 end | |
445 | |
446 struct[:members] = tmp.compact | |
447 end | |
448 end | |
449 end | |
450 | |
451 header.structs << struct if body | |
452 end | |
453 end | |
454 | |
455 def functions (header, inline = false) | |
456 if inline | |
457 inline_func_re = /(inline|__inline__)\s+((__attribute__\(\([^)]*\)\)\s+)?([\w\s\*<>]+)\s*\(([^)]*)\)\s*)\{/ | |
458 res = @cpp_result.scan(inline_func_re) | |
459 res.each do |x| | |
460 x.delete_at(0) | |
461 x.delete_at(1) | |
462 end | |
463 else | |
464 skip_inline_re = /(static)?\s(inline|__inline__)[^{;]+(;|\{([^{}]*(\{[^}]+\})?)*\})\s*/ | |
465 func_re = /(^([\w\s\*<>]+)\s*\(([^)]*)\)\s*)(__attribute__[^;]+)?;/ | |
466 res = @cpp_result.gsub(skip_inline_re, '').scan(func_re) | |
467 end | |
468 | |
469 funcs = res.map do |m| | |
470 orig, base, args = m | |
471 base.sub!(/^.*extern\s/, "") | |
472 func = constant?(base) | |
473 | |
474 if func | |
475 args = args.strip.split(",").map do |i| | |
476 constant?(i) | |
477 end | |
478 | |
479 next if args.any? do |x| | |
480 x.nil? | |
481 end | |
482 | |
483 args = [] if args.size == 1 && args[0].return_type == "void" | |
484 FunctionInfo.new(func, args, orig, inline) | |
485 end | |
486 end.compact | |
487 | |
488 if inline | |
489 header.inline_functions = funcs | |
490 else | |
491 header.functions = funcs | |
492 end | |
493 end | |
494 | |
495 def prepare (path) | |
496 @file_content = File.read(path) | |
497 @file_content.gsub!(%r{(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)}, ""); | |
498 @complete_cpp_result, @cpp_result = do_cpp(path, false, true, "") | |
499 end | |
500 | |
501 def scan (frameworks, headers) | |
502 @frameworks = frameworks | |
503 @headers = headers | |
504 | |
505 @frameworks.each do |framework| | |
506 framework.headers.each do |header| | |
507 prepare(header.path) | |
508 | |
509 imports(header) | |
510 cftypes(header) | |
511 externs(header) | |
512 constants(header) | |
513 enums(header) | |
514 structs(header) | |
515 typedefs(header) | |
516 functions(header) | |
517 functions(header, true) | |
518 defines(header) | |
519 methods(header) | |
520 function_pointer_types(header) | |
521 end | |
522 end | |
523 | |
524 @headers.each do |header| | |
525 prepare(header.path) | |
526 | |
527 imports(header) | |
528 cftypes(header) | |
529 externs(header) | |
530 constants(header) | |
531 enums(header) | |
532 structs(header) | |
533 typedefs(header) | |
534 functions(header) | |
535 functions(header, true) | |
536 defines(header) | |
537 methods(header) | |
538 function_pointer_types(header) | |
539 end | |
540 end | |
541 | |
542 def get_var_info (str, multi = false) | |
543 str.strip! | |
544 | |
545 return nil if str.empty? | |
546 | |
547 if str == "..." | |
548 VarInfo.new("...", "...", str) | |
549 else | |
550 str = "***dummy*** " + str if str[-1].chr == '*' || str.index(/\s/).nil? | |
551 tokens = multi ? str.split(',') : [str] | |
552 part = tokens.first | |
553 re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/ | |
554 m = re.match(part) | |
555 | |
556 if m | |
557 return nil if m[1].split(/\s+/).any? do |x| | |
558 ['end', 'typedef'].include?(x) | |
559 end | |
560 | |
561 m = m.to_a[1..-1].compact.map do |i| | |
562 i.strip | |
563 end | |
564 | |
565 m[0] += m[2] if m.size == 3 | |
566 m[0] = 'void' if m[1] == 'void' | |
567 | |
568 var = begin | |
569 VarInfo.new(m[0], m[1], part) | |
570 rescue | |
571 return nil | |
572 end | |
573 | |
574 if tokens.size > 1 | |
575 [var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }] | |
576 else | |
577 var | |
578 end | |
579 end | |
580 end | |
581 end | |
582 | |
583 def constant? (str, multi = false) | |
584 str.strip! | |
585 return nil if str.empty? | |
586 | |
587 if str == '...' | |
588 VarInfo.new('...', '...', str) | |
589 else | |
590 str << " dummy" if str[-1].chr == '*' or str.index(/\s/).nil? | |
591 tokens = multi ? str.split(',') : [str] | |
592 part = tokens.first | |
593 re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/ | |
594 m = re.match(part) | |
595 | |
596 if m | |
597 return nil if m[1].split(/\s+/).any? do |x| | |
598 ['end', 'typedef'].include?(x) | |
599 end | |
600 | |
601 m = m.to_a[1..-1].compact.map do |i| | |
602 i.strip | |
603 end | |
604 | |
605 m[0] += m[2] if m.size == 3 | |
606 m[0] = 'void' if m[1] == 'void' | |
607 | |
608 var = begin | |
609 VarInfo.new(m[0], m[1], part) | |
610 rescue | |
611 return nil | |
612 end | |
613 | |
614 if tokens.size > 1 | |
615 [var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }] | |
616 else | |
617 var | |
618 end | |
619 end | |
620 end | |
621 end | |
622 | |
623 class VarInfo | |
624 | |
625 attr_reader :name, :orig, :const, :type_modifier | |
626 attr_accessor :octype, :resolved_type, :resolved_type64, :return_type, :stripped_return_type | |
627 | |
628 def initialize (type, name, orig) | |
629 @return_type = type.clone | |
630 @name = name | |
631 @orig = orig | |
632 @const = false | |
633 @type_modifier = "" | |
634 | |
635 @return_type.gsub!(/\[[^\]]*\]/, "*") | |
636 | |
637 if @return_type =~ /\bconst\b/ | |
638 @const = true | |
639 @return_type.gsub!("const", "") | |
640 end | |
641 | |
642 if @return_type =~ /\b(in|out|inout|oneway|bycopy|byref)\b/ | |
643 | |
644 case $1 | |
645 when "in" | |
646 @type_modifier << "n" | |
647 when "out" | |
648 @type_modifier << "o" | |
649 when "inout" | |
650 @type_modifier << "N" | |
651 when "oneway" | |
652 @type_modifier << "w" | |
653 when "bycopy" | |
654 @type_modifier << "c" | |
655 when "byref" | |
656 @type_modifier << "r" | |
657 end | |
658 | |
659 @return_type.gsub!("#{$1}", "") | |
660 end | |
661 | |
662 @return_type.gsub!(/\s?\*\s?/, "*") | |
663 @return_type.gsub!(/^\(|\)$/, "") | |
664 @return_type.strip! | |
665 | |
666 t = type.gsub(/\b(__)?const\b/, "") | |
667 t.gsub!(/<[^>]*>/, '') | |
668 t.gsub!(/\b(in|out|inout|oneway|const)\b/, "") | |
669 t.gsub!(/\b__private_extern__\b/, "") | |
670 t.gsub!(/^\s*\(?\s*/, "") | |
671 t.gsub!(/\s*\)?\s*$/, "") | |
672 | |
673 raise "Empty type (was '#{type}')" if t.empty? | |
674 | |
675 @stripped_return_type = t | |
676 end | |
677 | |
678 def function_pointer? (function_pointer_types) | |
679 type = @function_pointer_types[@stripped_return_type] || @stripped_return_type | |
680 @function_pointer_type ||= FunctionPointerInfo.new_from_type(type) | |
681 end | |
682 | |
683 def <=>(x) | |
684 self.name <=> x.name | |
685 end | |
686 | |
687 def hash | |
688 @name.hash | |
689 end | |
690 | |
691 def eql? (o) | |
692 @name == o.name | |
693 end | |
694 end | |
695 | |
696 class FunctionInfo < VarInfo | |
697 | |
698 attr_reader :args, :argc | |
699 | |
700 def initialize (function, args, orig, inline = false) | |
701 super(function.return_type, function.name, orig) | |
702 | |
703 @args = args | |
704 @argc = @args.size | |
705 @variadic = false | |
706 | |
707 if @args[-1] && @args[-1].return_type == "..." | |
708 @argc -= 1 | |
709 @variadic = true | |
710 @args.pop | |
711 end | |
712 | |
713 @inline = inline | |
714 self | |
715 end | |
716 | |
717 def variadic? | |
718 @variadic | |
719 end | |
720 | |
721 def inline? | |
722 @inline | |
723 end | |
724 end | |
725 | |
726 class FunctionPointerInfo < FunctionInfo | |
727 def initialize (return_type, arg_types, orig) | |
728 args = arg_types.map do |x| | |
729 VarInfo.new(x, "", "") | |
730 end | |
731 | |
732 super(VarInfo.new(return_type, "", ""), args, orig) | |
733 end | |
734 | |
735 def self.new_from_type (type) | |
736 @cache ||= {} | |
737 info = @cache[type] | |
738 | |
739 return info if info | |
740 | |
741 tokens = type.split(/\(\*\)/) | |
742 return nil if tokens.size != 2 | |
743 | |
744 return_type = tokens.first.strip | |
745 rest = tokens.last.sub(/^\s*\(\s*/, "").sub(/\s*\)\s*$/, "") | |
746 | |
747 arg_types = rest.split(/,/).map do |x| | |
748 x.strip | |
749 end | |
750 | |
751 @cache[type] = self.new(return_type, arg_types, type) | |
752 end | |
753 end | |
754 | |
755 class MethodInfo < FunctionInfo | |
756 | |
757 attr_reader :selector | |
758 | |
759 def initialize (method, selector, is_class, args, orig) | |
760 super(method, args, orig) | |
761 | |
762 @selector = selector | |
763 @is_class = is_class | |
764 self | |
765 end | |
766 | |
767 def class_method? | |
768 @is_class | |
769 end | |
770 | |
771 def <=>(o) | |
772 @selector <=> o.selector | |
773 end | |
774 | |
775 def hash | |
776 @selector.hash | |
777 end | |
778 | |
779 def eql? (o) | |
780 @selector == o.selector | |
781 end | |
782 end | |
783 | |
784 def do_cpp (path, fails_on_error = true, do_64 = true, flags = "") | |
785 f_on = false | |
786 err_file = '/tmp/.cpp.err' | |
787 cpp_line = "#{CPP} #{CPPFLAGS} #{flags} #{do_64 ? '-D__LP64__' : ''} \"#{path}\" 2>#{err_file}" | |
788 complete_result = `#{cpp_line}` | |
789 | |
790 if $?.to_i != 0 && fails_on_error | |
791 $stderr.puts File.read(err_file) | |
792 File.unlink(err_file) | |
793 raise "#{CPP} returned #{$?.to_int/256} exit status\nline was: #{cpp_line}" | |
794 end | |
795 | |
796 result = complete_result.select do |s| | |
797 # First pass to only grab non-empty lines and the pre-processed lines | |
798 # only from the target header (and not the entire pre-processing result). | |
799 next if s.strip.empty? | |
800 m = %r{^#\s*\d+\s+"([^"]+)"}.match(s) | |
801 f_on = (File.basename(m[1]) == File.basename(path)) if m | |
802 f_on | |
803 end.select do |s| | |
804 # Second pass to ignore all pro-processor comments that were left. | |
805 /^#/.match(s) == nil | |
806 end.join | |
807 | |
808 File.unlink(err_file) | |
809 return [complete_result, result] | |
810 end | |
811 end | |
812 | |
813 class DStepGenerator | |
814 | |
815 VERSION = 1.0 | |
816 | |
817 attr_accessor :out_file, :scaner | |
818 | |
819 def initialize | |
820 @do_64bit = false | |
821 @frameworks = [] | |
822 @framework_paths = [] | |
823 @headers = [] | |
824 @import_directives = "" | |
825 @informal_protocols = [] | |
826 @classes = [] | |
827 @scaner = HeaderScaner.new | |
828 @scaner.do_64bit = @do_64bit | |
829 end | |
830 | |
831 def do_64bit | |
832 return @do_64bit | |
833 end | |
834 | |
835 def do_64bit= (do_64bit) | |
836 @do_64bit = do_64bit | |
837 @scaner.do_64bit = do_64bit | |
838 end | |
839 | |
840 def collect | |
841 scaner.scan(@frameworks, @headers) | |
842 @classes = scaner.classes | |
843 @informal_protocols = scaner.protocols | |
844 end | |
845 | |
846 def handle_framework (framework) | |
847 val = framework.name | |
848 path = framework_path(val) | |
849 | |
850 raise "Can't locate framework '#{val}'" if path.nil? | |
851 @framework_paths << File.dirname(path) | |
852 raise "Can't find framework '#{val}'" if path.nil? | |
853 | |
854 parent_path, name = path.scan(/^(.+)\/(\w+)\.framework\/?$/)[0] | |
855 | |
856 if @private | |
857 headers_path = File.join(path, "PrivateHeaders") | |
858 raise "Can't locate private framework headers at '#{headers_path}'" unless File.exist?(headers_path) | |
859 | |
860 headers = Dir.glob(File.join(headers_path, "**", "*.h")) | |
861 public_headers_path = File.join(path, "Headers") | |
862 public_headers = if File.exist?(public_headers_path) | |
863 HeaderScaner::CPPFLAGS << " -I#{public_headers_path} " | |
864 Dir.glob(File.join8(headers_path, "**", "*.h")) | |
865 else | |
866 [] | |
867 end | |
868 else | |
869 headers_path = File.join(path, "Headers") | |
870 raise "Can't locate public framework headers at '#{headers_path}'" unless File.exist?(headers_path) | |
871 public_headers = headers = Dir.glob(File.join(headers_path, "**", "*.h")) | |
872 end | |
873 | |
874 # We can't just "#import <x/x.h>" as the main Framework header might not include _all_ headers. | |
875 # So we are tricking this by importing the main header first, then all headers. | |
876 header_basenames = (headers | public_headers).map do |x| | |
877 x.sub(/#{headers_path}\/*/, "") | |
878 end | |
879 | |
880 if idx = header_basenames.index("#{name}.h") | |
881 header_basenames.delete_at(idx) | |
882 header_basenames.unshift("#{name}.h") | |
883 end | |
884 | |
885 @import_directives = header_basenames.map do |x| | |
886 "#import <#{name}/#{x}>" | |
887 end.join("\n") | |
888 | |
889 @import_directives << "\n" | |
890 | |
891 @compiler_flags ||= "-F\"#{parent_path}\" -framework #{name}" | |
892 @cpp_flags ||= "" | |
893 @cpp_flags << "-F\"#{parent_path}\" " | |
894 | |
895 headers.each do |header| | |
896 header_file = HeaderFile.new | |
897 header_file.path = header | |
898 header_file.name = File.basename(header, File.extname(header)) | |
899 header_file.framework = framework.name | |
900 | |
901 framework.headers << header_file | |
902 end | |
903 | |
904 # Memorize the dependencies. | |
905 @dependencies = DStepGenerator.dependencies_of_framework(path) | |
906 end | |
907 | |
908 def framework_path (val) | |
909 return path if File.exist?(val) | |
910 | |
911 val += ".framework" unless /\.framework$/.match(val) | |
912 paths = ["/System/Library/Frameworks", "/Library/Frameworks", "#{ENV['HOME']}/Library/Frameworks"] | |
913 paths << "/System/Library/PrivateFrameworks" if @private | |
914 | |
915 paths.each do |dir| | |
916 path = File.join(dir, val) | |
917 return path if File.exist?(path) | |
918 end | |
919 | |
920 return nil | |
921 end | |
922 | |
923 def self.dependencies_of_framework (path) | |
924 @dependencies ||= {} | |
925 name = File.basename(path, ".framework") | |
926 path = File.join(path, name) | |
927 deps = @dependencies[path] | |
928 | |
929 if deps.nil? | |
930 deps = `otool -L "#{path}"`.scan(/\t([^\s]+)/).map do |m| | |
931 dpath = m[0] | |
932 next if File.basename(dpath) == name | |
933 next if dpath.include?("PrivateFrameworks") | |
934 next unless dpath.sub!(/\.framework\/Versions\/\w+\/\w+$/, "") | |
935 dpath + ".framework" | |
936 end.compact | |
937 | |
938 @dependencies[path] = deps | |
939 end | |
940 | |
941 return deps | |
942 end | |
943 | |
944 def compile_and_execute (code, enable_64bit = false) | |
945 compiler_line = "gcc " | |
946 src = File.new(unique_tmp_path("src", ".m"), "w") | |
947 src << code | |
948 src.close | |
949 | |
950 arch_flag = if enable_64bit | |
951 "-arch x86_64 -arch ppc64" | |
952 else | |
953 "-arch i386 -arch ppc" | |
954 end | |
955 | |
956 compiler_line << arch_flag | |
957 | |
958 bin = unique_tmp_path "bin" | |
959 log = unique_tmp_path "log" | |
960 | |
961 line = "#{compiler_line} -o #{bin} #{src.path} #{@compiler_flags}> #{log}" | |
962 | |
963 unless system(line) | |
964 msg = "Cannot compile Objective-C code ...aborting\nCommand was: #{line}\n\nLog:\n#{File.read(log)}\n\n" | |
965 $stderr << msg | |
966 File.delete src.path | |
967 raise msg | |
968 end | |
969 | |
970 result = `#{bin}` | |
971 | |
972 unless $?.success? | |
973 raise "Cannot execute compiled Objective-C code ... aborting\nCommand was: #{line}\nBinary is: #{bin}" | |
974 end | |
975 | |
976 File.delete bin | |
977 File.delete log | |
978 File.delete src.path | |
979 | |
980 return result | |
981 end | |
982 | |
983 def unique_tmp_path (base, extension = "", dir = Dir.tmpdir) | |
984 i = 0 | |
985 loop do | |
986 path = File.join(dir, "#{base}-#{Process.pid}-#{i}#{extension}") | |
987 return path unless File.exists?(path) | |
988 i += 1 | |
989 end | |
990 end | |
991 | |
992 def add_header (path) | |
993 header = HeaderFile.new | |
994 header.path = path | |
995 header.name = File.basename(path, File.extname(path)) | |
996 @import_directives << "#include <#{path}>\n" | |
997 @headers << header | |
998 end | |
999 | |
1000 def add_framework (name) | |
1001 framework = Framework.new | |
1002 framework.name = name | |
1003 handle_framework(framework) | |
1004 #@import_directives << "#import <#{framework.name}/#{framework.name}.h>\n" | |
1005 @frameworks << framework | |
1006 end | |
1007 | |
1008 def collect_header_types (header, enable_64bit) | |
1009 types = [] | |
1010 | |
1011 header.cftypes.each do |cftype| | |
1012 types << cftype[:type] | |
1013 end | |
1014 | |
1015 header.constants.each do |constant| | |
1016 types << constant.stripped_return_type | |
1017 end | |
1018 | |
1019 header.structs.each do |struct| | |
1020 types << struct[:name] | |
1021 | |
1022 struct[:members].each do |member| | |
1023 types << member[:declaredType] | |
1024 end | |
1025 end | |
1026 | |
1027 header.typedefs.each do |typedef| | |
1028 types << typedef.stripped_return_type | |
1029 end | |
1030 | |
1031 header.functions.each do |function| | |
1032 types << function.stripped_return_type | |
1033 | |
1034 function.args.each do |arg| | |
1035 types << arg.stripped_return_type | |
1036 end | |
1037 end | |
1038 | |
1039 types | |
1040 end | |
1041 | |
1042 def collect_classes_types (enable_64bit) | |
1043 types = [] | |
1044 | |
1045 @classes.each do |clazz, value| | |
1046 value.methods.each do |method| | |
1047 types << method.stripped_return_type | |
1048 | |
1049 method.args.each do |arg| | |
1050 types << arg.stripped_return_type | |
1051 end | |
1052 end | |
1053 end | |
1054 | |
1055 types | |
1056 end | |
1057 | |
1058 def collect_informal_protocols_types (enable_64bit) | |
1059 types = [] | |
1060 | |
1061 @informal_protocols.each do |name, methods| | |
1062 methods.each do |method| | |
1063 types << method.stripped_return_type | |
1064 | |
1065 method.args.each do |arg| | |
1066 types << arg.stripped_return_type | |
1067 end | |
1068 end | |
1069 end | |
1070 | |
1071 types | |
1072 end | |
1073 | |
1074 def resolve_header_types (header, enable_64bit, resolved_types, x) | |
1075 i = x | |
1076 | |
1077 header.cftypes.each do |cftype| | |
1078 cftype[enable_64bit ? :type64 : :type] = resolved_types[i] | |
1079 i += 1 | |
1080 end | |
1081 | |
1082 header.constants.each do |constant| | |
1083 constant.resolved_type = resolved_types[i] unless enable_64bit | |
1084 constant.resolved_type64 = resolved_types[i] if enable_64bit | |
1085 i += 1 | |
1086 end | |
1087 | |
1088 header.structs.each do |struct| | |
1089 struct[enable_64bit ? :type64 : :type] = resolved_types[i] | |
1090 i += 1 | |
1091 | |
1092 struct[:members].each do |member| | |
1093 member[enable_64bit ? :type64 : :type] = resolved_types[i] | |
1094 i += 1 | |
1095 end | |
1096 end | |
1097 | |
1098 header.typedefs.each do |typedef| | |
1099 typedef.resolved_type = resolved_types[i] unless enable_64bit | |
1100 typedef.resolved_type64 = resolved_types[i] if enable_64bit | |
1101 i += 1 | |
1102 end | |
1103 | |
1104 header.functions.each do |function| | |
1105 function.resolved_type = resolved_types[i] unless enable_64bit | |
1106 function.resolved_type64 = resolved_types[i] if enable_64bit | |
1107 i += 1 | |
1108 | |
1109 function.args.each do |arg| | |
1110 arg.resolved_type = resolved_types[i] unless enable_64bit | |
1111 arg.resolved_type64 = resolved_types[i] if enable_64bit | |
1112 i += 1 | |
1113 end | |
1114 end | |
1115 | |
1116 i | |
1117 end | |
1118 | |
1119 def resolve_methods_types (enable_64bit, resolved_types, x) | |
1120 i = x | |
1121 | |
1122 @classes.each do |clazz, value| | |
1123 value.methods.each do |method| | |
1124 method.resolved_type = resolved_types[i] unless enable_64bit | |
1125 method.resolved_type64 = resolved_types[i] if enable_64bit | |
1126 i += 1 | |
1127 | |
1128 method.args.each do |arg| | |
1129 arg.resolved_type = resolved_types[i] unless enable_64bit | |
1130 arg.resolved_type64 = resolved_types[i] if enable_64bit | |
1131 i += 1 | |
1132 end | |
1133 end | |
1134 end | |
1135 | |
1136 i | |
1137 end | |
1138 | |
1139 def resolve_informal_protocols_types (enable_64bit, resolved_types, x) | |
1140 i = x | |
1141 | |
1142 @informal_protocols.each do |name, methods| | |
1143 methods.each do |method| | |
1144 method.resolved_type = resolved_types[i] unless enable_64bit | |
1145 method.resolved_type64 = resolved_types[i] if enable_64bit | |
1146 i += 1 | |
1147 | |
1148 method.args.each do |arg| | |
1149 arg.resolved_type = resolved_types[i] unless enable_64bit | |
1150 arg.resolved_type64 = resolved_types[i] if enable_64bit | |
1151 i += 1 | |
1152 end | |
1153 end | |
1154 end | |
1155 | |
1156 i | |
1157 end | |
1158 | |
1159 def resolve_types (enable_64bit = false) | |
1160 code = "#include <stdio.h>\n" | |
1161 code << @import_directives | |
1162 types = [] | |
1163 | |
1164 @frameworks.each do |framework| | |
1165 framework.headers.each do |header| | |
1166 types << collect_header_types(header, enable_64bit) | |
1167 end | |
1168 end | |
1169 | |
1170 @headers.each do |header| | |
1171 types << collect_header_types(header, enable_64bit) | |
1172 end | |
1173 | |
1174 types << collect_classes_types(enable_64bit) | |
1175 types << collect_informal_protocols_types(enable_64bit) | |
1176 | |
1177 code << "int main () \n{\n" | |
1178 types.flatten! | |
1179 | |
1180 types.each do |type| | |
1181 code << ' printf("%s\n", ' + "@encode(#{type}));\n" | |
1182 end | |
1183 | |
1184 code << " return 0;\n}" | |
1185 | |
1186 resolved_types = [] | |
1187 | |
1188 compile_and_execute(code, enable_64bit).split("\n").each do |line| | |
1189 resolved_types << line | |
1190 end | |
1191 | |
1192 i = 0 | |
1193 | |
1194 @frameworks.each do |framework| | |
1195 framework.headers.each do |header| | |
1196 i = resolve_header_types(header, enable_64bit, resolved_types, i) | |
1197 end | |
1198 end | |
1199 | |
1200 @headers.each do |header| | |
1201 i = resolve_header_types(header, enable_64bit, resolved_types, i) | |
1202 end | |
1203 | |
1204 i = resolve_methods_types(enable_64bit, resolved_types, i) | |
1205 i = resolve_informal_protocols_types(enable_64bit, resolved_types, i) | |
1206 end | |
1207 | |
1208 def generate_header (xml, header) | |
1209 xml.file :name => header.name do | |
1210 header.imports.each do |import| | |
1211 xml.import import | |
1212 end | |
1213 | |
1214 header.defines.each do |define| | |
1215 xml.define define | |
1216 end | |
1217 | |
1218 header.cftypes.each do |cftype| | |
1219 xml.cftype cftype | |
1220 end | |
1221 | |
1222 header.constants.each do |constant| | |
1223 xml.constant :name => constant.name, :declaredType => constant.return_type, :type => constant.resolved_type, :type64 => constant.resolved_type64, :const => constant.const | |
1224 end | |
1225 | |
1226 header.enums.each do |enum| | |
1227 xml.enum :name => enum[:name] do | |
1228 enum[:members].each do |member| | |
1229 xml.member member | |
1230 end | |
1231 end | |
1232 end | |
1233 | |
1234 header.structs.each do |struct| | |
1235 xml.struct :name => struct[:name], :type => struct[:type], :type64 => struct[:type64] do | |
1236 struct[:members].each do |member| | |
1237 xml.member member | |
1238 end | |
1239 end | |
1240 end | |
1241 | |
1242 header.typedefs.each do |typedef| | |
1243 xml.typedef :name => typedef.name, :declaredType => typedef.return_type, :type => typedef.resolved_type, :type64 => typedef.resolved_type64, :const => typedef.const | |
1244 end | |
1245 | |
1246 header.functions.each do |function| | |
1247 xml.function :name => function.name, :inline => function.inline?, :variadic => function.variadic? do | |
1248 function.args.each do |arg| | |
1249 xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier | |
1250 end | |
1251 | |
1252 xml.returnValue :declaredType => function.return_type, :type => function.resolved_type, :type64 => function.resolved_type64, :const => function.const, :typeModifier => function.type_modifier | |
1253 end | |
1254 end | |
1255 end | |
1256 end | |
1257 | |
1258 def generate_classes (xml) | |
1259 @classes.each do |clazz, value| | |
1260 xml.class :name => clazz, :parent => value.parent, :file => value.file do | |
1261 value.methods.each do |method| | |
1262 xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do | |
1263 method.args.each do |arg| | |
1264 xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier | |
1265 end | |
1266 | |
1267 xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier | |
1268 end | |
1269 end | |
1270 end | |
1271 end | |
1272 end | |
1273 | |
1274 def generate_informal_protocols (xml) | |
1275 @informal_protocols.each do |name, methods| | |
1276 xml.informalProtocol :name => name do | |
1277 methods.each do |method| | |
1278 xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do | |
1279 method.args.each do |arg| | |
1280 xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier | |
1281 end | |
1282 | |
1283 xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier | |
1284 end | |
1285 end | |
1286 end | |
1287 end | |
1288 end | |
1289 | |
1290 def generate | |
1291 resolve_types | |
1292 resolve_types(true) if @do_64bit | |
1293 | |
1294 file = STDOUT if @out_file == nil | |
1295 file = File.open @out_file, "w" unless @out_file == nil | |
1296 | |
1297 xml = XmlMarkup.new(:target => file, :indent => 4) | |
1298 xml.instruct! | |
1299 | |
1300 xml.dstep :xmlns => "http://www.dsource.org/projects/dstep", "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation" => "http://www.dsource.org/projects/dstep/trunk/scripts/dstep.xsd" do | |
1301 @frameworks.each do |framework| | |
1302 xml.framework :name => framework.name do | |
1303 framework.headers.each do |header| | |
1304 generate_header(xml, header) | |
1305 end | |
1306 end | |
1307 end | |
1308 | |
1309 @headers.each do |header| | |
1310 generate_header(xml, header) | |
1311 end | |
1312 | |
1313 generate_classes(xml) | |
1314 generate_informal_protocols(xml) | |
1315 end | |
1316 | |
1317 file.close unless file == STDOUT | |
1318 end | |
1319 end | |
1320 | |
1321 def die (*msg) | |
1322 $stderr.puts msg | |
1323 exit 1 | |
1324 end | |
1325 | |
1326 if __FILE__ == $0 | |
1327 dstep_gen = DStepGenerator.new | |
1328 | |
1329 OptionParser.new do |opts| | |
1330 opts.banner = "Usage: #{File.basename(__FILE__)} [options] <headers...>" | |
1331 opts.separator "" | |
1332 opts.separator "Options:" | |
1333 | |
1334 opts.on("-f", "--framework FRAMEWORK", "Generate metadata for the given framework.") do |opt| | |
1335 dstep_gen.add_framework(opt) | |
1336 end | |
1337 | |
1338 opts.on(nil, "--64-bit", "Write 64-bit annotations.") do | |
1339 dstep_gen.do_64bit = true | |
1340 end | |
1341 | |
1342 opts.on("-o", "--output FILE", "Write output to the given file.") do |opt| | |
1343 die "Output file can't be specified more than once" if dstep_gen.out_file | |
1344 dstep_gen.out_file = opt | |
1345 end | |
1346 | |
1347 # opts.on("-d", "--output-dir PATH", "Write ouptut to the given paht, use this with the --framework option") do |opt| | |
1348 # die "Output directory can't be specified more than once" if dstep_gen.out_dir | |
1349 # dstep_gen.out_dir = opt | |
1350 # end | |
1351 | |
1352 | |
1353 | |
1354 help_msg = "Use the `-h' flag or for help." | |
1355 | |
1356 opts.on("-h", "--help", "Show this message.") do | |
1357 puts opts, help_msg | |
1358 exit | |
1359 end | |
1360 | |
1361 opts.on('-v', '--version', 'Show version.') do | |
1362 puts DStepGenerator::VERSION | |
1363 exit | |
1364 end | |
1365 | |
1366 opts.separator "" | |
1367 | |
1368 if ARGV.empty? | |
1369 die opts.banner | |
1370 else | |
1371 begin | |
1372 opts.parse!(ARGV) | |
1373 | |
1374 ARGV.each do |header| | |
1375 dstep_gen.add_header(header) | |
1376 end | |
1377 | |
1378 dstep_gen.collect | |
1379 dstep_gen.generate | |
1380 rescue => e | |
1381 msg = e.message | |
1382 msg = "Internal error" if msg.empty? | |
1383 | |
1384 die msg, opts.banner, help_msg | |
1385 end | |
1386 end | |
1387 end | |
1388 end |