rakish 0.9.01.beta

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.
@@ -0,0 +1,2049 @@
1
+ # Rakish module utilities
2
+ #
3
+ # $Id $
4
+
5
+ # this is becoming a "gem" we wat to require files with "rakish/FileName"
6
+
7
+ gemPath = File.expand_path("#{File.dirname(File.expand_path(__FILE__))}/..");
8
+ $LOAD_PATH.unshift(gemPath) unless $LOAD_PATH.include?(gemPath)
9
+
10
+ require 'open3.rb'
11
+
12
+ module Kernel # :nodoc:
13
+
14
+ #--
15
+ if false
16
+
17
+ if defined?(rakish_original_require) then
18
+ # Ruby ships with a custom_require, override its require
19
+ remove_method :require
20
+ else
21
+ # The Kernel#require from before RubyGems was loaded.
22
+ alias rakish_original_require require
23
+ private :rakish_original_require
24
+ end
25
+
26
+ @@_rakish_={}
27
+
28
+ def require path
29
+ # $: is search path list
30
+ # $" is array of loaded files?
31
+
32
+ if rakish_original_require path
33
+ puts("************ requiring #{path}");
34
+ puts(" loaded #{$".last}");
35
+ true
36
+ else
37
+ false
38
+ end
39
+ end
40
+ private :require
41
+ end # end false
42
+
43
+ end # Kernel
44
+ #++
45
+
46
+ require 'set'
47
+ require 'logger'
48
+
49
+
50
+ #-- stupid thing needed because rake doesn't check for "" arguments so we make an explicit task
51
+ #++
52
+ task "" do
53
+ end
54
+
55
+ # Module containg the package Rakish
56
+ # includes the module ::Rakish::Logger
57
+ #
58
+ # :include:doc/RakishOverview.html
59
+ #
60
+ # For more information see UserGuide[link:./doc/UserGuide.html]
61
+ #
62
+ module Rakish
63
+
64
+ # set to true if called from windows - cygwin
65
+ HostIsCygwin_ = (RUBY_PLATFORM =~ /(cygwin)/i) != nil;
66
+ # set to true if called on a windows host
67
+ HostIsWindows_ = (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil;
68
+ # set to true if called on a unix host
69
+ HostIsUnix_ = (!HostIsWindows_);
70
+ # set to true on a MacOS or iOS host
71
+ HostIsMac_ = (/darwin/ =~ RUBY_PLATFORM) != nil;
72
+
73
+
74
+ # Logger module
75
+ # To use this Logger initialization include it in a class or module
76
+ # enables log.debug { "message" } etc
77
+ # from methods or initializations in that class
78
+ # Other than for INFO level output
79
+ # output messages are formatted to include the file and line number where
80
+ # log.[level] was invoked.
81
+
82
+ module Logger
83
+
84
+ @@_logger_ = ::Logger.new(STDOUT);
85
+
86
+ # Returns the singleton instance of ::Logger managed by the Rakish::Logger
87
+ def self.log
88
+ @@_logger_
89
+ end
90
+ @@_logger_.formatter = proc do |severity, datetime, progname, msg|
91
+
92
+ fileLine = "";
93
+ unless('INFO' === severity)
94
+ caller.each do |clr|
95
+ unless(/\/logger.rb:/ =~ clr)
96
+ fileLine = clr;
97
+ break;
98
+ end
99
+ end
100
+ fileLine = fileLine.split(':in `',2)[0];
101
+ fileLine.sub!(/:(\d)/, '(\1');
102
+ fileLine += ') : ';
103
+ end
104
+
105
+ if(msg.is_a? Exception)
106
+ "#{fileLine}#{msg}\n #{formatBacktraceLine(msg.backtrace[0])}\n"
107
+ else
108
+ "#{fileLine}#{msg}\n"
109
+ end
110
+ end
111
+
112
+ # Format a single backtrace line as defined for the IDE we are using.
113
+ def self.formatBacktraceLine(line)
114
+ sp = line.split(':in `',2);
115
+ sp0 = sp[0].sub(/:(\d)/, '(\1');
116
+ sp1 = sp.length > 1 ? "in `#{sp[1]}" : "";
117
+ "#{sp0}) : #{sp1}";
118
+ end
119
+
120
+ # Format all lines in a backtrace using formatBacktraceLine() into a
121
+ # single printable string with entries separated by "\\n"
122
+ def self.formatBacktrace(backtrace)
123
+ out=[];
124
+ backtrace.each do |line|
125
+ out << formatBacktraceLine(line);
126
+ end
127
+ out.join("\n");
128
+ end
129
+
130
+ # Defines the method "log" in any including
131
+ # module or class at the class "static" level
132
+ def self.included(by) # :nodoc:
133
+ by.class.send(:define_method, :log) do
134
+ ::Rakish.log
135
+ end
136
+ end
137
+
138
+ # Returns the singleton instance of ::Logger managed by the Rakish::Logger
139
+ def log
140
+ STDOUT.flush;
141
+ ::Rakish.log
142
+ end
143
+ end
144
+
145
+ def self.log # :nodoc:
146
+ Rakish::Logger.log
147
+ end
148
+
149
+ # Execute shell command in sub process and pipe output to Logger at info level
150
+ # cmdline - single string command line, or array of command and arguments
151
+ # opts:
152
+ # :verbose - if set to true (testable value is true) will print command when executing
153
+ # :env - optional environment hash for spawned process
154
+ #
155
+ # returns status return from spawned process.
156
+ # uses Open3:popen2()
157
+
158
+ def self.execLogged(cmdline, opts={})
159
+ begin
160
+ if(cmdline.respond_to?(:to_ary))
161
+ log.info("\"#{cmdline.join("\" \"")}\"") if opts[:verbose]
162
+ # it is the array form of command
163
+ unless (Hash === cmdline[0]) # to handle ruby style environment argument - env is cmdline[0]
164
+ cmdline.unshift(opts[:env]) if opts[:env];
165
+ end
166
+ else
167
+ # TODO: handle parsing command line into array of arguments if opts[:env]
168
+ log.info("#{cmdline}") if opts[:verbose]
169
+ end
170
+
171
+ # this will redirect both stdout and stderr to the "output" pipe
172
+ # TODO: handle throwing exception if the process aborts with an error return code
173
+ exit_status = nil;
174
+ Open3.popen2(*cmdline, :err => [:child, :out]) do |i,output,t|
175
+ while line = output.gets do
176
+ log.info line.strip!
177
+ end
178
+ exit_status = t.value; # Process::Status object returned.
179
+ end
180
+ return(exit_status);
181
+ # IO.popen(cmdline) do |output|
182
+ # while line = output.gets do
183
+ # log.info line.strip!
184
+ # end
185
+ # end
186
+ # return $?
187
+ rescue => e
188
+ if(opts[:verbose])
189
+ if(cmdline.respond_to?(:to_ary))
190
+ cmdline.shift if opts[:env];
191
+ cmdline = "\"#{cmdline.join("\" \"")}\"";
192
+ end
193
+ log.error("failure executing: #{cmdline}");
194
+ end
195
+ log.error(e);
196
+ raise(e);
197
+ end
198
+ end
199
+
200
+ # convenience method like Rake::task
201
+ def self.task(*args,&block)
202
+ ::Rake::Task.define_task(*args, &block)
203
+ end
204
+
205
+ end
206
+
207
+
208
+ # Rake extensions
209
+ module Rake
210
+
211
+ module DSL # :nodoc: so if a version doesn't have it it works
212
+ end
213
+
214
+ class << self
215
+ # get a new generated unique name for "anonymous" classes
216
+ # and tasks and other uses
217
+ def get_unique_name
218
+ @_s_||=0
219
+ @_s_ += 1
220
+ :"_nona_#{@_s_}"
221
+ end
222
+ end
223
+
224
+
225
+
226
+
227
+ # Rake::TaskManager extensions
228
+ module TaskManager
229
+
230
+ # Two difference here:
231
+ # flattening of dependency lists
232
+ # so arguments can be arrays of arrays
233
+ # and it will recognize a leading ':' as an indicator of global root namespace scope
234
+ # in additon to "rake:"
235
+ def resolve_args(args)
236
+ if args.last.is_a?(Hash)
237
+ deps = args.pop
238
+ ret = resolve_args_with_dependencies(args, deps)
239
+ ret[2].flatten!
240
+ ret[2].map! do |e|
241
+ (e=~/^:/)?"rake#{e}":e
242
+ end
243
+ ret
244
+ else
245
+ resolve_args_without_dependencies(args)
246
+ end
247
+ end
248
+ end
249
+
250
+ # Rake::Application extensions
251
+ class Application
252
+
253
+ # Display the error message that caused the exception.
254
+ # formatted the way we like it for a particualar IDE
255
+
256
+ def display_error_message(ex)
257
+
258
+ $stdout.flush;
259
+ $stderr.flush;
260
+
261
+ $stderr.puts "#{name} aborted!: #{ex.message}"
262
+ backtrace = ex.backtrace;
263
+
264
+ if options.trace
265
+ $stderr.puts Rakish::Logger.formatBacktrace(backtrace)
266
+ else
267
+
268
+ lineNum = 0;
269
+ useLine = 0;
270
+
271
+ if(ex.message =~ /wrong number of arguments/)
272
+ useLine = 1;
273
+ end
274
+
275
+ backtrace.each do |line|
276
+ lineNum = lineNum + 1;
277
+ sp = line.split(':in `',2);
278
+ if(sp.length > 1)
279
+ if(sp[1] =~ /const_missing'$/)
280
+ useLine = lineNum;
281
+ break;
282
+ end
283
+ end
284
+ end
285
+
286
+ $stderr.puts(Rakish::Logger.formatBacktraceLine(backtrace[useLine]));
287
+ $stderr.puts(rakefile_location(backtrace)); # this seems to be broken !!
288
+ end
289
+
290
+ $stderr.puts "Tasks: #{ex.chain}" if has_chain?(ex)
291
+ $stderr.puts "(See full trace by running task with --trace)" unless options.trace
292
+ end
293
+ end
294
+
295
+ class Task
296
+ include Rakish::Logger
297
+
298
+ rake_extension('config') do
299
+ # note commented because Rdoc does not parse this
300
+ # attr_accessor :config
301
+ end
302
+ # optional "config" field on Rake Task objects
303
+ attr_accessor :config
304
+
305
+ rake_extension('data') do
306
+ # note commented because RDoc does not parse this
307
+ # attr_accessor :data
308
+ end
309
+ # optional "per instance" field on Rake Task objects
310
+ attr_accessor :data
311
+
312
+ # see Rake.Task as this overrides it's method
313
+ # to flatten dependencies so they can be provided as
314
+ # nested arrays or arguments
315
+ def enhance(args,&b)
316
+ # instead of |=
317
+ @prerequisites = [@prerequisites,args].flatten if args
318
+ @actions << b if block_given?
319
+ self
320
+ end
321
+
322
+ def scopeExec(args=nil) # :nodoc:
323
+ @application.in_namespace_scope(@scope) do
324
+ FileUtils.cd @_p_.projectDir do
325
+ _baseExec_(args);
326
+ end
327
+ end
328
+ end
329
+ private :scopeExec
330
+
331
+ rake_extension('setProjectScope') do
332
+ def setProjectScope(d) # :nodoc:
333
+ return self if(@_p_)
334
+ instance_eval do
335
+ alias :_baseExec_ :execute
336
+ alias :execute :scopeExec
337
+ end
338
+ @_p_=d;
339
+ self
340
+ end
341
+ end
342
+
343
+ class << self
344
+ # define a task with a unique anonymous name
345
+ # does not handle :name=>[] dependencies because the generated name is
346
+ # not known at the time of this declaratation
347
+ def define_unique_task(*args,&b)
348
+ args.unshift(Rake.get_unique_name)
349
+ Rake.application.define_task(self,*args,&b);
350
+ end
351
+ end
352
+ end
353
+
354
+ # Extension to force all file tasks to reference the full path for the file name.
355
+ # We want to make sure all File tasks are named after the full path
356
+ # so there is only one present for any given file.
357
+ class FileTask
358
+ class << self
359
+ # Apply the scope to the task name according to the rules for this kind
360
+ # of task. File based tasks ignore the scope when creating the name.
361
+ def scope_name(scope, task_name)
362
+ File.expand_path(task_name)
363
+ end
364
+ end
365
+ end
366
+
367
+ module TaskManager
368
+
369
+ if(RUBY_VERSION =~ /^2./)
370
+ def _trc # :nodoc:
371
+ puts("** namespace \":#{@scope.path}\"");
372
+ end
373
+ else # ruby 1.9.X
374
+ def _trc # :nodoc:
375
+ puts("** namespace \":#{@scope.join(':')}\"");
376
+ end
377
+ end
378
+ private :_trc
379
+
380
+ # Directory tasks are always in root list so this should be a bit faster
381
+ rake_extension('directory_task_defined?') do
382
+ # Return true if a directory creation task for the diven path is defined.
383
+ def directory_task_defined?(path)
384
+ @tasks.has_key?(path)
385
+ end
386
+ end
387
+
388
+ rake_extension('in_namespace_scope') do
389
+ # Allows for explicitly setting an "absolute" namespace
390
+ # Executes the block in the provided scope.
391
+ def in_namespace_scope(scope)
392
+ prior = @scope;
393
+ @scope = scope;
394
+ if options.trace
395
+ _trc
396
+ end
397
+ ns = NameSpace.new(self,scope);
398
+ yield(ns)
399
+ ns
400
+ ensure
401
+ @scope = prior;
402
+ if options.trace
403
+ _trc
404
+ end
405
+ end
406
+ end
407
+
408
+ if(RUBY_VERSION =~ /^2./)
409
+ # this allows for explicitly setting an "absolute" namespace from string
410
+ rake_extension('in_namespace_path') do
411
+ def in_namespace_path(name,&b)
412
+
413
+ # handle new scope which is a linked list instead of an array
414
+ prior=@scope
415
+ if(name.instance_of?(String))
416
+ spl = name.split(':');
417
+ newScope = @scope;
418
+ if(spl.size() == 0 || spl[0].length == 0) # absolute, begins with ':'
419
+ spl.shift();
420
+ # Rakish.log.debug("prior scope is \"#{name}\" => \"#{prior.path}\"");
421
+ newScope = Scope.make;
422
+ # else # relative
423
+ # TODO: handle '..:..: for back a level ???
424
+ end
425
+ spl.each do |elem|
426
+ newScope = Scope.new(elem,newScope);
427
+ end
428
+ @scope = newScope;
429
+ # Rakish.log.debug("new scope is \"#{@scope.path}\"");
430
+ elsif(name) # untested
431
+ Rakish.log.debug("prior scope is \"#{prior.path}\"");
432
+ @scope=Scope.new(name,@scope);
433
+ Rakish.log.debug("new scope is \"#{@scope.path}\"");
434
+ else # untested
435
+ Rakish.log.debug("prior scope is \"#{prior.path}\"");
436
+ @scope=Scope.make; # explicit to root
437
+ Rakish.log.debug("new scope is \"#{@scope.path}\"");
438
+ end
439
+ if options.trace
440
+ _trc
441
+ end
442
+ ns = NameSpace.new(self,@scope);
443
+ yield(ns)
444
+ ns
445
+ ensure
446
+ @scope=prior;
447
+ if options.trace
448
+ _trc
449
+ end
450
+ end
451
+ end
452
+
453
+ else # Ruby 1.9.X
454
+ # this allows for explicitly setting an "absolute" namespace from string
455
+ rake_extension('in_namespace_path') do
456
+ def in_namespace_path(name,&b)
457
+ prior=@scope
458
+ if(name.instance_of?(String))
459
+ spl = name.split(':');
460
+ if(spl.size() == 0 || spl[0].length == 0) # absolute
461
+ spl.shift();
462
+ @scope = spl;
463
+ else # relative
464
+ @scope = Array.new(prior).concat(spl);
465
+ end
466
+ elsif(name)
467
+ @scope=Array.new(prior).push(name);
468
+ else
469
+ @scope=Array.new
470
+ end
471
+ if options.trace
472
+ _trc
473
+ end
474
+ ns = NameSpace.new(self,@scope);
475
+ yield(ns)
476
+ ns
477
+ ensure
478
+ @scope=prior;
479
+ if options.trace
480
+ _trc
481
+ end
482
+ end
483
+ end
484
+ end # ruby 1.9.X
485
+ end
486
+ end
487
+
488
+ module Rakish
489
+
490
+ # extension to Ruby ::File class
491
+ class ::File
492
+
493
+ unless defined? ::File.path_is_absolute?
494
+
495
+ if(HostIsWindows_)
496
+ # Return true if path is an absolute path
497
+ def self.path_is_absolute?(path)
498
+ f0 = path[0]
499
+ f0 == '/' or f0 == '\\' or path[1] == ':'
500
+ end
501
+ else
502
+ # Return true if file is an absolute path
503
+ def self.path_is_absolute?(path)
504
+ path[0] == '/'
505
+ end
506
+ end
507
+ end
508
+ end
509
+
510
+ # Extensons to root level Module
511
+
512
+ class ::Module
513
+
514
+ # Static method used like ruby's attr_accessor declaration
515
+ # for use in declaring added properties on a class
516
+ # inheriting from a Rakish::PropertyBag or including a Rakish::PropertyBagMod
517
+
518
+ def attr_property(*args)
519
+ if(self.include? ::Rakish::PropertyBagMod)
520
+ args.each do |s|
521
+ # add "property" assignment operator method s= to this class
522
+ # equivalent of: def s=(v) { @_h[s]=v }
523
+ # where s is the input symbol, it is formatted as a string and passed
524
+ # to eval
525
+ eval("self.send(:define_method,:#{s}=){|v|@_h[:#{s}]=v}")
526
+ end
527
+ else
528
+ raise("can only add properties to PropertyBag object")
529
+ end
530
+ end
531
+
532
+ # Allows for the declaration and initialization of
533
+ # "Constructor" chains in the way C++ or Java operates where base classes (here mixin modules)
534
+ # can be optionally "automagically" invoked in inclusion order on class instance initialization.
535
+ # Any Class and Module may now take advantage of this. see ::Class.initializeIncluded
536
+ #
537
+ # Module HasConstructor
538
+ # addInitBlock do |args|
539
+ # @myVariable_ = "initialized in constructor of HasContructor"
540
+ # log.debug("initializing HasConstructor - on #{self} XX #{arg[0]}");
541
+ # end
542
+ # end
543
+ #
544
+ # class MyObject
545
+ # include HasConstructor
546
+ #
547
+ # initialize(*args)
548
+ # # Of course all modules included on this object and by their recursively
549
+ # # included modules must respond properly to args so likely best to use
550
+ # # named hash args and a naming convention for what included modules
551
+ # # use as opposed to an array of positional arguments
552
+ # self.class.initializeIncluded(self,args);
553
+ # end
554
+ # end
555
+
556
+ def addInitBlock(&b)
557
+ (@_init_||=[])<<b if block_given?
558
+ end
559
+
560
+ def _initBlocks_ # :nodoc:
561
+ @_init_;
562
+ end
563
+
564
+ end
565
+
566
+ class ::Class
567
+
568
+ # Monkey Hack to enable constructor inheritance like C++ on mixin modules
569
+ # and call the "initBlocks" of all modules included in this class in the included order
570
+ # installed by included modules which are nicely provided by the ruby ancestors list
571
+ # To do this use this in a Class instance initializer, the arguments will be passed to all superclass mixin init blocks:
572
+ #
573
+ # obj.class.initializeIncluded(obj,*args);
574
+ #
575
+ # see ::Module and ::Module.addInitBlock() for example.
576
+
577
+ def initializeIncluded(obj,*args)
578
+ # call the included modules init blocks with arguments
579
+ self.ancestors.reverse_each do |mod|
580
+ inits = mod._initBlocks_;
581
+ if inits
582
+ inits.each do |b|
583
+ obj.instance_exec(args,&b);
584
+ end
585
+ end
586
+ end
587
+ end
588
+ end
589
+
590
+ # Container for searchable path set for finding files in
591
+ class SearchPath
592
+
593
+ # Initialize a new SearchPath calls setPath(*paths)
594
+ def initialize(*paths)
595
+ setPath(*paths);
596
+ end
597
+
598
+ if( HostIsWindows_ )
599
+ @@osDelimiter_=';';
600
+ else
601
+ @@osDelimiter_=':';
602
+ end
603
+
604
+ # Clear and set path set and call adPath(*paths)
605
+ #
606
+ # If no paths are provided then this path set is left empty.
607
+ def setPath(*paths)
608
+ @path_=[];
609
+ addPath(*paths);
610
+ end
611
+
612
+ # So we can pass this into SearchPath.new or SearchPath.addPath
613
+ def to_ary # :nodoc:
614
+ @path_
615
+ end
616
+
617
+ # Iterate over all paths in this SearchPath
618
+ def each(&b)
619
+ @path_.each(&b)
620
+ end
621
+
622
+ # Add a path or path list to this search path
623
+ # If a path entry begins with a '.' is will be left in the search path
624
+ # as a relative path, otherwise it will be expanded to an absolute path when added.
625
+ #
626
+ # Named opts:
627
+ # :delimiter => path entry delimiter
628
+ #
629
+ def addPath(*paths)
630
+ opts = (Hash === paths.last) ? paths.pop : {}
631
+ delimiter = opts[:delimiter] ||@@osDelimiter_;
632
+ paths.flatten!
633
+ paths.each do |path|
634
+ pa = path.split(delimiter);
635
+ pa.each do |p|
636
+ p=File.absolute_path(p) unless p[0]=='.'
637
+ @path_ << p;
638
+ end
639
+ end
640
+ @path_.uniq!
641
+ end
642
+
643
+ # like array.join() to build path strings
644
+ def join(s)
645
+ @path_.join(s);
646
+ end
647
+
648
+ private
649
+ def onNotFound(name,opts) # :nodoc:
650
+ msg = "could not find file #{name}\n from: #{File.expand_path('.')}\n in:\n #{join("\n ")}"
651
+ case opts[:onMissing]
652
+ when 'log.error'
653
+ ::Rakish.log.error msg;
654
+ when 'log.warn'
655
+ ::Rakish.log.warn msg;
656
+ when 'log.debug'
657
+ ::Rakish.log.debug msg;
658
+ when 'log.info'
659
+ ::Rakish.log.debug msg;
660
+ else
661
+ raise Exception.new(msg);
662
+ end
663
+ end
664
+ public
665
+
666
+ # Find a file with the given name (or relative subpath) in this search set
667
+ # If the input name is an absolute path it is simply returned.
668
+ # named ops:
669
+ # :suffi => If set search for file with suffix in order of suffi list
670
+ # '' is a valid suffix in this case. suffi must have leading dot
671
+ # as in '.exe'
672
+ # :onMissing => reporting action to perform when file is not found
673
+ # 'log.error','log.warn',log.debug','log.info',
674
+ # any other true (or non false/nil) value raises an exception
675
+ # I prefer 'raise'
676
+ #
677
+
678
+ def findFile(name,opts={})
679
+ if(File.path_is_absolute?(name))
680
+ onNotFound(name,opts) if(opts[:onMissing] && (!File.exists?(name)))
681
+ return(name);
682
+ end
683
+ found = nil;
684
+ suffi = opts[:suffi];
685
+ @path_.each do |path|
686
+ path = "#{path}/#{name}";
687
+ unless suffi
688
+ if(File.exists?(path))
689
+ found=File.absolute_path(path);
690
+ break;
691
+ end
692
+ else
693
+ suffi.each do |suff|
694
+ fpath="#{path}.exe";
695
+ if(File.exists?(fpath))
696
+ found=File.absolute_path(fpath);
697
+ break;
698
+ end
699
+ end
700
+ break if(found)
701
+ end
702
+ end
703
+ onNotFound(name,opts) if(!found && opts[:onMissing]);
704
+ found;
705
+ end
706
+ end
707
+
708
+ # Intended to clean up things to minimize thread usage and queue up these so as to
709
+ # keep avaiable processor cores saturated but without thread thrashing. Spawning lots of threads
710
+ # does not help in the process spawning case and actually slows things down.
711
+ class MultiProcessTask < Rake::Task
712
+ private
713
+ def invoke_prerequisites(args, invocation_chain)
714
+ threads = @prerequisites.collect { |p|
715
+ Thread.new(p) { |r| application[r].invoke_with_call_chain(args, invocation_chain) }
716
+ }
717
+ threads.each { |t| t.join }
718
+ end
719
+ end
720
+
721
+ module LoadableModule
722
+ include Rakish::Logger
723
+
724
+ @@loadedByFile_ = {};
725
+
726
+ def self.load(fileName)
727
+ fileName = File.expand_path(fileName);
728
+ mod = @@loadedByFile_[fileName];
729
+ return mod if mod;
730
+ begin
731
+ Thread.current[:loadReturn] = nil;
732
+ Kernel.load(fileName);
733
+ mod = Thread.current[:loadReturn];
734
+ @@loadedByFile_[fileName] = mod if(mod);
735
+ rescue => e
736
+ log.error { e };
737
+ mod = nil;
738
+ end
739
+ Thread.current[:loadReturn] = nil;
740
+ mod
741
+ end
742
+ def self.onLoaded(retVal)
743
+ Thread.current[:loadReturn] = retVal;
744
+ end
745
+ end
746
+
747
+ public
748
+
749
+
750
+ # a bunch of utility functions used by Projects and configurations
751
+ module Util
752
+ include ::Rake::DSL
753
+ include Rakish::Logger
754
+
755
+ # Very simple module for Git used to initialize my projects
756
+ module Git # :nodoc:
757
+
758
+ class << self
759
+ def clone(src,dest,opts={})
760
+ if(!File.directory?(dest))
761
+
762
+ origin = opts[:remote] || "origin";
763
+
764
+ puts("Git.clone -o \"#{origin}\" -n \"#{src}\" \"#{dest}\"");
765
+
766
+ system("git clone -o \"#{origin}\" -n \"#{src}\" \"#{dest}\"");
767
+ cd dest do
768
+ system("git config -f ./.git/config --replace-all core.autocrlf true");
769
+ system("git reset -q --hard");
770
+ end
771
+ end
772
+ end
773
+
774
+ def addRemote(dir, name, uri)
775
+ cd dir do
776
+ system("git remote add \"#{name}\" \"#{uri}\"");
777
+ end
778
+ end
779
+ end
780
+ end
781
+
782
+ # convenience method like Rake::task
783
+ def task(*args,&block)
784
+ Rake::Task.define_task(*args, &block)
785
+ end
786
+
787
+ # Like each but checks for null and if object doesn't respond to each
788
+ #
789
+ # use like
790
+ # eachof [1,2,3] do |v|
791
+ # end
792
+ #
793
+ def eachof(v,&b)
794
+ v.each &b rescue yield v if v # TODO: should use a narrower exception type ?
795
+ end
796
+
797
+ protected
798
+ # Task action to simply copy source to destination
799
+ SimpleCopyAction_ = ->(t) { FileUtils.cp(t.source, t.name) }
800
+
801
+ # Task action to do nothing.
802
+ DoNothingAction_ = ->(t) {}
803
+
804
+
805
+ public
806
+
807
+ # Execute shell command and pipe output to Logger
808
+ def execLogged(cmd, opts={})
809
+ Rakish.execLogged(cmd,opts)
810
+ end
811
+
812
+ # Get current namespace as a string.
813
+ def currentNamespace
814
+ ":#{Rake.application.current_scope.join(':')}";
815
+ end
816
+
817
+ # Used like Rake's 'namespace' to execute the block
818
+ # in the specified namespace, except it enables
819
+ # explicit specification of absolute namespace paths
820
+ # ie:
821
+ # ':' selects the root namespace
822
+ # ':levelOne:levelTwo' is an absolute specification to ':'
823
+ #
824
+ def namespace(name=nil,&block)
825
+ Rake.application.in_namespace_path(name, &block)
826
+ end
827
+
828
+ # Get time stamp of file or directory
829
+ def filetime(name)
830
+ File.exists?(name) ? File.mtime(name.to_s) : Rake::EARLY
831
+ end
832
+
833
+ # Are there any tasks in the iterable 'tasks' list with an earlier 'time' than the given time stamp?
834
+ def any_task_earlier?(tasks,time)
835
+ tasks.any? { |n| n.timestamp < time }
836
+ end
837
+
838
+ # Get simple task action block (lambda) to copy from t.source to t.name
839
+ # do |t|
840
+ # cp t.source, t.name
841
+ # end
842
+ def simpleCopyAction()
843
+ SimpleCopyAction_
844
+ end
845
+
846
+ def deleteFileArray(files,opts={}) # :nodoc:
847
+ noglob = opts[:noglob]
848
+
849
+ files.each do |f|
850
+ if(f.respond_to?(:to_ary))
851
+ deleteFileArray(f,opts)
852
+ next
853
+ end
854
+ f = f.to_s
855
+ unless noglob
856
+ if(f =~ /[*?]/)
857
+ deleteFileArray(FileList.new(f),opts)
858
+ next
859
+ end
860
+ end
861
+ rm f,opts
862
+ end
863
+ end
864
+
865
+ # delete list of files (a single file or no files) similar to system("rm [list of files]")
866
+ # accepts Strings, FileList(s) and FileSet(s) and arrays of them
867
+ #
868
+ # named options: all [true|false]:
869
+ #
870
+ # :force => default true
871
+ # :noop => just print (if verbose) don't do anything
872
+ # :verbose => print "rm ..." for each file
873
+ # :noglob => do not interpret '*' or '?' as wildcard chars
874
+ #
875
+ #
876
+ def deleteFiles(*files)
877
+ opts = (Hash === files.last) ? files.pop : {}
878
+ opts[:force]||=true # default to true
879
+ opts[:verbose]||=false # default to false
880
+ deleteFileArray(files,opts)
881
+ end
882
+ alias :deleteFile :deleteFiles
883
+
884
+ def getCallingLocation(count = 0)
885
+ myLoc = nil
886
+ count += 1;
887
+ if(RUBY_VERSION =~ /^1\.9\.\d/)
888
+ caller.each do |clr|
889
+ if(count <= 0)
890
+ myLoc = clr;
891
+ break;
892
+ end
893
+ count = count - 1;
894
+ if(clr =~ /:in `<top \(required\)>'$/)
895
+ myLoc = $`;
896
+ break;
897
+ end
898
+ end
899
+ end
900
+ return(myLoc);
901
+ end
902
+
903
+ # ensures a task is present to create directory dir
904
+ def ensureDirectoryTask(dir)
905
+ unless(dir)
906
+ loc = getCallingLocation();
907
+ log.debug("warning: #{loc} ensuring NIL directory");
908
+ else
909
+ unless Rake.application.directory_task_defined?(dir)
910
+ Rake.each_dir_parent(dir) do |d|
911
+ file_create d do |t|
912
+ FileUtils.mkdir_p t.name if ! File.exist?(t.name)
913
+ end
914
+ end
915
+ end
916
+ end
917
+ dir
918
+ end
919
+
920
+ def copyRule(destdir, srcdir, suffix) # :nodoc:
921
+
922
+ # puts(" copy rule #{srcdir} #{destdir} #{suffix}" );
923
+
924
+ if(srcdir =~ /\/\z/)
925
+ srcdir = $`
926
+ end
927
+
928
+ if(destdir =~ /\/\z/)
929
+ # too bad rake doesn't check both, but string based
930
+ ensureDirectoryTask(destdir)
931
+ destdir = $`
932
+ end
933
+ ensureDirectoryTask(destdir)
934
+
935
+ # puts("creating rule for #{srcdir} -> #{destdir}");
936
+
937
+ regex =
938
+ Regexp.new('^' + Regexp.escape(destdir) + '\/[^\/]+' + suffix + '\z', true);
939
+
940
+ Rake::Task::create_rule( regex => [
941
+ proc { |task_name|
942
+ task_name =~ /\/[^\/]+\z/
943
+ task_name = srcdir + $&
944
+ }
945
+ ]) do |t|
946
+ cp(t.source, t.name);
947
+ end
948
+ end
949
+
950
+ # This will "pre-process" input lines using the ruby escape sequence
951
+ # '#{}' for substitutions
952
+ #
953
+ # in the binding
954
+ # linePrefix is an optional prefix to prepend to each line.
955
+ #
956
+ # setIndent means set a variable "indent" in the environment
957
+ # to be the indent level of the current raw line
958
+ #
959
+ # lines = input lines (has to implement each_line)
960
+ # fout = output file (has to implement puts, print)
961
+ # bnd = "binding" to context to evaluate substitutions in
962
+ def rubyLinePP(lines,fout,bnd,opts={})
963
+
964
+ setIndent = eval('defined? indent',bnd)
965
+ linePrefix = opts[:linePrefix];
966
+
967
+ rawLine = nil;
968
+ lineNum = 0;
969
+ begin
970
+ lines.each_line do |line|
971
+ ++lineNum;
972
+ rawLine = line;
973
+ fout.print(linePrefix) if linePrefix;
974
+ fout.puts line.gsub(/\#\{[^\#]+\}/) { |m|
975
+ eval("indent=#{$`.length}",bnd) if setIndent;
976
+ eval('"'+m+'"',bnd)
977
+ }
978
+ end
979
+ rescue => e
980
+ log.error do
981
+ bt=[]
982
+ e.backtrace.each do |bline|
983
+ bt << Logger.formatBacktraceLine(bline);
984
+ break if bline =~ /(.*)rubyLinePP/;
985
+ end
986
+ "error processing line #{lineNum}: #{e}\n\"#{rawLine.chomp}\"\n#{bt.join("\n")}"
987
+ end
988
+ end
989
+ end
990
+
991
+ # This will "preprocess" an entire file using the ruby escape sequence
992
+ # '#{}' for substitutions
993
+ #
994
+ # ffrom = input file path
995
+ # fto = output file path
996
+ # bnd = "binding" to context to evaluate substitutions in
997
+ def rubyPP(ffrom,fto,bnd)
998
+
999
+ begin
1000
+ File.open(fto,'w') do |file|
1001
+ File.open(ffrom,'r') do |fin|
1002
+ rubyLinePP(fin,file,bnd)
1003
+ end
1004
+ end
1005
+ rescue => e
1006
+ log.error("error precessing: #{ffrom} #{e}")
1007
+ raise e
1008
+ end
1009
+ end
1010
+
1011
+ # Get relative path between path and relto
1012
+ # returns absolute path of path if the roots
1013
+ # are different.
1014
+ def getRelativePath(path,relto=nil)
1015
+
1016
+ relto ||= pwd
1017
+ relto = File.expand_path(relto)
1018
+ path = File.expand_path(path.to_s)
1019
+ if( path =~ /^#{relto}\//)
1020
+ return("./#{$'}")
1021
+ end
1022
+
1023
+ # puts("### #{path} relto #{relto}")
1024
+
1025
+ rtspl = relto.split('/')
1026
+ pspl = path.split('/')
1027
+ pspsz = pspl.size
1028
+
1029
+ cmpi = 0
1030
+ rtspl.each_index do |i|
1031
+ if(i < pspsz)
1032
+ next if rtspl[i] == pspl[i]
1033
+ end
1034
+ cmpi = i;
1035
+ break;
1036
+ end
1037
+
1038
+ # puts(" ##### cmpi #{cmpi}")
1039
+
1040
+ if(cmpi < 1)
1041
+ return('.') if(relto == path)
1042
+ return(path)
1043
+ end
1044
+
1045
+ diff = rtspl.size - cmpi
1046
+
1047
+ # puts(" ##### diff #{diff}")
1048
+ op=[]
1049
+ 1.upto(diff) { op << '..' }
1050
+ cmpi.upto(pspsz-1) { |i| op << pspl[i] }
1051
+ op.join('/')
1052
+ end
1053
+
1054
+ # Same as getRelativePath except the returned path uses '\\' instead of '/'
1055
+ # as a path separator
1056
+ def getWindowsRelativePath(path,relto=nil)
1057
+ getRelativePath(path,relto).gsub('/','\\');
1058
+ end
1059
+
1060
+ # return true if host environment is windows or cygwin
1061
+ def hostIsWindows?
1062
+ HostIsWindows_
1063
+ end
1064
+
1065
+ # open and read first line of a file and return it
1066
+ # returns nil if the file is not present.
1067
+ def readOneLineFile(path)
1068
+ line = nil
1069
+ begin
1070
+ File::open(path,'r') do |ifile|
1071
+ line = ifile.readline();
1072
+ line.chomp!()
1073
+ end
1074
+ rescue
1075
+ end
1076
+ line
1077
+ end
1078
+
1079
+ # open and read an entire file into a string
1080
+ # throws excpetion if the file is not present.
1081
+ def readFileToString(path)
1082
+ str = nil
1083
+ File::open(path,'r') do |f|
1084
+ str = f.read();
1085
+ end
1086
+ str
1087
+ end
1088
+
1089
+ if( HostIsWindows_ )
1090
+ if(HostIsCygwin_)
1091
+ @@stderrToNUL = "2>/dev/null"
1092
+ else
1093
+ @@stderrToNUL = "2>NUL:"
1094
+ end
1095
+ else
1096
+ @@stderrToNUL = "2>/dev/null"
1097
+ end
1098
+
1099
+ def textFilesDiffer(a,b)
1100
+ differ = true;
1101
+
1102
+ sh "diff #{@@stderrToNUL} --brief \"#{a}\" \"#{b}\"", :verbose=>false do |ok, res|
1103
+ differ = false if ok
1104
+ end
1105
+ differ
1106
+ end
1107
+
1108
+ private
1109
+ if( HostIsWindows_ )
1110
+ def u2d(file)
1111
+ system "unix2dos \"#{file}\" #{@@stderrToNUL}"
1112
+ end
1113
+ else
1114
+ def u2d(file)
1115
+ system "unix2dos -q \"#{file}\""
1116
+ end
1117
+ end
1118
+
1119
+ public
1120
+ # convert unix EOLs to dos EOLs on a file in place
1121
+ def unix2dos(file)
1122
+ begin
1123
+ u2d(file)
1124
+ rescue => e
1125
+ puts("Warning: #{e}")
1126
+ end
1127
+ end
1128
+
1129
+ # Returns hash containing the difference between a "parent" hash
1130
+ # and an overriding "child" hash
1131
+ def hashDiff(parent,child)
1132
+ dif={}
1133
+ child.defines.each do |k,v|
1134
+ next if(parent.has_key?(k) && (parent[k] == v))
1135
+ dif[k]=v
1136
+ end
1137
+ dif
1138
+ end
1139
+
1140
+ # Prepends a parent path to an array of file names
1141
+ #
1142
+ # returns a FileList containing the joined paths
1143
+ def addPathParent(prefix,files)
1144
+
1145
+ if(prefix =~ /\/\z/)
1146
+ prefix = $`
1147
+ end
1148
+ if(files.instance_of?(String))
1149
+ a = prefix + '/' + files;
1150
+ else
1151
+ a = FileList.new() # can one allocate to files.size?
1152
+ files.each_index { |i|
1153
+ a[i] = prefix + '/' + files[i];
1154
+ }
1155
+ end
1156
+ return(a)
1157
+ end
1158
+
1159
+ if( HostIsWindows_ )
1160
+ @@binPathOpts_ = { :suffi => [ '.exe', '.bat' ] };
1161
+ else
1162
+ @@binPathOpts_ = {};
1163
+ end
1164
+
1165
+ # Find executable in the "bin" search path
1166
+ # return nil if not found.
1167
+ #
1168
+ # currently the search path is set to the value of ENV['PATH']
1169
+ #
1170
+ def self.findInBinPath(name)
1171
+ @@binpath||=SearchPath.new(ENV['PATH']);
1172
+ @@binpath.findFile(name,@@binPathOpts_);
1173
+ end
1174
+
1175
+ # Create a single simple file task to process source to dest
1176
+ #
1177
+ # if &block is not given, then a simple copy action
1178
+ # do |t| { FileUtils::cp(t.source, t.name) }
1179
+ # is used
1180
+ #
1181
+ # <b>named arguments:</b>
1182
+ # :config => a config object to set on the created task
1183
+ #
1184
+ # returns the task created
1185
+
1186
+ def createFileTask(dest,source,opts={},&block)
1187
+ block = SimpleCopyAction_ unless block_given?
1188
+ dest = File.expand_path(dest.to_s)
1189
+ dir = File.dirname(dest)
1190
+ ensureDirectoryTask(dir)
1191
+ task = file dest=>[source,dir], &block
1192
+ task.sources = task.prerequisites
1193
+ if(dir = opts[:config]) # re-use dir
1194
+ task.config = dir
1195
+ end
1196
+ task
1197
+ end
1198
+
1199
+ # Look up a task in the task table using a leading ':' as
1200
+ # the indicator of an absolute 'path' like 'rake:'
1201
+ def lookupTask(tname)
1202
+ Rake.application.lookup((tname=~/^:/)?"rake#{tname}":tname);
1203
+ end
1204
+
1205
+ # Create a single simple "copy" task to process source file
1206
+ # file of same name in destination directory
1207
+ #
1208
+ # if &block is not given, then a simple copy action
1209
+ # do |t| { cp(t.source, t.name) }
1210
+ # is used
1211
+ #
1212
+ # <b>named arguments:</b>
1213
+ # :config => a config object to set on the created task
1214
+ #
1215
+ # returns the task created
1216
+
1217
+ def createCopyTask(destDir,sourceFile,opts={},&block)
1218
+ createFileTask("#{destDir}/#{File.basename(sourceFile)}",sourceFile,opts,&block)
1219
+ end
1220
+
1221
+
1222
+ # For all files in files create a file task to process the file from the
1223
+ # source file files[n] to destdir/basename(files[n])
1224
+ #
1225
+ # if &block is not given, then a simple copy task
1226
+ # do |t| { cp t.source t.name }
1227
+ # is used
1228
+ #
1229
+ # <b>named arguments:</b>
1230
+ #
1231
+ # :config => 'config' object to set on the created tasks
1232
+ # :basedir => directory which will be used to truncate the
1233
+ # path of file[n] to get the relative path of the output
1234
+ # file from destdir.
1235
+ # ie: if basedir == "xx/yy" and file[n] == /aa/bb/xx/yy/file.x
1236
+ # then the output file will be 'outdir'+/xx/yy/file.x
1237
+ # :preserve if true will not overwrite existing task
1238
+ #
1239
+ # returns an array of all tasks created
1240
+ #
1241
+
1242
+ def createCopyTasks(destdir,*files,&block)
1243
+
1244
+ block = SimpleCopyAction_ unless block_given?
1245
+ opts = (Hash === files.last) ? files.pop : {}
1246
+ preserve = opts[:preserve];
1247
+
1248
+ files = FileSet.new(files); # recursively expand wildcards.
1249
+
1250
+ destdir = File.expand_path(destdir);
1251
+ if(destdir =~ /\/\z/)
1252
+ # too bad rake doesn't check both, it is string based
1253
+ ensureDirectoryTask(destdir)
1254
+ end
1255
+ ensureDirectoryTask(destdir)
1256
+
1257
+ config = opts[:config]
1258
+ if(regx = opts[:basedir])
1259
+ basedir = File.expand_path(regx);
1260
+ if(basedir =~ /\/\z/)
1261
+ basedir = $`
1262
+ end
1263
+ regx = Regexp.new('^' + Regexp.escape(basedir+'/'),Regexp::IGNORECASE);
1264
+ end
1265
+
1266
+ tsk = nil; # declaration only, used below in loop
1267
+ flist=[]
1268
+ files.each do |f| # as not a set, flatten if an array
1269
+ f = f.to_s
1270
+ f = File.expand_path(f)
1271
+ dir = destdir
1272
+ next if(File.directory?(f))
1273
+ if(regx)
1274
+ if(f =~ regx)
1275
+ dir = File.dirname($');
1276
+ if(dir.length == 0 || dir == '.')
1277
+ dir = destdir
1278
+ elsif(destdir.length > 0)
1279
+ dir = "#{destdir}/#{dir}"
1280
+ ensureDirectoryTask(dir)
1281
+ end
1282
+ end
1283
+ end
1284
+
1285
+ destFile = "#{dir}/#{File.basename(f)}"; # name of task
1286
+ if((!preserve) || ((tsk = Rake.application.lookup(destFile)) == nil))
1287
+ tsk = file destFile => [ f, dir ], &block
1288
+ tsk.sources = tsk.prerequisites
1289
+ tsk.config = config if config
1290
+ end
1291
+ flist << tsk # will always be task and not name here
1292
+ end
1293
+ flist
1294
+ end
1295
+ end
1296
+
1297
+ # nothing uses this now - bad idea probably
1298
+ # # include in our own module ( not needed )
1299
+ # # include Rakish::Util
1300
+ #
1301
+ # private
1302
+ # class Utils < Module
1303
+ # include Util
1304
+ # end
1305
+ # @@utils = Utils.new
1306
+ #
1307
+ # public
1308
+ # def self.utils
1309
+ # @@utils
1310
+ # end
1311
+
1312
+ # Generic dynamic propety bag functionality module1
1313
+ # allows 'dot' access to dynamicly set properties.
1314
+ # ie: value = bag.nameOfProperty
1315
+ # bag.nameOfProperty = newValue
1316
+ #
1317
+ # Throws an execption if the property or method is not found on this
1318
+ # node or any of it's parents.
1319
+ #
1320
+ # Assignment to a property that does not exist will add a new field *only* if
1321
+ # done so within an enableNewFields block
1322
+ #
1323
+ # bag.newProperty = "new value" # throws missing method exception
1324
+ # bag.enableNewFields do |s|
1325
+ # s.newProperty = "new value" # OK
1326
+ # end
1327
+ #
1328
+
1329
+ module PropertyBagMod
1330
+
1331
+ # constructor for PropertyBagMod to be called by including classes
1332
+ # This will take an array of parents scanned like a like a path, first hit wins.
1333
+ # by default parents o level above are split into heritable and non-heritable parents
1334
+ # the first parent i the list is considered heritable subsequent parents are in the
1335
+ # search path but are not considerd "heritable" so they will not be fround from
1336
+ # inhering childern.
1337
+ def init_PropertyBag(*args)
1338
+ @_h = (Hash === args.last) ? args.pop : {}
1339
+ allP = [];
1340
+ @parents_ = allP;
1341
+
1342
+ if(args.length > 0)
1343
+ hp = nil;
1344
+ # make parents array the array in the of order of encounter of all unique parents.
1345
+ args.each do |p|
1346
+ if(p)
1347
+ allP << p;
1348
+ php = p.heritableParents
1349
+ hp||=[p,php].flatten;
1350
+ allP << php;
1351
+ else
1352
+ hp||=[]
1353
+ end
1354
+ end
1355
+ @parents_ = allP.flatten.uniq;
1356
+ if( hp.length != @parents_.length)
1357
+ @_hpc_ = hp.length;
1358
+ end
1359
+ end
1360
+ # log.debug("****** parents #{@parents_} allP #{allP}")
1361
+ end
1362
+
1363
+ # get the first parent of this property bag if any
1364
+ def parent
1365
+ (parents.length > 0 ? @parents_[0] : nil);
1366
+ end
1367
+
1368
+ # get the list of ancestors (flattened) in search order.
1369
+ def parents
1370
+ @parents_||=[]
1371
+ end
1372
+ # This will supply inheritable parents to children.
1373
+ #
1374
+ # By default only parents[0] and it's hetitableParents are inheritable by children
1375
+ # added parents resolve in the search only.
1376
+ def heritableParents
1377
+ return parents unless @_hpc_;
1378
+ @parents_.take(@_hpc_)
1379
+ end
1380
+
1381
+ # Enable creation of new fields in a property bag within the supplied block.
1382
+ # may be called recursively
1383
+
1384
+ def enableNewFields(&b)
1385
+ # field creation locked by default, can be recursively "unlocked"
1386
+ # by using reference count
1387
+ @ul_=@ul_ ? @ul_+1:1
1388
+ yield self
1389
+ remove_instance_variable(:@ul_) if((@ul_-=1) < 1)
1390
+ self # return self for convenience
1391
+ end
1392
+
1393
+ # Returns false if outside an enableNewFields block the nesting count if
1394
+ # inside a enebleNewFields block if(newFieldsEnabled?) will test true in this case.
1395
+ def newFieldsEnabled?()
1396
+ @ul_ ? @ul_:false
1397
+ end
1398
+
1399
+ # item from "Module" we want overidable
1400
+ # note name does NOT inherit from parents
1401
+ def name # :nodoc:
1402
+ @_h[:name]
1403
+ end
1404
+
1405
+ # item from "Module" we want overidable
1406
+ # note name does NOT inherit from parents
1407
+ def name=(v) # :nodoc:
1408
+ @_h[:name]=v
1409
+ end
1410
+
1411
+ def self.included(by) # :nodoc:
1412
+ end
1413
+
1414
+ # set or create property irrespective of property (field) creation lock on this object
1415
+ def set(k,v)
1416
+ if self.class.method_defined? k
1417
+ raise PropertyBagMod::cantOverrideX_(k)
1418
+ end
1419
+ @_h[k]=v
1420
+ end
1421
+
1422
+ # Get non-nil value for property 0n any level above, traverse up parent tree via flattened
1423
+ # ancestor list to get first inherited value or nil if not found.
1424
+ # opts - none define at present
1425
+ def getAnyAbove(sym)
1426
+ parents.each do |p|
1427
+ val = p.getMy(sym);
1428
+ return(val) if val;
1429
+ end
1430
+ nil
1431
+ end
1432
+
1433
+ # Get non-nil value for property from heritable parents on any level above,
1434
+ # traverse up parent tree via
1435
+ # ancestor list to get first inherited value or nil if not found.
1436
+ # opts - none defined at present
1437
+ def getInherited(sym)
1438
+ return(getAnyAbove(sym)) unless @_hpc_;
1439
+ pc = @_hpc_;
1440
+ @parents_.each do |p|
1441
+ break if(pc < 1)
1442
+ val = p.getMy(sym);
1443
+ return(val) if val;
1444
+ pc=pc-1;
1445
+ end
1446
+ nil
1447
+ end
1448
+
1449
+
1450
+ # Get value for property, traverse up parent tree to get first inherited
1451
+ # value if not present on this node, returns nil if property not found or
1452
+ # it's value is explicitly set to nil
1453
+ def get(sym)
1454
+ if((v=@_h[sym]).nil?)
1455
+ unless @_h.has_key?(sym)
1456
+ if(self.class.method_defined? sym)
1457
+ v=__send__(sym)
1458
+ elsif(@parents_.length > 0)
1459
+ @parents_.each do |p|
1460
+ val = p.getMy(sym);
1461
+ return(val) if val;
1462
+ end
1463
+ end
1464
+ end
1465
+ end
1466
+ v
1467
+ end
1468
+
1469
+ protected
1470
+ def self.cantOverrideX_(k) # :nodoc:
1471
+ "can't overide method \"#{k}\" with a property"
1472
+ end
1473
+
1474
+ # returns true if any ancestor has a key defined in the hashtable
1475
+ def hasAncestorKey(sym) # :nodoc:
1476
+ @parents_.each do |p|
1477
+ return(true) if p.has_key?(sym);
1478
+ end
1479
+ false
1480
+ end
1481
+
1482
+ def _h # :nodoc: _h accessor
1483
+ @_h
1484
+ end
1485
+
1486
+ def raiseUndef_(sym) # :nodoc:
1487
+ c = caller;
1488
+ caller.each do |clr|
1489
+ c.shift
1490
+ unless(clr =~ /\/Rakish.rb:\d+:in `(method_missing|__send__)'/)
1491
+ # log.debug("\n#{Logger.formatBacktraceLine(clr)} - ##### undefined property or method \"#{sym}\"");
1492
+ raise RuntimeError, "\n#{Logger.formatBacktraceLine(clr)} - undefined property or method \"#{sym}\"", c
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ public
1498
+ # Get value for property.
1499
+ # Does *not* traverse up tree, gets local value only.
1500
+ # returns nil if value is either nil or not present
1501
+ def getMy(s)
1502
+ (self.class.method_defined? s) ? self.send(s) : @_h[s]
1503
+ end
1504
+
1505
+ # true if a property is set in hash on this object
1506
+ # does not detect methods
1507
+ def has_key?(k) # :nodoc:
1508
+ @_h.has_key?(k)
1509
+ end
1510
+
1511
+
1512
+ # needed so we can flatten the parents array.
1513
+ def to_ary # :nodoc:
1514
+ nil
1515
+ end
1516
+
1517
+ # allows 'dot' access to properties.
1518
+ # ie: value = bag.nameOfProperty
1519
+ # bag.nameOfProperty = newValue
1520
+ #
1521
+ # Throws an execption if the property or method is not found on this
1522
+ # node or any of it's parents.
1523
+ #
1524
+ # Assignment to a property that does not exist will add a new field *only* if
1525
+ # done so within an enableNewFields block
1526
+ #
1527
+ def method_missing(sym, *args, &block) # :nodoc:
1528
+
1529
+ if((v=@_h[sym]).nil?)
1530
+ unless @_h.has_key?(sym) # if property exists nil is a valid value
1531
+ if sym.to_s =~ /=$/ # it's an attempted asignment ie: ':sym='
1532
+ sym = $`.to_sym # $` has symbol with '=' chopped off
1533
+ unless @ul_ # if not locked check if there is an inherited
1534
+ # property key declared by an ancestor to assign to
1535
+ unless(has_key?(sym))
1536
+ super unless hasAncestorKey(sym); # raise no method exception if no key!
1537
+ end
1538
+ end
1539
+ if(self.class.method_defined? sym)
1540
+ raise PropertyBagMod::cantOverrideX_(sym)
1541
+ end
1542
+ return(@_h[sym]=args[0]) # assign value to property
1543
+ elsif(@parents_.length > 0) # "recurse" to parents
1544
+ # we don't actually recurse here but check the flattened parent list in order
1545
+ eqSym = "#{sym}=".to_sym;
1546
+ anyDefined = self.class.method_defined?(eqSym);
1547
+ @parents_.each do |p|
1548
+ return(p.send(sym)) if(p.class.method_defined? sym);
1549
+ v = p._h[sym];
1550
+ return(v) if (v || p._h.has_key?(sym));
1551
+ anyDefined||=p.class.method_defined?(eqSym);
1552
+ end
1553
+ return v if(anyDefined);
1554
+ raiseUndef_(sym)
1555
+ else
1556
+ return v if (self.class.method_defined?("#{sym}="));
1557
+ raiseUndef_ sym;
1558
+ end
1559
+ end
1560
+ end
1561
+ v
1562
+ end
1563
+ end
1564
+
1565
+ # General purpose property bag class includes PropertyBagMod
1566
+ class PropertyBag < Module
1567
+ include PropertyBagMod
1568
+
1569
+ def initialize(*args,&block)
1570
+ init_PropertyBag(*args)
1571
+ enableNewFields(&block) if(block_given?)
1572
+ end
1573
+ end
1574
+
1575
+ # A case independent set for file paths
1576
+ # intended to act like a Ruby class Set for File path names
1577
+ class FileSet < Module
1578
+
1579
+ # Create a FileSet containing an initial set of files
1580
+ # contained in 'files'. It will acccept 'wildcard'
1581
+ # entries as defined for a Rake::FileList which are expanded
1582
+ # relative to the current directory at the time entries are added
1583
+
1584
+ def initialize(*files)
1585
+ @h={}
1586
+ add_ary(files) unless(files.empty?)
1587
+ end
1588
+
1589
+ # Case independent string key. WARNING does not clone and intern keys
1590
+ # so the source strings must not be changed after being set.
1591
+ class Key # :nodoc:
1592
+ def initialize(str)
1593
+ @s=str.to_s
1594
+ @h=@s.downcase.hash
1595
+ end
1596
+ def hash
1597
+ @h
1598
+ end
1599
+ def eql?(o)
1600
+ @s.casecmp(o)==0
1601
+ end
1602
+ def to_s
1603
+ @s
1604
+ end
1605
+ alias :to_str :to_s
1606
+ end
1607
+
1608
+ protected
1609
+ def add_ary(files,v=nil) # :nodoc:
1610
+ files.each do |f|
1611
+ if(f.respond_to?(:to_ary))
1612
+ add_ary(f) # recurse
1613
+ next
1614
+ end
1615
+ f = f.to_s # :nodoc:
1616
+ unless(f =~ /[*?]/) # unless a glob?
1617
+ f = File.expand_path(f)
1618
+ f = Key.new(f)
1619
+ @ordered << f if @h[f]
1620
+ @h[f]=v
1621
+ else
1622
+ add_ary(FileList.new(File.expand_path(f)))
1623
+ end
1624
+ end
1625
+ end
1626
+ def delete_a(f) # :nodoc:
1627
+ if(f.respond_to?(:to_ary))
1628
+ f.each do |x| delete_a(x) end
1629
+ else
1630
+ @h.delete(Key.new(f))
1631
+ end
1632
+ end
1633
+
1634
+ public
1635
+ # Add files contained in 'files' to this set. It will acccept 'wildcard'
1636
+ # entries which are expanded relative to the current directory.
1637
+ # relative to the current directory at the time entries are added
1638
+ def include(*files)
1639
+ add_ary(files)
1640
+ end
1641
+
1642
+ # Add a single file path to this set if it is not present.
1643
+ #
1644
+ # returns true if the path was not previously in the set, false otherwise
1645
+ # note does NOT expand the path when inserted
1646
+ def add?(f)
1647
+ f = Key.new(f)
1648
+ return false if @h[f]
1649
+ @ordered << f if @ordered
1650
+ @h[f]=nil
1651
+ true
1652
+ end
1653
+ # Add a single file path to this set
1654
+ # note does NOT expand the path when inserted
1655
+ # returns self for convenience
1656
+ def add(f)
1657
+ add?(f);
1658
+ self
1659
+ end
1660
+ alias :<< :add
1661
+
1662
+ # Remove path or paths from this set if they are present
1663
+ # It does not accept wildcards, but will accept FileLists
1664
+ def delete(*args)
1665
+ delete_a(args)
1666
+ end
1667
+
1668
+ # Returns true if the path is in this set
1669
+ # false otherwise.
1670
+ def include?(f)
1671
+ @h.has_key?(Key.new(f))
1672
+ end
1673
+
1674
+ # Returns true if this set is empty
1675
+ def empty?
1676
+ @h.empty?
1677
+ end
1678
+
1679
+ # Iterates over each path in the set
1680
+ def each(&b) # :yields: path
1681
+ @h.each do |k,v|
1682
+ yield(k.to_s)
1683
+ end
1684
+ end
1685
+
1686
+ # Like array.join, joins all paths with separator
1687
+ def join(separator)
1688
+ out = nil;
1689
+ each do |p|
1690
+ if out
1691
+ out += separator; # TODO: not too efficient - memory thrashing
1692
+ out += p;
1693
+ else
1694
+ out = p;
1695
+ end
1696
+ end
1697
+ out||'';
1698
+ end
1699
+
1700
+ # Returns then number of entries in this set
1701
+ def size
1702
+ @h.size
1703
+ end
1704
+ # Returns an array of all path entries in this set
1705
+ def to_ary
1706
+ @h.keys
1707
+ end
1708
+ end
1709
+
1710
+ # A FileSet where the order entries are installed is preserved
1711
+ class OrderedFileSet < FileSet
1712
+ def initialize
1713
+ super
1714
+ @ordered=[]
1715
+ end
1716
+ alias :add :add?
1717
+ alias :<< :add?
1718
+
1719
+ # Iterates over each path (key) in the set
1720
+ # in the order added
1721
+ def each(&b) # :yields: path
1722
+ @ordered.each do |k|
1723
+ yield(k.to_s) unless k.nil?
1724
+ end
1725
+ end
1726
+ # Returns then number of entries in this set
1727
+ def size
1728
+ @h.size
1729
+ end
1730
+ # Returns an array of all path entries in this set
1731
+ # in the order added
1732
+ def to_ary
1733
+ @ordered
1734
+ end
1735
+
1736
+ def join # :nodoc:
1737
+ @ordered.join
1738
+ end
1739
+
1740
+ end
1741
+
1742
+
1743
+ # Case independent "path" hash
1744
+ # preserves order of addition
1745
+ class FileHash < FileSet
1746
+ def initialize()
1747
+ super
1748
+ end
1749
+ def addAll
1750
+
1751
+ end
1752
+ def [](k)
1753
+ @h[Key.new(k)]
1754
+ end
1755
+ def []=(k,v)
1756
+ @h[Key.new(k)]=v
1757
+ end
1758
+
1759
+ def each_pair(&b)
1760
+ @h.each_pair do |k,v|
1761
+ yield k,v
1762
+ end
1763
+ end
1764
+ # Returns array of all values in the hash
1765
+ def values
1766
+ @h.values
1767
+ end
1768
+ end
1769
+
1770
+ # A set of "output" directories each assigned a set of source files.
1771
+ # The intention to map a set of input files to a set of output files
1772
+ # in a differing directory structure.
1773
+ class FileCopySet
1774
+ include Util
1775
+
1776
+ private
1777
+
1778
+ def add_set(set) # :nodoc:
1779
+ set.filesByDir do |dir,files|
1780
+ ilist = (@byDir_[dir] ||= [])
1781
+ add_files_a(ilist,files,nil)
1782
+ end
1783
+ end
1784
+ public
1785
+
1786
+ # Create new FileCopySet with optional argument to initialize this set as a
1787
+ # deep copy of another FileCopySet
1788
+ def initialize(cloneFrom=nil)
1789
+ @byDir_=FileHash.new
1790
+ if(cloneFrom && FileCopySet === cloneFrom)
1791
+ add_set(cloneFrom)
1792
+ end
1793
+ end
1794
+
1795
+ def debug=(v) # :nodoc:
1796
+ @debug=v
1797
+ end
1798
+
1799
+ class Entry # :nodoc: all
1800
+ def initialize(f,data=nil)
1801
+ @f=f
1802
+ @data = data unless data.nil?
1803
+ end
1804
+ def to_s
1805
+ @f.to_s
1806
+ end
1807
+ alias :to_str :to_s
1808
+ alias :source :to_s
1809
+ def dest
1810
+ File.basename(@f.to_s)
1811
+ end
1812
+ def data
1813
+ @data
1814
+ end
1815
+ end
1816
+
1817
+ protected
1818
+
1819
+ @@truncLen_ = File.expand_path("/./").length;
1820
+
1821
+ # Cleans up a relative path 'rp' without expanding against the current directory
1822
+ # ie:
1823
+ # ./a/b/c/../../ => ./a
1824
+ # ./ => .
1825
+ #
1826
+ # NOTE: this will not work if the relative path evaluates to below itself
1827
+ # which is improper for a destination directory
1828
+ #
1829
+ def cleanupDestdir(dd) # :nodoc:
1830
+ dd=dd.to_s;
1831
+ unless(File.path_is_absolute?(dd))
1832
+ dd = File.expand_path("/./#{dd}");
1833
+ dd = "./#{dd[@@truncLen_,10000]}";
1834
+ dd='.' if(dd=='./')
1835
+ end
1836
+ dd
1837
+ end
1838
+
1839
+ def add_simple_a(list,files,data) # :nodoc:
1840
+ files.each do |f|
1841
+ list << Entry.new(File.expand_path(f.to_s),data)
1842
+ end
1843
+ end
1844
+ def add_files_a(list,files,data) # :nodoc:
1845
+ files.each do |f|
1846
+ if(f.respond_to?(:to_ary))
1847
+ add_files_a(list,f,data)
1848
+ next
1849
+ end
1850
+ if(f =~ /[*?]/)
1851
+ add_simple_a(list,FileList.new(f),data)
1852
+ next
1853
+ end
1854
+ list << Entry.new(File.expand_path(f.to_s),data)
1855
+ end
1856
+ end
1857
+
1858
+ public
1859
+ # Add a directory (in destination) with no source files to this set, if not already there.
1860
+ # dir = './set/dir' or '.' for the root of a relative destination
1861
+ def addDir(dir)
1862
+ @byDir_[cleanupDestdir(dir)]||=[]
1863
+ end
1864
+
1865
+ # add files all assigned to the destdir directory
1866
+ def addFiles(destdir, *files)
1867
+ if(!files.empty?)
1868
+ ilist = (@byDir_[cleanupDestdir(destdir)] ||= [])
1869
+ add_files_a(ilist,files,nil)
1870
+ end
1871
+ end
1872
+ protected
1873
+
1874
+ def add_filet_a(destdir,regx,files,data) # :nodoc:
1875
+ files.each do |f|
1876
+ if(f.respond_to?(:to_ary))
1877
+ add_filet_a(destdir,regx,f,data)
1878
+ next
1879
+ end
1880
+ if(f =~ /[*?]/)
1881
+ add_filet_a(destdir,regx,FileList.new(f),data)
1882
+ next
1883
+ end
1884
+ f = File.expand_path(f.to_s)
1885
+ isDir = File.directory?(f)
1886
+ f =~ regx
1887
+ if(isDir)
1888
+ dir = ($') ? ((destdir.length > 0) ? "#{destdir}/#{$'}" : $') : destdir
1889
+ @byDir_[dir]||=[]
1890
+ else
1891
+ dir = File.dirname($');
1892
+ if(dir.length == 0 || dir == '.')
1893
+ dir = destdir
1894
+ elsif(destdir.length > 0)
1895
+ dir = "#{destdir}/#{dir}"
1896
+ end
1897
+ (@byDir_[dir]||=[]) << Entry.new(f,data)
1898
+ end
1899
+ end
1900
+ end
1901
+
1902
+ public
1903
+
1904
+ # Adds all the files from a subtree into the destdir in the set
1905
+ # the subtree will have it's leading "basedir" removed from the file path
1906
+ # and replaced with the "basedir" before adding to the archive.
1907
+ # ie:
1908
+ # file = '/a/b/c/d/e/file.txt'
1909
+ # basedir = '/a/b/c'
1910
+ # destdir = './set/dir' or '.' for the root of a relative destination
1911
+ #
1912
+ # added to set = 'set/dir/d/e/file.txt'
1913
+ #
1914
+ # <b>named options:</b>
1915
+ # :data => user value to assign to all entries added to this set
1916
+ #
1917
+ def addFileTree(destdir, basedir, *files)
1918
+ opts = (Hash === files.last) ? files.pop : {}
1919
+ destdir = cleanupDestdir(destdir);
1920
+ basedir = File.expand_path(basedir)
1921
+ regx = Regexp.new('^' + Regexp.escape(basedir+'/'),Regexp::IGNORECASE);
1922
+ add_filet_a(destdir,regx,files,opts[:data])
1923
+ end
1924
+
1925
+ # Retrieve all source files by assigned output directory
1926
+ # one source file may be assigned to more than one output directory
1927
+ #
1928
+ def filesByDir(&block) # :yields: directory, iterable of files
1929
+ @byDir_.each_pair do |k,v|
1930
+ yield(k.to_s,v)
1931
+ end
1932
+ end
1933
+
1934
+ # Return array of all source files in this set
1935
+ def sources
1936
+ v = @byDir_.values.flatten;
1937
+ v.uniq!
1938
+ v
1939
+ end
1940
+
1941
+ def prettyPrint
1942
+ filesByDir do |k,v|
1943
+ if(v.length > 0)
1944
+ puts("\n");
1945
+ end
1946
+ puts("directory #{k}");
1947
+ v.each do |f|
1948
+ puts( " \"#{f}\"")
1949
+ end
1950
+ end
1951
+ end
1952
+
1953
+ # Generate processing tasks for all files in this copy set
1954
+ # using the task action provided or a simple copy if not
1955
+ # hash args
1956
+ # :suffixMap - map from source suffi to destination suffi
1957
+ # :config - value to set for task.config
1958
+ # TODO:ensureOutputDirs - if true ensure a task is present for creating
1959
+ # the output directory and assign it as prerequisite
1960
+ # default is true
1961
+ # TODO: :outputDir value of directory to place output files
1962
+
1963
+ def generateFileTasks(args={}, &block)
1964
+
1965
+ ensureOutputDirs = args[:ensureOutputDirs];
1966
+ suffixMap = args[:suffixMap]||{};
1967
+ tasks = [];
1968
+ block = SimpleCopyAction_ unless block_given?
1969
+
1970
+ filesByDir do |dir,files|
1971
+ # TODO: maybe have an output directory option and maybe relative path option for destinations ?
1972
+ # dir = File.join(destDir,dir);
1973
+ ensureDirectoryTask(dir)
1974
+
1975
+ files.each do |srcfile|
1976
+ destname = File.basename(srcfile);
1977
+ oldext = File.extname(destname);
1978
+ if(oldext)
1979
+ newext = suffixMap[oldext];
1980
+ destname = destname.pathmap("%n#{newext}") if newext
1981
+ end
1982
+ dest = File.join(dir, destname);
1983
+ task = file dest=>[srcfile,dir], &block
1984
+ task.sources = task.prerequisites
1985
+ # set configuration data on task if desired
1986
+ if(cfg = args[:config])
1987
+ task.config = cfg
1988
+ end
1989
+ tasks << task
1990
+ end
1991
+ end
1992
+ tasks
1993
+ end
1994
+
1995
+ end
1996
+
1997
+ # not used yet intending to use it for MultiTask queueing
1998
+ class CountingSemaphore
1999
+ def initialize(initvalue = 0)
2000
+ @counter = initvalue
2001
+ @waiting_list = []
2002
+ end
2003
+
2004
+ def wait
2005
+ Thread.critical = true
2006
+ if (@counter -= 1) < 0
2007
+ @waiting_list.push(Thread.current)
2008
+ Thread.stop
2009
+ end
2010
+ self
2011
+ ensure
2012
+ Thread.critical = false
2013
+ end
2014
+
2015
+ def signal
2016
+ Thread.critical = true
2017
+ begin
2018
+ if (@counter += 1) <= 0
2019
+ t = @waiting_list.shift
2020
+ t.wakeup if t
2021
+ end
2022
+ rescue ThreadError
2023
+ retry
2024
+ end
2025
+ self
2026
+ ensure
2027
+ Thread.critical = false
2028
+ end
2029
+
2030
+ alias down wait
2031
+ alias up signal
2032
+ alias P wait
2033
+ alias V signal
2034
+
2035
+ def exclusive
2036
+ wait
2037
+ yield
2038
+ ensure
2039
+ signal
2040
+ end
2041
+
2042
+ alias synchronize exclusive
2043
+
2044
+ end
2045
+
2046
+ # :nodoc: not used curently
2047
+ # Semaphore = CountingSemaphore
2048
+
2049
+ end