ripper_ruby_parser 1.0.0 → 1.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.
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