rant 0.4.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/NEWS +31 -0
  2. data/README +3 -1
  3. data/Rantfile +53 -2
  4. data/doc/advanced.rdoc +86 -1
  5. data/doc/c.rdoc +8 -0
  6. data/doc/homepage/index.html +2 -0
  7. data/doc/rant.1 +4 -0
  8. data/doc/rant.rdoc +38 -0
  9. data/doc/rant_vs_rake.rdoc +13 -0
  10. data/doc/rantfile.rdoc +93 -63
  11. data/doc/sys.rdoc +568 -0
  12. data/lib/rant/coregen.rb +43 -16
  13. data/lib/rant/import/command.rb +7 -4
  14. data/lib/rant/import/filelist/more.rb +57 -0
  15. data/lib/rant/import/metadata.rb +5 -1
  16. data/lib/rant/import/nodes/default.rb +3 -24
  17. data/lib/rant/import/signedfile.rb +1 -8
  18. data/lib/rant/import/sys/more.rb +2 -1
  19. data/lib/rant/import/var/booleans.rb +65 -0
  20. data/lib/rant/import/var/lists.rb +34 -0
  21. data/lib/rant/import/var/numbers.rb +116 -0
  22. data/lib/rant/import/var/strings.rb +43 -0
  23. data/lib/rant/import.rb +19 -3
  24. data/lib/rant/node.rb +39 -6
  25. data/lib/rant/rantlib.rb +44 -8
  26. data/lib/rant/rantsys.rb +22 -54
  27. data/lib/rant/rantvar.rb +89 -256
  28. data/misc/TODO +18 -0
  29. data/misc/devel-notes +26 -1
  30. data/test/action.rant +24 -0
  31. data/test/deprecated/test_0_5_4.rb +53 -0
  32. data/test/deprecated/test_0_6_0.rb +1 -1
  33. data/test/dryrun/Rantfile +10 -0
  34. data/test/dryrun/foo.c +8 -0
  35. data/test/dryrun/test_dryrun.rb +31 -0
  36. data/test/import/c/dependencies/Rantfile +1 -1
  37. data/test/import/command/Rantfile +1 -1
  38. data/test/import/sys/test_tgz.rb +22 -0
  39. data/test/subdirs2/root.rant +11 -1
  40. data/test/subdirs2/sub1/sub.rant +3 -0
  41. data/test/subdirs2/test_subdirs2.rb +19 -0
  42. data/test/test_action.rb +75 -0
  43. data/test/test_filelist.rb +13 -10
  44. data/test/test_rant_interface.rb +2 -2
  45. data/test/test_rule.rb +121 -3
  46. data/test/test_sys_methods.rb +558 -0
  47. data/test/test_var.rb +10 -0
  48. data/test/tutil.rb +81 -8
  49. metadata +19 -2
data/lib/rant/rantlib.rb CHANGED
@@ -40,6 +40,7 @@ unless Regexp.respond_to? :union # new in 1.8.1
40
40
  end
41
41
  if RUBY_VERSION < "1.8.2"
42
42
  class Array
43
+ undef_method :flatten, :flatten!
43
44
  def flatten
44
45
  cp = self.dup
45
46
  cp.flatten!
@@ -63,11 +64,12 @@ if RUBY_VERSION < "1.8.2"
63
64
  end
64
65
  end
65
66
  end
66
- end
67
- if RUBY_VERSION < "1.8.1"
68
- module FileUtils
69
- def fu_list(arg)
70
- arg.respond_to?(:to_ary) ? arg.to_ary : [arg]
67
+ if RUBY_VERSION < "1.8.1"
68
+ module FileUtils
69
+ undef_method :fu_list
70
+ def fu_list(arg)
71
+ arg.respond_to?(:to_ary) ? arg.to_ary : [arg]
72
+ end
71
73
  end
72
74
  end
73
75
  end
@@ -85,7 +87,7 @@ class Array
85
87
  def shell_pathes
86
88
  warn caller[0]
87
89
  warn "[WARNING] Array#shell_pathes is highly deprecated " +
88
- "and will not come with future (0.5.0 and later) Rant releases."
90
+ "and will not come with future (0.5.2 and later) Rant releases."
89
91
  warn "Use `ary.map { |path| sys.sp path }' in Rantfiles."
90
92
  map { |path| Rant::Sys.sp(path) }
91
93
  =begin
@@ -138,8 +140,8 @@ module Rant::Lib
138
140
  # Note: This method splits on the pattern <tt>:(\d+)(:|$)</tt>,
139
141
  # assuming anything before is the filename.
140
142
  def parse_caller_elem(elem)
141
- return { :file => "", :ln => 0 } if elem.nil?
142
- if elem =~ /^(.+):(\d+)(:|$)/
143
+ return { :file => "", :ln => 0 } unless elem
144
+ if elem =~ /^(.+):(\d+)(?::|$)/
143
145
  { :file => $1, :ln => $2.to_i }
144
146
  else
145
147
  # should never occur
@@ -337,6 +339,8 @@ class Rant::RantApp
337
339
  "Multiple files may be specified with this option." ],
338
340
  [ "--force-run","-a", GetoptLong::REQUIRED_ARGUMENT,
339
341
  "Force rebuild of TARGET and all dependencies." ],
342
+ [ "--dry-run", "-n", GetoptLong::NO_ARGUMENT,
343
+ "Print info instead of actually executing actions." ],
340
344
  [ "--tasks", "-T", GetoptLong::NO_ARGUMENT,
341
345
  "Show a list of all described tasks and exit." ],
342
346
 
@@ -449,6 +453,10 @@ class Rant::RantApp
449
453
  end
450
454
  end
451
455
  end
456
+ def resolve_root_ref(path)
457
+ return File.join(@rootdir, path[1..-1]) if path =~ /^@/
458
+ path.sub(/^\\(?=@)/, '')
459
+ end
452
460
  # Returns an absolute path. If path resolves to a directory this
453
461
  # method ensures that the returned absolute path doesn't end in a
454
462
  # slash.
@@ -860,6 +868,12 @@ class Rant::RantApp
860
868
  @rootdir : @current_subdir})"
861
869
  @last_build_subdir = @current_subdir
862
870
  end
871
+ # TODO: model feels sick... this functionality should
872
+ # be implemented in Node
873
+ if @opts[:dry_run]
874
+ task.dry_run
875
+ true
876
+ end
863
877
  end
864
878
 
865
879
  private
@@ -958,6 +972,9 @@ class Rant::RantApp
958
972
  # of tasks invoked.
959
973
  def build(target, opt = {})
960
974
  opt[:force] = true if @force_targets.delete(target)
975
+ # Currently either the whole application has to by run in
976
+ # dry-run mode or nothing.
977
+ opt[:dry_run] = @opts[:dry_run]
961
978
  matching_tasks = 0
962
979
  old_subdir = @current_subdir
963
980
  old_pwd = Dir.pwd
@@ -981,6 +998,8 @@ class Rant::RantApp
981
998
  # Currently always returns an array (which might actually be an
982
999
  # empty array, but never nil).
983
1000
  def resolve(task_name, rel_project_dir = @current_subdir)
1001
+ # alternative implementation:
1002
+ # rec_save_resolve(task_name, nil, rel_project_dir)
984
1003
  s = @tasks[expand_path(rel_project_dir, task_name)]
985
1004
  case s
986
1005
  when nil
@@ -1004,6 +1023,23 @@ class Rant::RantApp
1004
1023
  end
1005
1024
  public :resolve
1006
1025
 
1026
+ def rec_save_resolve(task_name, excl_hook, rel_project_dir = @current_subdir)
1027
+ s = @tasks[expand_path(rel_project_dir, task_name)]
1028
+ case s
1029
+ when nil
1030
+ @resolve_hooks.each { |s|
1031
+ next if s == excl_hook
1032
+ s = s[task_name, rel_project_dir]
1033
+ return s if s
1034
+ }
1035
+ []
1036
+ when Rant::Node: [s]
1037
+ else
1038
+ s
1039
+ end
1040
+ end
1041
+ public :rec_save_resolve
1042
+
1007
1043
  # This hook will be invoked when no matching task is found for a
1008
1044
  # target. It may create one or more tasks for the target, which is
1009
1045
  # given as argument, on the fly and return an array of the created
data/lib/rant/rantsys.rb CHANGED
@@ -125,6 +125,12 @@ module Rant
125
125
  @files.size
126
126
  end
127
127
 
128
+ alias _object_respond_to? respond_to?
129
+ private :_object_respond_to?
130
+ def respond_to?(msg)
131
+ _object_respond_to?(msg) or @files.respond_to?(msg)
132
+ end
133
+
128
134
  def method_missing(sym, *args, &block)
129
135
  if @files.respond_to? sym
130
136
  resolve if @pending
@@ -324,55 +330,6 @@ end
324
330
  end
325
331
  private :apply_no_dir
326
332
 
327
- # Remove all files which have the given name.
328
- def no_file(name)
329
- @actions << [:apply_ary_method, :reject!,
330
- lambda { |entry|
331
- entry == name && !@keep[entry] && test(?f, entry)
332
- }]
333
- @pending = true
334
- self
335
- end
336
-
337
- # Remove all entries which contain an element
338
- # with the given suffix.
339
- def no_suffix(suffix)
340
- @actions << [:no_suffix, suffix]
341
- @pending = true
342
- self
343
- end
344
-
345
- def apply_no_suffix(suffix)
346
- elems = nil
347
- elem = nil
348
- @files.reject! { |entry|
349
- elems = Sys.split_all(entry)
350
- elems.any? { |elem|
351
- elem =~ /#{suffix}$/ && !@keep[entry]
352
- }
353
- }
354
- end
355
- private :apply_no_suffix
356
-
357
- # Remove all entries which contain an element
358
- # with the given prefix.
359
- def no_prefix(prefix)
360
- @actions << [:no_prefix, prefix]
361
- @pending = true
362
- self
363
- end
364
-
365
- def apply_no_prefix(prefix)
366
- elems = elem = nil
367
- @files.reject! { |entry|
368
- elems = Sys.split_all(entry)
369
- elems.any? { |elem|
370
- elem =~ /^#{prefix}/ && !@keep[entry]
371
- }
372
- }
373
- end
374
- private :apply_no_prefix
375
-
376
333
  # Get a string with all entries. This is very usefull
377
334
  # if you invoke a shell:
378
335
  # files # => ["foo/bar", "with space"]
@@ -417,6 +374,11 @@ end
417
374
  def apply_ary_method_1(meth, arg1, block=nil)
418
375
  @files.send meth, arg1, &block
419
376
  end
377
+ =begin
378
+ def apply_lazy_operation(meth, args, block)
379
+ @files.send(meth, *args, &block)
380
+ end
381
+ =end
420
382
  end # class FileList
421
383
 
422
384
  class RacFileList < FileList
@@ -645,19 +607,25 @@ end
645
607
 
646
608
  # If supported, make a hardlink, otherwise
647
609
  # fall back to copying.
648
- def safe_ln(*args)
610
+ def safe_ln(src, dest)
611
+ dest = dest.to_str
612
+ src = src.respond_to?(:to_ary) ? src.to_ary : src.to_str
649
613
  unless Sys.symlink_supported
650
- cp(*args)
614
+ cp(src, dest)
651
615
  else
652
616
  begin
653
- ln(*args)
654
- rescue Exception #Errno::EOPNOTSUPP
617
+ ln(src, dest)
618
+ rescue Exception # SystemCallError # Errno::EOPNOTSUPP
655
619
  Sys.symlink_supported = false
656
- cp(*args)
620
+ cp(src, dest)
657
621
  end
658
622
  end
659
623
  end
660
624
 
625
+ def ln_f(src, dest)
626
+ ln(src, dest, :force => true)
627
+ end
628
+
661
629
  # Split a path in all elements.
662
630
  def split_all(path)
663
631
  base, last = File.split(path)
data/lib/rant/rantvar.rb CHANGED
@@ -15,8 +15,9 @@
15
15
  #
16
16
  # If you're looking for general info about Rant, read the
17
17
  # README[link:files/README.html].
18
+
18
19
  module Rant
19
- VERSION = '0.4.8'
20
+ VERSION = '0.5.0'
20
21
 
21
22
  # Those are the filenames for rantfiles.
22
23
  # Case matters!
@@ -115,7 +116,7 @@ module Rant
115
116
  # holds constraints for values in @store
116
117
  @constraints = {}
117
118
  # set by default query
118
- @current_var = nil
119
+ @current_var = nil # this line will go in 0.5.4
119
120
  end
120
121
 
121
122
  def query(*args, &block)
@@ -128,7 +129,7 @@ module Rant
128
129
  if Hash === arg
129
130
  if arg.size == 1
130
131
  arg.each { |k,v|
131
- @current_var = k
132
+ @current_var = k # this line will go in 0.5.4
132
133
  self[k] = v if self[k].nil?
133
134
  }
134
135
  self
@@ -138,16 +139,21 @@ module Rant
138
139
  else
139
140
  self[arg]
140
141
  end
141
- when 2..3
142
- @current_var, cf, val = *args
143
- self.is cf
144
- self[@current_var] = val if val
142
+ when 2, 3
143
+ vid, cf, val = *args
144
+ @current_var = vid # this line will go in 0.5.4
145
+ constrain vid,
146
+ get_factory(cf).rant_constraint
147
+ self[vid] = val if val
145
148
  else
146
- raise QueryError, "to many arguments"
149
+ raise QueryError, "too many arguments"
147
150
  end
148
151
  end
149
152
 
150
153
  def is ct, *ct_args
154
+ warn caller[0]
155
+ warn "method `var.is' is deprecated and will not be " +
156
+ "in Rant 0.5.4 and later"
151
157
  constrain @current_var,
152
158
  get_factory(ct).rant_constraint(*ct_args)
153
159
  self
@@ -166,13 +172,53 @@ module Rant
166
172
  def get_factory id
167
173
  if String === id || Symbol === id
168
174
  begin
175
+ ### temporary solution ###
176
+ raise unless Constraints.const_defined? id
177
+ ##########################
169
178
  id = Constraints.const_get(id)
170
179
  rescue
171
- raise NotAConstraintFactoryError.new(id), caller
180
+ ### temporary solution ###
181
+ id_sym = id.to_sym
182
+ rl =
183
+ if [:Integer, :IntegerInRange, :Float,
184
+ :FloatInRange].include? id_sym
185
+ 'rant/import/var/numbers'
186
+ elsif [:Bool, :BoolTrue].include? id_sym
187
+ 'rant/import/var/booleans'
188
+ elsif :List == id_sym
189
+ 'rant/import/var/lists'
190
+ elsif [:String, :ToString].include? id_sym
191
+ 'rant/import/var/strings'
192
+ else
193
+ ##########################
194
+ raise NotAConstraintFactoryError.new(id), caller
195
+ end
196
+ warn "Explicitely <code>import " +
197
+ "'#{rl.sub(/^rant\/import\//, '')}'" +
198
+ "</code> to use the #{id_sym.inspect} " +
199
+ "constraint."
200
+ require rl #rant-import:remove
201
+ retry
172
202
  end
173
203
  end
174
204
  unless id.respond_to? :rant_constraint
175
- raise NotAConstraintFactoryError.new(id), caller
205
+ ### temporary solution ###
206
+ rl =
207
+ if id == true || id == false
208
+ 'rant/import/var/booleans'
209
+ elsif id == String
210
+ 'rant/import/var/strings'
211
+ elsif id.kind_of? Range
212
+ 'rant/import/var/numbers'
213
+ else
214
+ ##########################
215
+ raise NotAConstraintFactoryError.new(id), caller
216
+ end
217
+ warn "Explicitely <code>import " +
218
+ "'#{rl.sub(/^rant\/import\//, '')}'" +
219
+ "</code> to use the #{id.inspect} " +
220
+ "constraint."
221
+ require rl #rant-import:remove
176
222
  end
177
223
  id
178
224
  end
@@ -278,237 +324,6 @@ module Rant
278
324
  end
279
325
  end
280
326
 
281
- module Constraints
282
-
283
- class String
284
- include Constraint
285
-
286
- class << self
287
- alias rant_constraint new
288
- end
289
-
290
- def filter(val)
291
- if val.respond_to? :to_str
292
- val.to_str
293
- elsif Symbol === val
294
- val.to_s
295
- else
296
- raise ConstraintError.new(self, val)
297
- end
298
- end
299
- def default
300
- ""
301
- end
302
- def to_s
303
- "string"
304
- end
305
- end
306
-
307
- class ToString < String
308
- class << self
309
- alias rant_constraint new
310
- end
311
- def filter(val)
312
- val.to_s
313
- end
314
- end
315
-
316
- class Integer
317
- include Constraint
318
-
319
- class << self
320
- def rant_constraint(range = nil)
321
- if range
322
- IntegerInRange.new(range)
323
- else
324
- self.new
325
- end
326
- end
327
- end
328
-
329
- def filter(val)
330
- Kernel::Integer(val)
331
- rescue
332
- raise ConstraintError.new(self, val)
333
- end
334
- def default
335
- 0
336
- end
337
- def to_s
338
- "integer"
339
- end
340
- end
341
-
342
- class IntegerInRange < Integer
343
- def initialize(range)
344
- @range = range
345
- end
346
- def filter(val)
347
- i = super
348
- if @range === i
349
- i
350
- else
351
- raise ConstraintError.new(self, val)
352
- end
353
- end
354
- def default
355
- @range.min
356
- end
357
- def to_s
358
- super + " #{@range}"
359
- end
360
- end
361
-
362
- class Float
363
- include Constraint
364
-
365
- class << self
366
- def rant_constraint(range = nil)
367
- if range
368
- FloatInRange.new(range)
369
- else
370
- self.new
371
- end
372
- end
373
- end
374
-
375
- def filter(val)
376
- Kernel::Float(val)
377
- rescue
378
- raise ConstraintError.new(self, val)
379
- end
380
- def default
381
- 0.0
382
- end
383
- def to_s
384
- "float"
385
- end
386
- end
387
-
388
- class FloatInRange < Float
389
- def initialize(range)
390
- @range = range
391
- end
392
- def filter(val)
393
- i = super
394
- if @range === i
395
- i
396
- else
397
- raise ConstraintError.new(self, val)
398
- end
399
- end
400
- def default
401
- @range.first
402
- end
403
- def to_s
404
- super + " #{@range}"
405
- end
406
- end
407
-
408
- class AutoList
409
- include Constraint
410
-
411
- class << self
412
- alias rant_constraint new
413
- end
414
-
415
- def filter(val)
416
- if val.respond_to? :to_ary
417
- val.to_ary
418
- elsif val.nil?
419
- raise ConstraintError.new(self, val)
420
- else
421
- [val]
422
- end
423
- end
424
- def default
425
- []
426
- end
427
- def to_s
428
- "list or single, non-nil value"
429
- end
430
- end
431
-
432
- class List
433
- include Constraint
434
-
435
- class << self
436
- alias rant_constraint new
437
- end
438
-
439
- def filter(val)
440
- if val.respond_to? :to_ary
441
- val.to_ary
442
- else
443
- raise ConstraintError.new(self, val)
444
- end
445
- end
446
- def default
447
- []
448
- end
449
- def to_s
450
- "list (Array)"
451
- end
452
- end
453
-
454
- Array = List
455
-
456
- class Bool
457
- include Constraint
458
- class << self
459
- alias rant_constraint new
460
- end
461
- def filter(val)
462
- if ::Symbol === val or ::Integer === val
463
- val = val.to_s
464
- end
465
- if val == true
466
- true
467
- elsif val == false || val == nil
468
- false
469
- elsif val.respond_to? :to_str
470
- case val.to_str
471
- when /^\s*true\s*$/i: true
472
- when /^\s*false\s*$/i: false
473
- when /^\s*y(es)?\s*$/i: true
474
- when /^\s*n(o)?\s*$/: false
475
- when /^\s*on\s*$/i: true
476
- when /^\s*off\s*$/i: false
477
- when /^\s*1\s*$/: true
478
- when /^\s*0\s*$/: false
479
- else
480
- raise ConstraintError.new(self, val)
481
- end
482
- else
483
- raise ConstraintError.new(self, val)
484
- end
485
- end
486
- def default
487
- false
488
- end
489
- def to_s
490
- "bool"
491
- end
492
- end
493
-
494
- class BoolTrue < Bool
495
- def default
496
- true
497
- end
498
- end
499
-
500
- #--
501
- # perhaps this should stay a secret ;)
502
- #++
503
- def true.rant_constraint
504
- BoolTrue.rant_constraint
505
- end
506
- def false.rant_constraint
507
- Bool.rant_constraint
508
- end
509
-
510
- end # module Constraints
511
-
512
327
  # A +vid+ has to be a String to be valid.
513
328
  def valid_vid(obj)
514
329
  case obj
@@ -537,18 +352,36 @@ module Rant
537
352
  end
538
353
 
539
354
  module_function :valid_constraint?, :valid_vid
540
- end # module RantVar
541
- end # module Rant
542
-
543
- class Range
544
- def rant_constraint
545
- case first
546
- when ::Integer
547
- Rant::RantVar::Constraints::IntegerInRange.new(self)
548
- when ::Float
549
- Rant::RantVar::Constraints::FloatInRange.new(self)
550
- else
551
- raise NotAConstraintFactoryError.new(self)
552
- end
553
- end
554
- end
355
+
356
+ module Constraints
357
+ class AutoList
358
+ include Constraint
359
+ class << self
360
+ alias rant_constraint new
361
+ end
362
+ def filter(val)
363
+ if val.respond_to? :to_ary
364
+ val.to_ary
365
+ elsif val.nil?
366
+ raise ConstraintError.new(self, val)
367
+ else
368
+ [val]
369
+ end
370
+ end
371
+ def default
372
+ []
373
+ end
374
+ def to_s
375
+ "list or single, non-nil value"
376
+ end
377
+ end
378
+ end # module Constraints
379
+ end # module RantVar
380
+ end # module Rant
381
+
382
+ ### temporary solution ###
383
+ #require 'rant/import/var/numbers' #rant-import:uncomment
384
+ #require 'rant/import/var/booleans' #rant-import:uncomment
385
+ #require 'rant/import/var/lists' #rant-import:uncomment
386
+ #require 'rant/import/var/strings' #rant-import:uncomment
387
+ ##########################
data/misc/TODO CHANGED
@@ -1,6 +1,24 @@
1
1
 
2
2
  = TODO
3
3
 
4
+ == Convinient symlink method
5
+
6
+ Implement a Rant::Sys.symlink (or similar name) method that converts
7
+ destination filenames to absolute pathes before actual linking.
8
+
9
+ == More tests for --dry-run
10
+
11
+ == Behaviour of `gen Rule' without block
12
+
13
+ Currently dies with <tt>undefined method `arity' for
14
+ nil:NilClass</tt>.
15
+
16
+ Done (0.4.9), printing an appropriate error message.
17
+
18
+ == Command: Hash interpolation
19
+
20
+ Decide about behaviour of hash interpolation in Command tasks.
21
+
4
22
  == Don't use +invoke+ result of dependency to check if update is
5
23
  necessary
6
24
 
data/misc/devel-notes CHANGED
@@ -1,6 +1,31 @@
1
1
 
2
+ == Rule recursion
3
+
4
+ Since Rant 0.4.9, rules created with <tt>gen Rule</tt> no longer
5
+ recurse. This means, that when looking for the Rule's source(s), this
6
+ specific rule itself is ignored. This prevents infinite recursion.
7
+
8
+ Consider:
9
+
10
+ gen Rule, "o" => ".foo" do something end
11
+
12
+ Say we call <tt>make "bo"</tt>.
13
+
14
+ Rant pre 0.4.9 behaviour:
15
+
16
+ The rule matches anything ending in "o". Since "bo" ends in "o", the
17
+ rule matches and searches for the source "b.foo". No task is defined
18
+ for "b.foo", but again our only rule matches and thus searches for the
19
+ source "b.fo.foo". Now we have our infinite recursion.
20
+
21
+ Rant 0.4.9 and later behaviour:
22
+
23
+ The only rule matches and searches for the source "b.foo". When a rule
24
+ searches for a source, the rule itself is excluded. No infinite
25
+ recursion.
26
+
2
27
  == Rant::Env.find_bin...
3
- ... could honour PATHEXT on windows.
28
+ ... should honour PATHEXT on windows.
4
29
 
5
30
  == Task alias
6
31
  Since +alias+ is a Ruby keyword, we could use +nick+ to create task
data/test/action.rant ADDED
@@ -0,0 +1,24 @@
1
+
2
+ import "sys/more"
3
+
4
+ gen Action, /\.t$/ do
5
+ puts 'executing action: rx /\.t$/'
6
+ source "action.t.rant"
7
+ end
8
+
9
+ file "action.t.rant" do |t|
10
+ sys.write_to_file t.name, <<-EOF
11
+ file "a.t" do |t|
12
+ sys.touch t.name
13
+ end
14
+ file "b.t" do |t|
15
+ sys.touch t.name
16
+ end
17
+ EOF
18
+ end
19
+
20
+ file "b.tt" do |t|
21
+ sys.touch t.name
22
+ end
23
+
24
+ subdirs "sub.t" if test ?d, "sub.t"