rant 0.4.2 → 0.4.4

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 (56) hide show
  1. data/NEWS +14 -0
  2. data/README +13 -7
  3. data/Rantfile +11 -0
  4. data/doc/md5.rdoc +49 -0
  5. data/doc/rantfile.rdoc +1 -1
  6. data/lib/rant/coregen.rb +193 -0
  7. data/lib/rant/import/archive/zip.rb +2 -0
  8. data/lib/rant/import/archive.rb +10 -2
  9. data/lib/rant/import/autoclean.rb +16 -7
  10. data/lib/rant/import/c/dependencies.rb +1 -1
  11. data/lib/rant/import/directedrule.rb +2 -2
  12. data/lib/rant/import/md5.rb +16 -0
  13. data/lib/rant/import/metadata.rb +162 -0
  14. data/lib/rant/import/nodes/default.rb +490 -0
  15. data/lib/rant/import/nodes/signed.rb +84 -0
  16. data/lib/rant/import/package/zip.rb +2 -0
  17. data/lib/rant/import/rubydoc.rb +5 -1
  18. data/lib/rant/import/rubypackage.rb +2 -1
  19. data/lib/rant/import/signature/md5.rb +38 -0
  20. data/lib/rant/import/signedfile.rb +235 -0
  21. data/lib/rant/import/subfile.rb +1 -1
  22. data/lib/rant/import.rb +5 -1
  23. data/lib/rant/node.rb +165 -0
  24. data/lib/rant/plugin/csharp.rb +2 -0
  25. data/lib/rant/rantlib.rb +64 -9
  26. data/lib/rant/rantsys.rb +39 -27
  27. data/lib/rant/rantvar.rb +32 -2
  28. data/misc/TODO +66 -0
  29. data/test/import/c/dependencies/test_on_the_fly.rb +52 -0
  30. data/test/import/metadata/Rantfile +16 -0
  31. data/test/import/metadata/sub/Rantfile +17 -0
  32. data/test/import/metadata/test_metadata.rb +126 -0
  33. data/test/import/nodes/signed/Rantfile +89 -0
  34. data/test/import/nodes/signed/sub1/Rantfile +6 -0
  35. data/test/import/nodes/signed/test_signed.rb +455 -0
  36. data/test/import/package/md5.rf +10 -0
  37. data/test/import/package/test_package.rb +127 -1
  38. data/test/import/signeddirectory/Rantfile +15 -0
  39. data/test/import/signeddirectory/test_signeddirectory.rb +84 -0
  40. data/test/import/signedfile/Rantfile +90 -0
  41. data/test/import/signedfile/sub1/Rantfile +4 -0
  42. data/test/import/signedfile/test_signedfile.rb +338 -0
  43. data/test/project1/Rantfile +0 -9
  44. data/test/project1/test_project.rb +2 -0
  45. data/test/project_rb1/test_project_rb1.rb +27 -10
  46. data/test/rant-import/test_rant-import.rb +46 -9
  47. data/test/subdirs/sub2/sub/rantfile +0 -5
  48. data/test/subdirs/test_subdirs.rb +0 -9
  49. data/test/test_examples.rb +131 -3
  50. data/test/test_filelist.rb +44 -0
  51. data/test/test_sys.rb +19 -1
  52. data/test/test_task.rb +2 -2
  53. data/test/tutil.rb +9 -3
  54. metadata +34 -4
  55. data/lib/rant/rantfile.rb +0 -897
  56. data/test/test_lighttask.rb +0 -68
data/lib/rant/rantfile.rb DELETED
@@ -1,897 +0,0 @@
1
-
2
- # rantfile.rb - Define task core for rant.
3
- #
4
- # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
-
6
- require 'rant/rantenv'
7
-
8
- module Rant
9
- class TaskFail < StandardError
10
- def initialize(*args)
11
- @task = args.shift
12
- #super(args.shift)
13
- @orig = args.shift
14
- end
15
- def task
16
- @task
17
- end
18
- def tname
19
- @task ? @task.name : nil
20
- end
21
- # the exception which caused the task to fail
22
- def orig
23
- @orig
24
- end
25
- end
26
-
27
- class Rantfile
28
-
29
- attr_reader :tasks, :path
30
- attr_accessor :project_subdir
31
-
32
- def initialize(path)
33
- @path = path or raise ArgumentError, "path required"
34
- @tasks = []
35
- @project_subdir = nil
36
- end
37
- def to_s
38
- @path
39
- end
40
- end # class Rantfile
41
-
42
- # Any +object+ is considered a _task_ if
43
- # <tt>Rant::Node === object</tt> is true.
44
- #
45
- # Most important classes including this module are the Rant::Task
46
- # class and the Rant::FileTask class.
47
- module Node
48
-
49
- INVOKE_OPT = {}.freeze
50
-
51
- # Name of the task, this is always a string.
52
- attr_reader :name
53
- # A reference to the Rant compiler this task belongs to.
54
- attr_reader :rac
55
- # Description for this task.
56
- attr_accessor :description
57
- # The rantfile this task was defined in.
58
- # Should be a Rant::Rantfile instance.
59
- attr_accessor :rantfile
60
- # The linenumber in rantfile where this task was defined.
61
- attr_accessor :line_number
62
- # The directory in which this task was defined, relative to
63
- # the projects root directory.
64
- attr_accessor :project_subdir
65
-
66
- def initialize
67
- @description = nil
68
- @rantfile = nil
69
- @line_number = nil
70
- @run = false
71
- @project_subdir = ""
72
- end
73
-
74
- # Returns the name of this task.
75
- def to_s
76
- name
77
- end
78
-
79
- def to_rant_target
80
- name
81
- end
82
-
83
- # Basically project_subdir/name
84
- #
85
- # The Rant compiler (or application) references tasks by their
86
- # full_name.
87
- def full_name
88
- sd = project_subdir
89
- sd.empty? ? name : File.join(sd, name)
90
- end
91
-
92
- # Change current working directory to the directory this task
93
- # was defined in.
94
- #
95
- # Important for subclasses: Call this method always before
96
- # invoking code from Rantfiles (e.g. task action blocks).
97
- def goto_task_home
98
- @rac.goto_project_dir project_subdir
99
- end
100
-
101
- def done?
102
- @done
103
- end
104
-
105
- def needed?
106
- !done?
107
- end
108
-
109
- # True during invoke. Used to encounter circular dependencies.
110
- def run?
111
- @run
112
- end
113
-
114
- # +opt+ is a Hash and shouldn't be modified.
115
- # All objects implementing the Rant::Node protocol should
116
- # know about the following +opt+ values:
117
- # <tt>:needed?</tt>::
118
- # Just check if this task is needed. Should do the same
119
- # as calling Node#needed?
120
- # <tt>:force</tt>::
121
- # Run task action even if needed? is false.
122
- # Returns true if task action was run.
123
- def invoke(opt = INVOKE_OPT)
124
- return circular_dep if run?
125
- @run = true
126
- begin
127
- return needed? if opt[:needed?]
128
- self.run if opt[:force] || self.needed?
129
- ensure
130
- @run = false
131
- end
132
- end
133
-
134
- # Cause task to fail. Usually called from inside the block
135
- # given to +act+.
136
- def fail msg = nil, orig = nil
137
- msg ||= ""
138
- raise TaskFail.new(self, orig), msg, caller
139
- end
140
-
141
- # Change pwd to task home directory and yield for each created
142
- # file/directory.
143
- #
144
- # Override in subclasses if your task instances create files.
145
- def each_target
146
- end
147
-
148
- def run
149
- return unless @block
150
- goto_task_home
151
- @block.arity == 0 ? @block.call : @block[self]
152
- end
153
- private :run
154
-
155
- def circular_dep
156
- rac.warn_msg "Circular dependency on task `#{full_name}'."
157
- false
158
- end
159
- private :circular_dep
160
-
161
- # Tasks are hashed by their full_name.
162
- def hash
163
- full_name.hash
164
- end
165
-
166
- def eql? other
167
- Node === other and full_name.eql? other.full_name
168
- end
169
- end # module Node
170
-
171
- # A very lightweight task for special purposes.
172
- class LightTask
173
- include Node
174
-
175
- class << self
176
- def rant_gen(rac, ch, args, &block)
177
- unless args.size == 1
178
- rac.abort("LightTask takes only one argument " +
179
- "which has to be the taskname (string or symbol)")
180
- end
181
- rac.prepare_task({args.first => [], :__caller__ => ch},
182
- block) { |name,pre,blk|
183
- # TODO: ensure pre is empty
184
- self.new(rac, name, &blk)
185
- }
186
- end
187
- end
188
-
189
- def initialize(rac, name)
190
- super()
191
- @rac = rac or raise ArgumentError, "no rac given"
192
- @name = name
193
- @needed = nil
194
- @block = nil
195
- @done = false
196
- yield self if block_given?
197
- end
198
-
199
- def rac
200
- @rac
201
- end
202
-
203
- def needed(&block)
204
- @needed = block
205
- end
206
-
207
- def act(&block)
208
- @block = block
209
- end
210
-
211
- def needed?
212
- return false if done?
213
- return true if @needed.nil?
214
- goto_task_home
215
- @needed.arity == 0 ? @needed.call : @needed[self]
216
- end
217
-
218
- def invoke(opt = INVOKE_OPT)
219
- return circular_dep if @run
220
- @run = true
221
- begin
222
- return needed? if opt[:needed?]
223
- # +run+ already calls +goto_task_home+
224
- #goto_task_home
225
- if opt[:force] && !@done or needed?
226
- run
227
- @done = true
228
- end
229
- rescue CommandError => e
230
- err_msg e.message if rac[:err_commands]
231
- self.fail(nil, e)
232
- rescue SystemCallError => e
233
- err_msg e.message
234
- self.fail(nil, e)
235
- ensure
236
- @run = false
237
- end
238
- end
239
- end # LightTask
240
-
241
- class Task
242
- include Node
243
- include Console
244
-
245
- T0 = Time.at(0).freeze
246
-
247
- class << self
248
- def rant_gen(rac, ch, args, &block)
249
- if args.size == 1
250
- UserTask.rant_gen(rac, ch, args, &block)
251
- else
252
- rac.abort("Task generator currently takes only one" +
253
- " argument. (generates a UserTask)")
254
- end
255
- end
256
- end
257
-
258
- def initialize(rac, name, prerequisites = [], &block)
259
- super()
260
- @rac = rac || Rant.rac
261
- @name = name or raise ArgumentError, "name not given"
262
- @pre = prerequisites || []
263
- @pre_resolved = false
264
- @block = block
265
- @run = false
266
- # success has one of three values:
267
- # nil no invoke
268
- # false invoked, but fail
269
- # true invoked and run successfully
270
- @success = nil
271
- end
272
-
273
- # Get a list of the *names* of all prerequisites. The
274
- # underlying list of prerequisites can't be modified by the
275
- # value returned by this method.
276
- def prerequisites
277
- @pre.collect { |pre| pre.to_s }
278
- end
279
- alias deps prerequisites
280
-
281
- # First prerequisite.
282
- def source
283
- @pre.first.to_s
284
- end
285
-
286
- # True if this task has at least one action (block to be
287
- # executed) associated.
288
- def has_actions?
289
- !!@block
290
- end
291
-
292
- # Add a prerequisite.
293
- def <<(pre)
294
- @pre_resolved = false
295
- @pre << pre
296
- end
297
-
298
- # Was this task ever invoked? If this is true, it doesn't
299
- # necessarily mean that the run was successfull!
300
- def invoked?
301
- !@success.nil?
302
- end
303
-
304
- # True if last task run fail.
305
- def fail?
306
- @success == false
307
- end
308
-
309
- # Task was run and didn't fail.
310
- def done?
311
- @success
312
- end
313
-
314
- # Enhance this task with the given dependencies and blk.
315
- def enhance(deps = nil, &blk)
316
- if deps
317
- @pre_resolved = false
318
- @pre.concat deps
319
- end
320
- if @block
321
- if blk
322
- first_block = @block
323
- @block = lambda { |t|
324
- first_block[t]
325
- blk[t]
326
- }
327
- end
328
- else
329
- @block = blk
330
- end
331
- end
332
-
333
- def needed?
334
- invoke(:needed? => true)
335
- end
336
-
337
- # Returns a true value if task was acutally run.
338
- # Raises Rant::TaskFail to signal task (or prerequiste) failure.
339
- def invoke(opt = INVOKE_OPT)
340
- return circular_dep if @run
341
- @run = true
342
- begin
343
- return if done?
344
- internal_invoke opt
345
- ensure
346
- @run = false
347
- end
348
- end
349
-
350
- def internal_invoke opt, ud_init = true
351
- goto_task_home
352
- update = ud_init || opt[:force]
353
- dep = nil
354
- uf = false
355
- each_dep { |dep|
356
- if dep.respond_to? :timestamp
357
- handle_timestamped(dep, opt) && update = true
358
- elsif Node === dep
359
- handle_node(dep, opt) && update = true
360
- else
361
- dep, uf = handle_non_node(dep, opt)
362
- uf && update = true
363
- dep
364
- end
365
- }
366
- # Never run a task block for a "needed?" query.
367
- return update if opt[:needed?]
368
- run if update
369
- @success = true
370
- # IMPORTANT: return update flag
371
- update
372
- rescue StandardError => e
373
- @success = false
374
- self.fail(nil, e)
375
- end
376
- private :internal_invoke
377
-
378
- # Called from internal_invoke. +dep+ is a prerequisite which
379
- # is_a? Node, but not a FileTask. +opt+ are opts as given to
380
- # Node#invoke.
381
- #
382
- # Override this method in subclasses to modify behaviour of
383
- # prerequisite handling.
384
- #
385
- # See also: handle_timestamped, handle_non_node
386
- def handle_node(dep, opt)
387
- dep.invoke opt
388
- end
389
-
390
- # Called from internal_invoke. +dep+ is a prerequisite which
391
- # is_a? FileTask. +opt+ are opts as given to Node#invoke.
392
- #
393
- # Override this method in subclasses to modify behaviour of
394
- # prerequisite handling.
395
- #
396
- # See also: handle_node, handle_non_node
397
- def handle_timestamped(dep, opt)
398
- dep.invoke opt
399
- end
400
-
401
- # Override in subclass if specific task can handle
402
- # non-task-prerequisites.
403
- #
404
- # Notes for overriding:
405
- # This method should do one of the two following:
406
- # [1] Fail with an exception.
407
- # [2] Return two values: replacement_for_dep, update_required
408
- #
409
- # See also: handle_node, handle_timestamped
410
- def handle_non_node(dep, opt)
411
- err_msg "Unknown task `#{dep}',",
412
- "referenced in `#{rantfile.path}', line #{@line_number}!"
413
- self.fail
414
- end
415
-
416
- # For each non-worker prerequiste, the value returned from yield
417
- # will replace the original prerequisite (of course only if
418
- # @pre_resolved is false).
419
- def each_dep
420
- t = nil
421
- if @pre_resolved
422
- return @pre.each { |t| yield(t) }
423
- end
424
- my_full_name = full_name
425
- my_project_subdir = project_subdir
426
- @pre.map! { |t|
427
- if Node === t
428
- # Remove references to self from prerequisites!
429
- if t.full_name == my_full_name
430
- nil
431
- else
432
- yield(t)
433
- t
434
- end
435
- else
436
- t = t.to_s if Symbol === t
437
- if t == my_full_name
438
- nil
439
- else
440
- #STDERR.puts "selecting `#{t}'"
441
- selection = @rac.resolve t,
442
- my_project_subdir
443
- #STDERR.puts selection.size
444
- if selection.empty?
445
- # use return value of yield
446
- yield(t)
447
- else
448
- selection.each { |st| yield(st) }
449
- selection
450
- end
451
- end
452
- end
453
- }
454
- @pre.flatten!
455
- @pre.compact!
456
- @pre_resolved = true
457
- end
458
- end # class Task
459
-
460
- # A UserTask is equivalent to a Task, but it additionally takes a
461
- # block (see #needed) which is used to determine if it is needed?.
462
- class UserTask < Task
463
-
464
- class << self
465
- def rant_gen(rac, ch, args, &block)
466
- unless args.size == 1
467
- rac.abort("UserTask takes only one argument " +
468
- "which has to be like one given to the " +
469
- "`task' function")
470
- end
471
- rac.prepare_task(args.first, nil, ch) { |name,pre,blk|
472
- self.new(rac, name, pre, &block)
473
- }
474
- end
475
- end
476
-
477
- def initialize(*args)
478
- super
479
- # super will set @block to a given block, but the block is
480
- # used for initialization, not ment as action
481
- @block = nil
482
- @needed = nil
483
- # allow setting of @block and @needed
484
- yield self if block_given?
485
- end
486
-
487
- def act(&block)
488
- @block = block
489
- end
490
-
491
- def needed(&block)
492
- @needed = block
493
- end
494
-
495
- # We simply override this method and call internal_invoke with
496
- # the +ud_init+ flag according to the result of a call to the
497
- # +needed+ block.
498
- def invoke(opt = INVOKE_OPT)
499
- return circular_dep if @run
500
- @run = true
501
- begin
502
- return if done?
503
- internal_invoke(opt, ud_init_by_needed)
504
- ensure
505
- @run = false
506
- end
507
- end
508
-
509
- private
510
- def ud_init_by_needed
511
- if @needed
512
- goto_task_home
513
- @needed.arity == 0 ? @needed.call : @needed[self]
514
- #else: true #??
515
- end
516
- end
517
- end # class UserTask
518
-
519
- class FileTask < Task
520
-
521
- def initialize(*args)
522
- super
523
- @ts = T0
524
- end
525
-
526
- def needed?
527
- return false if done?
528
- invoke(:needed? => true)
529
- end
530
-
531
- def invoke(opt = INVOKE_OPT)
532
- return circular_dep if @run
533
- @run = true
534
- begin
535
- return if done?
536
- goto_task_home
537
- if File.exist? @name
538
- @ts = File.mtime @name
539
- internal_invoke opt, false
540
- else
541
- @ts = T0
542
- internal_invoke opt, true
543
- end
544
- ensure
545
- @run = false
546
- end
547
- end
548
-
549
- def timestamp
550
- File.exist?(@name) ? File.mtime(@name) : T0
551
- end
552
-
553
- def handle_timestamped(dep, opt)
554
- return true if dep.invoke opt
555
- #puts "***`#{dep.name}' requires update" if dep.timestamp > @ts
556
- dep.timestamp > @ts
557
- end
558
-
559
- def handle_non_node(dep, opt)
560
- goto_task_home # !!??
561
- unless File.exist? dep
562
- err_msg @rac.pos_text(rantfile.path, line_number),
563
- "in prerequisites: no such file or task: `#{dep}'"
564
- self.fail
565
- end
566
- [dep, File.mtime(dep) > @ts]
567
- end
568
-
569
- def each_target
570
- goto_task_home
571
- yield name
572
- end
573
- end # class FileTask
574
-
575
- class AutoSubFileTask < FileTask
576
- private
577
- def run
578
- dir, = File.split(name)
579
- unless dir == "."
580
- dt = @rac.resolve(dir, project_subdir).last
581
- dt.invoke if DirTask === dt
582
- end
583
- super
584
- end
585
- end # class AutoSubFileTask
586
-
587
- # An instance of this class is a task to create a _single_
588
- # directory.
589
- class DirTask < Task
590
-
591
- class << self
592
-
593
- # Generate a task for making a directory path.
594
- # Prerequisites can be given, which will be added as
595
- # prerequistes for the _last_ directory.
596
- #
597
- # A special feature is used if you provide a block: The
598
- # block will be called after complete directory creation.
599
- # After the block execution, the modification time of the
600
- # directory will be updated.
601
- def rant_gen(rac, ch, args, &block)
602
- case args.size
603
- when 1
604
- name, pre = rac.normalize_task_arg(args.first, ch)
605
- self.task(rac, ch, name, pre, &block)
606
- when 2
607
- basedir = args.shift
608
- if basedir.respond_to? :to_str
609
- basedir = basedir.to_str
610
- else
611
- rac.abort_at(ch,
612
- "Directory: basedir argument has to be a string.")
613
- end
614
- name, pre = rac.normalize_task_arg(args.first, ch)
615
- self.task(rac, ch, name, pre, basedir, &block)
616
- else
617
- rac.abort_at(ch, "Directory takes one argument, " +
618
- "which should be like one given to the `task' command.")
619
- end
620
- end
621
-
622
- # Returns the task which creates the last directory
623
- # element (and has all other necessary directories as
624
- # prerequisites).
625
- def task(rac, ch, name, prerequisites=[], basedir=nil, &block)
626
- dirs = ::Rant::Sys.split_path(name)
627
- if dirs.empty?
628
- rac.abort_at(ch,
629
- "Not a valid directory name: `#{name}'")
630
- end
631
- path = basedir
632
- last_task = nil
633
- task_block = nil
634
- desc_for_last = rac.pop_desc
635
- dirs.each { |dir|
636
- pre = [path]
637
- pre.compact!
638
- if dir.equal?(dirs.last)
639
- rac.cx.desc desc_for_last
640
- pre = prerequisites + pre
641
- task_block = block
642
- end
643
- path = path.nil? ? dir : File.join(path, dir)
644
- last_task = rac.prepare_task({:__caller__ => ch,
645
- path => pre}, task_block) { |name,pre,blk|
646
- self.new(rac, name, pre, &blk)
647
- }
648
- }
649
- last_task
650
- end
651
- end
652
-
653
- def initialize(*args)
654
- super
655
- @ts = T0
656
- @isdir = nil
657
- end
658
-
659
- def invoke(opt = INVOKE_OPT)
660
- return circular_dep if @run
661
- @run = true
662
- begin
663
- return if done?
664
- goto_task_home
665
- @isdir = test(?d, @name)
666
- if @isdir
667
- @ts = @block ? test(?M, @name) : Time.now
668
- internal_invoke opt, false
669
- else
670
- @ts = T0
671
- internal_invoke opt, true
672
- end
673
- ensure
674
- @run = false
675
- end
676
- end
677
-
678
- def handle_timestamped(dep, opt)
679
- return @block if dep.invoke opt
680
- @block && dep.timestamp > @ts
681
- end
682
-
683
- def handle_non_node(dep, opt)
684
- goto_task_home
685
- unless File.exist? dep
686
- err_msg @rac.pos_text(rantfile.path, line_number),
687
- "in prerequisites: no such file or task: `#{dep}'"
688
- self.fail
689
- end
690
- [dep, @block && File.mtime(dep) > @ts]
691
- end
692
-
693
- def run
694
- @rac.sys.mkdir @name unless @isdir
695
- if @block
696
- @block.arity == 0 ? @block.call : @block[self]
697
- goto_task_home
698
- @rac.sys.touch @name
699
- end
700
- end
701
-
702
- def each_target
703
- goto_task_home
704
- yield name
705
- end
706
- end # class DirTask
707
-
708
- # A SourceNode describes dependencies between source files. Thus
709
- # there is no action attached to a SourceNode. The target should
710
- # be an existing file as well as all dependencies.
711
- #
712
- # An example would be a C source file which depends on other C
713
- # source files because of <tt>#include</tt> statements.
714
- #
715
- # Rantfile usage:
716
- # gen SourceNode, "myext.c" => %w(ruby.h myext.h)
717
- class SourceNode
718
- include Node
719
-
720
- def self.rant_gen(rac, ch, args)
721
- unless args.size == 1
722
- rac.abort_at(ch, "SourceNode takes one argument.")
723
- end
724
- if block_given?
725
- rac.abort_at(ch, "SourceNode doesn't take a block.")
726
- end
727
- rac.prepare_task(args.first, nil, ch) { |name, pre, blk|
728
- new(rac, name, pre, &blk)
729
- }
730
- end
731
-
732
- def initialize(rac, name, prerequisites = [])
733
- super()
734
- @rac = rac
735
- @name = name or raise ArgumentError, "name not given"
736
- @pre = prerequisites
737
- @run = false
738
- # The timestamp is the latest of this file and all
739
- # dependencies:
740
- @ts = nil
741
- end
742
-
743
- # Use this readonly!
744
- def prerequisites
745
- @pre
746
- end
747
-
748
- # Note: The timestamp will only be calculated once!
749
- def timestamp
750
- # Circular dependencies don't generate endless
751
- # recursion/loops because before calling the timestamp
752
- # method of any other node, we set @ts to some non-nil
753
- # value.
754
- return @ts if @ts
755
- goto_task_home
756
- if File.exist?(@name)
757
- @ts = File.mtime @name
758
- else
759
- rac.abort(rac.pos_text(@rantfile, @line_number),
760
- "SourceNode: no such file -- #@name")
761
- end
762
- sd = project_subdir
763
- @pre.each { |f|
764
- nodes = rac.resolve f, sd
765
- if nodes.empty?
766
- if File.exist? f
767
- mtime = File.mtime f
768
- @ts = mtime if mtime > @ts
769
- else
770
- rac.abort(rac.pos_text(@rantfile, @line_number),
771
- "SourceNode: no such file -- #{f}")
772
- end
773
- else
774
- nodes.each { |node|
775
- if node.respond_to? :timestamp
776
- node_ts = node.timestamp
777
- @ts = node_ts if node_ts > @ts
778
- else
779
- rac.abort(rac.pos_text(@rantfile, @line_number),
780
- "SourceNode can't depend on #{node.name}")
781
- end
782
- }
783
- end
784
- }
785
- @ts
786
- end
787
-
788
- def needed?
789
- false
790
- end
791
-
792
- def invoke(opt = INVOKE_OPT)
793
- false
794
- end
795
-
796
- end # class SourceNode
797
-
798
- module Generators
799
- Task = ::Rant::Task
800
- LightTask = ::Rant::LightTask
801
- Directory = ::Rant::DirTask
802
- SourceNode = ::Rant::SourceNode
803
-
804
- class Rule < ::Proc
805
- # Generate a rule by installing an at_resolve hook for
806
- # +rac+.
807
- def self.rant_gen(rac, ch, args, &block)
808
- unless args.size == 1
809
- rac.abort_at(ch, "Rule takes only one argument.")
810
- end
811
- arg = args.first
812
- target = nil
813
- src_arg = nil
814
- if Symbol === arg
815
- target = ".#{arg}"
816
- elsif arg.respond_to? :to_str
817
- target = arg.to_str
818
- elsif Regexp === arg
819
- target = arg
820
- elsif Hash === arg && arg.size == 1
821
- arg.each_pair { |target, src_arg| }
822
- src_arg = src_arg.to_str if src_arg.respond_to? :to_str
823
- target = target.to_str if target.respond_to? :to_str
824
- src_arg = ".#{src_arg}" if Symbol === src_arg
825
- target = ".#{target}" if Symbol === target
826
- else
827
- rac.abort_at(ch, "Rule argument " +
828
- "has to be a hash with one key-value pair.")
829
- end
830
- esc_target = nil
831
- target_rx = case target
832
- when String
833
- esc_target = Regexp.escape(target)
834
- /#{esc_target}$/
835
- when Regexp
836
- target
837
- else
838
- rac.abort_at(ch, "rule target has " +
839
- "to be a string or regular expression")
840
- end
841
- src_proc = case src_arg
842
- when String
843
- unless String === target
844
- rac.abort(ch, "rule target has to be a string " +
845
- "if source is a string")
846
- end
847
- lambda { |name| name.sub(/#{esc_target}$/, src_arg) }
848
- when Proc: src_arg
849
- when nil: lambda { |name| [] }
850
- else
851
- rac.abort_at(ch, "rule source has to be " +
852
- "String or Proc")
853
- end
854
- blk = self.new { |task_name|
855
- if target_rx =~ task_name
856
- have_src = true
857
- src = src_proc[task_name]
858
- if src.respond_to? :to_ary
859
- src.each { |f|
860
- if rac.resolve(f).empty? && !test(?e, f)
861
- have_src = false
862
- break
863
- end
864
- }
865
- else
866
- if rac.resolve(src).empty? && !test(?e, src)
867
- have_src = false
868
- end
869
- end
870
- if have_src
871
- t = rac.file(:__caller__ => ch,
872
- task_name => src_proc[task_name], &block)
873
- t.project_subdir = rac.current_subdir
874
- [t]
875
- end
876
- end
877
- }
878
- blk.target_rx = target_rx
879
- rac.resolve_hooks << blk
880
- nil
881
- end
882
- attr_accessor :target_rx
883
- end # class Rule
884
-
885
- class Action
886
- def self.rant_gen(rac, ch, args, &block)
887
- unless args.empty?
888
- rac.warn_msg(rac.pos_text(ch[:file], ch[:ln]),
889
- "Action doesn't take arguments.")
890
- end
891
- unless (rac[:tasks] || rac[:stop_after_load])
892
- yield
893
- end
894
- end
895
- end
896
- end # module Generators
897
- end # module Rant