bitgirder-platform 0.1.7

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 (51) hide show
  1. data/LICENSE.txt +176 -0
  2. data/bin/ensure-test-db +117 -0
  3. data/bin/install-mysql +375 -0
  4. data/bin/tomcat7 +569 -0
  5. data/lib/bitgirder/concurrent.rb +400 -0
  6. data/lib/bitgirder/core.rb +1235 -0
  7. data/lib/bitgirder/etl.rb +58 -0
  8. data/lib/bitgirder/event/file.rb +485 -0
  9. data/lib/bitgirder/event/logger/testing.rb +140 -0
  10. data/lib/bitgirder/event/logger.rb +137 -0
  11. data/lib/bitgirder/event/testing.rb +88 -0
  12. data/lib/bitgirder/http.rb +255 -0
  13. data/lib/bitgirder/io/testing.rb +33 -0
  14. data/lib/bitgirder/io.rb +959 -0
  15. data/lib/bitgirder/irb.rb +35 -0
  16. data/lib/bitgirder/mysql.rb +60 -0
  17. data/lib/bitgirder/ops/java.rb +117 -0
  18. data/lib/bitgirder/ops/ruby.rb +235 -0
  19. data/lib/bitgirder/testing.rb +152 -0
  20. data/lib/doc-gen0.rb +0 -0
  21. data/lib/doc-gen1.rb +0 -0
  22. data/lib/doc-gen10.rb +0 -0
  23. data/lib/doc-gen11.rb +0 -0
  24. data/lib/doc-gen12.rb +0 -0
  25. data/lib/doc-gen13.rb +0 -0
  26. data/lib/doc-gen14.rb +0 -0
  27. data/lib/doc-gen15.rb +0 -0
  28. data/lib/doc-gen16.rb +0 -0
  29. data/lib/doc-gen17.rb +14 -0
  30. data/lib/doc-gen18.rb +0 -0
  31. data/lib/doc-gen19.rb +0 -0
  32. data/lib/doc-gen2.rb +0 -0
  33. data/lib/doc-gen20.rb +182 -0
  34. data/lib/doc-gen21.rb +0 -0
  35. data/lib/doc-gen3.rb +0 -0
  36. data/lib/doc-gen4.rb +0 -0
  37. data/lib/doc-gen5.rb +0 -0
  38. data/lib/doc-gen6.rb +0 -0
  39. data/lib/doc-gen7.rb +0 -0
  40. data/lib/doc-gen8.rb +0 -0
  41. data/lib/doc-gen9.rb +0 -0
  42. data/lib/mingle/bincodec.rb +512 -0
  43. data/lib/mingle/codec.rb +54 -0
  44. data/lib/mingle/http.rb +156 -0
  45. data/lib/mingle/io/stream.rb +142 -0
  46. data/lib/mingle/io.rb +160 -0
  47. data/lib/mingle/json.rb +257 -0
  48. data/lib/mingle/service.rb +110 -0
  49. data/lib/mingle-em.rb +92 -0
  50. data/lib/mingle.rb +2917 -0
  51. metadata +100 -0
@@ -0,0 +1,959 @@
1
+ require 'bitgirder/core'
2
+
3
+ module BitGirder
4
+ module Io
5
+
6
+ require 'fileutils'
7
+ require 'tempfile'
8
+ require 'json'
9
+ require 'yaml'
10
+ require 'base64'
11
+ require 'tmpdir'
12
+
13
+ include BitGirder::Core
14
+ include BitGirderMethods
15
+ extend BitGirderMethods
16
+
17
+ ORDER_LITTLE_ENDIAN = :little_endian
18
+ ORDER_BIG_ENDIAN = :big_endian
19
+
20
+ # Lazily load and assert presence of utf8 encoding
21
+ def enc_utf8
22
+
23
+ @@enc_utf8 ||=
24
+ ( Encoding.find( "utf-8" ) or raise "No utf-8 encoding found (?!)" )
25
+ end
26
+
27
+ module_function :enc_utf8
28
+
29
+ def fu()
30
+ BitGirderLogger.get_logger.is_debug? ? FileUtils::Verbose : FileUtils
31
+ end
32
+
33
+ module_function :fu
34
+
35
+ def as_encoded( str, enc )
36
+
37
+ not_nil( str, :str )
38
+ not_nil( enc, :enc )
39
+
40
+ str.encoding == enc ? str : str.encode( enc )
41
+ end
42
+
43
+ module_function :as_encoded
44
+
45
+ def strict_encode64( str )
46
+
47
+ if RUBY_VERSION >= "1.9"
48
+ Base64.strict_encode64( str )
49
+ else
50
+ Base64.encode64( str ).split( /\n/ ).join( "" )
51
+ end
52
+ end
53
+
54
+ module_function :strict_encode64
55
+
56
+ # Despite the name, this method only enforces strictness when running in a ruby
57
+ # >= 1.9 for now, though we may backport and handcode integrity checks at some
58
+ # point for other rubies
59
+ def strict_decode64( str )
60
+
61
+ if RUBY_VERSION >= "1.9"
62
+ Base64.strict_decode64( str )
63
+ else
64
+ Base64.decode64( str )
65
+ end
66
+ end
67
+
68
+ module_function :strict_decode64
69
+
70
+ # Returns i as a little-endian 2's complement byte array; Algorithm is from
71
+ # http://stackoverflow.com/questions/5284369/ruby-return-byte-array-containing-twos-complement-representation-of-bignum-fi
72
+ # (with some cosmetic differences, including the return val's endianness).
73
+ #
74
+ # Though not stated there, it's worth noting that the reason for the end
75
+ # condition test of the 7th (sign) bit is to avoid stopping prematurely on
76
+ # inputs such as 0xff00, dropping the sign information from the result
77
+ #
78
+ def int_to_byte_array( i )
79
+
80
+ not_nil( i, :i )
81
+
82
+ res = []
83
+
84
+ begin
85
+ res << ( i & 0xff )
86
+ i >>= 8
87
+ end until ( i == 0 || i == -1 ) && ( res[ -1 ][ 7 ] == i[ 7 ] )
88
+
89
+ res
90
+ end
91
+
92
+ module_function :int_to_byte_array
93
+
94
+ def file_exists( d )
95
+
96
+ raise "File or directory #{d} does not exist" unless File.exist?( d )
97
+ d
98
+ end
99
+
100
+ module_function :file_exists
101
+
102
+ def open_tempfile( basename = nil, *rest, &blk )
103
+
104
+ basename ||= [ "bg-tmp-", ".tmp" ]
105
+ Tempfile::open( basename, *rest, &blk )
106
+ end
107
+
108
+ def mktmpdir( *argv, &blk )
109
+ Dir.mktmpdir( *argv, &blk )
110
+ end
111
+
112
+ module_function :mktmpdir
113
+
114
+ def fsize( obj )
115
+
116
+ stat =
117
+ case obj
118
+ when File, Tempfile then File.stat( obj.path )
119
+ when String then File.stat( obj )
120
+ else raise TypeError, "Unhandled type for fsize: #{obj.class}"
121
+ end
122
+
123
+ stat.size
124
+ end
125
+
126
+ module_function :fsize
127
+
128
+ module_function :open_tempfile
129
+
130
+ def write_file( text, file )
131
+
132
+ fu().mkdir_p( File.dirname( file ) )
133
+ File.open( file, "w" ) { |io| io.print text }
134
+ end
135
+
136
+ module_function :write_file
137
+
138
+ def first_line( file )
139
+ File.open( file ) { |io| io.readline.chomp }
140
+ end
141
+
142
+ module_function :first_line
143
+
144
+ def slurp_io( io, blk_sz = 4096 )
145
+
146
+ not_nil( io, :io )
147
+
148
+ while s = io.read( blk_sz ); res = res ? res << s : s; end
149
+
150
+ res
151
+ end
152
+
153
+ module_function :slurp_io
154
+
155
+ def slurp( io, blk_sz = 4096 )
156
+
157
+ case io
158
+ when IO, Tempfile then slurp_io( io, blk_sz )
159
+ when String then File.open( io ) { |io2| slurp_io( io2, blk_sz ) }
160
+ else raise "Unknown slurp target: #{io} (#{io.class})"
161
+ end
162
+ end
163
+
164
+ module_function :slurp
165
+
166
+ def as_write_dest( obj )
167
+
168
+ not_nil( obj, :obj )
169
+
170
+ case obj
171
+ when IO, Tempfile then yield( io )
172
+ when String then File.open( obj, "w" ) { |io| yield( io ) }
173
+ else raise TypeError, "Unknown write dest: #{obj.class}"
174
+ end
175
+ end
176
+
177
+ module_function :as_write_dest
178
+
179
+ def as_read_src( obj )
180
+
181
+ not_nil( obj, :obj )
182
+
183
+ case obj
184
+ when IO, Tempfile then yield( obj )
185
+ when String then File.open( file_exists( obj ) ) { |io| yield( io ) }
186
+ else raise TypeError, "Unkown read src: #{obj.class}"
187
+ end
188
+ end
189
+
190
+ module_function :as_read_src
191
+
192
+ def as_json( obj ); JSON.generate( not_nil( obj, :obj ) ); end
193
+
194
+ module_function :as_json
195
+
196
+ def parse_json( str ); JSON.parse( not_nil( str, :str ) ); end
197
+
198
+ module_function :parse_json
199
+
200
+ def dump_json( obj, file )
201
+
202
+ not_nil( obj, :obj )
203
+ not_nil( file, :file )
204
+
205
+ json = as_json( obj )
206
+
207
+ as_write_dest( file ) { |io| io.print( json ) }
208
+ end
209
+
210
+ module_function :dump_json
211
+
212
+ def load_json( file )
213
+
214
+ not_nil( file, :file )
215
+ as_read_src( file ) { |io| parse_json( slurp( io ) ) }
216
+ end
217
+
218
+ module_function :load_json
219
+
220
+ def dump_yaml( obj, dest )
221
+
222
+ not_nil( obj, :obj )
223
+ not_nil( dest, :dest )
224
+
225
+ as_write_dest( dest ) { |io| YAML.dump( obj, io ) }
226
+ end
227
+
228
+ module_function :dump_yaml
229
+
230
+ def load_yaml( src )
231
+
232
+ not_nil( src, :src )
233
+
234
+ as_read_src( src ) { |io| YAML.load( io ) }
235
+ end
236
+
237
+ module_function :load_yaml
238
+
239
+ def is_executable( file )
240
+
241
+ file_exists( file )
242
+ raise "Not an executable: #{file}" unless File.executable?( file )
243
+
244
+ file
245
+ end
246
+
247
+ module_function :is_executable
248
+
249
+ # Effectively enables mdkir_p as an inline function to enable statements
250
+ # like:
251
+ #
252
+ # some_dir = ensure_dir( some_dir )
253
+ #
254
+ def ensure_dir( d )
255
+
256
+ fu().mkdir_p( d ) unless File.exist?( d )
257
+ d
258
+ end
259
+
260
+ module_function :ensure_dir
261
+
262
+ def ensure_wiped( d )
263
+
264
+ fu().rm_rf( d )
265
+ ensure_dir( d )
266
+ end
267
+
268
+ module_function :ensure_wiped
269
+
270
+ def ensure_dirs( *dirs )
271
+ dirs.each { |dir| ensure_dir( dir ) }
272
+ end
273
+
274
+ module_function :ensure_dirs
275
+
276
+ # Ensures that the directory referred to as dirname( file ) exists, and
277
+ # returns file itself (not the parent). Fails if dirname does not return
278
+ # anything meaningful for file.
279
+ def ensure_parent( file )
280
+
281
+ parent = File.dirname( file )
282
+ raise "No parent exists for #{file}" unless parent
283
+
284
+ ensure_dir( parent )
285
+
286
+ file
287
+ end
288
+
289
+ module_function :ensure_parent
290
+
291
+ def which( cmd, fail_on_miss = false )
292
+
293
+ not_nil( cmd, "cmd" )
294
+
295
+ if ( f = `which #{cmd}`.strip ) and f.empty?
296
+ raise "Cannot find command #{cmd.inspect} in path" if fail_on_miss
297
+ else
298
+ f
299
+ end
300
+ end
301
+
302
+ module_function :which
303
+
304
+ def debug_wait2( opts )
305
+
306
+ not_nil( opts, "opts" )
307
+
308
+ pid = has_key( opts, :pid )
309
+
310
+ name = opts[ :name ]
311
+ name_str = name ? "#{name} (pid #{pid})" : pid.to_s
312
+ code( "Waiting on #{name_str}" )
313
+
314
+ pid, status = Process::wait2( pid )
315
+ msg = "Process #{pid} exited with status #{status.exitstatus}"
316
+
317
+ if status.success?
318
+ code( msg )
319
+ else
320
+ opts[ :check_status ] ? ( raise msg ) : warn( msg )
321
+ end
322
+
323
+ [ pid, status ]
324
+ end
325
+
326
+ module_function :debug_wait2
327
+
328
+ def debug_kill( sig, pid, opts = {} )
329
+
330
+ not_nil( opts, "opts" )
331
+
332
+ msg = "Sending #{sig} to #{pid}"
333
+ msg += " (#{opts[ :name ]})" if opts.key?( :name )
334
+
335
+ BitGirderLogger.get_logger.code( msg )
336
+ Process.kill( sig, pid )
337
+ end
338
+
339
+ module_function :debug_kill
340
+
341
+ def read_full( io, len, buf = nil )
342
+
343
+ args = [ len ]
344
+ args << buf if buf
345
+ buf = io.read( *args )
346
+
347
+ if ( sz = buf == nil ? 0 : buf.bytesize ) < len
348
+ raise EOFError.new( "EOF after #{sz} bytes (wanted #{len})" )
349
+ end
350
+
351
+ buf
352
+ end
353
+
354
+ module_function :read_full
355
+
356
+ class DataUnitError < StandardError; end
357
+
358
+ class DataUnit < BitGirderClass
359
+
360
+ bg_attr :name
361
+ bg_attr :byte_scale
362
+
363
+ private_class_method :new
364
+
365
+ BYTE = self.send( :new, :name => :byte, :byte_scale => 1 )
366
+ KILOBYTE = self.send( :new, :name => :kilobyte, :byte_scale => 2 ** 10 )
367
+ MEGABYTE = self.send( :new, :name => :megabyte, :byte_scale => 2 ** 20 )
368
+ GIGABYTE = self.send( :new, :name => :gigabyte, :byte_scale => 2 ** 30 )
369
+ TERABYTE = self.send( :new, :name => :terabyte, :byte_scale => 2 ** 40 )
370
+ PETABYTE = self.send( :new, :name => :petabyte, :byte_scale => 2 ** 50 )
371
+
372
+ UNITS = [ BYTE, KILOBYTE, MEGABYTE, GIGABYTE, TERABYTE, PETABYTE ]
373
+
374
+ map_instance_of( String, Symbol ) do |val|
375
+
376
+ # Avoid string ops on val if we can
377
+ if res = UNITS.find { |u| u.name == res }
378
+ return res
379
+ end
380
+
381
+ case val.to_s.downcase.to_sym
382
+ when :b, :byte, :bytes then BYTE
383
+ when :k, :kb, :kilobyte, :kilobytes then KILOBYTE
384
+ when :m, :mb, :megabyte, :megabytes then MEGABYTE
385
+ when :g, :gb, :gigabyte, :gigabytes then GIGABYTE
386
+ when :t, :tb, :terabyte, :terabytes then TERABYTE
387
+ when :p, :pb, :petabyte, :petabytes then PETABYTE
388
+ else raise DataUnitError, "Unknown data unit: #{val}"
389
+ end
390
+ end
391
+ end
392
+
393
+ class DataSizeError < StandardError; end
394
+
395
+ class DataSize < BitGirderClass
396
+
397
+ include Comparable
398
+
399
+ bg_attr :unit, :processor => DataUnit
400
+
401
+ bg_attr :size, :validation => :nonnegative
402
+
403
+ map_instance_of( Integer ) do |val|
404
+ DataSize.new( :size => val, :unit => DataUnit::BYTE )
405
+ end
406
+
407
+ map_instance_of( String ) do |val|
408
+
409
+ if md = /^\s*(\d+)\s*([a-zA-Z]*)\s*$/.match( val )
410
+ if ( unit = md[ 2 ] ) == "" then unit = DataUnit::BYTE end
411
+ DataSize.new( :size => md[ 1 ].to_i, :unit => unit )
412
+ else
413
+ raise DataSizeError, "Invalid data size: #{val.inspect}"
414
+ end
415
+ end
416
+
417
+ public
418
+ def bytes
419
+ @size * @unit.byte_scale
420
+ end
421
+
422
+ public
423
+ def ==( o )
424
+ return true if o.equal?( self )
425
+ return false unless o.is_a?( DataSize )
426
+ return self.bytes == o.bytes
427
+ end
428
+
429
+ public
430
+ def <=>( o )
431
+ return nil unless o.is_a?( DataSize )
432
+ return self.bytes <=> o.bytes
433
+ end
434
+
435
+ public
436
+ def to_s
437
+ bytes.to_s
438
+ end
439
+ end
440
+
441
+ class BinaryConverter < BitGirderClass
442
+
443
+ require 'bigdecimal'
444
+
445
+ bg_attr :order,
446
+ :processor => :symbol,
447
+ :validation => lambda { |o|
448
+ unless o == ORDER_LITTLE_ENDIAN || o == ORDER_BIG_ENDIAN
449
+ raise "Unrecognized order: #{o}"
450
+ end
451
+ }
452
+
453
+ # Set @plat_order as an instance variable instead of as a class constant. We
454
+ # could theoretically set plat order as a class constant, but don't for 2
455
+ # reasons. One is that, in the event there is a bug in our detection, we
456
+ # don't want to fail any class requiring this file, since some code may
457
+ # never touch the BinaryConverter class anyway. Two is that we can use
458
+ # reflection during testing to simulate a different platform byte ordering
459
+ # on a test-by-test basis, rather than having to change a global constant
460
+ def initialize( *argv )
461
+
462
+ super( *argv )
463
+
464
+ @plat_order = BinaryConverter.detect_platform_order
465
+ end
466
+
467
+ private
468
+ def is_plat_order?
469
+ @order == @plat_order
470
+ end
471
+
472
+ private
473
+ def impl_write_int( num, nm, fmt )
474
+
475
+ not_nil( num, nm )
476
+ res = [ num ].pack( fmt )
477
+
478
+ res.reverse! unless is_plat_order?
479
+
480
+ res
481
+ end
482
+
483
+ private
484
+ def impl_read_int( str, nm, fmt )
485
+
486
+ not_nil( str, nm )
487
+
488
+ str = str.reverse unless is_plat_order?
489
+ str.unpack( fmt )[ 0 ]
490
+ end
491
+
492
+ {
493
+ :int8 => "c",
494
+ :int32 => "l",
495
+ :int64 => "q"
496
+ }.
497
+ each_pair do |type, fmt|
498
+
499
+ [ [ type, fmt ], [ :"u#{type}", fmt.upcase ] ].each do |pair|
500
+
501
+ type2, fmt2 = *pair
502
+ wm, rm = %w{ write read }.map { |s| :"#{s}_#{type2}" }
503
+
504
+ define_method( wm ) { |i| impl_write_int( i, :i, fmt2 ) }
505
+ define_method( rm ) { |s| impl_read_int( s, :s, fmt2 ) }
506
+
507
+ public wm, rm
508
+ end
509
+ end
510
+
511
+ private
512
+ def get_float_format( is64 )
513
+
514
+ fmt =
515
+ case @order
516
+ when ORDER_LITTLE_ENDIAN then 'e'
517
+ when ORDER_BIG_ENDIAN then 'g'
518
+ else raise "Unhandled order: #@order"
519
+ end
520
+
521
+ fmt = fmt.upcase if is64
522
+
523
+ fmt
524
+ end
525
+
526
+ private
527
+ def impl_write_float( num, nm, is64 )
528
+
529
+ not_nil( num, nm )
530
+
531
+ fmt = get_float_format( is64 )
532
+ [ num ].pack( fmt )
533
+ end
534
+
535
+ public
536
+ def write_float32( f )
537
+ impl_write_float( f, :f, false )
538
+ end
539
+
540
+ public
541
+ def write_float64( d )
542
+ impl_write_float( d, :d, true )
543
+ end
544
+
545
+ private
546
+ def impl_read_float( str, nm, is64 )
547
+
548
+ not_nil( str, nm )
549
+
550
+ fmt = get_float_format( is64 )
551
+ str.unpack( fmt )[ 0 ]
552
+ end
553
+
554
+ public
555
+ def read_float32( s )
556
+ impl_read_float( s, :s, false )
557
+ end
558
+
559
+ public
560
+ def read_float64( s )
561
+ impl_read_float( s, :s, true )
562
+ end
563
+
564
+ public
565
+ def write_bignum( i )
566
+
567
+ not_nil( i, :i )
568
+
569
+ arr = Io.int_to_byte_array( i )
570
+ arr.reverse! if @order == ORDER_BIG_ENDIAN
571
+
572
+ res = write_int32( arr.size )
573
+ arr.each { |b| res << b.chr }
574
+
575
+ res
576
+ end
577
+
578
+ private
579
+ def inflate_bignum( s )
580
+
581
+ # Ensure we process bytes in big-endian order
582
+ s = s.reverse if @order == ORDER_LITTLE_ENDIAN
583
+
584
+ res = s[ 0, 1 ].unpack( 'c' )[ 0 ] # res is now set and correctly signed
585
+
586
+ s[ 1 .. -1 ].each_byte do |b|
587
+ res <<= 8
588
+ res += b
589
+ end
590
+
591
+ res
592
+ end
593
+
594
+ public
595
+ def read_bignum( s )
596
+ read_bignum_with_info( s )[ 0 ]
597
+ end
598
+
599
+ public
600
+ def read_bignum_with_info( s )
601
+
602
+ not_nil( s, :s )
603
+
604
+ if ( len = s.size ) < 4
605
+ raise "Input does not have a size (input len: #{len})"
606
+ elsif len == 4
607
+ raise "Input has no integer data (input len: #{len})"
608
+ end
609
+
610
+ sz = read_int32( s[ 0, 4 ] )
611
+ rem = [ len - 4, len - sz ].min
612
+
613
+ if ( rem = s.size - 4 ) < sz
614
+ raise "Input specifies size #{sz} but actually is only of " \
615
+ "length #{rem}"
616
+ end
617
+
618
+ info = { :total_len => 4 + sz, :hdr_len => 4, :num_len => sz }
619
+ [ inflate_bignum( s[ 4, sz ] ), info ]
620
+ end
621
+
622
+ # write_bigdec (read_bigdec does the inverse) interprets ruby BigDecimal
623
+ # first as an unscaled value and a scale as defined in Java's BigDecimal
624
+ # class, and then serializes them as the concatenation of write_bignum(
625
+ # unscaled ) and write_int32( scale ), with each component's byte-order
626
+ # determined by @order
627
+ public
628
+ def write_bigdec( n )
629
+
630
+ not_nil( n, :n )
631
+ raise "Not a BigDecimal" unless n.is_a?( BigDecimal )
632
+
633
+ sign, unscaled, base, exp = n.split
634
+ raise "Unexpected base: #{base}" unless base == 10
635
+ raise "NaN not supported" if sign == 0
636
+
637
+ if ( unscaled_i = unscaled.to_i ) == 0
638
+ write_bignum( 0 ) + write_int32( 0 )
639
+ else
640
+ # sci_exp is the exponent we'd get using scientific notation
641
+ sci_exp = -unscaled.size + exp
642
+ write_bignum( unscaled_i * sign ) + write_int32( sci_exp )
643
+ end
644
+ end
645
+
646
+ public
647
+ def read_bigdec_with_info( s )
648
+
649
+ not_nil( s, :s )
650
+
651
+ unscaled, info1 = read_bignum_with_info( s )
652
+ unscaled_len = has_key( info1, :total_len )
653
+
654
+ scale = read_int32( s[ unscaled_len, 4 ] )
655
+
656
+ info2 = {
657
+ :total_len => unscaled_len + 4,
658
+ :unscaled_len => unscaled_len,
659
+ :scale_len => 4
660
+ }
661
+
662
+ num_str = unscaled.to_s( 10 ) + "e" + ( scale ).to_s
663
+
664
+ [ BigDecimal.new( num_str ), info2 ]
665
+ end
666
+
667
+ public
668
+ def read_bigdec( s )
669
+ read_bigdec_with_info( s )[ 0 ]
670
+ end
671
+
672
+ def self.detect_platform_order
673
+
674
+ case test = [ 1 ].pack( 's' )
675
+ when "\x01\x00" then ORDER_LITTLE_ENDIAN
676
+ when "\x00\x01" then ORDER_BIG_ENDIAN
677
+ else raise "Undetected byte order: #{test.inspect}"
678
+ end
679
+ end
680
+ end
681
+
682
+ class BinaryIo < BitGirderClass
683
+
684
+ bg_attr :io
685
+ bg_attr :order
686
+
687
+ attr_reader :conv
688
+
689
+ # pos returns the zero-indexed position of the next byte that would be read,
690
+ # in the case of a reader, or the number of bytes that have been written in
691
+ # the case of a writer. This value is only valid as long as no exceptions
692
+ # have been encountered in any of the read|write methods and all access to
693
+ # the underlying io object has been through this instance the read* methods
694
+ attr_reader :pos
695
+
696
+ public
697
+ def close
698
+ @io.close if @io.respond_to?( :close )
699
+ end
700
+
701
+ private
702
+ def impl_initialize
703
+
704
+ super
705
+ @conv = BinaryConverter.new( :order => @order )
706
+ @pos = 0
707
+ end
708
+
709
+ def self.new_with_order( ord, opts )
710
+ self.new( { :order => ord }.merge( opts ) )
711
+ end
712
+
713
+ def self.new_le( opts )
714
+ self.new_with_order( ORDER_LITTLE_ENDIAN, opts )
715
+ end
716
+
717
+ def self.new_be( opts )
718
+ self.new_with_order( ORDER_BIG_ENDIAN, opts )
719
+ end
720
+ end
721
+
722
+ class BinaryWriter < BinaryIo
723
+
724
+ {
725
+ :int8 => 1,
726
+ :int32 => 4,
727
+ :int64 => 8,
728
+ :uint8 => 1,
729
+ :uint32 => 4,
730
+ :uint64 => 8,
731
+ :float32 => 4,
732
+ :float64 => 8
733
+
734
+ }.each_pair do |meth, sz|
735
+
736
+ meth = :"write_#{meth}"
737
+
738
+ define_method( meth ) do |val|
739
+ @io.write( @conv.send( meth, val ) )
740
+ @pos += sz
741
+ end
742
+ end
743
+
744
+ public
745
+ def write_bool( b )
746
+ write_int8( b ? 1 : 0 )
747
+ end
748
+
749
+ public
750
+ def write_full( buf )
751
+ @io.write( buf )
752
+ @pos += buf.bytesize
753
+ end
754
+
755
+ alias write write_full
756
+
757
+ private
758
+ def impl_write_buffer32( buf, sz )
759
+
760
+ write_int32( sz )
761
+ write_full( buf )
762
+ end
763
+
764
+ public
765
+ def write_buffer32( buf )
766
+
767
+ not_nil( buf, :buf )
768
+ impl_write_buffer32( buf, buf.bytesize )
769
+ end
770
+
771
+ public
772
+ def write_utf8( str )
773
+
774
+ not_nil( str, :str )
775
+
776
+ RubyVersions.when_19x { str = Io.as_encoded( str, Encoding::UTF_8 ) }
777
+
778
+ impl_write_buffer32( str, str.bytesize )
779
+ end
780
+ end
781
+
782
+ class BinaryReader < BinaryIo
783
+
784
+ public
785
+ def eof?
786
+ @io.eof?
787
+ end
788
+
789
+ public
790
+ def peekc
791
+
792
+ res = @io.getc.tap { |c| @io.ungetc( c ) }
793
+
794
+ case res
795
+ when String then res
796
+ when Fixnum then res.chr
797
+ else raise "Unexpected getc val: #{res.class}"
798
+ end
799
+ end
800
+
801
+ public
802
+ def peek_int8
803
+ peekc.unpack( 'c' )[ 0 ]
804
+ end
805
+
806
+ public
807
+ def read_full( len, buf = nil )
808
+ Io.read_full( @io, len, buf ).tap { @pos += len }
809
+ end
810
+
811
+ public
812
+ def read( *argv )
813
+ @io.read( *argv ).tap { |res| @pos += res.bytesize }
814
+ end
815
+
816
+ {
817
+ :int8 => 1,
818
+ :int32 => 4,
819
+ :int64 => 8,
820
+ :uint8 => 1,
821
+ :uint32 => 4,
822
+ :uint64 => 8,
823
+ :float32 => 4,
824
+ :float64 => 8
825
+
826
+ }.each_pair do |k, v|
827
+ meth = :"read_#{k}"
828
+ define_method( meth ) { @conv.send( meth, read_full( v ) ) }
829
+ end
830
+
831
+ public
832
+ def read_bool
833
+ read_int8 != 0
834
+ end
835
+
836
+ alias read_boolean read_bool
837
+
838
+ public
839
+ def read_buffer32
840
+ read_full( read_int32 )
841
+ end
842
+
843
+ public
844
+ def read_utf8
845
+
846
+ len = read_int32
847
+
848
+ str = "" * len
849
+ read_full( len, str )
850
+ RubyVersions.when_19x { str.force_encoding( "utf-8" ) }
851
+
852
+ str
853
+ end
854
+ end
855
+
856
+ class UnixProcessBuilder < BitGirderClass
857
+
858
+ bg_attr :cmd
859
+
860
+ bg_attr :argv, :default => []
861
+
862
+ bg_attr :env, :default => {}
863
+
864
+ bg_attr :opts, :default => {}
865
+
866
+ bg_attr :show_env_in_debug, :mutable => true, :required => false
867
+
868
+ def initialize( opts )
869
+ super( opts )
870
+ end
871
+
872
+ private
873
+ def debug_call( opts )
874
+
875
+ dbg = { :cmd => @cmd, :argv => @argv, :opts => @opts }
876
+ dbg[ :env ] = @env if @show_env_in_debug
877
+
878
+ code( "Doing #{opts[ :call_type ]} with #{dbg.inspect}" )
879
+ end
880
+
881
+ private
882
+ def str_map( val )
883
+
884
+ case val
885
+
886
+ when Array then val.map { |o| str_map( o ) }
887
+
888
+ when Hash
889
+ val.inject( {} ) do |h, pair|
890
+ h[ str_map( pair[ 0 ] ) ] = str_map( pair[ 1 ] )
891
+ h
892
+ end
893
+
894
+ else val.to_s
895
+ end
896
+ end
897
+
898
+ private
899
+ def get_call_argv
900
+
901
+ [ str_map( @env ), str_map( @cmd ), str_map( @argv ), @opts ].flatten
902
+ end
903
+
904
+ def get_call_argv18
905
+
906
+ res = get_call_argv
907
+ [ res[ 1 ], *( res[ 2 ] ) ] # [ cmd, *argv ]
908
+ end
909
+
910
+ public
911
+ def spawn
912
+
913
+ debug_call( :call_type => "spawn" )
914
+ Process.spawn( *get_call_argv )
915
+ end
916
+
917
+ public
918
+ def exec
919
+
920
+ debug_call( :call_type => "exec" )
921
+ Kernel.exec( *get_call_argv )
922
+ end
923
+
924
+ public
925
+ def system( opts = {} )
926
+
927
+ debug_call( :call_type => "system" )
928
+
929
+ proc_res_raise = lambda {
930
+ raise "Command exited with status #{$?.exitstatus}"
931
+ }
932
+
933
+ if RUBY_VERSION >= "1.9"
934
+ proc_res_raise.call unless Kernel.system( *get_call_argv )
935
+ else
936
+ if pid = Kernel.fork
937
+ Process.wait( pid )
938
+ proc_res_raise.call unless $?.success?
939
+ else
940
+ Kernel.exec( *get_call_argv18 )
941
+ end
942
+ end
943
+ end
944
+
945
+ public
946
+ def popen( mode, &blk )
947
+
948
+ debug_call( :call_type => "popen" )
949
+
950
+ if RUBY_VERSION >= "1.9"
951
+ IO.popen( get_call_argv, mode, &blk )
952
+ else
953
+ IO.popen( get_call_argv18.join( " " ), mode, &blk )
954
+ end
955
+ end
956
+ end
957
+
958
+ end
959
+ end