ripper_ruby_parser 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +3 -3
  4. data/Rakefile +4 -4
  5. data/lib/ripper_ruby_parser/commenting_ripper_parser.rb +5 -7
  6. data/lib/ripper_ruby_parser/parser.rb +2 -3
  7. data/lib/ripper_ruby_parser/sexp_handlers/arguments.rb +2 -6
  8. data/lib/ripper_ruby_parser/sexp_handlers/assignment.rb +16 -17
  9. data/lib/ripper_ruby_parser/sexp_handlers/blocks.rb +5 -5
  10. data/lib/ripper_ruby_parser/sexp_handlers/conditionals.rb +6 -7
  11. data/lib/ripper_ruby_parser/sexp_handlers/hashes.rb +4 -5
  12. data/lib/ripper_ruby_parser/sexp_handlers/helper_methods.rb +7 -11
  13. data/lib/ripper_ruby_parser/sexp_handlers/literals.rb +35 -26
  14. data/lib/ripper_ruby_parser/sexp_handlers/loops.rb +19 -18
  15. data/lib/ripper_ruby_parser/sexp_handlers/method_calls.rb +15 -15
  16. data/lib/ripper_ruby_parser/sexp_handlers/methods.rb +9 -18
  17. data/lib/ripper_ruby_parser/sexp_handlers/operators.rb +10 -10
  18. data/lib/ripper_ruby_parser/sexp_processor.rb +11 -14
  19. data/lib/ripper_ruby_parser/syntax_error.rb +1 -3
  20. data/lib/ripper_ruby_parser/version.rb +1 -1
  21. data/test/end_to_end/comparison_test.rb +0 -1
  22. data/test/end_to_end/lib_comparison_test.rb +0 -2
  23. data/test/end_to_end/line_numbering_test.rb +0 -1
  24. data/test/end_to_end/samples_comparison_test.rb +1 -1
  25. data/test/end_to_end/test_comparison_test.rb +0 -3
  26. data/test/pt_testcase/pt_test.rb +4 -4
  27. data/test/samples/inline.rb +704 -0
  28. data/test/test_helper.rb +39 -37
  29. data/test/unit/commenting_ripper_parser_test.rb +57 -59
  30. data/test/unit/parser_blocks_test.rb +19 -3
  31. data/test/unit/parser_conditionals_test.rb +0 -1
  32. data/test/unit/parser_literals_test.rb +25 -25
  33. data/test/unit/parser_method_calls_test.rb +19 -15
  34. data/test/unit/parser_test.rb +31 -24
  35. data/test/unit/sexp_processor_test.rb +1 -14
  36. metadata +67 -51
  37. data/lib/ripper_ruby_parser/sexp_ext.rb +0 -14
@@ -0,0 +1,704 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ ##
4
+ # Ruby Inline is a framework for writing ruby extensions in foreign
5
+ # languages.
6
+ #
7
+ # == SYNOPSIS
8
+ #
9
+ # require 'inline'
10
+ # class MyClass
11
+ # inline do |builder|
12
+ # builder.include "<math.h>"
13
+ # builder.c %q{
14
+ # long factorial(int max) {
15
+ # int i=max, result=1;
16
+ # while (i >= 2) { result *= i--; }
17
+ # return result;
18
+ # }
19
+ # }
20
+ # end
21
+ # end
22
+ #
23
+ # == DESCRIPTION
24
+ #
25
+ # Inline allows you to write foreign code within your ruby code. It
26
+ # automatically determines if the code in question has changed and
27
+ # builds it only when necessary. The extensions are then automatically
28
+ # loaded into the class/module that defines it.
29
+ #
30
+ # You can even write extra builders that will allow you to write
31
+ # inlined code in any language. Use Inline::C as a template and look
32
+ # at Module#inline for the required API.
33
+ #
34
+ # == PACKAGING
35
+ #
36
+ # To package your binaries into a gem, use hoe's INLINE and
37
+ # FORCE_PLATFORM env vars.
38
+ #
39
+ # Example:
40
+ #
41
+ # rake package INLINE=1
42
+ #
43
+ # or:
44
+ #
45
+ # rake package INLINE=1 FORCE_PLATFORM=mswin32
46
+ #
47
+ # See hoe for more details.
48
+ #
49
+
50
+ require "rbconfig"
51
+ require "digest/md5"
52
+ require 'fileutils'
53
+ require 'rubygems'
54
+
55
+ $TESTING = false unless defined? $TESTING
56
+
57
+ class CompilationError < RuntimeError; end
58
+
59
+ ##
60
+ # The Inline module is the top-level module used. It is responsible
61
+ # for instantiating the builder for the right language used,
62
+ # compilation/linking when needed, and loading the inlined code into
63
+ # the current namespace.
64
+
65
+ module Inline
66
+ VERSION = '3.7.0'
67
+
68
+ WINDOZE = /win(32|64)/ =~ RUBY_PLATFORM
69
+ RUBINIUS = defined? RUBY_ENGINE
70
+ DEV_NULL = (WINDOZE ? 'nul' : '/dev/null')
71
+ GEM = (WINDOZE ? 'gem.bat' : 'gem')
72
+ RAKE = if WINDOZE then
73
+ 'rake.bat'
74
+ elsif RUBINIUS then
75
+ File.join(Gem.bindir, 'rake')
76
+ else
77
+ "#{Gem.ruby} -S rake"
78
+ end
79
+
80
+
81
+ warn "RubyInline v #{VERSION}" if $DEBUG
82
+
83
+ protected
84
+
85
+ def self.rootdir
86
+ env = ENV['INLINEDIR'] || ENV['HOME']
87
+
88
+ # in case both INLINEDIR and HOME aren't defined, and under Windows
89
+ # default to HOMEDRIVE + HOMEPATH values
90
+ env = ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if env.nil? and WINDOZE
91
+
92
+ if env.nil? then
93
+ abort "Define INLINEDIR or HOME in your environment and try again"
94
+ end
95
+
96
+ unless defined? @@rootdir and env == @@rootdir and test ?d, @@rootdir then
97
+ rootdir = env
98
+ Dir.mkdir rootdir, 0700 unless test ?d, rootdir
99
+ Dir.assert_secure rootdir
100
+ @@rootdir = rootdir
101
+ end
102
+
103
+ @@rootdir
104
+ end
105
+
106
+ def self.directory
107
+ directory = File.join(rootdir, ".ruby_inline")
108
+ unless defined? @@directory and directory == @@directory
109
+ @@directory = File.join(self.rootdir, ".ruby_inline")
110
+ end
111
+ Dir.assert_secure directory
112
+ @@directory
113
+ end
114
+
115
+ # Inline::C is the default builder used and the only one provided by
116
+ # Inline. It can be used as a template to write builders for other
117
+ # languages. It understands type-conversions for the basic types and
118
+ # can be extended as needed.
119
+
120
+ class C
121
+
122
+ protected unless $TESTING
123
+
124
+ MAGIC_ARITY_THRESHOLD = 15
125
+ MAGIC_ARITY = -1
126
+
127
+ @@type_map = {
128
+ 'char' => [ 'NUM2CHR', 'CHR2FIX' ],
129
+ 'char *' => [ 'STR2CSTR', 'rb_str_new2' ],
130
+ 'double' => [ 'NUM2DBL', 'rb_float_new' ],
131
+ 'int' => [ 'F'+'IX2INT', 'INT2FIX' ],
132
+ 'long' => [ 'NUM2INT', 'INT2NUM' ],
133
+ 'unsigned int' => [ 'NUM2UINT', 'UINT2NUM' ],
134
+ 'unsigned long' => [ 'NUM2UINT', 'UINT2NUM' ],
135
+ 'unsigned' => [ 'NUM2UINT', 'UINT2NUM' ],
136
+ 'VALUE' => [ '', '' ],
137
+ # Can't do these converters because they conflict with the above:
138
+ # ID2SYM(x), SYM2ID(x), NUM2DBL(x), F\IX2UINT(x)
139
+ }
140
+
141
+ def ruby2c(type)
142
+ raise ArgumentError, "Unknown type #{type.inspect}" unless @@type_map.has_key? type
143
+ @@type_map[type].first
144
+ end
145
+
146
+ def c2ruby(type)
147
+ raise ArgumentError, "Unknown type #{type.inspect}" unless @@type_map.has_key? type
148
+ @@type_map[type].last
149
+ end
150
+
151
+ def strip_comments(src)
152
+ # strip c-comments
153
+ src = src.gsub(%r%\s*/\*.*?\*/%m, '')
154
+ # strip cpp-comments
155
+ src = src.gsub(%r%^\s*//.*?\n%, '')
156
+ src = src.gsub(%r%[ \t]*//[^\n]*%, '')
157
+ src
158
+ end
159
+
160
+ def parse_signature(src, raw=false)
161
+
162
+ sig = self.strip_comments(src)
163
+ # strip preprocessor directives
164
+ sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
165
+ # strip {}s
166
+ sig.gsub!(/\{[^\}]*\}/, '{ }')
167
+ # clean and collapse whitespace
168
+ sig.gsub!(/\s+/, ' ')
169
+
170
+ unless defined? @types then
171
+ @types = 'void|' + @@type_map.keys.map{|x| Regexp.escape(x)}.join('|')
172
+ end
173
+
174
+ if /(#{@types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
175
+ return_type, function_name, arg_string = $1, $2, $3
176
+ args = []
177
+ arg_string.split(',').each do |arg|
178
+
179
+ # helps normalize into 'char * varname' form
180
+ arg = arg.gsub(/\s*\*\s*/, ' * ').strip
181
+
182
+ if /(((#{@types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
183
+ args.push([$4, $1])
184
+ elsif arg != "void" then
185
+ warn "WAR\NING: '#{arg}' not understood"
186
+ end
187
+ end
188
+
189
+ arity = args.size
190
+ arity = MAGIC_ARITY if raw
191
+
192
+ return {
193
+ 'return' => return_type,
194
+ 'name' => function_name,
195
+ 'args' => args,
196
+ 'arity' => arity
197
+ }
198
+ end
199
+
200
+ raise SyntaxError, "Can't parse signature: #{sig}"
201
+ end # def parse_signature
202
+
203
+ def generate(src, options={})
204
+ options = {:expand_types=>options} unless Hash === options
205
+
206
+ expand_types = options[:expand_types]
207
+ singleton = options[:singleton]
208
+ result = self.strip_comments(src)
209
+
210
+ signature = parse_signature(src, !expand_types)
211
+ function_name = signature['name']
212
+ method_name = options[:method_name] || function_name
213
+ return_type = signature['return']
214
+ arity = signature['arity']
215
+
216
+ raise ArgumentError, "too many arguments" if arity > MAGIC_ARITY_THRESHOLD
217
+
218
+ if expand_types then
219
+ prefix = "static VALUE #{function_name}("
220
+ if arity == MAGIC_ARITY then
221
+ prefix += "int argc, VALUE *argv, VALUE self"
222
+ else
223
+ prefix += "VALUE self"
224
+ prefix += signature['args'].map { |arg, type| ", VALUE _#{arg}"}.join
225
+ end
226
+ prefix += ") {\n"
227
+ prefix += signature['args'].map { |arg, type|
228
+ " #{type} #{arg} = #{ruby2c(type)}(_#{arg});\n"
229
+ }.join
230
+
231
+ # replace the function signature (hopefully) with new sig (prefix)
232
+ result.sub!(/[^;\/\"\>]+#{function_name}\s*\([^\{]+\{/, "\n" + prefix)
233
+ result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
234
+ unless return_type == "void" then
235
+ raise SyntaxError, "Couldn't find return statement for #{function_name}" unless
236
+ result =~ /return/
237
+ result.gsub!(/return\s+([^\;\}]+)/) do
238
+ "return #{c2ruby(return_type)}(#{$1})"
239
+ end
240
+ else
241
+ result.sub!(/\s*\}\s*\Z/, "\nreturn Qnil;\n}")
242
+ end
243
+ else
244
+ prefix = "static #{return_type} #{function_name}("
245
+ result.sub!(/[^;\/\"\>]+#{function_name}\s*\(/, prefix)
246
+ result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
247
+ end
248
+
249
+ delta = if result =~ /\A(static.*?\{)/m then
250
+ $1.split(/\n/).size
251
+ else
252
+ warn "WAR\NING: Can't find signature in #{result.inspect}\n" unless $TESTING
253
+ 0
254
+ end
255
+
256
+ file, line = caller[1].split(/:/)
257
+ result = "# line #{line.to_i + delta} \"#{file}\"\n" + result unless $DEBUG and not $TESTING
258
+
259
+ @src << result
260
+ @sig[function_name] = [arity,singleton,method_name]
261
+
262
+ return result if $TESTING
263
+ end # def generate
264
+
265
+ def module_name
266
+ unless defined? @module_name then
267
+ module_name = @mod.name.gsub('::','__')
268
+ md5 = Digest::MD5.new
269
+ @sig.keys.sort_by { |x| x.to_s }.each { |m| md5 << m.to_s }
270
+ @module_name = "Inline_#{module_name}_#{md5.to_s[0,4]}"
271
+ end
272
+ @module_name
273
+ end
274
+
275
+ def so_name
276
+ unless defined? @so_name then
277
+ @so_name = "#{Inline.directory}/#{module_name}.#{Config::CONFIG["DLEXT"]}"
278
+ end
279
+ @so_name
280
+ end
281
+
282
+ attr_reader :rb_file, :mod
283
+ if $TESTING then
284
+ attr_writer :mod
285
+ attr_accessor :src, :sig, :flags, :libs
286
+ end
287
+
288
+ public
289
+
290
+ def initialize(mod)
291
+ raise ArgumentError, "Class/Module arg is required" unless Module === mod
292
+ # new (but not on some 1.8s) -> inline -> real_caller|eval
293
+ stack = caller
294
+ meth = stack.shift until meth =~ /in .(inline|test_|setup)/ or stack.empty?
295
+ raise "Couldn't discover caller" if stack.empty?
296
+ real_caller = stack.first
297
+ real_caller = stack[3] if real_caller =~ /\(eval\)/
298
+ real_caller = real_caller.split(/:/, 3)[0..1]
299
+ @real_caller = real_caller.join ':'
300
+ @rb_file = File.expand_path real_caller.first
301
+
302
+ @mod = mod
303
+ @src = []
304
+ @inc = []
305
+ @sig = {}
306
+ @flags = []
307
+ @libs = []
308
+ @init_extra = []
309
+ @include_ruby_first = true
310
+ end
311
+
312
+ ##
313
+ # Attempts to load pre-generated code returning true if it succeeds.
314
+
315
+ def load_cache
316
+ begin
317
+ file = File.join("inline", File.basename(so_name))
318
+ if require file then
319
+ dir = Inline.directory
320
+ warn "WAR\NING: #{dir} exists but is not being used" if test ?d, dir and $VERBOSE
321
+ return true
322
+ end
323
+ rescue LoadError
324
+ end
325
+ return false
326
+ end
327
+
328
+ ##
329
+ # Loads the generated code back into ruby
330
+
331
+ def load
332
+ require "#{so_name}" or raise LoadError, "require on #{so_name} failed"
333
+ end
334
+
335
+ ##
336
+ # Builds the source file, if needed, and attempts to compile it.
337
+
338
+ def build
339
+ so_name = self.so_name
340
+ so_exists = File.file? so_name
341
+ unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then
342
+
343
+ unless File.directory? Inline.directory then
344
+ warn "NOTE: creating #{Inline.directory} for RubyInline" if $DEBUG
345
+ Dir.mkdir Inline.directory, 0700
346
+ end
347
+
348
+ src_name = "#{Inline.directory}/#{module_name}.c"
349
+ old_src_name = "#{src_name}.old"
350
+ should_compare = File.write_with_backup(src_name) do |io|
351
+ if @include_ruby_first
352
+ @inc.unshift "#include \"ruby.h\""
353
+ else
354
+ @inc.push "#include \"ruby.h\""
355
+ end
356
+
357
+ io.puts
358
+ io.puts @inc.join("\n")
359
+ io.puts
360
+ io.puts @src.join("\n\n")
361
+ io.puts
362
+ io.puts
363
+ io.puts "#ifdef __cplusplus"
364
+ io.puts "extern \"C\" {"
365
+ io.puts "#endif"
366
+ io.puts " __declspec(dllexport)" if WINDOZE
367
+ io.puts " void Init_#{module_name}() {"
368
+ io.puts " VALUE c = rb_cObject;"
369
+
370
+ # TODO: use rb_class2path
371
+ # io.puts " VALUE c = rb_path2class(#{@mod.name.inspect});"
372
+ io.puts @mod.name.split("::").map { |n|
373
+ " c = rb_const_get(c,rb_intern(\"#{n}\"));"
374
+ }.join("\n")
375
+
376
+ @sig.keys.sort.each do |name|
377
+ arity, singleton, method_name = @sig[name]
378
+ if singleton then
379
+ io.print " rb_define_singleton_method(c, \"#{method_name}\", "
380
+ else
381
+ io.print " rb_define_method(c, \"#{method_name}\", "
382
+ end
383
+ io.puts "(VALUE(*)(ANYARGS))#{name}, #{arity});"
384
+ end
385
+ io.puts @init_extra.join("\n") unless @init_extra.empty?
386
+
387
+ io.puts
388
+ io.puts " }"
389
+ io.puts "#ifdef __cplusplus"
390
+ io.puts "}"
391
+ io.puts "#endif"
392
+ io.puts
393
+ end
394
+
395
+ # recompile only if the files are different
396
+ recompile = true
397
+ if so_exists and should_compare and
398
+ FileUtils.compare_file(old_src_name, src_name) then
399
+ recompile = false
400
+
401
+ # Updates the timestamps on all the generated/compiled files.
402
+ # Prevents us from entering this conditional unless the source
403
+ # file changes again.
404
+ t = Time.now
405
+ File.utime(t, t, src_name, old_src_name, so_name)
406
+ end
407
+
408
+ if recompile then
409
+
410
+ hdrdir = %w(srcdir archdir rubyhdrdir).map { |name|
411
+ Config::CONFIG[name]
412
+ }.find { |dir|
413
+ dir and File.exist? File.join(dir, "/ruby.h")
414
+ } or abort "ERROR: Can't find header dir for ruby. Exiting..."
415
+
416
+ flags = @flags.join(' ')
417
+ libs = @libs.join(' ')
418
+
419
+ config_hdrdir = if RUBY_VERSION > '1.9' then
420
+ "-I #{File.join hdrdir, RbConfig::CONFIG['arch']}"
421
+ else
422
+ nil
423
+ end
424
+
425
+ cmd = [ Config::CONFIG['LDSHARED'],
426
+ flags,
427
+ Config::CONFIG['CCDLFLAGS'],
428
+ Config::CONFIG['CFLAGS'],
429
+ '-I', hdrdir,
430
+ config_hdrdir,
431
+ '-I', Config::CONFIG['includedir'],
432
+ "-L#{Config::CONFIG['libdir']}",
433
+ '-o', so_name.inspect,
434
+ File.expand_path(src_name).inspect,
435
+ libs,
436
+ crap_for_windoze ].join(' ')
437
+
438
+ # TODO: remove after osx 10.5.2
439
+ cmd += ' -flat_namespace -undefined suppress' if
440
+ RUBY_PLATFORM =~ /darwin9\.[01]/
441
+ cmd += " 2> #{DEV_NULL}" if $TESTING and not $DEBUG
442
+
443
+ warn "Building #{so_name} with '#{cmd}'" if $DEBUG
444
+ result = `#{cmd}`
445
+ warn "Output:\n#{result}" if $DEBUG
446
+ if $? != 0 then
447
+ bad_src_name = src_name + ".bad"
448
+ File.rename src_name, bad_src_name
449
+ raise CompilationError, "error executing #{cmd.inspect}: #{$?}\nRenamed #{src_name} to #{bad_src_name}"
450
+ end
451
+
452
+ # NOTE: manifest embedding is only required when using VC8 ruby
453
+ # build or compiler.
454
+ # Errors from this point should be ignored if Config::CONFIG['arch']
455
+ # (RUBY_PLATFORM) matches 'i386-mswin32_80'
456
+ if WINDOZE and RUBY_PLATFORM =~ /_80$/ then
457
+ Dir.chdir Inline.directory do
458
+ cmd = "mt /manifest lib.so.manifest /outputresource:so.dll;#2"
459
+ warn "Embedding manifest with '#{cmd}'" if $DEBUG
460
+ result = `#{cmd}`
461
+ warn "Output:\n#{result}" if $DEBUG
462
+ if $? != 0 then
463
+ raise CompilationError, "error executing #{cmd}: #{$?}"
464
+ end
465
+ end
466
+ end
467
+
468
+ warn "Built successfully" if $DEBUG
469
+ end
470
+
471
+ else
472
+ warn "#{so_name} is up to date" if $DEBUG
473
+ end # unless (file is out of date)
474
+ end # def build
475
+
476
+ ##
477
+ # Returns extra compilation flags for windoze platforms. Ugh.
478
+
479
+ def crap_for_windoze
480
+ # gawd windoze land sucks
481
+ case RUBY_PLATFORM
482
+ when /mswin32/ then
483
+ " -link /LIBPATH:\"#{Config::CONFIG['libdir']}\" /DEFAULTLIB:\"#{Config::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
484
+ when /mingw32/ then
485
+ " -Wl,--enable-auto-import -L#{Config::CONFIG['libdir']} -lmsvcrt-ruby18"
486
+ when /i386-cygwin/ then
487
+ ' -L/usr/local/lib -lruby.dll'
488
+ else
489
+ ''
490
+ end
491
+ end
492
+
493
+ ##
494
+ # Adds compiler options to the compiler command line. No
495
+ # preprocessing is done, so you must have all your dashes and
496
+ # everything.
497
+
498
+ def add_compile_flags(*flags)
499
+ @flags.push(*flags)
500
+ end
501
+
502
+ ##
503
+ # Adds linker flags to the link command line. No preprocessing is
504
+ # done, so you must have all your dashes and everything.
505
+
506
+ def add_link_flags(*flags)
507
+ @libs.push(*flags)
508
+ end
509
+
510
+ ##
511
+ # Adds custom content to the end of the init function.
512
+
513
+ def add_to_init(*src)
514
+ @init_extra.push(*src)
515
+ end
516
+
517
+ ##
518
+ # Registers C type-casts +r2c+ and +c2r+ for +type+.
519
+
520
+ def add_type_converter(type, r2c, c2r)
521
+ warn "WAR\NING: overridding #{type} on #{caller[0]}" if @@type_map.has_key? type
522
+ @@type_map[type] = [r2c, c2r]
523
+ end
524
+
525
+ ##
526
+ # Maps a ruby constant to C (with the same name)
527
+
528
+ def map_ruby_const(*names)
529
+ names.each do |name|
530
+ self.prefix "static VALUE #{name};"
531
+ self.add_to_init " #{name} = rb_const_get(c, rb_intern(#{name.to_s.inspect}));"
532
+ end
533
+ end
534
+
535
+ ##
536
+ # Maps a C constant to ruby (with the same
537
+ # name). +names_and_types+ is a hash that maps the name of the
538
+ # constant to its C type.
539
+
540
+ def map_c_const(names_and_types)
541
+ names_and_types.each do |name, typ|
542
+ self.add_to_init " rb_define_const(c, #{name.to_s.inspect}, #{c2ruby(typ.to_s)}(#{name}));"
543
+ end
544
+ end
545
+
546
+ ##
547
+ # Adds an include to the top of the file. Don't forget to use
548
+ # quotes or angle brackets.
549
+
550
+ def include(header)
551
+ @inc << "#include #{header}"
552
+ end
553
+
554
+ ##
555
+ # Specifies that the the ruby.h header should be included *after* custom
556
+ # header(s) instead of before them.
557
+
558
+ def include_ruby_last
559
+ @include_ruby_first = false
560
+ end
561
+
562
+ ##
563
+ # Adds any amount of text/code to the source
564
+
565
+ def prefix(code)
566
+ @src << code
567
+ end
568
+
569
+ ##
570
+ # Adds a C function to the source, including performing automatic
571
+ # type conversion to arguments and the return value. The Ruby
572
+ # method name can be overridden by providing method_name. Unknown
573
+ # type conversions can be extended by using +add_type_converter+.
574
+
575
+ def c src, options = {}
576
+ options = {
577
+ :expand_types => true,
578
+ }.merge options
579
+ self.generate src, options
580
+ end
581
+
582
+ ##
583
+ # Same as +c+, but adds a class function.
584
+
585
+ def c_singleton src, options = {}
586
+ options = {
587
+ :expand_types => true,
588
+ :singleton => true,
589
+ }.merge options
590
+ self.generate src, options
591
+ end
592
+
593
+ ##
594
+ # Adds a raw C function to the source. This version does not
595
+ # perform any type conversion and must conform to the ruby/C
596
+ # coding conventions. The Ruby method name can be overridden
597
+ # by providing method_name.
598
+
599
+ def c_raw src, options = {}
600
+ self.generate src, options
601
+ end
602
+
603
+ ##
604
+ # Same as +c_raw+, but adds a class function.
605
+
606
+ def c_raw_singleton src, options = {}
607
+ options = {
608
+ :singleton => true,
609
+ }.merge options
610
+ self.generate src, options
611
+ end
612
+
613
+ end # class Inline::C
614
+ end # module Inline
615
+
616
+ class Module
617
+
618
+ ##
619
+ # options is a hash that allows you to pass extra data to your
620
+ # builder. The only key that is guaranteed to exist is :testing.
621
+
622
+ attr_reader :options
623
+
624
+ ##
625
+ # Extends the Module class to have an inline method. The default
626
+ # language/builder used is C, but can be specified with the +lang+
627
+ # parameter.
628
+
629
+ def inline(lang = :C, options={})
630
+ case options
631
+ when TrueClass, FalseClass then
632
+ warn "WAR\NING: 2nd argument to inline is now a hash, changing to {:testing=>#{options}}" unless options
633
+ options = { :testing => options }
634
+ when Hash
635
+ options[:testing] ||= false
636
+ else
637
+ raise ArgumentError, "BLAH"
638
+ end
639
+
640
+ builder_class = begin
641
+ Inline.const_get(lang)
642
+ rescue NameError
643
+ require "inline/#{lang}"
644
+ Inline.const_get(lang)
645
+ end
646
+
647
+ @options = options
648
+ builder = builder_class.new self
649
+
650
+ yield builder
651
+
652
+ unless options[:testing] then
653
+ unless builder.load_cache then
654
+ builder.build
655
+ builder.load
656
+ end
657
+ end
658
+ end
659
+ end
660
+
661
+ class File
662
+
663
+ ##
664
+ # Equivalent to +File::open+ with an associated block, but moves
665
+ # any existing file with the same name to the side first.
666
+
667
+ def self.write_with_backup(path) # returns true if file already existed
668
+
669
+ # move previous version to the side if it exists
670
+ renamed = false
671
+ if test ?f, path then
672
+ renamed = true
673
+ File.rename path, path + ".old"
674
+ end
675
+
676
+ File.open(path, "w") do |io|
677
+ yield(io)
678
+ end
679
+
680
+ return renamed
681
+ end
682
+ end # class File
683
+
684
+ class Dir
685
+
686
+ ##
687
+ # +assert_secure+ checks that if a +path+ exists it has minimally
688
+ # writable permissions. If not, it prints an error and exits. It
689
+ # only works on +POSIX+ systems. Patches for other systems are
690
+ # welcome.
691
+
692
+ def self.assert_secure(path)
693
+ mode = File.stat(path).mode
694
+ unless ((mode % 01000) & 0022) == 0 then
695
+ if $TESTING then
696
+ raise SecurityError, "Directory #{path} is insecure"
697
+ else
698
+ abort "#{path} is insecure (#{'%o' % mode}). It may not be group or world writable. Exiting."
699
+ end
700
+ end
701
+ rescue Errno::ENOENT
702
+ # If it ain't there, it's certainly secure
703
+ end
704
+ end