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,1235 @@
1
+ # Placed here so that the rest of the libs and executables in our codebase
2
+ # needn't worry about requiring rubygems when being run in ruby 1.8.
3
+ require 'rubygems' if RUBY_VERSION < "1.9"
4
+
5
+ module BitGirder
6
+ module Core
7
+
8
+ ENV_BITGIRDER_DEBUG = "BITGIRDER_DEBUG"
9
+
10
+ EXIT_SUCCESS = 0
11
+ EXIT_FAILURE = 1
12
+
13
+ class RubyVersions
14
+
15
+ def self.when_geq( ver, val = nil )
16
+ if RUBY_VERSION >= ver
17
+ yield( val )
18
+ else
19
+ val
20
+ end
21
+ end
22
+
23
+ def self.is_19x?
24
+ RUBY_VERSION >= "1.9"
25
+ end
26
+
27
+ def self.when_19x( val = nil, &blk )
28
+ self.when_geq( "1.9", val, &blk )
29
+ end
30
+
31
+ def self.jruby?
32
+ RUBY_PLATFORM == "java"
33
+ end
34
+ end
35
+
36
+ class BitGirderLogger
37
+
38
+ require 'time'
39
+ require 'thread'
40
+
41
+ private_class_method :new
42
+
43
+ # Our log levels
44
+ CODE = :CODE
45
+ CONSOLE = :CONSOLE
46
+ WARN = :WARN
47
+ DEFAULT = CONSOLE
48
+
49
+ attr_reader :level
50
+
51
+ def initialize
52
+ @lock = Mutex.new
53
+ end
54
+
55
+ # makes a string msg from argv, which can either be an exception and a
56
+ # message or just a message
57
+ private
58
+ def make_msg( argv )
59
+
60
+ case len = argv.length
61
+
62
+ when 1 then argv.shift
63
+
64
+ when 2
65
+ e, msg = *argv
66
+ msg << "\n" << e.message.to_s
67
+
68
+ bt = e.backtrace
69
+ ( msg << "\n" << bt.join( "\n" ) ) if bt
70
+
71
+ msg
72
+
73
+ else raise ArgumentError, "Wrong number of arguments: #{len}"
74
+ end
75
+ end
76
+
77
+ def self.level_of( lev )
78
+ case lev
79
+ when CODE then 1
80
+ when CONSOLE then 2
81
+ when WARN then 3
82
+ else raise "Invalid level: #{lev}"
83
+ end
84
+ end
85
+
86
+ private
87
+ def send_msg( lev, time, argv )
88
+
89
+ if self.class.level_of( lev ) >= self.class.level_of( @level )
90
+ str = make_msg( argv )
91
+ @lock.synchronize { puts "[#{time.iso8601( 6 )}]: #{lev}: #{str}" }
92
+ end
93
+ end
94
+
95
+ public
96
+ def code( *argv )
97
+ send_msg( CODE, Time.now, argv )
98
+ end
99
+
100
+ alias debug code
101
+
102
+ public
103
+ def warn( *argv )
104
+ send_msg( WARN, Time.now, argv )
105
+ end
106
+
107
+ public
108
+ def console( *argv )
109
+ send_msg( CONSOLE, Time.now, argv )
110
+ end
111
+
112
+ public
113
+ def is_debug?
114
+ @level == CODE
115
+ end
116
+
117
+ public
118
+ def level=( lev )
119
+
120
+ @level =
121
+ case lev
122
+ when CODE, CONSOLE, WARN then lev
123
+ else raise ArgumentError, "Unknown level: #{lev}"
124
+ end
125
+ end
126
+
127
+ def self.is_debug_env_set?
128
+ ( ENV[ ENV_BITGIRDER_DEBUG ] or "" ).strip =~ /^(true|yes)$/
129
+ end
130
+
131
+ @logger = new
132
+ @logger.level = self.is_debug_env_set? ? CODE : DEFAULT
133
+
134
+ def self.get_logger
135
+ @logger
136
+ end
137
+
138
+ class <<self
139
+
140
+ [ :debug, :code, :warn, :console ].each do |meth|
141
+
142
+ define_method( meth ) do |*argv|
143
+ get_logger.send( meth, *argv )
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ module Reflect
150
+
151
+ # Returns an array of Symbol regardless of ruby version (1.8 uses String,
152
+ # 1.9 Symbols)
153
+ def self.instance_methods_of( v )
154
+
155
+ res = v.instance_methods
156
+
157
+ if res.size > 0 && res[ 0 ].is_a?( String )
158
+ res = res.map { |s| s.to_sym }
159
+ end
160
+
161
+ res
162
+ end
163
+ end
164
+
165
+ module BitGirderMethods
166
+
167
+ module_function
168
+
169
+ def code( *argv )
170
+ BitGirderLogger.get_logger.code( *argv )
171
+ end
172
+
173
+ def console( *argv )
174
+ BitGirderLogger.get_logger.console( *argv )
175
+ end
176
+
177
+ alias debug code
178
+
179
+ def warn( *argv )
180
+ BitGirderLogger.get_logger.warn( *argv )
181
+ end
182
+
183
+ # Each constant corresponds to a prefix in an error message appropriate to
184
+ # that type
185
+ #
186
+ PARAM_TYPE_ARG = "Argument"
187
+ PARAM_TYPE_ENVVAR = "Environment Variable"
188
+ PARAM_TYPE_KEY = "Value for key"
189
+
190
+ def check_fail_prefix( name, param_type )
191
+
192
+ # Since we'll most often be using symbols instead of strings for arg
193
+ # parameter names we special case displaying them as strings
194
+ name_str =
195
+ if param_type == PARAM_TYPE_ARG && name.is_a?( Symbol )
196
+ %{"#{name_str}"}
197
+ else
198
+ name.inspect
199
+ end
200
+
201
+ "#{param_type} #{name.inspect}"
202
+ end
203
+
204
+ def not_nil( val, name = nil, param_type = PARAM_TYPE_ARG )
205
+
206
+ if val.nil?
207
+ if name
208
+ raise "#{check_fail_prefix( name, param_type )} cannot be nil"
209
+ else
210
+ raise "Value is nil"
211
+ end
212
+ else
213
+ val
214
+ end
215
+ end
216
+
217
+ def compares_to( val,
218
+ comp_target,
219
+ comp_type,
220
+ name = nil,
221
+ param_type = PARAM_TYPE_ARG )
222
+
223
+ if val.send( comp_type, comp_target )
224
+ val
225
+ else
226
+ raise "#{check_fail_prefix( name, param_type )}' is out of range " \
227
+ "(#{comp_target} #{comp_type} #{val})"
228
+ end
229
+ end
230
+
231
+ def nonnegative( val, name = nil, param_type = PARAM_TYPE_ARG )
232
+ compares_to( val, 0, :>=, name, param_type )
233
+ end
234
+
235
+ def positive( val, name = nil, param_type = PARAM_TYPE_ARG )
236
+ compares_to( val, 0, :>, name, param_type )
237
+ end
238
+
239
+ # changes "some-arg" to :some_arg
240
+ def ext_to_sym( str )
241
+
242
+ not_nil( str, "str" )
243
+
244
+ # do str.to_s in case it's some random string-ish thing (like a sym) but
245
+ # not an actual String
246
+ str.to_s.gsub( /-/, "_" ).to_sym
247
+ end
248
+
249
+ # Returns two arrays (both possibly empty) of stuff in argv before the first
250
+ # '--', if any, and stuff after, if any, including any subsequent
251
+ # appearances of '--'
252
+ def split_argv( argv )
253
+
254
+ not_nil( argv, :argv )
255
+
256
+ first, last = [], nil
257
+
258
+ argv.each do |arg|
259
+
260
+ if last then last << arg
261
+ else
262
+ if arg == "--" then last = []; else first << arg end
263
+ end
264
+ end
265
+
266
+ [ first, last ]
267
+ end
268
+
269
+ # changes "some-arg" to SomeArg (we can add in namespace qualifiers too if
270
+ # needed, for instance 'some-mod/some-other-mod/some-class' becomes
271
+ # SomeMod::SomeOtherMod::SomeClass)
272
+ def ext_to_class_name( str )
273
+
274
+ not_nil( str, "str" )
275
+
276
+ str = str.to_s
277
+
278
+ res = ""
279
+
280
+ if str.size > 0
281
+ res = str[ 0 ].upcase +
282
+ str[ 1 .. -1 ].gsub( /-(.)/ ) { |m| m[ 1 ].upcase }
283
+ end
284
+
285
+ res
286
+ end
287
+
288
+ # Changes SomeClass to :some_class
289
+ def class_name_to_sym( cls_name )
290
+
291
+ not_nil( cls_name, "cls_name" )
292
+
293
+ if cls_name.size == 0
294
+ raise "Name is empty string"
295
+ else
296
+ str = cls_name[ 0 ].downcase +
297
+ cls_name[ 1 .. -1 ].
298
+ gsub( /([A-Z])/ ) { |m| "_#{m.downcase}" }
299
+
300
+ str.to_sym
301
+ end
302
+ end
303
+
304
+ # Changes :some_sym to "some-sym"
305
+ def sym_to_ext_id( sym )
306
+ not_nil( sym, "sym" ).to_s.gsub( /_/, "-" )
307
+ end
308
+
309
+ # Changes :some_sym to --some-sym ('cli' == 'command line interface')
310
+ def sym_to_cli_switch( sym )
311
+ "--" + sym_to_ext_id( sym )
312
+ end
313
+
314
+ # helper method to do the sym-conversion part only (no validity checking
315
+ # done on either the sym or val)
316
+ def set_var( sym, val )
317
+ instance_variable_set( "@#{sym}".to_sym, val )
318
+ end
319
+
320
+ # It is assumed that map responds to []= and is not nil. The 'key' parameter
321
+ # may be nil. Returns the value if key is present and not nil
322
+ #
323
+ def has_key( map, key, param_typ = PARAM_TYPE_KEY )
324
+ not_nil( map[ key ], key, param_typ )
325
+ end
326
+
327
+ def has_keys( map, *keys )
328
+ keys.inject( [] ) { |arr, key| arr << has_key( map, key ) }
329
+ end
330
+
331
+ def has_env( key )
332
+ has_key( ENV, key, PARAM_TYPE_ENVVAR )
333
+ end
334
+
335
+ def set_from_key( map, *syms )
336
+ syms.each { |sym| set_var( sym, has_key( map, sym ) ) }
337
+ end
338
+
339
+ def unpack_argv_hash( argh, vars, expct = false )
340
+
341
+ vars.inject( [] ) do |arr, sym|
342
+
343
+ val = argh[ sym ]
344
+
345
+ if val != nil
346
+ arr << val
347
+ else
348
+ raise "Missing parameter: #{sym} in arg hash" if expct
349
+ end
350
+
351
+ arr
352
+ end
353
+ end
354
+
355
+ def unpack_argv_array( arr, vars, trace )
356
+
357
+ if arr.size == vars.size
358
+ arr
359
+ else
360
+ msg = "wrong number of arguments (#{arr.size} for #{vars.size})"
361
+ raise( Exception, msg, trace )
362
+ end
363
+ end
364
+
365
+ def argv_to_argh( argv, syms )
366
+
367
+ argv = unpack_argv_array( argv, syms, caller )
368
+
369
+ argh = {}
370
+ argv.size.times { |i| argh[ syms[ i ] ] = argv[ i ] }
371
+
372
+ argh
373
+ end
374
+
375
+ def to_bool( obj )
376
+
377
+ case obj
378
+
379
+ when true then true
380
+ when false then false
381
+ when nil then false
382
+ when /^\s*(true|yes)\s*$/i then true
383
+ when /^\s*(false|no)\s*$/i then false
384
+ else raise "Invalid boolean string: #{obj}"
385
+ end
386
+ end
387
+
388
+ def raisef( *argv )
389
+
390
+ raise if argv.empty?
391
+
392
+ cls = nil
393
+
394
+ case v = argv.first
395
+ when Exception then raise v
396
+ when Class then cls = argv.shift
397
+ end
398
+
399
+ argv2 = cls == nil ? [] : [ cls ]
400
+ argv2 << sprintf( *argv ) unless argv.empty?
401
+
402
+ raise *argv2
403
+ end
404
+ end
405
+
406
+ class BitGirderAttribute
407
+
408
+ include BitGirderMethods
409
+
410
+ class InvalidModifier < RuntimeError; end
411
+
412
+ PROCESSOR_BOOLEAN = lambda { |v| BitGirderMethods.to_bool( v ) }
413
+
414
+ PROCESSOR_SYMBOL = lambda { |v| v == nil ? nil : v.to_sym }
415
+
416
+ # If v is not nil we return calling to_i() on it; if it is nil we return it
417
+ # as-is since nil is used to communicate to the user of the attribute that
418
+ # no value whatsoever was provided (nil.to_i otherwise would return 0)
419
+ PROCESSOR_INTEGER = lambda { |v| v == nil ? nil : v.to_i }
420
+
421
+ # See PROCESSOR_INTEGER
422
+ PROCESSOR_FLOAT = lambda { |v| v == nil ? nil : v.to_f }
423
+
424
+ VALIDATION_NOT_NIL = lambda { |val| raise "Missing value" if val == nil }
425
+
426
+ # Implies not nil
427
+ VALIDATION_NOT_EMPTY = lambda { |val|
428
+
429
+ VALIDATION_NOT_NIL.call( val )
430
+ raise "Need at least one" if val.empty?
431
+ }
432
+
433
+ VALIDATION_NONNEGATIVE = lambda { |val| val >= 0 or raise "value < 0" }
434
+
435
+ VALIDATION_POSITIVE = lambda { |val|
436
+
437
+ VALIDATION_NOT_NIL.call( val )
438
+ val > 0 or raise "value <= 0"
439
+ }
440
+
441
+ VALIDATION_FILE_EXISTS = lambda { |val|
442
+
443
+ VALIDATION_NOT_NIL.call( val )
444
+
445
+ unless File.exist?( val )
446
+ raise "File or directory #{val} does not exist"
447
+ end
448
+ }
449
+
450
+ VALIDATION_OPT_FILE_EXISTS = lambda { |val|
451
+ val && VALIDATION_FILE_EXISTS.call( val )
452
+ }
453
+
454
+ ATTR_MODIFIERS = [
455
+ :identifier,
456
+ :default,
457
+ :description,
458
+ :validation,
459
+ :processor,
460
+ :is_list,
461
+ :list_validation,
462
+ :required,
463
+ :mutable
464
+ ]
465
+
466
+ attr_reader *ATTR_MODIFIERS
467
+
468
+ # Rather than have a class def silently fail on a mis-typed attribute
469
+ # modifier (":processr" instead of ":processor") we explicitly check and
470
+ # fail if any of the supplied mods are not one expected by this class
471
+ private
472
+ def check_valid_modifiers( supplied )
473
+
474
+ supplied.each do |key|
475
+ ATTR_MODIFIERS.include?( key ) or
476
+ raise InvalidModifier.new(
477
+ "Invalid attribute modifier: #{key.inspect}" )
478
+ end
479
+ end
480
+
481
+ private
482
+ def get_processor( p )
483
+
484
+ case p
485
+ when :boolean then PROCESSOR_BOOLEAN
486
+ when :symbol then PROCESSOR_SYMBOL
487
+ when :integer then PROCESSOR_INTEGER
488
+ when :float then PROCESSOR_FLOAT
489
+
490
+ when Class
491
+ if p.ancestors.include?( BitGirderClass )
492
+ lambda { |o| p.as_instance( o ) }
493
+ else
494
+ raise ArgumentError, "Not a #{BitGirderClass}: #{p}"
495
+ end
496
+
497
+ else p
498
+ end
499
+ end
500
+
501
+ private
502
+ def get_validation( v )
503
+
504
+ case v
505
+ when :not_nil then VALIDATION_NOT_NIL
506
+ when :nonnegative then VALIDATION_NONNEGATIVE
507
+ when :positive then VALIDATION_POSITIVE
508
+ when :file_exists then VALIDATION_FILE_EXISTS
509
+ when :not_empty then VALIDATION_NOT_EMPTY
510
+ when :opt_file_exists then VALIDATION_OPT_FILE_EXISTS
511
+ else v
512
+ end
513
+ end
514
+
515
+ public
516
+ def initialize( argh )
517
+
518
+ check_valid_modifiers( argh.keys )
519
+
520
+ set_from_key( argh, :identifier )
521
+
522
+ [ :default, :description, :validation,
523
+ :is_list, :list_validation, :required, :mutable ].each do |sym|
524
+ set_var( sym, argh[ sym ] )
525
+ end
526
+
527
+ @validation = get_validation( @validation )
528
+ @list_validation = get_validation( @list_validation )
529
+
530
+ @processor = get_processor( argh[ :processor ] )
531
+
532
+ @id_sym = :"@#@identifier"
533
+ end
534
+
535
+ public
536
+ def get_instance_value( inst )
537
+ inst.instance_variable_get( @id_sym )
538
+ end
539
+ end
540
+
541
+ class BitGirderClassDefinition
542
+
543
+ extend BitGirderMethods
544
+ include BitGirderMethods
545
+
546
+ REQUIRED_ATTRS =
547
+ [ :cls, :attrs, :attr_syms, :decl_order, :instance_mappers ]
548
+
549
+ attr_reader *REQUIRED_ATTRS
550
+
551
+ def initialize( opts )
552
+
553
+ REQUIRED_ATTRS.each do |attr|
554
+
555
+ val = BitGirderMethods.has_key( opts, attr )
556
+ instance_variable_set( :"@#{attr}", val )
557
+ end
558
+ end
559
+
560
+ public
561
+ def add_attr( opts )
562
+
563
+ unless opts.key?( :required ) || opts.key?( :default )
564
+ opts[ :required ] = true
565
+ end
566
+
567
+ attr = BitGirderAttribute.new( opts )
568
+
569
+ if @attrs.key?( attr.identifier )
570
+ raise "Attribute #{attr.identifier.inspect} already defined"
571
+ else
572
+ ident = attr.identifier
573
+ @attrs[ ident ] = attr
574
+ @attr_syms << "@#{ident}".to_sym
575
+ @decl_order << ident
576
+ @cls.send( attr.mutable ? :attr_accessor : :attr_reader, ident )
577
+ end
578
+ end
579
+
580
+ private
581
+ def get_supplied_attr_value( hash, ident )
582
+ hash[ ident ] || hash[ ident.to_s ]
583
+ end
584
+
585
+ private
586
+ def get_default_val( attr )
587
+
588
+ case d = attr.default
589
+ when Proc then d.call
590
+ when Class then d.new
591
+ when Array, Hash then d.clone
592
+ else d == nil && attr.is_list ? [] : d
593
+ end
594
+ end
595
+
596
+ private
597
+ def apply_processor( attr, val )
598
+
599
+ if p = attr.processor
600
+ if attr.is_list
601
+ val = val.map { |elt| p.call( elt ) }
602
+ else
603
+ val = p.call( val )
604
+ end
605
+ else
606
+ val
607
+ end
608
+ end
609
+
610
+ private
611
+ def get_initial_value( hash, ident, attr )
612
+
613
+ # Get the val as the caller-supplied one, which we allow to be either
614
+ # the symbol or string form, supplying the default as needed; note that
615
+ # we use key membership and explicit tests for nil instead of boolean
616
+ # operators since we want to allow the value 'false' from the caller or
617
+ # as a default
618
+ val = hash.key?( ident ) ? hash[ ident ] : hash[ ident.to_s ]
619
+
620
+ if val == nil
621
+ val = get_default_val( attr ) # could still end up being nil
622
+ else
623
+ val = apply_processor( attr, val )
624
+ end
625
+
626
+ val
627
+ end
628
+
629
+ private
630
+ def validate_list_attr_val( attr, val )
631
+
632
+ if list_func = attr.list_validation
633
+ list_func.call( val )
634
+ end
635
+
636
+ if func = attr.validation
637
+ val.each { |v| func.call( v ) }
638
+ end
639
+ end
640
+
641
+ private
642
+ def validate_scalar_attr_val( attr, val )
643
+
644
+ if ( func = attr.validation ) && ( attr.required || ( ! val.nil? ) )
645
+ func.call( val )
646
+ end
647
+ end
648
+
649
+ private
650
+ def validate_attr_val( attr, val, trace, listener )
651
+
652
+ begin
653
+
654
+ if attr.is_list || attr.list_validation
655
+ validate_list_attr_val( attr, val )
656
+ else
657
+ validate_scalar_attr_val( attr, val )
658
+ end
659
+
660
+ val
661
+
662
+ rescue Exception => e
663
+
664
+ listener.call( attr, e )
665
+ raise e, "#{attr.identifier}: #{e.message}", trace
666
+ end
667
+ end
668
+
669
+ private
670
+ def set_attr_value( inst, hash, ident, attr, trace, listener )
671
+
672
+ val = get_initial_value( hash, ident, attr )
673
+
674
+ validate_attr_val( attr, val, trace, listener )
675
+
676
+ inst.instance_variable_set( :"@#{attr.identifier}", val )
677
+ end
678
+
679
+ private
680
+ def impl_initialize( inst )
681
+
682
+ if inst.respond_to?( meth = :impl_initialize, true )
683
+ inst.send( meth )
684
+ end
685
+ end
686
+
687
+ public
688
+ def init_instance( inst, argv )
689
+
690
+ argh =
691
+ if argv.size == 0
692
+ {}
693
+ else
694
+ if argv.size == 1 and ( arg = argv[ 0 ] ).is_a?( Hash )
695
+ arg
696
+ else
697
+ argv_to_argh( argv, @decl_order )
698
+ end
699
+ end
700
+
701
+ trace = caller
702
+ listener = BitGirderClassDefinition.get_validation_listener
703
+
704
+ @attrs.each_pair { |ident, attr|
705
+ set_attr_value( inst, argh, ident, attr, trace, listener )
706
+ }
707
+
708
+ impl_initialize( inst )
709
+ end
710
+
711
+ public
712
+ def hash_instance( inst )
713
+ @decl_order.map { |id| @attrs[ id ].get_instance_value( inst ) }.hash
714
+ end
715
+
716
+ @@class_defs = {}
717
+ @@validation_listener = nil
718
+
719
+ def self.get_class_defs
720
+ @@class_defs.dup
721
+ end
722
+
723
+ def self.get_validation_listener
724
+
725
+ if res = @@validation_listener
726
+ @@validation_listener = nil
727
+ res
728
+ else
729
+ lambda { |attr, e| } # no-op is default
730
+ end
731
+ end
732
+
733
+ def self.validation_listener=( l )
734
+
735
+ BitGirderMethods.not_nil( l, :l )
736
+ raise "A validation listener is already set" if @@validation_listener
737
+
738
+ @@validation_listener = l
739
+ end
740
+
741
+ def self.default_instance_mappers_for( cls )
742
+
743
+ [
744
+ InstanceMapper.new(
745
+ :processor => lambda { |val| val.is_a?( cls ) && val } ),
746
+
747
+ InstanceMapper.new(
748
+ :processor => lambda { |val|
749
+ # Use send in case cls made it private
750
+ val.is_a?( Hash ) && cls.send( :new, val )
751
+ }
752
+ )
753
+ ]
754
+ end
755
+
756
+ def self.for_class( cls )
757
+
758
+ unless res = @@class_defs[ cls ]
759
+
760
+ attrs, decl_order =
761
+ if cls == BitGirderClass || cls == BitGirderError
762
+ [ {}, [] ]
763
+ else
764
+ cd = self.for_class( cls.superclass )
765
+ [ Hash.new.merge( cd.attrs ), Array.new( cd.decl_order ) ]
766
+ end
767
+
768
+ @@class_defs[ cls ] = res = new(
769
+ :cls => cls,
770
+ :attrs => attrs,
771
+ :attr_syms => attrs.keys.map { |id| "@#{id}".to_sym },
772
+ :decl_order => decl_order,
773
+ :instance_mappers => self.default_instance_mappers_for( cls )
774
+ )
775
+ end
776
+
777
+ res
778
+ end
779
+
780
+ def self.code( *argv )
781
+ BitGirderLogger.code( *argv )
782
+ end
783
+
784
+ def self.init_instance( inst, argv )
785
+ self.for_class( inst.class ).init_instance( inst, argv )
786
+ end
787
+ end
788
+
789
+ class InstanceMapper
790
+
791
+ include BitGirderMethods
792
+
793
+ attr_reader :processor
794
+
795
+ # Does nil and other checks for public frontends
796
+ def initialize( opts )
797
+
798
+ not_nil( opts, :opts )
799
+ @processor = has_key( opts, :processor )
800
+ end
801
+ end
802
+
803
+ module BitGirderStructure
804
+
805
+ private
806
+ def impl_initialize; end
807
+
808
+ def initialize( *argv )
809
+ BitGirderClassDefinition.init_instance( self, argv )
810
+ end
811
+
812
+ public
813
+ def ==( other )
814
+
815
+ return true if self.equal?( other )
816
+ return false unless other.class == self.class
817
+
818
+ ! self.class.get_class_def.attr_syms.find do |sym|
819
+
820
+ other_val = other.instance_variable_get( sym )
821
+ self_val = instance_variable_get( sym )
822
+
823
+ other_val != self_val
824
+ end
825
+ end
826
+
827
+ alias eql? ==
828
+
829
+ def self.included( cls )
830
+
831
+ cls.class_eval do
832
+
833
+ def self.get_class_def
834
+ BitGirderClassDefinition.for_class( self )
835
+ end
836
+
837
+ def self.bg_attr( id, opts = {} )
838
+
839
+ argh = {
840
+ :identifier => id,
841
+ :validation => :not_nil
842
+ }.
843
+ merge( opts )
844
+
845
+ self.get_class_def.add_attr( argh )
846
+ end
847
+
848
+ def self.map_instances( opts )
849
+
850
+ im = InstanceMapper.new( opts ) # Validation in InstanceMapper
851
+ self.get_class_def.instance_mappers.unshift( im )
852
+ end
853
+
854
+ def self.map_instance_of( *classes, &blk )
855
+
856
+ self.map_instances(
857
+ :processor => lambda { |val|
858
+ classes.find { |cls| val.is_a?( cls ) } &&
859
+ blk.call( val )
860
+ }
861
+ )
862
+ end
863
+
864
+ def self.bg_abstract( meth )
865
+
866
+ define_method( meth ) do
867
+ raise "Abstract method #{self.class}##{meth} " \
868
+ "not implemented"
869
+ end
870
+ end
871
+
872
+ def self.as_instance( val )
873
+
874
+ return val if val.is_a?( self )
875
+
876
+ BitGirderMethods.not_nil( val, :val )
877
+
878
+ cd = self.get_class_def
879
+
880
+ cd.instance_mappers.each do |im|
881
+ ( res = im.processor.call( val ) ) && ( return res )
882
+ end
883
+
884
+ raise TypeError,
885
+ "Don't know how to convert #{val.class} to #{self}"
886
+ end
887
+
888
+ def self.install_hash
889
+
890
+ cd = self.get_class_def
891
+ define_method( :hash ) { cd.hash_instance( self ) }
892
+ end
893
+ end
894
+ end
895
+ end
896
+
897
+ class BitGirderClass
898
+
899
+ include BitGirderMethods
900
+ extend BitGirderMethods
901
+
902
+ include BitGirderStructure
903
+ end
904
+
905
+ class BitGirderError < StandardError
906
+
907
+ include BitGirderMethods
908
+ include BitGirderStructure
909
+ end
910
+
911
+ class BitGirderCliApplication < BitGirderClass
912
+
913
+ require 'optparse'
914
+
915
+ bg_attr :app_class,
916
+ :description => "Class object to be run as application"
917
+
918
+ bg_attr :main_method,
919
+ :description => "Entry method to application",
920
+ :default => :run,
921
+ :processor => lambda { |str| BitGirderMethods::ext_to_sym( str ) }
922
+
923
+ def initialize( opts = {} )
924
+
925
+ super( opts )
926
+ raise "No such method: #@main_method" unless respond_to?( @main_method )
927
+
928
+ @verbose = BitGirderLogger.is_debug_env_set?
929
+ end
930
+
931
+ private
932
+ def create_base_parser
933
+
934
+ p = OptionParser.new
935
+
936
+ p.on( "-v", "--[no-]verbose", "Run verbosely" ) { |flag|
937
+
938
+ @verbose = flag
939
+
940
+ BitGirderLogger.get_logger.level = to_bool( flag ) ?
941
+ BitGirderLogger::CODE : BitGirderLogger::DEFAULT
942
+ }
943
+
944
+ p.on( "-h", "--help", "Print this help" ) {
945
+ puts p
946
+ exit 0
947
+ }
948
+ end
949
+
950
+ private
951
+ def default_to_s( defl )
952
+
953
+ case defl
954
+ when STDOUT then "stdout"
955
+ when STDIN then "stdin"
956
+ when STDERR then "stderr"
957
+ else defl.to_s
958
+ end
959
+ end
960
+
961
+ private
962
+ def get_opt_parser_argv( attr )
963
+
964
+ argv = []
965
+
966
+ switch_str = attr.identifier.to_s.gsub( /_/, "-" )
967
+
968
+ if attr.processor == BitGirderAttribute::PROCESSOR_BOOLEAN
969
+ argv << "--[no-]#{switch_str}"
970
+ else
971
+ argv << "--#{switch_str} VAL"
972
+ end
973
+
974
+ if desc = attr.description
975
+
976
+ # attr.default could be the boolean false, which we still want to
977
+ # display
978
+ unless ( defl = attr.default ) == nil
979
+ desc += " (Default: #{default_to_s( defl )})"
980
+ end
981
+
982
+ argv << desc
983
+ end
984
+
985
+ argv
986
+ end
987
+
988
+ private
989
+ def parse_arg( argh )
990
+
991
+ attr = argh[ :attr ]
992
+ ident = attr.identifier
993
+
994
+ prev = argh[ :argh ][ ident ] || attr.default # could be nil either way
995
+
996
+ val = argh[ :arg ]
997
+ # val = attr.processor.call( val ) if attr.processor
998
+
999
+ if attr.is_list
1000
+ if prev == nil
1001
+ val = [ val ]
1002
+ else
1003
+ val = ( prev << val )
1004
+ end
1005
+ end
1006
+
1007
+ argh[ :argh ][ ident ] = val
1008
+ end
1009
+
1010
+ private
1011
+ def configure_parser
1012
+
1013
+ p = create_base_parser
1014
+ argh = {}
1015
+
1016
+ cd = BitGirderClassDefinition.for_class( @app_class )
1017
+ ( cd.attrs.keys - [ :main_method, :app_class ] ).each do |ident|
1018
+
1019
+ attr = cd.attrs[ ident ]
1020
+ argv = get_opt_parser_argv( attr )
1021
+
1022
+ p.on( *argv ) { |arg|
1023
+ parse_arg( :arg => arg, :argh => argh, :attr => attr )
1024
+ }
1025
+ end
1026
+
1027
+ [ p, argh ]
1028
+ end
1029
+
1030
+ private
1031
+ def create_app_obj( argh )
1032
+
1033
+ BitGirderClassDefinition.validation_listener =
1034
+ lambda { |attr, e|
1035
+
1036
+ warn( e, "Validation of attr #{attr} failed" ) if @verbose
1037
+ cli_opt = sym_to_cli_switch( attr.identifier )
1038
+ raise "#{cli_opt}: #{e.message}"
1039
+ }
1040
+
1041
+ @app_class.new( argh )
1042
+ end
1043
+
1044
+ private
1045
+ def make_run_ctx( argv_remain )
1046
+
1047
+ { :argv_remain => argv_remain, :verbose => @verbose }
1048
+ end
1049
+
1050
+ private
1051
+ def fail_run_main( e )
1052
+
1053
+ log = BitGirderLogger.get_logger
1054
+
1055
+ if @verbose
1056
+ log.warn( e, "App failed" )
1057
+ else
1058
+ STDERR.puts( e.message )
1059
+ end
1060
+
1061
+ exit EXIT_FAILURE
1062
+ end
1063
+
1064
+ private
1065
+ def run_main( app_obj, argv_remain )
1066
+
1067
+ meth = app_obj.class.instance_method( @main_method )
1068
+
1069
+ args =
1070
+ case arity = meth.arity
1071
+ when 0 then []
1072
+ when 1 then [ make_run_ctx( argv_remain ) ]
1073
+ else raise "Invalid arity for #{meth}: #{arity}"
1074
+ end
1075
+
1076
+ begin
1077
+ meth.bind( app_obj ).call( *args )
1078
+ rescue Exception => e
1079
+ fail_run_main( e )
1080
+ end
1081
+ end
1082
+
1083
+ public
1084
+ def run( argv = ARGV )
1085
+
1086
+ p, argh = configure_parser
1087
+
1088
+ begin
1089
+ p.parse!( argv = argv.dup )
1090
+ app_obj = create_app_obj( argh )
1091
+ rescue SystemExit => e
1092
+ skip_main = true
1093
+ rescue Exception => e
1094
+
1095
+ puts e
1096
+ puts p.to_s
1097
+
1098
+ if @verbose || BitGirderLogger.is_debug_env_set?
1099
+ puts e.backtrace.join( "\n" )
1100
+ end
1101
+
1102
+ exit EXIT_FAILURE
1103
+ end
1104
+
1105
+ run_main( app_obj, argv ) unless skip_main
1106
+ end
1107
+
1108
+ def self.run( cls )
1109
+
1110
+ BitGirderMethods::not_nil( cls, :cls )
1111
+ self.new( :app_class => cls ).run( ARGV )
1112
+ end
1113
+ end
1114
+
1115
+ class ObjectPath
1116
+
1117
+ include BitGirderMethods
1118
+ extend BitGirderMethods
1119
+
1120
+ attr_reader :parent
1121
+
1122
+ # Not to be called by public methods; all paths should be derived from a
1123
+ # parent path or initialized with get_root or get_root_list
1124
+ private_class_method :new
1125
+ def initialize( parent )
1126
+ @parent = parent # parent nil ==> this is a root path
1127
+ end
1128
+
1129
+ def descend( elt )
1130
+ DictionaryPath.send( :new, self, not_nil( elt, "elt" ) )
1131
+ end
1132
+
1133
+ def start_list
1134
+ ListPath.send( :new, self, 0 )
1135
+ end
1136
+
1137
+ # Returns a path starting with the specified root element
1138
+ def self.get_root( root_elt )
1139
+
1140
+ not_nil( root_elt, :root_elt )
1141
+ DictionaryPath.send( :new, nil, root_elt )
1142
+ end
1143
+
1144
+ def self.get_root_list
1145
+ ListPath.send( :new, nil, 0 )
1146
+ end
1147
+
1148
+ private
1149
+ def collect_path
1150
+
1151
+ res = [ node = self ]
1152
+
1153
+ # This loop is correct on the assumption that the caller obtained this
1154
+ # instance by the accepted means
1155
+ while node = node.parent
1156
+ res.unshift( node )
1157
+ end
1158
+
1159
+ res
1160
+ end
1161
+
1162
+ public
1163
+ def format( formatter = DefaultObjectPathFormatter.new )
1164
+
1165
+ not_nil( formatter, "formatter" )
1166
+
1167
+ formatter.format_path_start( str = "" )
1168
+
1169
+ collect_path.each_with_index do |elt, idx|
1170
+
1171
+ case elt
1172
+
1173
+ when DictionaryPath
1174
+ formatter.format_separator( str ) unless idx == 0
1175
+ formatter.format_key( str, elt.key )
1176
+
1177
+ when ListPath then formatter.format_list_index( str, elt.index )
1178
+
1179
+ else raise "Unexpected path type: #{elt.class}"
1180
+ end
1181
+ end
1182
+
1183
+ str
1184
+ end
1185
+ end
1186
+
1187
+ class DictionaryPath < ObjectPath
1188
+
1189
+ attr_reader :key
1190
+
1191
+ def initialize( parent, key )
1192
+
1193
+ super( parent )
1194
+ @key = not_nil( key, "key" )
1195
+ end
1196
+ end
1197
+
1198
+ class ListPath < ObjectPath
1199
+
1200
+ attr_reader :index
1201
+
1202
+ def initialize( parent, index )
1203
+
1204
+ super( parent )
1205
+ @index = nonnegative( index, "index" )
1206
+ end
1207
+
1208
+ def next
1209
+ ListPath.send( :new, self.parent, @index + 1 )
1210
+ end
1211
+ end
1212
+
1213
+ # Subclasses can override methods to customize as desired
1214
+ class DefaultObjectPathFormatter
1215
+
1216
+ public; def format_path_start( str ); end
1217
+
1218
+ public
1219
+ def format_separator( str )
1220
+ str << "."
1221
+ end
1222
+
1223
+ public
1224
+ def format_key( str, key )
1225
+ str << key.to_s
1226
+ end
1227
+
1228
+ public
1229
+ def format_list_index( str, indx )
1230
+ str << "[ #{indx} ]"
1231
+ end
1232
+ end
1233
+
1234
+ end
1235
+ end