Commit | Line | Data |
3fea05b9 |
1 | #!/usr/bin/perl -w |
2 | |
3 | eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}' |
4 | if 0; # not running under some shell |
5 | #======================================================================== |
6 | # |
7 | # ttree |
8 | # |
9 | # DESCRIPTION |
10 | # Script for processing all directory trees containing templates. |
11 | # Template files are processed and the output directed to the |
12 | # relvant file in an output tree. The timestamps of the source and |
13 | # destination files can then be examined for future invocations |
14 | # to process only those files that have changed. In other words, |
15 | # it's a lot like 'make' for templates. |
16 | # |
17 | # AUTHOR |
18 | # Andy Wardley <abw@wardley.org> |
19 | # |
20 | # COPYRIGHT |
21 | # Copyright (C) 1996-2003 Andy Wardley. All Rights Reserved. |
22 | # Copyright (C) 1998-2003 Canon Research Centre Europe Ltd. |
23 | # |
24 | # This module is free software; you can redistribute it and/or |
25 | # modify it under the same terms as Perl itself. |
26 | # |
27 | #------------------------------------------------------------------------ |
28 | # |
29 | # $Id: ttree 1196 2009-04-07 13:34:14Z abw $ |
30 | # |
31 | #======================================================================== |
32 | |
33 | use strict; |
34 | use Template; |
35 | use AppConfig qw( :expand ); |
36 | use File::Copy; |
37 | use File::Path; |
38 | use File::Spec; |
39 | use File::Basename; |
40 | use Text::ParseWords qw(quotewords); |
41 | |
42 | my $NAME = "ttree"; |
43 | my $VERSION = 2.90; |
44 | my $HOME = $ENV{ HOME } || ''; |
45 | my $RCFILE = $ENV{"\U${NAME}rc"} || "$HOME/.${NAME}rc"; |
46 | my $TTMODULE = 'Template'; |
47 | |
48 | #------------------------------------------------------------------------ |
49 | # configuration options |
50 | #------------------------------------------------------------------------ |
51 | |
52 | # offer create a sample config file if it doesn't exist, unless a '-f' |
53 | # has been specified on the command line |
54 | unless (-f $RCFILE or grep(/^(-f|-h|--help)$/, @ARGV) ) { |
55 | print("Do you want me to create a sample '.ttreerc' file for you?\n", |
56 | "(file: $RCFILE) [y/n]: "); |
57 | my $y = <STDIN>; |
58 | if ($y =~ /^y(es)?/i) { |
59 | write_config($RCFILE); |
60 | exit(0); |
61 | } |
62 | } |
63 | |
64 | # read configuration file and command line arguments - I need to remember |
65 | # to fix varlist() and varhash() in AppConfig to make this nicer... |
66 | my $config = read_config($RCFILE); |
67 | my $dryrun = $config->nothing; |
68 | my $verbose = $config->verbose || $dryrun; |
69 | my $colour = $config->colour; |
70 | my $summary = $config->summary; |
71 | my $recurse = $config->recurse; |
72 | my $preserve = $config->preserve; |
73 | my $all = $config->all; |
74 | my $libdir = $config->lib; |
75 | my $ignore = $config->ignore; |
76 | my $copy = $config->copy; |
77 | my $accept = $config->accept; |
78 | my $absolute = $config->absolute; |
79 | my $relative = $config->relative; |
80 | my $suffix = $config->suffix; |
81 | my $binmode = $config->binmode; |
82 | my $depends = $config->depend; |
83 | my $depsfile = $config->depend_file; |
84 | my ($n_proc, $n_unmod, $n_skip, $n_copy, $n_mkdir) = (0) x 5; |
85 | |
86 | my $srcdir = $config->src |
87 | || die "Source directory not set (-s)\n"; |
88 | my $destdir = $config->dest |
89 | || die "Destination directory not set (-d)\n"; |
90 | die "Source and destination directories may not be the same:\n $srcdir\n" |
91 | if $srcdir eq $destdir; |
92 | |
93 | # unshift any perl5lib directories onto front of INC |
94 | unshift(@INC, @{ $config->perl5lib }); |
95 | |
96 | # get all template_* options from the config and fold keys to UPPER CASE |
97 | my %ttopts = $config->varlist('^template_', 1); |
98 | my $ttmodule = delete($ttopts{ module }); |
99 | my $ucttopts = { |
100 | map { my $v = $ttopts{ $_ }; defined $v ? (uc $_, $v) : () } |
101 | keys %ttopts, |
102 | }; |
103 | |
104 | # get all template variable definitions |
105 | my $replace = $config->get('define'); |
106 | |
107 | # now create complete parameter hash for creating template processor |
108 | my $ttopts = { |
109 | %$ucttopts, |
110 | RELATIVE => $relative, |
111 | ABSOLUTE => $absolute, |
112 | INCLUDE_PATH => [ $srcdir, @$libdir ], |
113 | OUTPUT_PATH => $destdir, |
114 | }; |
115 | |
116 | # load custom template module |
117 | if ($ttmodule) { |
118 | my $ttpkg = $ttmodule; |
119 | $ttpkg =~ s[::][/]g; |
120 | $ttpkg .= '.pm'; |
121 | require $ttpkg; |
122 | } |
123 | else { |
124 | $ttmodule = $TTMODULE; |
125 | } |
126 | |
127 | |
128 | #------------------------------------------------------------------------ |
129 | # inter-file dependencies |
130 | #------------------------------------------------------------------------ |
131 | |
132 | if ($depsfile or $depends) { |
133 | $depends = dependencies($depsfile, $depends); |
134 | } |
135 | else { |
136 | $depends = { }; |
137 | } |
138 | |
139 | my $global_deps = $depends->{'*'} || [ ]; |
140 | |
141 | # add any PRE_PROCESS, etc., templates as global dependencies |
142 | foreach my $ttopt (qw( PRE_PROCESS POST_PROCESS PROCESS WRAPPER )) { |
143 | my $deps = $ucttopts->{ $ttopt } || next; |
144 | my @deps = ref $deps eq 'ARRAY' ? (@$deps) : ($deps); |
145 | next unless @deps; |
146 | push(@$global_deps, @deps); |
147 | } |
148 | |
149 | # remove any duplicates |
150 | $global_deps = { map { ($_ => 1) } @$global_deps }; |
151 | $global_deps = [ keys %$global_deps ]; |
152 | |
153 | # update $depends hash or delete it if there are no dependencies |
154 | if (@$global_deps) { |
155 | $depends->{'*'} = $global_deps; |
156 | } |
157 | else { |
158 | delete $depends->{'*'}; |
159 | $global_deps = undef; |
160 | } |
161 | $depends = undef |
162 | unless keys %$depends; |
163 | |
164 | my $DEP_DEBUG = $config->depend_debug(); |
165 | |
166 | |
167 | #------------------------------------------------------------------------ |
168 | # pre-amble |
169 | #------------------------------------------------------------------------ |
170 | |
171 | if ($colour) { |
172 | no strict 'refs'; |
173 | *red = \&_red; |
174 | *green = \&_green; |
175 | *yellow = \&_yellow; |
176 | *blue = \&_blue; |
177 | } |
178 | else { |
179 | no strict 'refs'; |
180 | *red = \&_white; |
181 | *green = \&_white; |
182 | *yellow = \&_white; |
183 | *blue = \&_white; |
184 | } |
185 | |
186 | if ($verbose) { |
187 | local $" = ', '; |
188 | |
189 | |
190 | print "$NAME $VERSION (Template Toolkit version $Template::VERSION)\n\n"; |
191 | |
192 | my $sfx = join(', ', map { "$_ => $suffix->{$_}" } keys %$suffix); |
193 | |
194 | print(" Source: $srcdir\n", |
195 | " Destination: $destdir\n", |
196 | "Include Path: [ @$libdir ]\n", |
197 | " Ignore: [ @$ignore ]\n", |
198 | " Copy: [ @$copy ]\n", |
199 | " Accept: [ @$accept ]\n", |
200 | " Suffix: [ $sfx ]\n"); |
201 | print(" Module: $ttmodule ", $ttmodule->module_version(), "\n") |
202 | unless $ttmodule eq $TTMODULE; |
203 | |
204 | if ($depends && $DEP_DEBUG) { |
205 | print "Dependencies:\n"; |
206 | foreach my $key ('*', grep { !/\*/ } keys %$depends) { |
207 | printf( " %-16s %s\n", $key, |
208 | join(', ', @{ $depends->{ $key } }) ) |
209 | if defined $depends->{ $key }; |
210 | |
211 | } |
212 | } |
213 | print "\n" if $verbose > 1; |
214 | print red("NOTE: dry run, doing nothing...\n") |
215 | if $dryrun; |
216 | } |
217 | |
218 | #------------------------------------------------------------------------ |
219 | # main processing loop |
220 | #------------------------------------------------------------------------ |
221 | |
222 | my $template = $ttmodule->new($ttopts) |
223 | || die $ttmodule->error(); |
224 | |
225 | if (@ARGV) { |
226 | # explicitly process files specified on command lines |
227 | foreach my $file (@ARGV) { |
228 | my $path = $srcdir ? File::Spec->catfile($srcdir, $file) : $file; |
229 | if ( -d $path ) { |
230 | process_tree($file); |
231 | } |
232 | else { |
233 | process_file($file, $path, force => 1); |
234 | } |
235 | } |
236 | } |
237 | else { |
238 | # implicitly process all file in source directory |
239 | process_tree(); |
240 | } |
241 | |
242 | if ($summary || $verbose) { |
243 | my $format = "%13d %s %s\n"; |
244 | print "\n" if $verbose > 1; |
245 | print( |
246 | " Summary: ", |
247 | $dryrun ? red("This was a dry run. Nothing was actually done\n") : "\n", |
248 | green(sprintf($format, $n_proc, $n_proc == 1 ? 'file' : 'files', 'processed')), |
249 | green(sprintf($format, $n_copy, $n_copy == 1 ? 'file' : 'files', 'copied')), |
250 | green(sprintf($format, $n_mkdir, $n_mkdir == 1 ? 'directory' : 'directories', 'created')), |
251 | yellow(sprintf($format, $n_unmod, $n_unmod == 1 ? 'file' : 'files', 'skipped (not modified)')), |
252 | yellow(sprintf($format, $n_skip, $n_skip == 1 ? 'file' : 'files', 'skipped (ignored)')) |
253 | ); |
254 | } |
255 | |
256 | exit(0); |
257 | |
258 | |
259 | #======================================================================== |
260 | # END |
261 | #======================================================================== |
262 | |
263 | |
264 | #------------------------------------------------------------------------ |
265 | # process_tree($dir) |
266 | # |
267 | # Walks the directory tree starting at $dir or the current directory |
268 | # if unspecified, processing files as found. |
269 | #------------------------------------------------------------------------ |
270 | |
271 | sub process_tree { |
272 | my $dir = shift; |
273 | my ($file, $path, $abspath, $check); |
274 | my $target; |
275 | local *DIR; |
276 | |
277 | my $absdir = join('/', $srcdir ? $srcdir : (), defined $dir ? $dir : ()); |
278 | $absdir ||= '.'; |
279 | |
280 | opendir(DIR, $absdir) || do { warn "$absdir: $!\n"; return undef; }; |
281 | |
282 | FILE: while (defined ($file = readdir(DIR))) { |
283 | next if $file eq '.' || $file eq '..'; |
284 | $path = defined $dir ? "$dir/$file" : $file; |
285 | $abspath = "$absdir/$file"; |
286 | |
287 | next unless -e $abspath; |
288 | |
289 | # check against ignore list |
290 | foreach $check (@$ignore) { |
291 | if ($path =~ /$check/) { |
292 | printf yellow(" - %-32s (ignored, matches /$check/)\n"), $path |
293 | if $verbose > 1; |
294 | $n_skip++; |
295 | next FILE; |
296 | } |
297 | } |
298 | |
299 | # check against acceptance list |
300 | if (@$accept) { |
301 | unless ((-d $abspath && $recurse) || grep { $path =~ /$_/ } @$accept) { |
302 | printf yellow(" - %-32s (not accepted)\n"), $path |
303 | if $verbose > 1; |
304 | $n_skip++; |
305 | next FILE; |
306 | } |
307 | } |
308 | |
309 | if (-d $abspath) { |
310 | if ($recurse) { |
311 | my ($uid, $gid, $mode); |
312 | |
313 | (undef, undef, $mode, undef, $uid, $gid, undef, undef, |
314 | undef, undef, undef, undef, undef) = stat($abspath); |
315 | |
316 | # create target directory if required |
317 | $target = "$destdir/$path"; |
318 | unless (-d $target || $dryrun) { |
319 | mkpath($target, $verbose, $mode) or |
320 | die red("Could not mkpath ($target): $!\n"); |
321 | |
322 | # commented out by abw on 2000/12/04 - seems to raise a warning? |
323 | # chown($uid, $gid, $target) || warn "chown($target): $!\n"; |
324 | |
325 | $n_mkdir++; |
326 | printf green(" + %-32s (created target directory)\n"), $path |
327 | if $verbose; |
328 | } |
329 | # recurse into directory |
330 | process_tree($path); |
331 | } |
332 | else { |
333 | $n_skip++; |
334 | printf yellow(" - %-32s (directory, not recursing)\n"), $path |
335 | if $verbose > 1; |
336 | } |
337 | } |
338 | else { |
339 | process_file($path, $abspath); |
340 | } |
341 | } |
342 | closedir(DIR); |
343 | } |
344 | |
345 | |
346 | #------------------------------------------------------------------------ |
347 | # process_file() |
348 | # |
349 | # File filtering and processing sub-routine called by process_tree() |
350 | #------------------------------------------------------------------------ |
351 | |
352 | sub process_file { |
353 | my ($file, $absfile, %options) = @_; |
354 | my ($dest, $destfile, $filename, $check, |
355 | $srctime, $desttime, $mode, $uid, $gid); |
356 | my ($old_suffix, $new_suffix); |
357 | my $is_dep = 0; |
358 | my $copy_file = 0; |
359 | |
360 | $absfile ||= $file; |
361 | $filename = basename($file); |
362 | $destfile = $file; |
363 | |
364 | # look for any relevant suffix mapping |
365 | if (%$suffix) { |
366 | if ($filename =~ m/\.(.+)$/) { |
367 | $old_suffix = $1; |
368 | if ($new_suffix = $suffix->{ $old_suffix }) { |
369 | $destfile =~ s/$old_suffix$/$new_suffix/; |
370 | } |
371 | } |
372 | } |
373 | $dest = $destdir ? "$destdir/$destfile" : $destfile; |
374 | |
375 | # print "proc $file => $dest\n"; |
376 | |
377 | # check against copy list |
378 | foreach my $copy_pattern (@$copy) { |
379 | if ($filename =~ /$copy_pattern/) { |
380 | $copy_file = 1; |
381 | $check = $copy_pattern; |
382 | last; |
383 | } |
384 | } |
385 | |
386 | # stat the source file unconditionally, so we can preserve |
387 | # mode and ownership |
388 | ( undef, undef, $mode, undef, $uid, $gid, undef, |
389 | undef, undef, $srctime, undef, undef, undef ) = stat($absfile); |
390 | |
391 | # test modification time of existing destination file |
392 | if (! $all && ! $options{ force } && -f $dest) { |
393 | $desttime = ( stat($dest) )[9]; |
394 | |
395 | if (defined $depends and not $copy_file) { |
396 | my $deptime = depend_time($file, $depends); |
397 | if (defined $deptime && ($srctime < $deptime)) { |
398 | $srctime = $deptime; |
399 | $is_dep = 1; |
400 | } |
401 | } |
402 | |
403 | if ($desttime >= $srctime) { |
404 | printf yellow(" - %-32s (not modified)\n"), $file |
405 | if $verbose > 1; |
406 | $n_unmod++; |
407 | return; |
408 | } |
409 | } |
410 | |
411 | # check against copy list |
412 | if ($copy_file) { |
413 | $n_copy++; |
414 | unless ($dryrun) { |
415 | copy($absfile, $dest) or die red("Could not copy ($absfile to $dest) : $!\n"); |
416 | |
417 | if ($preserve) { |
418 | chown($uid, $gid, $dest) || warn red("chown($dest): $!\n"); |
419 | chmod($mode, $dest) || warn red("chmod($dest): $!\n"); |
420 | } |
421 | } |
422 | |
423 | printf green(" > %-32s (copied, matches /$check/)\n"), $file |
424 | if $verbose; |
425 | |
426 | return; |
427 | } |
428 | |
429 | $n_proc++; |
430 | |
431 | if ($verbose) { |
432 | printf(green(" + %-32s"), $file); |
433 | print(green(" (changed suffix to $new_suffix)")) if $new_suffix; |
434 | print "\n"; |
435 | } |
436 | |
437 | # process file |
438 | unless ($dryrun) { |
439 | $template->process($file, $replace, $destfile, |
440 | $binmode ? {binmode => $binmode} : {}) |
441 | || print(red(" ! "), $template->error(), "\n"); |
442 | |
443 | if ($preserve) { |
444 | chown($uid, $gid, $dest) || warn red("chown($dest): $!\n"); |
445 | chmod($mode, $dest) || warn red("chmod($dest): $!\n"); |
446 | } |
447 | } |
448 | } |
449 | |
450 | |
451 | #------------------------------------------------------------------------ |
452 | # dependencies($file, $depends) |
453 | # |
454 | # Read the dependencies from $file, if defined, and merge in with |
455 | # those passed in as the hash array $depends, if defined. |
456 | #------------------------------------------------------------------------ |
457 | |
458 | sub dependencies { |
459 | my ($file, $depend) = @_; |
460 | my %depends = (); |
461 | |
462 | if (defined $file) { |
463 | my ($fh, $text, $line); |
464 | open $fh, $file or die "Can't open $file, $!"; |
465 | local $/ = undef; |
466 | $text = <$fh>; |
467 | close($fh); |
468 | $text =~ s[\\\n][]mg; |
469 | |
470 | foreach $line (split("\n", $text)) { |
471 | next if $line =~ /^\s*(#|$)/; |
472 | chomp $line; |
473 | my ($file, @files) = quotewords('\s*:\s*', 0, $line); |
474 | $file =~ s/^\s+//; |
475 | @files = grep(defined, quotewords('(,|\s)\s*', 0, @files)); |
476 | $depends{$file} = \@files; |
477 | } |
478 | } |
479 | |
480 | if (defined $depend) { |
481 | foreach my $key (keys %$depend) { |
482 | $depends{$key} = [ quotewords(',', 0, $depend->{$key}) ]; |
483 | } |
484 | } |
485 | |
486 | return \%depends; |
487 | } |
488 | |
489 | |
490 | |
491 | #------------------------------------------------------------------------ |
492 | # depend_time($file, \%depends) |
493 | # |
494 | # Returns the mtime of the most recent in @files. |
495 | #------------------------------------------------------------------------ |
496 | |
497 | sub depend_time { |
498 | my ($file, $depends) = @_; |
499 | my ($deps, $absfile, $modtime); |
500 | my $maxtime = 0; |
501 | my @pending = ($file); |
502 | my @files; |
503 | my %seen; |
504 | |
505 | # push any global dependencies onto the pending list |
506 | if ($deps = $depends->{'*'}) { |
507 | push(@pending, @$deps); |
508 | } |
509 | |
510 | print " # checking dependencies for $file...\n" |
511 | if $DEP_DEBUG; |
512 | |
513 | # iterate through the list of pending files |
514 | while (@pending) { |
515 | $file = shift @pending; |
516 | next if $seen{ $file }++; |
517 | |
518 | if (File::Spec->file_name_is_absolute($file) && -f $file) { |
519 | $modtime = (stat($file))[9]; |
520 | print " # $file [$modtime]\n" |
521 | if $DEP_DEBUG; |
522 | } |
523 | else { |
524 | $modtime = 0; |
525 | foreach my $dir ($srcdir, @$libdir) { |
526 | $absfile = File::Spec->catfile($dir, $file); |
527 | if (-f $absfile) { |
528 | $modtime = (stat($absfile))[9]; |
529 | print " # $absfile [$modtime]\n" |
530 | if $DEP_DEBUG; |
531 | last; |
532 | } |
533 | } |
534 | } |
535 | $maxtime = $modtime |
536 | if $modtime > $maxtime; |
537 | |
538 | if ($deps = $depends->{ $file }) { |
539 | push(@pending, @$deps); |
540 | print " # depends on ", join(', ', @$deps), "\n" |
541 | if $DEP_DEBUG; |
542 | } |
543 | } |
544 | |
545 | return $maxtime; |
546 | } |
547 | |
548 | |
549 | #------------------------------------------------------------------------ |
550 | # read_config($file) |
551 | # |
552 | # Handles reading of config file and/or command line arguments. |
553 | #------------------------------------------------------------------------ |
554 | |
555 | sub read_config { |
556 | my $file = shift; |
557 | my $verbose = 0; |
558 | my $verbinc = sub { |
559 | my ($state, $var, $value) = @_; |
560 | $state->{ VARIABLE }->{ verbose } = $value ? ++$verbose : --$verbose; |
561 | }; |
562 | my $config = AppConfig->new( |
563 | { |
564 | ERROR => sub { die(@_, "\ntry `$NAME --help'\n") } |
565 | }, |
566 | 'help|h' => { ACTION => \&help }, |
567 | 'src|s=s' => { EXPAND => EXPAND_ALL }, |
568 | 'dest|d=s' => { EXPAND => EXPAND_ALL }, |
569 | 'lib|l=s@' => { EXPAND => EXPAND_ALL }, |
570 | 'cfg|c=s' => { EXPAND => EXPAND_ALL, DEFAULT => '.' }, |
571 | 'verbose|v' => { DEFAULT => 0, ACTION => $verbinc }, |
572 | 'recurse|r' => { DEFAULT => 0 }, |
573 | 'nothing|n' => { DEFAULT => 0 }, |
574 | 'preserve|p' => { DEFAULT => 0 }, |
575 | 'absolute' => { DEFAULT => 0 }, |
576 | 'relative' => { DEFAULT => 0 }, |
577 | 'colour|color'=> { DEFAULT => 0 }, |
578 | 'summary' => { DEFAULT => 0 }, |
579 | 'all|a' => { DEFAULT => 0 }, |
580 | 'define=s%', |
581 | 'suffix=s%', |
582 | 'binmode=s', |
583 | 'ignore=s@', |
584 | 'copy=s@', |
585 | 'accept=s@', |
586 | 'depend=s%', |
587 | 'depend_debug|depdbg', |
588 | 'depend_file|depfile=s' => { EXPAND => EXPAND_ALL }, |
589 | 'template_module|module=s', |
590 | 'template_anycase|anycase', |
591 | 'template_encoding|encoding=s', |
592 | 'template_eval_perl|eval_perl', |
593 | 'template_load_perl|load_perl', |
594 | 'template_interpolate|interpolate', |
595 | 'template_pre_chomp|pre_chomp|prechomp', |
596 | 'template_post_chomp|post_chomp|postchomp', |
597 | 'template_trim|trim', |
598 | 'template_pre_process|pre_process|preprocess=s@', |
599 | 'template_post_process|post_process|postprocess=s@', |
600 | 'template_process|process=s', |
601 | 'template_wrapper|wrapper=s', |
602 | 'template_recursion|recursion', |
603 | 'template_expose_blocks|expose_blocks', |
604 | 'template_default|default=s', |
605 | 'template_error|error=s', |
606 | 'template_debug|debug=s', |
607 | 'template_start_tag|start_tag|starttag=s', |
608 | 'template_end_tag|end_tag|endtag=s', |
609 | 'template_tag_style|tag_style|tagstyle=s', |
610 | 'template_compile_ext|compile_ext=s', |
611 | 'template_compile_dir|compile_dir=s' => { EXPAND => EXPAND_ALL }, |
612 | 'template_plugin_base|plugin_base|pluginbase=s@' => { EXPAND => EXPAND_ALL }, |
613 | 'perl5lib|perllib=s@' => { EXPAND => EXPAND_ALL }, |
614 | ); |
615 | |
616 | # add the 'file' option now that we have a $config object that we |
617 | # can reference in a closure |
618 | $config->define( |
619 | 'file|f=s@' => { |
620 | EXPAND => EXPAND_ALL, |
621 | ACTION => sub { |
622 | my ($state, $item, $file) = @_; |
623 | $file = $state->cfg . "/$file" |
624 | unless $file =~ /^[\.\/]|(?:\w:)/; |
625 | $config->file($file) } |
626 | } |
627 | ); |
628 | |
629 | # process main config file, then command line args |
630 | $config->file($file) if -f $file; |
631 | $config->args(); |
632 | |
633 | $config; |
634 | } |
635 | |
636 | |
637 | sub ANSI_escape { |
638 | my $attr = shift; |
639 | my $text = join('', @_); |
640 | return join("\n", |
641 | map { |
642 | # look for an existing escape start sequence and add new |
643 | # attribute to it, otherwise add escape start/end sequences |
644 | s/ \e \[ ([1-9][\d;]*) m/\e[$1;${attr}m/gx |
645 | ? $_ |
646 | : "\e[${attr}m" . $_ . "\e[0m"; |
647 | } |
648 | split(/\n/, $text, -1) # -1 prevents it from ignoring trailing fields |
649 | ); |
650 | } |
651 | |
652 | sub _red(@) { ANSI_escape(31, @_) } |
653 | sub _green(@) { ANSI_escape(32, @_) } |
654 | sub _yellow(@) { ANSI_escape(33, @_) } |
655 | sub _blue(@) { ANSI_escape(34, @_) } |
656 | sub _white(@) { @_ } # nullop |
657 | |
658 | |
659 | #------------------------------------------------------------------------ |
660 | # write_config($file) |
661 | # |
662 | # Writes a sample configuration file to the filename specified. |
663 | #------------------------------------------------------------------------ |
664 | |
665 | sub write_config { |
666 | my $file = shift; |
667 | |
668 | open(CONFIG, ">$file") || die "failed to create $file: $!\n"; |
669 | print(CONFIG <<END_OF_CONFIG); |
670 | #------------------------------------------------------------------------ |
671 | # sample .ttreerc file created automatically by $NAME version $VERSION |
672 | # |
673 | # This file originally written to $file |
674 | # |
675 | # For more information on the contents of this configuration file, see |
676 | # |
677 | # perldoc ttree |
678 | # ttree -h |
679 | # |
680 | #------------------------------------------------------------------------ |
681 | |
682 | # The most flexible way to use ttree is to create a separate directory |
683 | # for configuration files and simply use the .ttreerc to tell ttree where |
684 | # it is. |
685 | # |
686 | # cfg = /path/to/ttree/config/directory |
687 | |
688 | # print summary of what's going on |
689 | verbose |
690 | |
691 | # recurse into any sub-directories and process files |
692 | recurse |
693 | |
694 | # regexen of things that aren't templates and should be ignored |
695 | ignore = \\b(CVS|RCS)\\b |
696 | ignore = ^# |
697 | |
698 | # ditto for things that should be copied rather than processed. |
699 | copy = \\.png\$ |
700 | copy = \\.gif\$ |
701 | |
702 | # by default, everything not ignored or copied is accepted; add 'accept' |
703 | # lines if you want to filter further. e.g. |
704 | # |
705 | # accept = \\.html\$ |
706 | # accept = \\.tt2\$ |
707 | |
708 | # options to rewrite files suffixes (htm => html, tt2 => html) |
709 | # |
710 | # suffix htm=html |
711 | # suffix tt2=html |
712 | |
713 | # options to define dependencies between templates |
714 | # |
715 | # depend *=header,footer,menu |
716 | # depend index.html=mainpage,sidebar |
717 | # depend menu=menuitem,menubar |
718 | # |
719 | |
720 | #------------------------------------------------------------------------ |
721 | # The following options usually relate to a particular project so |
722 | # you'll probably want to put them in a separate configuration file |
723 | # in the directory specified by the 'cfg' option and then invoke tree |
724 | # using '-f' to tell it which configuration you want to use. |
725 | # However, there's nothing to stop you from adding default 'src', |
726 | # 'dest' or 'lib' options in the .ttreerc. The 'src' and 'dest' options |
727 | # can be re-defined in another configuration file, but be aware that 'lib' |
728 | # options accumulate so any 'lib' options defined in the .ttreerc will |
729 | # be applied every time you run ttree. |
730 | #------------------------------------------------------------------------ |
731 | # # directory containing source page templates |
732 | # src = /path/to/your/source/page/templates |
733 | # |
734 | # # directory where output files should be written |
735 | # dest = /path/to/your/html/output/directory |
736 | # |
737 | # # additional directories of library templates |
738 | # lib = /first/path/to/your/library/templates |
739 | # lib = /second/path/to/your/library/templates |
740 | |
741 | END_OF_CONFIG |
742 | |
743 | close(CONFIG); |
744 | print "$file created. Please edit accordingly and re-run $NAME\n"; |
745 | } |
746 | |
747 | |
748 | #------------------------------------------------------------------------ |
749 | # help() |
750 | # |
751 | # Prints help message and exits. |
752 | #------------------------------------------------------------------------ |
753 | |
754 | sub help { |
755 | print<<END_OF_HELP; |
756 | $NAME $VERSION (Template Toolkit version $Template::VERSION) |
757 | |
758 | usage: $NAME [options] [files] |
759 | |
760 | Options: |
761 | -a (--all) Process all files, regardless of modification |
762 | -r (--recurse) Recurse into sub-directories |
763 | -p (--preserve) Preserve file ownership and permission |
764 | -n (--nothing) Do nothing, just print summary (enables -v) |
765 | -v (--verbose) Verbose mode. Use twice for more verbosity: -v -v |
766 | -h (--help) This help |
767 | -s DIR (--src=DIR) Source directory |
768 | -d DIR (--dest=DIR) Destination directory |
769 | -c DIR (--cfg=DIR) Location of configuration files |
770 | -l DIR (--lib=DIR) Library directory (INCLUDE_PATH) (multiple) |
771 | -f FILE (--file=FILE) Read named configuration file (multiple) |
772 | |
773 | Display options: |
774 | --colour / --color Enable colo(u)rful verbose output. |
775 | --summary Show processing summary. |
776 | |
777 | File search specifications (all may appear multiple times): |
778 | --ignore=REGEX Ignore files matching REGEX |
779 | --copy=REGEX Copy files matching REGEX |
780 | --accept=REGEX Process only files matching REGEX |
781 | |
782 | File Dependencies Options: |
783 | --depend foo=bar,baz Specify that 'foo' depends on 'bar' and 'baz'. |
784 | --depend_file FILE Read file dependancies from FILE. |
785 | --depend_debug Enable debugging for dependencies |
786 | |
787 | File suffix rewriting (may appear multiple times) |
788 | --suffix old=new Change any '.old' suffix to '.new' |
789 | |
790 | File encoding options |
791 | --binmode=value Set binary mode of output files |
792 | --encoding=value Set encoding of input files |
793 | |
794 | Additional options to set Template Toolkit configuration items: |
795 | --define var=value Define template variable |
796 | --interpolate Interpolate '\$var' references in text |
797 | --anycase Accept directive keywords in any case. |
798 | --pre_chomp Chomp leading whitespace |
799 | --post_chomp Chomp trailing whitespace |
800 | --trim Trim blank lines around template blocks |
801 | --eval_perl Evaluate [% PERL %] ... [% END %] code blocks |
802 | --load_perl Load regular Perl modules via USE directive |
803 | --absolute Enable the ABSOLUTE option |
804 | --relative Enable the RELATIVE option |
805 | --pre_process=TEMPLATE Process TEMPLATE before each main template |
806 | --post_process=TEMPLATE Process TEMPLATE after each main template |
807 | --process=TEMPLATE Process TEMPLATE instead of main template |
808 | --wrapper=TEMPLATE Process TEMPLATE wrapper around main template |
809 | --default=TEMPLATE Use TEMPLATE as default |
810 | --error=TEMPLATE Use TEMPLATE to handle errors |
811 | --debug=STRING Set TT DEBUG option to STRING |
812 | --start_tag=STRING STRING defines start of directive tag |
813 | --end_tag=STRING STRING defined end of directive tag |
814 | --tag_style=STYLE Use pre-defined tag STYLE |
815 | --plugin_base=PACKAGE Base PACKAGE for plugins |
816 | --compile_ext=STRING File extension for compiled template files |
817 | --compile_dir=DIR Directory for compiled template files |
818 | --perl5lib=DIR Specify additional Perl library directories |
819 | --template_module=MODULE Specify alternate Template module |
820 | |
821 | See 'perldoc ttree' for further information. |
822 | |
823 | END_OF_HELP |
824 | |
825 | exit(0); |
826 | } |
827 | |
828 | __END__ |
829 | |
830 | |
831 | #------------------------------------------------------------------------ |
832 | # IMPORTANT NOTE |
833 | # This documentation is generated automatically from source |
834 | # templates. Any changes you make here may be lost. |
835 | # |
836 | # The 'docsrc' documentation source bundle is available for download |
837 | # from http://www.template-toolkit.org/docs.html and contains all |
838 | # the source templates, XML files, scripts, etc., from which the |
839 | # documentation for the Template Toolkit is built. |
840 | #------------------------------------------------------------------------ |
841 | |
842 | =head1 NAME |
843 | |
844 | Template::Tools::ttree - Process entire directory trees of templates |
845 | |
846 | =head1 SYNOPSIS |
847 | |
848 | ttree [options] [files] |
849 | |
850 | =head1 DESCRIPTION |
851 | |
852 | The F<ttree> script is used to process entire directory trees containing |
853 | template files. The resulting output from processing each file is then |
854 | written to a corresponding file in a destination directory. The script |
855 | compares the modification times of source and destination files (where |
856 | they already exist) and processes only those files that have been modified. |
857 | In other words, it is the equivalent of 'make' for the Template Toolkit. |
858 | |
859 | It supports a number of options which can be used to configure |
860 | behaviour, define locations and set Template Toolkit options. The |
861 | script first reads the F<.ttreerc> configuration file in the HOME |
862 | directory, or an alternative file specified in the TTREERC environment |
863 | variable. Then, it processes any command line arguments, including |
864 | any additional configuration files specified via the C<-f> (file) |
865 | option. |
866 | |
867 | =head2 The F<.ttreerc> Configuration File |
868 | |
869 | When you run F<ttree> for the first time it will ask you if you want |
870 | it to create a F<.ttreerc> file for you. This will be created in your |
871 | home directory. |
872 | |
873 | $ ttree |
874 | Do you want me to create a sample '.ttreerc' file for you? |
875 | (file: /home/abw/.ttreerc) [y/n]: y |
876 | /home/abw/.ttreerc created. Please edit accordingly and re-run ttree |
877 | |
878 | The purpose of this file is to set any I<global> configuration options |
879 | that you want applied I<every> time F<ttree> is run. For example, you |
880 | can use the C<ignore> and C<copy> option to provide regular expressions |
881 | that specify which files should be ignored and which should be copied |
882 | rather than being processed as templates. You may also want to set |
883 | flags like C<verbose> and C<recurse> according to your preference. |
884 | |
885 | A minimal F<.ttreerc>: |
886 | |
887 | # ignore these files |
888 | ignore = \b(CVS|RCS)\b |
889 | ignore = ^# |
890 | ignore = ~$ |
891 | |
892 | # copy these files |
893 | copy = \.(gif|png|jpg|pdf)$ |
894 | |
895 | # recurse into directories |
896 | recurse |
897 | |
898 | # provide info about what's going on |
899 | verbose |
900 | |
901 | In most cases, you'll want to create a different F<ttree> configuration |
902 | file for each project you're working on. The C<cfg> option allows you |
903 | to specify a directory where F<ttree> can find further configuration |
904 | files. |
905 | |
906 | cfg = /home/abw/.ttree |
907 | |
908 | The C<-f> command line option can be used to specify which configuration |
909 | file should be used. You can specify a filename using an absolute or |
910 | relative path: |
911 | |
912 | $ ttree -f /home/abw/web/example/etc/ttree.cfg |
913 | $ ttree -f ./etc/ttree.cfg |
914 | $ ttree -f ../etc/ttree.cfg |
915 | |
916 | If the configuration file does not begin with C</> or C<.> or something |
917 | that looks like a MS-DOS absolute path (e.g. C<C:\\etc\\ttree.cfg>) then |
918 | F<ttree> will look for it in the directory specified by the C<cfg> option. |
919 | |
920 | $ ttree -f test1 # /home/abw/.ttree/test1 |
921 | |
922 | The C<cfg> option can only be used in the F<.ttreerc> file. All the |
923 | other options can be used in the F<.ttreerc> or any other F<ttree> |
924 | configuration file. They can all also be specified as command line |
925 | options. |
926 | |
927 | Remember that F<.ttreerc> is always processed I<before> any |
928 | configuration file specified with the C<-f> option. Certain options |
929 | like C<lib> can be used any number of times and accumulate their values. |
930 | |
931 | For example, consider the following configuration files: |
932 | |
933 | F</home/abw/.ttreerc>: |
934 | |
935 | cfg = /home/abw/.ttree |
936 | lib = /usr/local/tt2/templates |
937 | |
938 | F</home/abw/.ttree/myconfig>: |
939 | |
940 | lib = /home/abw/web/example/templates/lib |
941 | |
942 | When F<ttree> is invoked as follows: |
943 | |
944 | $ ttree -f myconfig |
945 | |
946 | the C<lib> option will be set to the following directories: |
947 | |
948 | /usr/local/tt2/templates |
949 | /home/abw/web/example/templates/lib |
950 | |
951 | Any templates located under F</usr/local/tt2/templates> will be used |
952 | in preference to those located under |
953 | F</home/abw/web/example/templates/lib>. This may be what you want, |
954 | but then again, it might not. For this reason, it is good practice to |
955 | keep the F<.ttreerc> as simple as possible and use different |
956 | configuration files for each F<ttree> project. |
957 | |
958 | =head2 Directory Options |
959 | |
960 | The C<src> option is used to define the directory containing the |
961 | source templates to be processed. It can be provided as a command |
962 | line option or in a configuration file as shown here: |
963 | |
964 | src = /home/abw/web/example/templates/src |
965 | |
966 | Each template in this directory typically corresponds to a single |
967 | web page or other document. |
968 | |
969 | The C<dest> option is used to specify the destination directory for the |
970 | generated output. |
971 | |
972 | dest = /home/abw/web/example/html |
973 | |
974 | The C<lib> option is used to define one or more directories containing |
975 | additional library templates. These templates are not documents in |
976 | their own right and typically comprise of smaller, modular components |
977 | like headers, footers and menus that are incorporated into pages templates. |
978 | |
979 | lib = /home/abw/web/example/templates/lib |
980 | lib = /usr/local/tt2/templates |
981 | |
982 | The C<lib> option can be used repeatedly to add further directories to |
983 | the search path. |
984 | |
985 | A list of templates can be passed to F<ttree> as command line arguments. |
986 | |
987 | $ ttree foo.html bar.html |
988 | |
989 | It looks for these templates in the C<src> directory and processes them |
990 | through the Template Toolkit, using any additional template components |
991 | from the C<lib> directories. The generated output is then written to |
992 | the corresponding file in the C<dest> directory. |
993 | |
994 | If F<ttree> is invoked without explicitly specifying any templates |
995 | to be processed then it will process every file in the C<src> directory. |
996 | If the C<-r> (recurse) option is set then it will additionally iterate |
997 | down through sub-directories and process and other template files it finds |
998 | therein. |
999 | |
1000 | $ ttree -r |
1001 | |
1002 | If a template has been processed previously, F<ttree> will compare the |
1003 | modification times of the source and destination files. If the source |
1004 | template (or one it is dependant on) has not been modified more |
1005 | recently than the generated output file then F<ttree> will not process |
1006 | it. The F<-a> (all) option can be used to force F<ttree> to process |
1007 | all files regardless of modification time. |
1008 | |
1009 | $ tree -a |
1010 | |
1011 | Any templates explicitly named as command line argument are always |
1012 | processed and the modification time checking is bypassed. |
1013 | |
1014 | =head2 File Options |
1015 | |
1016 | The C<ignore>, C<copy> and C<accept> options are used to specify Perl |
1017 | regexen to filter file names. Files that match any of the C<ignore> |
1018 | options will not be processed. Remaining files that match any of the |
1019 | C<copy> regexen will be copied to the destination directory. Remaining |
1020 | files that then match any of the C<accept> criteria are then processed |
1021 | via the Template Toolkit. If no C<accept> parameter is specified then |
1022 | all files will be accepted for processing if not already copied or |
1023 | ignored. |
1024 | |
1025 | # ignore these files |
1026 | ignore = \b(CVS|RCS)\b |
1027 | ignore = ^# |
1028 | ignore = ~$ |
1029 | |
1030 | # copy these files |
1031 | copy = \.(gif|png|jpg|pdf)$ |
1032 | |
1033 | # accept only .tt2 templates |
1034 | accept = \.tt2$ |
1035 | |
1036 | The C<suffix> option is used to define mappings between the file |
1037 | extensions for source templates and the generated output files. The |
1038 | following example specifies that source templates with a C<.tt2> |
1039 | suffix should be output as C<.html> files: |
1040 | |
1041 | suffix tt2=html |
1042 | |
1043 | Or on the command line, |
1044 | |
1045 | --suffix tt2=html |
1046 | |
1047 | You can provide any number of different suffix mappings by repeating |
1048 | this option. |
1049 | |
1050 | The C<binmode> option is used to set the encoding of the output file. |
1051 | For example use C<--binmode=:utf8> to set the output format to unicode. |
1052 | |
1053 | =head2 Template Dependencies |
1054 | |
1055 | The C<depend> and C<depend_file> options allow you to specify |
1056 | how any given template file depends on another file or group of files. |
1057 | The C<depend> option is used to express a single dependency. |
1058 | |
1059 | $ ttree --depend foo=bar,baz |
1060 | |
1061 | This command line example shows the C<--depend> option being used to |
1062 | specify that the F<foo> file is dependant on the F<bar> and F<baz> |
1063 | templates. This option can be used many time on the command line: |
1064 | |
1065 | $ ttree --depend foo=bar,baz --depend crash=bang,wallop |
1066 | |
1067 | or in a configuration file: |
1068 | |
1069 | depend foo=bar,baz |
1070 | depend crash=bang,wallop |
1071 | |
1072 | The file appearing on the left of the C<=> is specified relative to |
1073 | the C<src> or C<lib> directories. The file(s) appearing on the right |
1074 | can be specified relative to any of these directories or as absolute |
1075 | file paths. |
1076 | |
1077 | For example: |
1078 | |
1079 | $ ttree --depend foo=bar,/tmp/baz |
1080 | |
1081 | To define a dependency that applies to all files, use C<*> on the |
1082 | left of the C<=>. |
1083 | |
1084 | $ ttree --depend *=header,footer |
1085 | |
1086 | or in a configuration file: |
1087 | |
1088 | depend *=header,footer |
1089 | |
1090 | Any templates that are defined in the C<pre_process>, C<post_process>, |
1091 | C<process> or C<wrapper> options will automatically be added to the |
1092 | list of global dependencies that apply to all templates. |
1093 | |
1094 | The C<depend_file> option can be used to specify a file that contains |
1095 | dependency information. |
1096 | |
1097 | $ ttree --depend_file=/home/abw/web/example/etc/ttree.dep |
1098 | |
1099 | Here is an example of a dependency file: |
1100 | |
1101 | # This is a comment. It is ignored. |
1102 | |
1103 | index.html: header footer menubar |
1104 | |
1105 | header: titlebar hotlinks |
1106 | |
1107 | menubar: menuitem |
1108 | |
1109 | # spanning multiple lines with the backslash |
1110 | another.html: header footer menubar \ |
1111 | sidebar searchform |
1112 | |
1113 | Lines beginning with the C<#> character are comments and are ignored. |
1114 | Blank lines are also ignored. All other lines should provide a |
1115 | filename followed by a colon and then a list of dependant files |
1116 | separated by whitespace, commas or both. Whitespace around the colon |
1117 | is also optional. Lines ending in the C<\> character are continued |
1118 | onto the following line. |
1119 | |
1120 | Files that contain spaces can be quoted. That is only necessary |
1121 | for files after the colon (':'). The file before the colon may be |
1122 | quoted if it contains a colon. |
1123 | |
1124 | As with the command line options, the C<*> character can be used |
1125 | as a wildcard to specify a dependency for all templates. |
1126 | |
1127 | * : config,header |
1128 | |
1129 | =head2 Template Toolkit Options |
1130 | |
1131 | F<ttree> also provides access to the usual range of Template Toolkit |
1132 | options. For example, the C<--pre_chomp> and C<--post_chomp> F<ttree> |
1133 | options correspond to the C<PRE_CHOMP> and C<POST_CHOMP> options. |
1134 | |
1135 | Run C<ttree -h> for a summary of the options available. |
1136 | |
1137 | =head1 AUTHORS |
1138 | |
1139 | Andy Wardley E<lt>abw@andywardley.comE<gt> |
1140 | |
1141 | L<http://www.andywardley.com/|http://www.andywardley.com/> |
1142 | |
1143 | With contributions from Dylan William Hardison (support for |
1144 | dependencies), Bryce Harrington (C<absolute> and C<relative> options), |
1145 | Mark Anderson (C<suffix> and C<debug> options), Harald Joerg and Leon |
1146 | Brocard who gets everywhere, it seems. |
1147 | |
1148 | =head1 VERSION |
1149 | |
1150 | 2.68, distributed as part of the |
1151 | Template Toolkit version 2.19, released on 27 April 2007. |
1152 | |
1153 | =head1 COPYRIGHT |
1154 | |
1155 | Copyright (C) 1996-2007 Andy Wardley. All Rights Reserved. |
1156 | |
1157 | |
1158 | This module is free software; you can redistribute it and/or |
1159 | modify it under the same terms as Perl itself. |
1160 | |
1161 | =head1 SEE ALSO |
1162 | |
1163 | L<tpage|Template::Tools::tpage> |
1164 | |