File Coverage

blib/lib/HTML/WebMake/Main.pm
Criterion Covered Total %
statement 99 1120 8.8
branch 6 396 1.5
condition 2 115 1.7
subroutine 28 122 22.9
pod 8 88 9.0
total 143 1841 7.7


line stmt bran cond sub pod time code
1             #
2              
3             =head1 NAME
4              
5             HTML::WebMake - a simple web site management system, allowing an entire site to
6             be created from a set of text and markup files and one WebMake file.
7              
8             =head1 SYNOPSIS
9              
10             my $f = new HTML::WebMake::Main ();
11             $f->readfile ($filename);
12             $f->make();
13             my $failures = $f->finish();
14             exit $failures;
15              
16             =head1 DESCRIPTION
17              
18             WebMake is a simple web site management system, allowing an entire site to be
19             created from a set of text and markup files and one WebMake file.
20              
21             It requires no dynamic scripting capabilities on the server; WebMake sites can
22             be deployed to a plain old FTP site without any problems.
23              
24             It allows the separation of responsibilities between the content editors, the
25             HTML page designers, and the site architect; only the site architect needs to
26             edit the WebMake file itself, or know perl or WebMake code.
27              
28             A multi-level website can be generated entirely from 1 or more WebMake files
29             containing content, links to content files, perl code (if needed), and output
30             instructions. Since the file-to-page mapping no longer applies, and since
31             elements of pages can be loaded from different files, this means that standard
32             file access permissions can be used to restrict editing by role.
33              
34             Since WebMake is written in perl, it is not limited to command-line invocation;
35             using the C module directly allows WebMake to be run from
36             other Perl scripts, or even mod_perl (WebMake uses C throughout,
37             and temporary globals are used only where strictly necessary).
38              
39             =head1 METHODS
40              
41             =over 4
42              
43             =cut
44              
45             package HTML::WebMake::Main;
46              
47              
48 1     1   13159 use Carp;
  1         3  
  1         93  
49 1     1   7 use File::Basename;
  1         2  
  1         115  
50 1     1   7 use File::Path;
  1         2  
  1         70  
51 1     1   6 use File::Spec;
  1         2  
  1         22  
52 1     1   6 use Cwd;
  1         2  
  1         71  
53 1     1   6 use strict;
  1         3  
  1         36  
54 1     1   983 use locale;
  1         300  
  1         7  
55 1     1   919 use POSIX qw(strftime);
  1         8204  
  1         8  
56              
57 1     1   1857 use HTML::WebMake;
  1         2  
  1         26  
58 1     1   572 use HTML::WebMake::Util;
  1         4  
  1         36  
59 1     1   609 use HTML::WebMake::File;
  1         5  
  1         44  
60 1     1   821 use HTML::WebMake::WmkFile;
  1         3  
  1         46  
61 1     1   9 use HTML::WebMake::Content;
  1         2  
  1         21  
62 1     1   783 use HTML::WebMake::NormalContent;
  1         5  
  1         32  
63 1     1   832 use HTML::WebMake::MetadataContent;
  1         4  
  1         31  
64 1     1   7 use HTML::WebMake::MediaContent;
  1         2  
  1         25  
65 1     1   6 use HTML::WebMake::Out;
  1         2  
  1         21  
66 1     1   587 use HTML::WebMake::SiteCache;
  1         5  
  1         46  
67 1     1   1855 use HTML::WebMake::SubstCtx;
  1         5  
  1         34  
68 1     1   1476 use HTML::WebMake::Metadata;
  1         4  
  1         39  
69 1     1   1923 use HTML::WebMake::PerlCode;
  1         4  
  1         33  
70 1     1   783 use HTML::WebMake::FormatConvert;
  1         5  
  1         34  
71 1     1   5 use HTML::WebMake::DataSource;
  1         2  
  1         18  
72 1     1   652 use HTML::WebMake::SiteMap;
  1         3  
  1         24  
73 1     1   505 use HTML::WebMake::UserTags;
  1         2  
  1         22  
74 1     1   481 use HTML::WebMake::WMLinkGlossary;
  1         2  
  1         46  
75              
76 1         18425 use vars qw{
77             @ISA $VERSION
78             $VERBOSE $DEBUG $DEFAULT_CLEAN_FEATURES $HTML_LOGGING
79             $SUBST_EVAL $SUBST_DEP_IGNORE $SUBST_META
80 1     1   5 };
  1         1  
81              
82             @ISA = qw();
83              
84             $VERSION = $HTML::WebMake::VERSION;
85 0     0 0 0 sub Version { $VERSION; }
86              
87             ###########################################################################
88              
89             $DEFAULT_CLEAN_FEATURES = "pack addimgsizes cleanattrs indent ".
90             "addxmlslashes fixcolors fixhrefs";
91              
92             $SUBST_EVAL = '(!E)';
93             $SUBST_DEP_IGNORE = '(!D)';
94             $SUBST_META = '(!M)';
95              
96             ###########################################################################
97              
98             =item $f = new HTML::WebMake::Main
99              
100             Constructs a new C object. You may pass the following
101             attribute-value pairs to the constructor.
102              
103             =over 4
104              
105             =item force_output
106              
107             Force output. Normally if a file is already up to date, it is not modified.
108             This will force the file to be re-made.
109              
110             =item force_cache_rebuild
111              
112             Force the cached metadata and dependency data for the site to be rebuilt.
113             Normally this is used to speed up partial rebuilds of the site. This
114             option implies C.
115              
116             =item risky_fast_rebuild
117              
118             Run more quickly, but take more risks. Normally, dynamic content, such as Perl
119             sections, sitemaps, or navigation links, are always considered to be in need of
120             rebuilding, as mapping their dependencies is often very difficult or
121             impossible. This switch forces them to be ignored for dependency-tracking
122             purposes, and so an output file that depends on them will not be rebuilt unless
123             a normal content item on that page changes.
124              
125             =item base_href
126              
127             Rewrite links to be absolute URLs based at this URL. By default, links are
128             specified as relative wherever possible.
129              
130             =item base_dir
131              
132             Generate output, and look for support files (images etc.), relative to this
133             directory.
134              
135             =item paranoid
136              
137             Paranoid mode; do not allow perl code evaluation or accesses to directories
138             above the WebMake file.
139              
140             =item debug
141              
142             Debug mode; more output.
143              
144             =back
145              
146             =cut
147              
148             sub new {
149 0     0 1 0 my $class = shift;
150 0   0     0 $class = ref($class) || $class;
151              
152 0         0 my $self = shift;
153 0   0     0 $self->{paranoid} ||= 0;
154 0   0     0 $self->{debug} ||= 0;
155 0 0       0 if (!defined $self->{verbose}) { $self->{verbose} = 1; }
  0         0  
156 0   0     0 $self->{base_href} ||= "";
157 0   0     0 $self->{base_dir} ||= "";
158 0   0     0 $self->{risky_fast_rebuild} ||= 0;
159 0   0     0 $self->{force_output} ||= 0;
160 0   0     0 $self->{force_cache_rebuild} ||= 0;
161              
162 0 0       0 if ($self->{force_cache_rebuild}) { $self->{force_output} = 1; }
  0         0  
163              
164 0         0 $self->{files} = { };
165 0         0 $self->{file_modtimes} = { };
166              
167 0         0 $self->{source_files} = [ ];
168              
169 0         0 $self->{outs} = { };
170 0         0 $self->{out_order} = [ ];
171              
172 0         0 $self->{contents} = { };
173 0         0 $self->{content_order} = [ ];
174              
175 0         0 $self->{metadatas} = { };
176 0         0 $self->{this_metas_added} = [ ];
177              
178 0         0 $self->{locations} = { };
179 0         0 $self->{location_order} = [ ];
180              
181             # increase the size of these hashes in anticipation of big filesets
182 0         0 keys %{$self->{outs}} = 300;
  0         0  
183 0         0 keys %{$self->{locations}} = 300;
  0         0  
184 0         0 keys %{$self->{metadatas}} = 500;
  0         0  
185 0         0 keys %{$self->{contents}} = 500;
  0         0  
186              
187 0         0 $self->{imgsizes} = { };
188 0         0 $self->{options} = { };
189              
190 0         0 $self->{failures} = 0;
191              
192 0         0 $self->{cache} = undef;
193 0         0 $self->{cachefname} = undef;
194 0         0 $self->{tmpdir} = undef;
195              
196 0         0 $self->{et_glossary} = undef;
197              
198 0         0 $self->{perl_interp} = undef;
199 0         0 $self->{htmlcleaner} = undef;
200 0         0 $self->{mapper} = undef;
201 0         0 $self->{usertags} = undef;
202 0         0 $self->{util} = new HTML::WebMake::Util();
203 0         0 $self->{have_image_size_module} = undef;
204              
205 0         0 $self->{subst_stack} = [ ];
206 0         0 $self->{current_subst} = undef;
207 0         0 $self->{dep_datasources} = { };
208 0         0 $self->{current_webmake_fname} = undef;
209              
210 0         0 bless ($self, $class);
211              
212 0         0 $DEBUG = $self->{debug};
213 0         0 $VERBOSE = $self->{verbose};
214 0         0 $HTML_LOGGING = $self->{html_logging};
215              
216 0         0 $self->{perl_lib_dir} = $self->find_perl_lib_dir();
217 0         0 $self->init_for_making();
218              
219 0         0 $self;
220             }
221              
222             sub init_for_making {
223 0     0 0 0 my ($self) = @_;
224              
225 0 0       0 if ($^O !~ /(win|os2|mac)/i) {
226             # which genius decided the mere sniff of getpwuid() should cause
227             # a crash on win32? Cheers mate.
228 0         0 eval ' $self->{current_user} = getpwuid ($>); ';
229             } else {
230 0         0 $self->{current_user} = "unknown";
231             }
232              
233 0         0 $self->{format_conv} = new HTML::WebMake::FormatConvert ($self);
234 0         0 $self->{metadata} = new HTML::WebMake::Metadata ($self);
235              
236 0         0 $self->{ignore_for_dependencies} =
237             new HTML::WebMake::File ($self, $SUBST_DEP_IGNORE);
238 0         0 $self->{meta_ignore_for_dependencies} =
239             new HTML::WebMake::File ($self, $SUBST_META);
240              
241             # define some builtin magic content items now.
242 0         0 $self->set_unmapped_content ("WebMake.GeneratorString",
243             "WebMake/$VERSION");
244 0         0 $self->set_unmapped_content ("WebMake.Version", $VERSION);
245 0         0 $self->set_unmapped_content ("WebMake.Who", $self->{current_user});
246             # others in get_deferred_builtin_content() method below.
247             # these are a little more computationally intensive.
248              
249 0         0 $self->{now} = time();
250 0         0 $self->{current_tick} = 0;
251             }
252              
253             # -------------------------------------------------------------------------
254              
255             sub sed_fname {
256 0     0 0 0 my ($self, $fname) = @_;
257              
258             # Interpolated variables:
259             # ~ = $HOME
260             # %f = .wmk file name, non-alphanums replaced with _
261             # %F = .wmk full path, non-alphanums replaced with _
262             # %l = perl lib dir for plugins
263             # %u = username
264             #
265 0         0 my $home = $ENV{'HOME'};
266 0   0     0 $home ||= '/';
267              
268 0         0 $fname =~ s/\~/${home}/g;
269 0         0 $fname =~ s/%u/$self->{current_user}/g;
270              
271 0         0 $fname =~ s{%f}{
272 0         0 my $val = basename ($self->{current_webmake_fname});
273 0         0 $val =~ s/\.wmk$//i; $val =~ s,[^A-Za-z0-9],_,g;
  0         0  
274 0         0 $val;
275             }ge;
276              
277 0         0 $fname =~ s{%F}{
278 0         0 my $val = $self->{current_webmake_fname};
279 0 0       0 if (!File::Spec->file_name_is_absolute ($val)) {
280 0         0 $val = File::Spec->catfile (getcwd, $val);
281             }
282 0         0 $val =~ s/\.wmk$//i; $val =~ s,[^A-Za-z0-9],_,g; $val =~ s/^_+//;
  0         0  
  0         0  
283 0         0 $val;
284             }ge;
285              
286 0         0 $fname =~ s{%l}{$self->{perl_lib_dir}}g;
287              
288 0 0       0 if ($^O eq 'MacOS') {
289 0         0 $fname =~ s/\//:/g;
290             }
291              
292 0         0 $fname;
293             }
294              
295             # -------------------------------------------------------------------------
296              
297             sub opencache {
298 0     0 0 0 my ($self) = @_;
299              
300 0         0 my $fname = $self->{cachefname};
301             # default: a file called {webmakefname}/cache.db in a
302             # .webmake subdirectory of the user's home. Each user needs
303             # their own cache file for privacy and security reasons, BTW.
304 0   0     0 $fname ||= "~/.webmake/%F/cache.db";
305 0         0 $fname = $self->sed_fname ($fname);
306              
307 0         0 dbg ("using site cache: $fname");
308 0         0 my $dir = dirname ($fname);
309 0 0       0 (-d $dir) or mkpath ($dir);
310              
311 0 0       0 if ($self->{force_cache_rebuild}) {
312             # if -F, always recreate the cache
313 0         0 unlink ($fname);
314             }
315              
316 0         0 $self->{cache} = new HTML::WebMake::SiteCache ($self, $fname);
317 0         0 $self->{cache}->tie();
318             }
319              
320             # -------------------------------------------------------------------------
321              
322             sub tmpdir {
323 0     0 0 0 my ($self) = @_;
324              
325 0 0       0 if (defined $self->{seddedtmpdir}) {
326 0         0 return $self->{seddedtmpdir};
327             }
328              
329 0         0 my $fname = $self->{tmpdir};
330 0   0     0 $fname ||= "~/.webmake";
331 0         0 $fname = $self->sed_fname ($fname);
332 0         0 dbg ("using temp dir: $fname");
333 0 0       0 (-d $fname) or mkpath ($fname);
334 0         0 $self->{seddedtmpdir} = $fname;
335             }
336              
337             # -------------------------------------------------------------------------
338              
339             sub cachedir {
340 0     0 0 0 my ($self) = @_;
341              
342 0 0       0 if (defined $self->{seddedcachedir}) {
343 0         0 return $self->{seddedcachedir};
344             }
345              
346 0         0 my $fname = $self->{cachedir};
347 0   0     0 $fname ||= "~/.webmake/%F";
348 0         0 $fname = $self->sed_fname ($fname);
349 0         0 dbg ("using cache dir: $fname");
350 0 0       0 (-d $fname) or mkpath ($fname);
351 0         0 $self->{seddedcachedir} = $fname;
352             }
353              
354             # -------------------------------------------------------------------------
355              
356             sub getcache {
357 0     0 0 0 my ($self) = @_;
358 0 0       0 if (defined $self->{cache}) {
359 0         0 return $self->{cache};
360             }
361              
362 0         0 $self->opencache();
363 0         0 return $self->{cache};
364             }
365              
366             # -------------------------------------------------------------------------
367              
368             sub getglossary {
369 0     0 0 0 my ($self) = @_;
370 0 0       0 if (defined $self->{et_glossary}) {
371 0         0 return $self->{et_glossary};
372             }
373              
374 0         0 $self->{et_glossary} =
375             new HTML::WebMake::WMLinkGlossary ($self, $self->getcache());
376 0         0 return $self->{et_glossary};
377             }
378              
379             # -------------------------------------------------------------------------
380              
381             sub setcachefile {
382 0     0 0 0 my ($self, $fname) = @_;
383 0         0 $self->{cachedir} = $fname;
384 0         0 $self->{cachefname} = $fname."/cache.db";
385             }
386              
387             # -------------------------------------------------------------------------
388              
389             =item $f->set_option ($optname, $optval);
390              
391             Set a WebMake option. Currently supported options are:
392              
393             =over 4
394              
395             =back
396              
397             =cut
398              
399             sub set_option {
400 0     0 1 0 my ($self, $optname, $optval) = @_;
401 0         0 $self->{options}->{$optname} = $optval;
402             }
403              
404             # -------------------------------------------------------------------------
405              
406             =item $f->readfile ($filename)
407              
408             Read and parse the given WebMake file.
409              
410             =cut
411              
412             sub readfile {
413 0     0 1 0 my ($self, $fname, $opts) = @_;
414 0         0 local ($_);
415              
416 0         0 $self->{current_webmake_fname} = $fname;
417              
418 0 0       0 open (IN, "<$fname") or croak "cannot open WebMake file $fname";
419 0         0 $_ = join ('', );
420 0         0 my @s = stat IN;
421 0         0 $self->set_file_modtime ($fname, $s[9]);
422 0         0 close IN;
423              
424 0         0 my $wmkf = new HTML::WebMake::WmkFile ($self, $fname);
425              
426 0 0 0     0 if (defined $opts && $opts->{parse_for_cgi}) {
427 0         0 $wmkf->{parse_for_cgi} = 1;
428             }
429              
430 0         0 $wmkf->parse ($_);
431 0         0 $self->{files}->{$fname} = $wmkf;
432              
433 0         0 $self->add_source_files ($fname);
434 0         0 1;
435             }
436              
437             # -------------------------------------------------------------------------
438              
439             =item $f->readstring ($string)
440              
441             Read and parse the given WebMake configuration (as a string).
442              
443             =cut
444              
445             sub readstring {
446 0     0 1 0 my ($self, $str, $opts) = @_;
447 0         0 local ($_);
448              
449 0         0 my $fname = '(readstring)';
450 0         0 $self->{current_webmake_fname} = $fname;
451 0         0 $self->set_file_modtime ($fname, $self->{now});
452              
453 0         0 my $wmkf = new HTML::WebMake::WmkFile ($self, $fname);
454 0         0 $wmkf->parse ($str);
455 0         0 $self->{files}->{$fname} = $wmkf;
456 0         0 1;
457             }
458              
459             # -------------------------------------------------------------------------
460              
461             # Internal API, used by HTML::WebMake::CGI modules. This parses the
462             # .wmk file (quickly) and generates a list of the editable items therein.
463             #
464             sub cgi_parse_file {
465 0     0 0 0 my ($self, $fname, $opts) = @_;
466              
467 0 0       0 if ($self->readfile ($fname, { 'parse_for_cgi' => 1 })) {
468 0         0 return $self->{files}->{$fname}->{cgi};
469             } else {
470 0         0 return undef;
471             }
472             }
473              
474             # -------------------------------------------------------------------------
475              
476             sub getmapper {
477 0     0 0 0 my ($self) = @_;
478 0 0       0 if (defined $self->{mapper}) {
479 0         0 return $self->{mapper};
480             }
481              
482 0         0 $self->{mapper} = new HTML::WebMake::SiteMap ($self);
483 0         0 return $self->{mapper};
484             }
485              
486             # -------------------------------------------------------------------------
487              
488             sub getusertags {
489 0     0 0 0 my ($self) = @_;
490 0 0       0 if (defined $self->{usertags}) {
491 0         0 return $self->{usertags};
492             }
493              
494 0         0 $self->{usertags} = new HTML::WebMake::UserTags ($self);
495 0         0 return $self->{usertags};
496             }
497              
498             # -------------------------------------------------------------------------
499              
500             sub add_out {
501 0     0 0 0 my ($self, $file, $wmkf, $name, $attrs, $text) = @_;
502              
503             # here's the trick: create a content item for the text, then the out
504             # itself is just a reference to that. This makes sitemapping much easier.
505 0         0 my $contname = "OUT:".$name;
506 0         0 my $contattrs = {
507             'name' => $contname,
508             'map' => 'false'
509             };
510 0 0       0 if (defined $attrs->{'format'}) {
511 0         0 $contattrs->{'format'} = $attrs->{'format'};
512             }
513 0         0 $self->add_content ($contname, $wmkf, $contattrs, $text);
514              
515 0         0 push (@{$self->{out_order}}, $file);
  0         0  
516 0         0 $self->{outs}->{$file} = new HTML::WebMake::Out ($wmkf, $name, $attrs);
517             }
518              
519             # -------------------------------------------------------------------------
520              
521             sub set_metadata_content_item ($$$$$) {
522 0     0 0 0 my ($self, $name, $file, $attrs, $text) = @_;
523              
524 0 0       0 if (!defined $self->{metadatas}->{$name}) {
525 0         0 push (@{$self->{content_order}}, $name);
  0         0  
526             }
527 0         0 my $cont = new HTML::WebMake::MetadataContent ($name,
528             $file, $attrs, $text);
529 0         0 $cont->set_declared (scalar @{$self->{content_order}});
  0         0  
530 0         0 $self->{metadatas}->{$name} = $cont;
531 0         0 $cont;
532             }
533              
534             # -------------------------------------------------------------------------
535              
536             sub add_new_content_to_map {
537 0     0 0 0 my ($self, $name, $cont) = @_;
538              
539 0 0       0 if (!defined $self->{contents}->{$name}) {
540 0         0 push (@{$self->{content_order}}, $name);
  0         0  
541             }
542 0         0 $cont->set_declared (scalar @{$self->{content_order}});
  0         0  
543 0         0 $self->{contents}->{$name} = $cont;
544             }
545              
546             sub add_content ($$$$$) {
547 0     0 0 0 my ($self, $name, $file, $attrs, $text) = @_;
548 0         0 dbg2 ("adding content \"$name\"");
549              
550 0         0 return new HTML::WebMake::NormalContent ($name,
551             $file, $attrs, $text, undef);
552             }
553              
554             sub add_content_defer_opening ($$$$$) {
555 0     0 0 0 my ($self, $name, $file, $attrs, $datasource) = @_;
556 0         0 dbg ("adding content \"$name\" (deferred opening)");
557              
558 0         0 return new HTML::WebMake::NormalContent ($name,
559             $file, $attrs, undef, $datasource);
560             }
561              
562             # -------------------------------------------------------------------------
563              
564             sub set_unmapped_content ($$$) {
565 0     0 0 0 my ($self, $key, $val) = @_;
566 0         0 dbg2 ("set \"$key\" (unmapped)");
567              
568 0         0 return new HTML::WebMake::NormalContent ($key,
569             $self->{ignore_for_dependencies},
570             {
571             'format' => 'text/html',
572             'map' => 'false',
573             'up' => $HTML::WebMake::SiteMap::ROOTNAME,
574             },
575             $val, undef);
576             }
577              
578             # -------------------------------------------------------------------------
579              
580             sub set_transient_content ($$$) {
581 0     0 0 0 my ($self, $key, $val) = @_;
582 0         0 dbg2 ("set \"$key\" (transient)");
583              
584 0         0 return new HTML::WebMake::NormalContent ($key,
585             $self->{ignore_for_dependencies},
586             {
587             'format' => 'text/html',
588             'map' => 'false',
589             'up' => $HTML::WebMake::SiteMap::ROOTNAME,
590             },
591             $val, undef);
592             }
593              
594             # -------------------------------------------------------------------------
595              
596             sub set_mapped_content ($$$$) {
597 0     0 0 0 my ($self, $key, $val, $upname) = @_;
598 0         0 dbg2 ("set \"$key\" (up = \"$upname\")");
599              
600 0         0 return new HTML::WebMake::NormalContent ($key,
601             $self->{ignore_for_dependencies},
602             {
603             'format' => 'text/html',
604             'map' => 'true',
605             'up' => $upname,
606             },
607             $val, undef);
608             }
609              
610             # -------------------------------------------------------------------------
611              
612             # convert some metadata into a content item, ie. set it in
613             # the contents hash. Return the value of the meta (subst'ed).
614             #
615             sub metadata_to_content {
616 0     0 0 0 my ($self, $from, $key, $val, $basecont) = @_;
617              
618 0 0       0 if (!defined $basecont) { croak "No base content defined"; }
  0         0  
619 0         0 my $base = $basecont->{name};
620 0         0 my $wmkf = new HTML::WebMake::File ($self, $basecont->get_filename());
621              
622 0         0 dbg2 ("created metadata content \$\[$key\]: base content=\"$base\"");
623 0         0 my $attrs = {
624             'map' => 'false',
625             'up' => $base,
626             };
627 0         0 $self->set_metadata_content_item ($key, $wmkf, $attrs, $val);
628 0         0 return $self->_curly_subst ($from, $key, 0);
629             }
630              
631             sub add_metadata {
632 0     0 0 0 my ($self, $from, $key, $val, $attrs, $setthisdotmetas) = @_;
633              
634 0         0 my $thiskey = "this.".$key;
635 0         0 my $fullkey = $from.".".$key;
636              
637 0 0       0 if ($setthisdotmetas) {
638 0         0 dbg2 ("set metadata $key == \"$thiskey\", \"$fullkey\"");
639             } else {
640 0         0 dbg2 ("set metadata $key == \"$fullkey\"");
641             }
642              
643 0         0 my $cont = $self->{contents}->{$from};
644 0         0 my $wmkf;
645 0 0       0 if (!defined $cont) {
646             # the metadata was set from an block.
647 0         0 $wmkf = $self->{meta_ignore_for_dependencies};
648             } else {
649 0         0 $wmkf = new HTML::WebMake::File ($self,
650             $cont->get_filename());
651             }
652              
653 0         0 $attrs->{up} = $from;
654              
655 0 0       0 if ($setthisdotmetas) {
656 0         0 $self->set_metadata_content_item ($thiskey, $wmkf, $attrs, $val);
657 0         0 push (@{$self->{this_metas_added}}, $thiskey);
  0         0  
658             }
659              
660 0         0 $self->set_metadata_content_item ($fullkey, $wmkf, $attrs, $val);
661 0         0 $self->getcache()->put_metadata ($fullkey, $val);
662             }
663              
664             # -------------------------------------------------------------------------
665              
666             sub del_content {
667 0     0 0 0 my ($self, $name) = @_;
668 0         0 dbg2 ("deleting content \"$name\"");
669 0         0 delete $self->{contents}->{$name};
670 0         0 delete $self->{metadatas}->{$name};
671             }
672              
673             sub get_content_obj {
674 0     0 0 0 my ($self, $name) = @_;
675              
676 0         0 my $obj = $self->{contents}->{$name};
677 0 0       0 if (!defined $obj) { $obj = $self->{metadatas}->{$name}; }
  0         0  
678 0         0 $obj;
679             }
680              
681             sub get_all_content_names {
682 0     0 0 0 my ($self) = @_;
683              
684             # garbage-collect the list in case del_content() has been called.
685             # now seems as good a time as any to do this...
686 0         0 my @list = ();
687 0         0 my %already_seen = ();
688              
689 0         0 foreach my $name (@{$self->{content_order}}) {
  0         0  
690 0 0 0     0 next unless (defined $self->{contents}->{$name} ||
691             defined $self->{metadatas}->{$name});
692              
693 0 0       0 next if defined $already_seen{$name};
694 0         0 $already_seen{$name} = 1;
695              
696 0         0 push (@list, $name);
697             }
698              
699 0         0 @{$self->{content_order}} = @list;
  0         0  
700 0         0 @list;
701             }
702              
703             # -------------------------------------------------------------------------
704             # garbage-collect the contents list periodically, unloading the content
705             # text for items that have not been used recently.
706              
707             sub gc_contents {
708 0     0 0 0 my ($self) = @_;
709              
710 0 0       0 my @conts = grep {
711 0         0 $_->is_from_datasource() && defined ($_->{last_used})
712 0         0 } values %{$self->{contents}};
713              
714             # halve the amount of dynamically-loadable content text loaded
715 0         0 my $shrinkby = $#conts / 2;
716 0         0 my $i = 0;
717 0         0 foreach my $cobj (sort { $a->{last_used} <=> $b->{last_used} } @conts)
  0         0  
718             {
719 0 0       0 last if ($i++ > $shrinkby);
720 0         0 $cobj->unload_text();
721             }
722             }
723              
724             # -------------------------------------------------------------------------
725              
726             sub add_url {
727 0     0 0 0 my ($self, $name, $location) = @_;
728 0         0 dbg2 ("adding URL \"$name\" = $location");
729 0 0       0 if (!defined $self->{locations}->{$name}) {
730 0         0 push (@{$self->{location_order}}, $name);
  0         0  
731             }
732 0         0 $self->{locations}->{$name} = $location;
733             }
734              
735             sub del_url {
736 0     0 0 0 my ($self, $name) = @_;
737 0         0 dbg2 ("deleting URL \"$name\"");
738 0         0 delete $self->{locations}->{$name};
739             }
740              
741             sub get_all_url_names {
742 0     0 0 0 my ($self) = @_;
743              
744             # garbage-collect the list in case del_url() has been called
745 0         0 my @list = ();
746 0         0 foreach my $name (@{$self->{location_order}}) {
  0         0  
747 0 0       0 next unless defined $self->{locations}->{$name};
748 0         0 push (@list, $name);
749             }
750 0         0 @{$self->{location_order}} = @list;
  0         0  
751 0         0 @list;
752             }
753              
754             # -------------------------------------------------------------------------
755              
756             sub add_sitemap {
757 0     0 0 0 my ($self, $name, $root, $file, $attrs, $text) = @_;
758              
759 0         0 my $fn;
760 0 0 0     0 if (defined $attrs->{all} && $self->{util}->parse_boolean ($attrs->{all}))
761             {
762 0         0 $fn = 'make_contentmap';
763             } else {
764 0         0 $fn = 'make_sitemap';
765             }
766              
767 0 0       0 if (defined $root) { $root = 'q{'.$root.'}'; }
  0         0  
768 0         0 else { $root = 'undef'; }
769              
770             # use a perl code call to generate the sitemap. cool eh?
771 0         0 $text .= '<{perl $self->'.$fn.' ('.$root.', q{'.$name.'}); }>';
772              
773 0         0 $attrs->{is_sitemap} = 1;
774 0         0 $self->add_content ($name, $file, $attrs, $text);
775             }
776              
777             # -------------------------------------------------------------------------
778              
779             sub add_navlinks {
780 0     0 0 0 my ($self, $name, $map, $file, $attrs, $text) = @_;
781              
782             # evaluate the map so the next, prev etc. links will work
783             # from now on. Tell the sitemapper to generate the link
784             # metadata on this run.
785 0         0 $self->getmapper()->{set_navlinks} = 1;
786 0         0 $self->curly_subst ($HTML::WebMake::Main::SUBST_EVAL, $map);
787 0         0 $self->getmapper()->{set_navlinks} = 0;
788              
789              
790 0         0 $attrs->{nav_up} = $attrs->{up};
791 0         0 $attrs->{nav_next} = $attrs->{next};
792 0         0 $attrs->{nav_prev} = $attrs->{prev};
793 0         0 $attrs->{nav_no_up} = $attrs->{noup};
794 0         0 $attrs->{nav_no_next} = $attrs->{nonext};
795 0         0 $attrs->{nav_no_prev} = $attrs->{noprev};
796              
797 0         0 $attrs->{is_navlinks} = 1;
798 0         0 delete ($attrs->{up});
799              
800 0         0 $self->add_content ($name, $file, $attrs, $text);
801             }
802              
803             # -------------------------------------------------------------------------
804              
805             sub add_breadcrumbs {
806 0     0 0 0 my ($self, $name, $map, $file, $attrs, $text) = @_;
807              
808             # load the map so the "up" links will be present
809 0         0 $self->curly_subst ($HTML::WebMake::Main::SUBST_EVAL, $map);
810              
811 0         0 $attrs->{is_breadcrumbs} = 1;
812 0         0 $attrs->{breadcrumb_level_name} = $attrs->{level};
813              
814 0         0 $attrs->{breadcrumb_top_name} = $attrs->{top};
815 0   0     0 $attrs->{breadcrumb_top_name} ||= $attrs->{level};
816              
817 0         0 $attrs->{breadcrumb_tail_name} = $attrs->{tail};
818 0   0     0 $attrs->{breadcrumb_tail_name} ||= $attrs->{level};
819              
820 0         0 delete $attrs->{level}; # now effectively renamed
821 0         0 delete $attrs->{top};
822 0         0 delete $attrs->{tail};
823              
824 0         0 $self->add_content ($name, $file, $attrs, "");
825             }
826              
827             # -------------------------------------------------------------------------
828              
829             sub subst {
830 0     0 0 0 my ($self, $from, $str, $evaluatingtags) = @_;
831              
832 0         0 my $current_subst = $self->{current_subst};
833 0 0       0 if (!defined $str) { return undef; }
  0         0  
834 0 0       0 if (!defined $from) { croak "No from defined in subst"; }
  0         0  
835 0 0       0 if (!defined $current_subst) {
836 0         0 croak "cannot subst outside _subst_open and _subst_close";
837             }
838              
839 0 0       0 if ($current_subst->{level} > 30) {
840 0         0 $self->infinite_subst_loop_error ($from, $$str);
841 0         0 return "";
842             }
843              
844             {
845 0         0 $current_subst->{level}++;
  0         0  
846 0         0 $self->eval_code_at_ref ($from, $str);
847              
848 0 0       0 if ($evaluatingtags) {
849 0         0 $self->getusertags()->subst_tags ($from, $str);
850             }
851              
852             # profiling optimisation, quicker to check for one char than do
853             # all the matches and subs below
854 0 0       0 goto done_substs if ($$str !~ /\$[\{\(\[]/s);
855              
856 0 0       0 if ($$str =~ /\$\{IMGSIZE\}/is) {
857             # magic tag:
858 0         0 $$str =~ s/]*?)\s*\$\{IMGSIZE\}\s*([^>]*?)\s*>/
859 0         0 $self->add_image_size ($from, $1, $2);
860             /gies;
861             }
862              
863             # references to content chunks: ${content}
864 0         0 $$str =~ s/\$\{([^\<\{\}]+)\}/ $self->_curly_subst ($from, $1, 1) /ges;
  0         0  
865             #}
866              
867             # references to out URLs: $(foo)
868 0         0 $$str =~ s/\$\(([^\<\(\)]+)\)/ $self->_round_subst ($from, $1); /ges;
  0         0  
869              
870             # references to metadata: $[this.foo] used within the chunk they're
871             # defined in.
872 0         0 $$str =~ s/\$\[this(\.[^\[\]]+)\]/ $self->_this_subst ($from, $1); /gies;
  0         0  
873              
874 0         0 done_substs:
875              
876             $current_subst->{level}--;
877             }
878              
879 0 0       0 if ($current_subst->{inf_loop}) { $$str = ""; }
  0         0  
880             }
881              
882             sub subst_deferred_refs {
883 0     0 0 0 my ($self, $from, $str) = @_;
884              
885 0 0       0 if (!defined $from) { croak "No from defined in subst"; }
  0         0  
886 0         0 my $tries = 0;
887              
888 0 0       0 if ($$str !~ /(?:\$|\<\{)/) { return; } #}
  0         0  
889              
890 0   0     0 do {
      0        
      0        
891 0 0       0 if ($tries++ > 20) {
892 0         0 $self->infinite_subst_loop_error ($from, $$str); return;
  0         0  
893             }
894              
895             # deferred refs to content chunks: $[content]
896 0         0 $$str =~ s/\$\[([^\[\]]+)\]/ $self->_curly_subst ($from, $1, 0); /ges;
  0         0  
897              
898             # do a subst in case the deferred ref contained normal refs
899 0         0 $self->subst ($from, $str);
900              
901             } while ($$str =~ /\$\{.*?\}/ ||
902             $$str =~ /\$\[.*?\]/ ||
903             $$str =~ /\$\(.*?\)/ ||
904             $$str =~ /<\{.*?\>\}/);
905             }
906              
907             # -------------------------------------------------------------------------
908              
909             sub infinite_subst_loop_error {
910 0     0 0 0 my ($self, $from, $str) = @_;
911              
912 0         0 $self->{current_subst}->{inf_loop} = 1;
913              
914             # try to trim it down to the troublesome bit if possible;
915             # include a bit of context to make its position clear
916 0         0 my $err = $str; $err =~ s/\s+/ /gs;
  0         0  
917 0 0       0 if (length $err > 60) {
918 0         0 $err =~ s/^.{6,}?(.{0,16}\$\{.*?\}.{0,16}).*?$/\[...\]$1\[...\]/gs;
919 0         0 $err =~ s/^.{6,}?(.{0,16}\$\[.*?\].{0,16}).*?$/\[...\]$1\[...\]/gs;
920 0         0 $err =~ s/^.{6,}?(.{0,16}\$\(.*?\).{0,16}).*?$/\[...\]$1\[...\]/gs;
921 0         0 $err =~ s/^.{6,}?(.{0,16}\<\{.*?\>\}.{0,16}).*?$/\[...\]$1\[...\]/gs;
922             # $err =~ s/^.{6,}?(.{0,16}(?:\$[\{\(\[]|\<\{).{0,16}).*?$/\[...\]$1\[...\]/gs;
923             }
924              
925 0         0 my $msg;
926 0 0       0 if ($str =~ /\$\[\]/) {
    0          
    0          
927 0         0 $msg = "empty deferred reference \$[]";
928             } elsif ($str =~ /\$\{\}/) {
929 0         0 $msg = "empty content reference \${}";
930             } elsif ($str =~ /\$\(/) {
931 0         0 $msg = "failed to parse URL reference";
932             } else {
933 0         0 $msg = "failed to parse content reference";
934             }
935              
936 0         0 $self->fail ($msg." in \"$from\"!\nOffending code: \"$err\"");
937             }
938              
939             # -------------------------------------------------------------------------
940              
941             sub fileless_subst {
942 0     0 0 0 my ($self, $from, $txt) = @_;
943 0         0 $self->_subst_open(undef, undef, undef, "text/html", 0); #{
944 0         0 $self->subst($from, \$txt);
945 0         0 $self->strip_metadata ($from, \$txt);
946 0         0 $self->_subst_close(); #}
947 0         0 $txt;
948             }
949              
950             sub curly_subst {
951 0     0 0 0 my ($self, $from, $txt) = @_;
952 0         0 $self->_subst_open (undef, undef, undef, "text/html", undef); #{
953 0         0 $txt = $self->_curly_subst ($from, $txt, 1);
954             # then do a normal subst to handle <{set}>, metadata, etc.
955 0         0 $self->subst ($from, \$txt);
956 0         0 $self->strip_metadata ($from, \$txt);
957 0         0 $self->_subst_close(); #}
958 0         0 $txt;
959             }
960              
961             sub curly_meta_subst {
962 0     0 0 0 my ($self, $from, $txt) = @_;
963 0         0 $self->_subst_open (undef, undef, undef, "text/html", undef); #{
964 0         0 $txt = $self->_curly_subst ($from, $txt, 0);
965 0         0 $self->_subst_close(); #}
966 0         0 $txt;
967             }
968              
969             sub curly_or_meta_subst {
970 0     0 0 0 my ($self, $from, $txt) = @_;
971 0         0 $self->_subst_open (undef, undef, undef, "text/html", undef); #{
972 0         0 $txt = $self->_curly_subst ($from, $txt, 2);
973             # then do a normal subst to handle <{set}>, metadata, etc.
974 0         0 $self->subst ($from, \$txt);
975 0         0 $self->strip_metadata ($from, \$txt);
976 0         0 $self->_subst_close(); #}
977 0         0 $txt;
978             }
979              
980             sub quiet_curly_meta_subst {
981 0     0 0 0 my ($self, $from, $txt) = @_;
982 0         0 $self->_subst_open(undef, undef, undef, "text/html", undef); #{
983 0         0 $self->{current_subst}->{quiet} = 1;
984 0         0 $txt = $self->_curly_subst($from, $txt, 0);
985 0         0 $self->{current_subst}->{quiet} = 0;
986 0         0 $self->_subst_close(); #}
987 0         0 $txt;
988             }
989              
990             sub round_subst {
991 0     0 0 0 my ($self, $from, $txt) = @_;
992 0         0 $self->_subst_open(undef, undef, undef, "text/html", undef); #{
993 0         0 $txt = $self->_round_subst($from, $txt);
994 0         0 $self->_subst_close(); #}
995 0         0 $txt;
996             }
997              
998             sub _this_subst {
999 0     0   0 my ($self, $from, $origkey) = @_;
1000              
1001             # trim off the default value from our working copy of the key
1002 0         0 my $key = $origkey;
1003 0         0 $key =~ s/\?([^\?]+)$//;
1004              
1005             # see if the current content chunk has this.$key defined
1006 0         0 my $thiskey = $from.$key;
1007 0         0 my $thiscont = $self->{metadatas}->{$thiskey};
1008              
1009 0 0       0 if (defined $thiscont) {
1010 0         0 my $meta = $self->_curly_subst ($from, $thiskey, 0);
1011 0         0 $meta; # it does? use it now
1012             } else {
1013 0         0 "\$\[this$origkey\]"; # nope, leave it for later
1014             }
1015             }
1016              
1017             # -------------------------------------------------------------------------
1018              
1019             sub _subst_open {
1020 0     0   0 my ($self, $filename, $outname, $dotdots, $fmt, $useurls) = @_;
1021              
1022 0         0 my $current_subst = $self->{current_subst};
1023 0 0       0 if (defined $current_subst) {
1024 0         0 push (@{$self->{subst_stack}}, $current_subst);
  0         0  
1025              
1026             # inherit the dotdots and filename from the previous subst, if
1027             # there is one.
1028 0 0       0 if (!defined $dotdots) {
1029 0         0 $dotdots = $current_subst->{dotdots};
1030             }
1031 0 0       0 if (!defined $filename) {
1032 0         0 $filename = $current_subst->{filename};
1033             }
1034 0 0       0 if (!defined $outname) {
1035 0         0 $outname = $current_subst->{outname};
1036             }
1037 0 0       0 if (!defined $useurls) {
1038 0         0 $useurls = $current_subst->{useurls};
1039             }
1040             }
1041              
1042             # if (!defined $dotdots) { $dotdots = ""; }
1043 0 0       0 if (!defined $filename) { $filename = $SUBST_EVAL; }
  0         0  
1044 0 0       0 if (!defined $outname) { $outname = $SUBST_EVAL; }
  0         0  
1045 0 0       0 if (!defined $useurls) { $useurls = 1; }
  0         0  
1046              
1047 0         0 $self->{current_subst} = new HTML::WebMake::SubstCtx
1048             ($self, $filename, $outname, $dotdots, $fmt, $useurls);
1049             }
1050              
1051             sub _subst_close {
1052 0     0   0 my ($self) = @_;
1053              
1054 0         0 $self->{current_subst} = pop (@{$self->{subst_stack}});
  0         0  
1055             }
1056              
1057             # -------------------------------------------------------------------------
1058              
1059             sub _curly_subst {
1060 0     0   0 my ($self, $from, $key, $contents_only) = @_;
1061             # if (!defined $from) { croak "No from defined in subst"; }
1062             # if (!defined $key) { croak "No key defined in subst"; }
1063              
1064             # warn "JMD CURLY $key";
1065              
1066 0         0 my $str;
1067 0         0 my $current_subst = $self->{current_subst};
1068 0 0       0 if ($current_subst->{inf_loop}) { $str = ""; goto ret; }
  0         0  
  0         0  
1069              
1070             # default values: ${foo?Untitled}
1071 0         0 my $defval = undef;
1072 0 0       0 if ($key =~ s/\?([^\?]*)$//) { $defval = $1; }
  0         0  
1073              
1074             # support ${templateName: parameter="foo"}
1075 0 0       0 if ($key =~ s/: (.*)$//) {
1076 0         0 my $attrs = $self->{util}->parse_xml_tag_attributes
1077             ("\${$key}", $1, $from, qw());
1078              
1079 0         0 foreach my $var (keys %{$attrs}) {
  0         0  
1080 0         0 $self->_eval_set ($from, $var, $attrs->{$var});
1081             }
1082             }
1083              
1084 0         0 my $cont;
1085              
1086 0 0       0 if ($contents_only) { # expanding a ${foo} ref
1087 0         0 $cont = $self->{contents}->{$key};
1088 0 0       0 if (!defined $cont) {
1089 0         0 $cont = $self->{metadatas}->{$key};
1090             }
1091              
1092             } else { # expanding a $[foo] ref
1093 0         0 $cont = $self->{metadatas}->{$key};
1094              
1095             # it's also possible to refer to content items using the metadata reference
1096             # type $[..], as in fact that reference type simply means a reference whose
1097             # loading is deferred until other references have been expanded. In
1098             # addition, navlinks and breadcrumbs do this too. To support this, check
1099             # the contents hash as well as the metadata one, if there's no hit in the
1100             # metadata hash.
1101 0 0       0 if (!defined $cont) {
1102 0         0 $cont = $self->{contents}->{$key};
1103             }
1104             }
1105              
1106 0 0       0 if (defined $cont) {
1107 0         0 $self->add_content_dependency ($cont);
1108              
1109 0 0       0 if ($contents_only == 1) {
1110 0 0       0 if ($cont->is_only_usable_from_deferred_refs()) {
1111 0         0 $self->fail ("content \$\{$key\} should only be used ".
1112             "as \$\[$key\] in \"$from\".");
1113             }
1114             }
1115              
1116 0 0       0 if ($current_subst->{useurls}) {
1117 0         0 $cont->add_ref_from_url ($current_subst->{filename});
1118             }
1119              
1120 0         0 my $fmt = $current_subst->{format};
1121 0         0 $str = $cont->get_text_as($fmt);
1122 0 0       0 if (!defined $str) {
1123 0         0 $self->fail ("unable to get text in format \"$fmt\" for ".
1124             "content \${$key} in \"$from\".");
1125 0         0 $str = ""; goto ret;
  0         0  
1126             }
1127 0         0 $self->subst ($key, \$str);
1128 0         0 goto ret;
1129             }
1130              
1131             # then, webmake magic vars
1132 0         0 $str = $self->get_deferred_builtin_content ($from, $key);
1133 0 0       0 if (defined $str) { goto ret; }
  0         0  
1134              
1135             # finally, metadata that hasn't been used yet as a content item
1136             # (quite expensive to look up)
1137 0         0 my $meta = $self->subst_metadata ($from, $key, $defval);
1138 0 0       0 if (defined $meta) { $str = $meta; goto ret; }
  0         0  
  0         0  
1139              
1140             # agh, I give up
1141 0 0       0 if (defined $defval) {
1142 0         0 $str = $defval;
1143             } else {
1144 0         0 vrb ("no value defined for content \${$key} in \"$from\".");
1145             }
1146              
1147 0         0 ret:
1148             $str;
1149             }
1150              
1151             # -------------------------------------------------------------------------
1152              
1153             sub _round_subst {
1154 0     0   0 my ($self, $from, $key) = @_;
1155              
1156             # warn "JMD ROUND $key";
1157              
1158 0 0       0 if (!defined $from) { croak "No from defined in subst"; }
  0         0  
1159 0 0       0 if ($self->{current_subst}->{inf_loop}) { return ""; }
  0         0  
1160              
1161 0         0 my $defval = undef;
1162 0 0       0 if ($key =~ s/\?([^\?]+)$//) { $defval = $1; }
  0         0  
1163              
1164 0         0 my $str;
1165 0 0       0 if ($key eq 'TOP/') { $str = ''; }
  0         0  
1166            
1167 0 0       0 if (!defined $str) {
1168 0 0       0 if ($key =~ /\$/) {
1169             # the key contains a content ref, either ${normal} or $[deferred].
1170             # subst for both of them.
1171 0         0 $self->subst ($from, \$key);
1172 0         0 $self->subst_deferred_refs ($from, \$key);
1173             }
1174              
1175 0         0 $str = $self->{locations}->{$key};
1176             }
1177              
1178 0 0 0     0 if ((!defined $str || $str eq '') && $key ne 'TOP/') {
      0        
1179 0 0       0 if (defined $defval) { return $defval; }
  0         0  
1180 0         0 vrb ("no value defined for output URL \$($key) in \"$from\".");
1181 0         0 return "";
1182             }
1183              
1184 0         0 $self->add_url_dependency ($key);
1185              
1186             # make it a valid relative URL
1187 0 0 0     0 if ($str !~ /^\// && $str !~ /^[-_a-zA-Z0-9]:/) {
1188 0 0       0 if (!defined $self->{current_subst}->{dotdots}) {
1189             # carp "oops? need to defer URL ref here: \"$str\"";
1190             } else {
1191 0         0 $str = $self->{current_subst}->{dotdots} . $str;
1192             }
1193             }
1194              
1195 0 0       0 if ($self->{base_href} ne '') {
1196 0         0 $str = $self->{base_href}.'/'.$str;
1197             }
1198              
1199             # trim out foo/bar/../../
1200 0 0       0 if ($str =~ m,/$,) {
1201 0         0 $str = HTML::WebMake::Main::canon_path ($str).'/';
1202             } else {
1203 0         0 $str = HTML::WebMake::Main::canon_path ($str);
1204             }
1205 0         0 $str =~ s,\\,/,gs; # urls always have / instead of \
1206 0         0 $str;
1207             }
1208              
1209             # -------------------------------------------------------------------------
1210              
1211             sub subst_metadata {
1212 0     0 0 0 my ($self, $from, $key, $defval) = @_;
1213              
1214             # out files cannot have metadata
1215 0 0       0 return "" if ($key =~ /^OUT:/);
1216              
1217             # metadata must have the format "blah.type"
1218 0 0       0 return "" unless ($key =~ /^(.*)\.([^\.]+?)$/);
1219 0         0 my ($base, $subkey) = ($1, $2);
1220              
1221 0 0       0 if ($from eq $base) { goto failed_to_find; }
  0         0  
1222              
1223             # see if it's a magic metadatum
1224 0         0 my $magicmeta = $self->get_magic_metadata ($from, $key, $base, $subkey);
1225 0 0       0 if (defined $magicmeta) { return $magicmeta; }
  0         0  
1226              
1227             # if it's an external (ie. not on "this") metadatum, try to
1228             # (a) get it from cache or (b) load the content to get it
1229 0 0       0 if ($base ne 'this') {
1230 0         0 my $meta;
1231 0         0 my $cont = $self->{contents}->{$base};
1232 0 0       0 goto failed_to_find if (!defined $cont);
1233              
1234             # just check the cache, if the datasource location has not
1235             # been modified.
1236 0 0 0     0 if ($self->check_content_dep ($cont->get_filename(),
1237             $self->{current_subst}->{filename}, undef)
1238             && !$self->{force_output})
1239             {
1240 0         0 $meta = $self->getcache()->get_metadata ($key);
1241 0 0       0 if (defined $meta) {
1242 0         0 return $self->metadata_to_content ($from, $key, $meta, $cont);
1243             }
1244 0         0 goto use_default_or_blank;
1245             }
1246              
1247             # if the content is generated, it can't have metadata
1248 0 0       0 if ($cont->is_generated_content()) {
1249 0         0 goto use_default_or_blank;
1250             }
1251              
1252             # load the content it may be defined in; that may cause it
1253             # to be loaded.
1254 0         0 $cont->load_metadata ($base, $key);
1255 0         0 $self->add_content_dependency ($cont);
1256              
1257 0         0 $meta = $self->getcache()->get_metadata ($key);
1258 0 0       0 if (defined $meta) {
1259 0         0 return $self->metadata_to_content ($from, $key, $meta, $cont);
1260             }
1261             }
1262              
1263             failed_to_find:
1264 0         0 $defval = $self->use_default_metadata($subkey, $defval);
1265 0 0       0 if (defined $defval) { return $defval; }
  0         0  
1266              
1267 0 0       0 if (!$self->{current_subst}->{quiet}) {
1268 0         0 vrb ("no value defined for metadata or content \$[$key] in \"$from\".");
1269             }
1270 0         0 return "";
1271              
1272 0         0 use_default_or_blank:
1273             $defval = $self->use_default_metadata($subkey, $defval);
1274 0 0       0 if (defined $defval) { return $defval; }
  0         0  
1275 0         0 return "";
1276             }
1277              
1278             sub use_default_metadata {
1279 0     0 0 0 my ($self, $subkey, $defval) = @_;
1280              
1281 0 0       0 if (!defined $defval) {
1282             # handle metadata that has generic builtin defaults
1283 0         0 $defval = $self->{metadata}->get_default_value ($subkey);
1284             }
1285              
1286 0         0 return $defval;
1287             }
1288              
1289             # -------------------------------------------------------------------------
1290              
1291             sub get_magic_metadata {
1292 0     0 0 0 my ($self, $from, $key, $base, $metaname) = @_;
1293              
1294 0         0 my $cont = $self->get_content_obj ($base);
1295 0 0       0 if (!defined $cont) { return undef; }
  0         0  
1296              
1297 0         0 my $val = $cont->get_magic_metadata ($from, $metaname);
1298 0 0       0 if (!defined $val) { return undef; }
  0         0  
1299              
1300             # write it to the cache so later invocations, that don't read or
1301             # parse the metadata-tagged file, will be able to use the value
1302 0         0 $self->getcache()->put_metadata ($key, $val);
1303 0         0 return $val;
1304             }
1305              
1306             # -------------------------------------------------------------------------
1307              
1308             sub get_deferred_builtin_content {
1309 0     0 0 0 my ($self, $from, $key) = @_;
1310              
1311 0 0       0 if ($key eq "WebMake.Time") {
1312 0         0 return strftime "%a %b %e %H:%M:%S %Y", localtime();
1313             }
1314 0 0       0 if ($key eq "WebMake.OutFile") {
1315 0         0 return $self->{current_subst}->{filename};
1316             }
1317 0 0       0 if ($key eq "WebMake.OutName") {
1318 0         0 return $self->{current_subst}->{outname};
1319             }
1320 0 0       0 if ($key eq "WebMake.PerlLib") {
1321 0         0 return $self->{perl_lib_dir};
1322             }
1323 0 0       0 if ($key eq "WebMake.SourceFiles") {
1324 0         0 return join (' ', $self->source_file_list());
1325             }
1326 0 0       0 if ($key eq "WebMake.GeneratedFiles") {
1327 0         0 return join (' ', $self->generated_file_list());
1328             }
1329 0         0 undef;
1330             }
1331              
1332             # -------------------------------------------------------------------------
1333              
1334             sub source_file_list {
1335 0     0 0 0 my ($self) = @_;
1336 0         0 return @{$self->{source_files}};
  0         0  
1337             }
1338              
1339             sub generated_file_list {
1340 0     0 0 0 my ($self) = @_;
1341 0         0 return sort keys %{$self->{outs}};
  0         0  
1342             }
1343              
1344             sub add_source_files {
1345 0     0 0 0 my $self = shift;
1346 0         0 push (@{$self->{source_files}}, @_);
  0         0  
1347             }
1348              
1349             # -------------------------------------------------------------------------
1350              
1351             sub find_perl_lib_dir {
1352 0     0 0 0 my ($self) = @_;
1353              
1354 0         0 my $append;
1355 0 0       0 if ($^O eq 'MacOS') {
1356 0         0 $append = ":HTML:WebMake:PerlLib";
1357             } else {
1358 0         0 $append = "/HTML/WebMake/PerlLib";
1359             }
1360              
1361 0         0 foreach my $dir (@INC) {
1362 0 0       0 if (-d $dir.$append) { return $dir.$append; }
  0         0  
1363             }
1364              
1365 0         0 $self->fail ("cannot find \$\{WebMake.PerlLib\} directory");
1366 0         0 return "";
1367             }
1368              
1369             # -------------------------------------------------------------------------
1370              
1371             # evaluate perl code from the WebMake file. We support perlpreproc and
1372             # perlpostdecl as tag names for backwards compat.
1373             # perlprint uses stdout from the code block.
1374             sub eval_code_at_parse {
1375 0     0 0 0 my ($self, $str) = @_;
1376              
1377 0 0       0 if ($$str !~ /\<\{/s) #}
1378             {
1379 0         0 return undef;
1380             }
1381              
1382 0         0 $self->{last_perl_code_text} = undef;
1383 0         0 $$str =~ s/^\s*\<\{(perlpreproc|perlpostdecl|perlout|perl)\s+(.+?)\s*\}\>/
1384 0         0 $self->_p_interpret($1, $2, '');
1385             /gies;
1386 0         0 $self->{last_perl_code_text};
1387             }
1388              
1389             # evaluate perl code at reference time.
1390             sub eval_code_at_ref {
1391 0     0 0 0 my ($self, $from, $str) = @_;
1392              
1393 0 0       0 if ($$str !~ /\<\{/s) #}
1394             {
1395 0         0 return undef;
1396             }
1397              
1398 0         0 $self->{last_perl_code_text} = undef;
1399 0         0 $$str =~ s/\<\{set\s*name\s*=\s*(.+?)\s+value\s*=\s*(.+?)\s*\}\>/
1400 0         0 $self->_eval_set ($from, $1, $2);
1401             /gies;
1402              
1403 0         0 $$str =~ s/\<\{set\s*(.+?)\s*=\s*\"(.+?)\"\s*\}\>/
1404 0         0 $self->_eval_set ($from, $1, $2);
1405             /gies;
1406              
1407 0         0 $$str =~ s/\<\{set\s*(.+?)\s*=\s*(.+?)\s*\}\>/
1408 0         0 $self->_eval_set ($from, $1, $2);
1409             /gies;
1410              
1411 0         0 $$str =~ s/\<\{(perlout|perl)\s*(.+?)\s*\}\>/
1412 0         0 $self->_p_interpret($1, $2, '');
1413             /gies;
1414 0         0 $self->{last_perl_code_text};
1415             }
1416              
1417             sub _eval_set {
1418 0     0   0 my ($self, $from, $name, $val) = @_;
1419 0         0 $name =~ s/^\"(.*)\"$/$1/g; # trim quotes
1420 0         0 $name =~ s/^\'(.*)\'$/$1/g;
1421 0         0 $val =~ s/^\"(.*)\"$/$1/g;
1422 0         0 $val =~ s/^\'(.*)\'$/$1/g;
1423 0         0 $self->set_unmapped_content ($name, $val);
1424 0         0 "";
1425             }
1426              
1427             # -------------------------------------------------------------------------
1428              
1429             # strip wayward metadata.
1430             sub strip_metadata ($$$) {
1431 0     0 0 0 my ($self, $from, $str) = @_;
1432              
1433 0 0       0 if (!defined $$str) { return; }
  0         0  
1434 0 0       0 if ($$str !~ /
  0         0  
1435              
1436 0         0 my $util = $self->{util};
1437 0         0 $$str = $util->strip_tags ($$str, "wmmeta",
1438             $self, \&tag_strip_wmmeta, qw(name));
1439             }
1440              
1441 0     0 0 0 sub tag_strip_wmmeta { ""; }
1442              
1443             # -------------------------------------------------------------------------
1444              
1445             sub _p_interpret ($$$$) {
1446 0     0   0 my ($self, $type, $txt, $defunderscoreval) = @_;
1447              
1448 0         0 $self->{last_perl_code_text} = '<{'.$type.' '.$txt.'}>';
1449 0         0 $self->getperlinterp()->interpret ($type, $txt, $defunderscoreval);
1450             }
1451              
1452             # -------------------------------------------------------------------------
1453              
1454             sub getperlinterp {
1455 0     0 0 0 my ($self) = @_;
1456 0 0       0 if (defined $self->{perlinterp}) {
1457 0         0 return $self->{perlinterp};
1458             }
1459              
1460 0         0 $self->{perlinterp} = new HTML::WebMake::PerlCode ($self);
1461 0         0 return $self->{perlinterp};
1462             }
1463              
1464             # -------------------------------------------------------------------------
1465              
1466             sub add_image_size {
1467 0     0 0 0 my ($self, $from, $before, $after) = @_;
1468 0         0 my $origdir = undef;
1469              
1470 0 0       0 if (!defined $from) { croak "No from defined in subst"; }
  0         0  
1471 0 0       0 if ($self->{current_subst}->{inf_loop}) { return ""; }
  0         0  
1472              
1473 0         0 my $attrtext = $before." ".$after;
1474 0         0 $attrtext =~ s/\s*\/\s*$//g; # trim /> tag ending
1475              
1476 0 0       0 if (!defined $self->{have_image_size_module}) {
1477 0 0       0 if (eval 'require Image::Size;') {
1478 0         0 $self->{have_image_size_module} = 1;
1479             } else {
1480 0         0 vrb ("\${IMGSIZE} tag: cannot load Image::Size module, not supported");
1481 0         0 $self->{have_image_size_module} = 0;
1482             }
1483             }
1484              
1485 0         0 my $attrs = $self->{util}->parse_xml_tag_attributes
1486             ("img", $attrtext, "\${IMGSIZE}", qw{src});
1487              
1488 0 0 0     0 if (!$self->{have_image_size_module} || !defined $attrs) {
1489 0         0 goto failed;
1490             }
1491              
1492 0         0 my $fname = $attrs->{src};
1493 0         0 $self->subst ($from, \$fname);
1494              
1495 0 0       0 if ($fname =~ /^!!/) { # magic string indicating CGI use
1496 0         0 goto failed;
1497             }
1498              
1499 0 0       0 if ($self->{base_dir} ne '') {
1500 0         0 $fname = File::Spec->catfile ($self->{base_dir}, $fname);
1501             }
1502              
1503             # check the caches
1504 0         0 my $sizestr = $self->{imgsizes}->{$fname};
1505 0 0       0 if (defined $sizestr) {
1506 0         0 return '';
1507             }
1508              
1509 0         0 $sizestr = $self->getcache()->get_metadata ($fname.".sizevalues");
1510 0 0       0 if (defined $sizestr) {
1511 0         0 return '';
1512             }
1513              
1514 0         0 my $origfname = $fname;
1515 0         0 my ($realfname, $relfname) = $self->expand_relative_filename ($fname);
1516              
1517 0 0       0 if (!defined ($realfname)) {
1518 0         0 warn "\${IMGSIZE}: cannot find image file \"$origfname\" in \"$from\"\n";
1519 0         0 goto failed;
1520             }
1521              
1522 0 0       0 if (!-r $realfname) {
1523 0         0 warn "\${IMGSIZE}: cannot read image file \"$realfname\" in \"$from\"\n";
1524 0         0 goto failed;
1525             }
1526              
1527 0         0 $sizestr = '';
1528 0 0       0 if (!eval '
1529             use Image::Size qw(html_imgsize);
1530             $sizestr = html_imgsize($realfname);
1531             1;')
1532             {
1533 0         0 warn "\${IMGSIZE}: Image::Size failed: $! in \"$from\"\n";
1534 0         0 goto failed;
1535             }
1536              
1537             # write it to the caches
1538 0         0 $self->getcache()->put_metadata ($fname.".sizevalues", $sizestr);
1539 0         0 $self->{imgsizes}->{$fname} = $sizestr;
1540              
1541             # chdir $origdir;
1542 0   0     0 $attrtext ||= '';
1543 0   0     0 $sizestr ||= '';
1544 0         0 return '';
1545              
1546 0         0 failed:
1547             # if (defined $origdir) { chdir $origdir; }
1548             return "";
1549             }
1550              
1551             # -------------------------------------------------------------------------
1552              
1553             sub erfcatdir {
1554 0 0   0 0 0 return $_[1] if (File::Spec->file_name_is_absolute ($_[1]));
1555 0 0       0 return $_[1] if ($_[0] eq '');
1556 0         0 return File::Spec->catdir ($_[0], $_[1]);
1557             }
1558              
1559             sub erfcatfile {
1560 0 0   0 0 0 return $_[1] if (File::Spec->file_name_is_absolute ($_[1]));
1561 0 0       0 return $_[1] if ($_[0] eq '');
1562 0         0 return File::Spec->catfile ($_[0], $_[1]);
1563             }
1564              
1565             sub canon_path {
1566 9     9 0 13348 my ($fname, $reldir) = @_;
1567             # return $fname if ($fname =~ /^\//); # absolute path
1568              
1569 9         54 $fname = File::Spec->canonpath ($fname);
1570 9         66 1 while $fname =~ s,/\./,/,g;
1571 9         28 1 while $fname =~ s,^\./,,g;
1572              
1573 9 100 66     75 if (defined($reldir) && $reldir ne '') {
1574             # next, try trimming "../../d1/d2/foo" down to "foo" for links
1575             # in the "d1/d2" directory. tricky! I should really have gone
1576             # for previous code that does this.
1577              
1578 8         15 my $dotdots = '../';
1579 8         97 $dotdots .= '../' while ($reldir =~ m,[\/\\],g);
1580 8         13 $dotdots .= $reldir; # "../../d1/d2"
1581              
1582 8         21 my $rhs = '';
1583 8         24 while ($dotdots ne '') {
1584 19 100       361 last if ($fname =~ s,^\Q${dotdots}\E[\/\\],${rhs},);
1585 14 100       91 last unless ($dotdots =~ s,[\/\\]([^\/\\]+)$,,);
1586 11         36 $rhs .= '../';
1587             }
1588             }
1589              
1590             # and now trim off useless dir navigation like "foo/bar/../../baz"
1591             # down to "baz".
1592              
1593             # first, deal with "foo/bar/../../[whatever]"
1594 9         48 1 while $fname =~ s,^[^/][^\./]*?/+\.\./,,g;
1595              
1596             # then "[whatever]/foo/bar/../../[whatever]"
1597 9         58 1 while $fname =~ s,/[^/][^\./]*?/+\.\./,/,g;
1598              
1599             # then "[whatever]/foo/bar/../.."
1600 9         28 1 while $fname =~ s,/[^/][^\./]*?/+\.\.$,,g;
1601              
1602             # and finally tidy up bonus slashes
1603 9         21 $fname =~ s,//+,/,gs;
1604              
1605 9         34 return $fname;
1606             }
1607              
1608             sub expand_relative_filename {
1609 0     0 0   my ($self, $fname) = @_;
1610              
1611 0 0         if (File::Spec->file_name_is_absolute ($fname)) {
1612 0           return ($fname, $fname);
1613             }
1614              
1615 0           my $curdir = File::Spec->curdir();
1616 0           my $topdir;
1617 0 0         if (defined $self->{current_subst}->{filename}) {
1618 0 0         if ($self->{base_dir} ne '') {
1619 0           $topdir = $self->{base_dir};
1620             } else {
1621 0           $topdir = $curdir;
1622             }
1623              
1624 0           my $dotdots = $self->{current_subst}->{dotdots};
1625 0           my $outdir = dirname ($self->{current_subst}->{filename});
1626              
1627 0           my @searchpath = ($curdir, $outdir);
1628 0 0         if (defined $self->{options}->{FileSearchPath}) {
1629 0           push (@searchpath, split (/:/, $self->{options}->{FileSearchPath}));
1630             }
1631              
1632 0           my @relsearchpath = map { erfcatdir($dotdots, $_) } @searchpath;
  0            
1633              
1634 0           foreach my $dir (@searchpath) {
1635 0           my $reldir = shift @relsearchpath;
1636 0           my $realfname = erfcatfile ($topdir, erfcatfile ($dir, $fname));
1637 0           my $relfname = erfcatfile ($topdir, erfcatfile ($reldir, $fname));
1638              
1639             # canonicalise the path BEFORE checking for its existence. This
1640             # is necessary, because a file path that contains "data/foo/../../blah"
1641             # will fail if "data/foo" dirs do not exist, but will pass if
1642             # it's canon'ed down to just "blah".
1643             #
1644             # warn "JMD searching $reldir $realfname $relfname";
1645 0           $realfname = canon_path ($realfname, $outdir);
1646 0           $relfname = canon_path ($relfname, $outdir);
1647             # warn "JMD post-searching $realfname $realfname";
1648              
1649 0 0         if (-e $realfname) {
1650 0           return ($realfname, $relfname);
1651             }
1652             }
1653              
1654             } else {
1655 0           warn "oops? don't know my current filename for expand_relative_filename";
1656             }
1657              
1658 0           return undef;
1659             }
1660              
1661             # -------------------------------------------------------------------------
1662              
1663             sub add_content_dependency {
1664 0     0 0   my ($self, $cont) = @_;
1665              
1666 0           my $fname = $cont->get_filename();
1667              
1668 0 0         if ($fname eq $SUBST_EVAL) {
    0          
    0          
1669 0 0         if ($self->{risky_fast_rebuild}) {
1670 0           dbg2 ("dependency: ". $cont->{name}.": [perl code, ignored]");
1671             } else {
1672 0           dbg2 ("dependency: ". $cont->{name}.": [perl code, always refreshed]");
1673 0           $self->{cont_dependencies}->{$fname} = 1;
1674             }
1675 0           return;
1676             }
1677             elsif ($fname eq $SUBST_DEP_IGNORE) {
1678             # dbg2 ("dependency: ". $cont->{name}.": [ignored as a dependency]");
1679 0           return;
1680             }
1681             elsif ($fname eq $SUBST_META) {
1682             # dbg2 ("dependency: ". $cont->{name}.": [metadata, not tracked]");
1683 0           return;
1684             }
1685              
1686 0           foreach my $fname ($cont->get_deps()) {
1687 0 0         if (!defined $self->{file_modtimes}->{$fname}) {
1688 0           die "$fname has no modtime recorded for dependencies";
1689             }
1690 0 0         if ($fname =~ m{\Q$self->{perl_lib_dir}\E}o) {
1691             # dbg2 ("dependency: ". $cont->{name}.": [perl lib, not tracked]");
1692 0           return;
1693             }
1694 0           dbg2 ("dependency: ". $cont->{name}.": $fname");
1695 0           $self->{cont_dependencies}->{$fname} = $self->{file_modtimes}->{$fname};
1696             }
1697             }
1698              
1699             sub add_url_dependency {
1700 0     0 0   my ($self, $url) = @_;
1701            
1702             # TODO: deal with URL dependencies
1703             }
1704              
1705             sub clear_dependencies {
1706 0     0 0   my ($self, $url) = @_;
1707              
1708 0           $self->{cont_dependencies} = { };
1709             }
1710              
1711             # -------------------------------------------------------------------------
1712              
1713             sub set_file_modtime {
1714 0     0 0   my ($self, $file, $mod) = @_;
1715 0           $self->{file_modtimes}->{$file} = $mod;
1716             }
1717              
1718             sub cached_get_modtime {
1719 0     0 0   my ($self, $file) = @_;
1720              
1721 0           my $nowmod = $self->{file_modtimes}->{$file};
1722 0 0         if (defined ($nowmod)) { return $nowmod; }
  0            
1723              
1724 0           my @s = stat $file; $self->set_file_modtime ($file, $s[9]);
  0            
1725 0           $s[9];
1726             }
1727              
1728             # similar to the above, but it can handle and
1729             # datasources too.
1730             sub cached_get_location_modtime {
1731 0     0 0   my ($self, $file) = @_;
1732 0 0         if ($file =~ /^([a-zA-Z0-9]+):/) {
1733 0           my $proto = $1;
1734 0 0         if (!defined $self->{dep_datasources}->{$proto}) {
1735 0           $self->{dep_datasources}->{$proto} = new
1736             HTML::WebMake::DataSource ($self, $file, "(depend)", { });
1737             }
1738 0           return $self->{dep_datasources}->{$proto}->get_location_mod_time ($file);
1739             } else {
1740 0           return $self->cached_get_modtime ($file);
1741             }
1742             }
1743              
1744             # -------------------------------------------------------------------------
1745              
1746             =item $f->make (@fnames)
1747              
1748             Make either the files named by $fnames (or all outputs if $fname is not
1749             supplied), based on the WebMake files read earlier.
1750              
1751             =cut
1752              
1753             sub make {
1754 0     0 1   my ($self, @fnames) = @_;
1755              
1756 0           $self->{renames_required} = [ ];
1757 0           $self->{content_deps_required} = [ ];
1758              
1759 0 0         if ($#fnames < 0) {
1760 0           @fnames = @{$self->{out_order}};
  0            
1761             }
1762              
1763 0           foreach my $outf (@fnames) {
1764 0           $self->make_file ($outf);
1765              
1766 0           $self->{current_tick}++;
1767 0 0         if ($self->{current_tick} % 50 == 0) { $self->gc_contents(); }
  0            
1768             }
1769              
1770 0           my $tries = 0;
1771 0           while ($self->finish_deferred_files(0)) {
1772 0 0         if ($tries++ > 3) {
1773 0           $self->fail ("loop or unreffed content item in deferred URLs, ".
1774             "cannot complete");
1775 0           $self->finish_deferred_files(1);
1776 0           last;
1777             }
1778             }
1779              
1780 0           my %done = ();
1781 0           foreach my $pair (@{$self->{renames_required}}) {
  0            
1782 0           my ($from, $to) = @{$pair};
  0            
1783 0           my $bak = $to.".bak";
1784              
1785 0 0         next if (defined $done{$from});
1786 0           dbg ("Renaming new file: $from -> $to");
1787              
1788 0           unlink ($bak);
1789 0 0 0       if (-f $to && !rename ($to, $bak)) {
1790 0           $self->fail ("Failed to rename \"$to\" to \"$bak\"!");
1791 0           next;
1792             }
1793 0 0         if (!rename ($from, $to)) {
1794 0           $self->fail ("Failed to rename \"$from\" to \"$to\"!");
1795 0           next;
1796             }
1797 0           unlink ($bak); # new version is in-place, backup no longer reqd
1798 0           $done{$from} = 1;
1799             }
1800              
1801 0           foreach my $pair (@{$self->{content_deps_required}}) {
  0            
1802 0           my ($fname, $deps) = @{$pair};
  0            
1803 0           $self->getcache()->set_content_deps ($fname, %{$deps});
  0            
1804             }
1805             }
1806              
1807             # -------------------------------------------------------------------------
1808              
1809             =item $pagetext = $f->make_to_string ($fname)
1810              
1811             Make the file named by $fname, and output its text to STDOUT, based on the
1812             WebMake files read earlier.
1813              
1814             =cut
1815              
1816             sub make_to_string {
1817 0     0 1   my ($self, $fname) = @_;
1818              
1819 0           $self->{making_to_string} = 1;
1820 0           $self->{making_to_string_output} = '';
1821              
1822 0           $self->make_file ($fname);
1823              
1824 0           my $out = $self->{making_to_string_output};
1825 0           delete $self->{making_to_string_output};
1826 0           return $out;
1827             }
1828              
1829             # -------------------------------------------------------------------------
1830              
1831             sub make_file ($$) {
1832 0     0 0   my ($self, $fname) = @_;
1833              
1834 0           my $outfname;
1835 0 0         if ($self->{base_dir} ne '') {
1836 0           $outfname = File::Spec->catfile ($self->{base_dir}, $fname);
1837             } else {
1838 0           $outfname = $fname;
1839             }
1840              
1841 0 0         if ($self->{force_output} == 0) {
1842 0 0         if ($self->depend_check ($fname, $outfname)) {
1843 0           dbg ("not making (dependencies unchanged): $outfname");
1844 0           return;
1845             }
1846             }
1847              
1848 0           my $out = $self->{outs}->{$fname};
1849 0 0         if (!defined $out) {
1850 0           $self->fail ("No target \"$fname\" found!"); return;
  0            
1851             }
1852              
1853 0           my $fmt = $out->get_format();
1854 0 0         if (!defined $fmt) {
1855 0           croak ("no format defined for $fname");
1856             }
1857              
1858 0           my $dotdots = '';
1859 0           ($dotdots .= '../') while ($fname =~ m,[/\\],g);
1860              
1861 0           my $useurls = 1;
1862 0 0         if (!$out->use_for_content_urls()) { $useurls = 0; }
  0            
1863              
1864 0           $self->clear_dependencies();
1865 0           delete $self->{contents}->{"__MainContentName"};
1866              
1867             # clear out any "this.blah" content items from the previous file
1868 0           dbg2 ("clearing \"this.*\" metadata for $fname");
1869 0           foreach my $name (@{$self->{this_metas_added}}) {
  0            
1870 0           delete $self->{metadatas}->{$name};
1871             }
1872 0           $self->{this_metas_added} = [ ];
1873              
1874 0           $self->_subst_open($fname, $out->{name}, $dotdots, $fmt, $useurls); #{
1875 0           my $txt = $out->get_text();
1876 0           $self->strip_metadata ($fname, \$txt);
1877 0           $self->subst_deferred_refs ($fname, \$txt);
1878              
1879 0 0         if ($txt =~ /{!!WMDEFER/) {
1880 0           $self->make_file_defer ($fname, $out, $outfname, $txt);
1881             } else {
1882 0           $self->make_file_finish ($fname, $out, $outfname, $txt);
1883             }
1884              
1885 0           $self->_subst_close(); #}
1886              
1887 0           1;
1888             }
1889              
1890             # -------------------------------------------------------------------------
1891              
1892             sub make_file_finish ($$$) {
1893 0     0 0   my ($self, $fname, $out, $outfname, $txt) = @_;
1894              
1895 0           my $dotdots = '';
1896 0           ($dotdots .= '../') while ($fname =~ m,[/\\],g);
1897              
1898             # unescape escaped references to our entities.
1899 0           $txt =~ s/\&wmdollar;/\$/gis;
1900              
1901             # clean HTML output.
1902 0 0         if ($out->get_format() =~ /^text\/html$/i) {
1903 0 0         my $cleanparams = !defined($out->{clean}) ? $DEFAULT_CLEAN_FEATURES : $out->{clean};
1904 0           $txt = $self->clean_html (\$txt, $fname, $cleanparams);
1905              
1906             # always trim the very first and last bits of whitespace in the
1907             # file anyway, for HTML output. Leave in 1 \n at EOF to look nice.
1908 0           $txt =~ s/^\s+//gs;
1909 0           $txt =~ s/\s+$/\n/gs;
1910              
1911             # convert EOLs to native format. Note that we don't have to
1912             # worry about \r\n, \r, or others; Perl will convert incoming
1913             # eols to \n while reading since we don't use "binmode".
1914 0           my $eol = $self->{util}->text_eol();
1915 0           $txt =~ s/\n/${eol}/gs;
1916             }
1917              
1918             # protection against var references that got through
1919 0 0         if ($outfname =~ /\$/) {
1920 0           $self->fail ("bad filename: $outfname"); return;
  0            
1921             }
1922              
1923 0 0 0       if (!$self->{making_to_string} && $self->{force_output} == 0 && -f $outfname)
      0        
1924             {
1925 0           my $curtxt;
1926 0 0 0       if ((-s $outfname == length($txt))
      0        
      0        
      0        
1927             && (open (IN, "<$outfname"))
1928             && ($curtxt = join ('', ))
1929             && (close IN)
1930             && ($curtxt eq $txt)
1931             )
1932             {
1933 0           dbg ("not making (text has not changed): $outfname");
1934 0           return;
1935             }
1936             }
1937              
1938 0           vrb ("making: $outfname");
1939 0           my $newfname = $outfname.".new";
1940              
1941 0 0         if ($self->{making_to_string}) {
1942 0           $self->{making_to_string_output} = $txt;
1943              
1944             } else {
1945 0 0         if (!open (OUT, ">$newfname")) {
1946             # make the dir, just in case that was the problem
1947 0 0         (-f $newfname) or mkpath (dirname ($newfname));
1948             # and try again...
1949 0 0         if (!open (OUT, ">$newfname")) {
1950 0           $self->fail ("Cannot write: $newfname"); return;
  0            
1951             }
1952             }
1953 0           print OUT $txt;
1954 0 0         if (!close (OUT)) {
1955 0           $self->fail ("Cannot write: $newfname"); return;
  0            
1956             }
1957              
1958 0           push (@{$self->{renames_required}}, [ $newfname, $outfname ]);
  0            
1959 0           push (@{$self->{content_deps_required}}, [ $fname,
  0            
1960             $self->{cont_dependencies} ]);
1961             }
1962 0           1;
1963             }
1964              
1965             # -------------------------------------------------------------------------
1966              
1967             sub make_file_defer {
1968 0     0 0   my ($self, $fname, $out, $outfname, $txt) = @_;
1969              
1970 0 0         if ($self->{making_to_string}) {
1971 0           die "cannot defer writes when making to string!";
1972             }
1973              
1974 0           dbg ("making (deferring write, some URLs are still unknown): $outfname");
1975 0           $self->{need_rewrite_for_deferred_urls}->{$fname} = $txt;
1976 0           $self->{need_rewrite_subst_context}->{$fname} = $self->{current_subst};
1977             }
1978              
1979             sub finish_deferred_files {
1980 0     0 0   my ($self, $give_up_if_still_deferred) = @_;
1981              
1982 0           my %new_deferred_list = ();
1983 0           my $still_have_deferreds = 0;
1984              
1985 0           foreach my $fname (keys %{$self->{need_rewrite_for_deferred_urls}})
  0            
1986             {
1987 0           dbg ("fixing URLs in deferred out file: $fname");
1988 0           my $txt = $self->{need_rewrite_for_deferred_urls}->{$fname};
1989 0           my $ctx = $self->{need_rewrite_subst_context}->{$fname};
1990              
1991 0           $self->_subst_open($ctx->{filename}, $ctx->{outname},
1992             $ctx->{dotdots}, $ctx->{format}, $ctx->{useurls});
1993              
1994             #{
1995 0           $txt =~ s/{!!WMDEFER_dotdots}/$ctx->{dotdots}/gs;
1996 0           $txt =~ s/{!!WMDEFER_content_url:([^}]+)}/
1997 0           $self->rewrite_a_deferred_url($1, $give_up_if_still_deferred);
1998             /ges;
1999              
2000 0           $self->_subst_close();
2001              
2002             #{
2003 0 0         if ($txt =~ /{!!WMDEFER_content_url:[^}]+}/) {
2004             # still have some left, keep it deferred
2005 0           $new_deferred_list{$fname} = $txt;
2006 0           $still_have_deferreds = 1;
2007 0           next;
2008             }
2009              
2010 0           dbg ("writing deferred out file: $fname");
2011 0           my $outfname;
2012 0 0         if ($self->{base_dir} ne '') {
2013 0           $outfname = File::Spec->catfile ($self->{base_dir}, $fname);
2014             } else {
2015 0           $outfname = $fname;
2016             }
2017              
2018 0           my $out = $self->{outs}->{$fname};
2019              
2020 0           $self->make_file_finish ($fname, $out, $outfname, $txt);
2021             }
2022              
2023 0 0         if ($still_have_deferreds) {
2024 0           %{$self->{need_rewrite_for_deferred_urls}} = %new_deferred_list;
  0            
2025 0           return 1;
2026             } else {
2027 0           return 0;
2028             }
2029             }
2030              
2031             sub rewrite_a_deferred_url {
2032 0     0 0   my ($self, $contname, $give_up_if_still_deferred) = @_;
2033              
2034 0           my $obj = $self->get_content_obj ($contname);
2035              
2036 0           my $url;
2037 0 0 0       if (!defined $obj || !defined ($url = $obj->get_url())) {
2038 0           $self->fail ("unable to get URL for content item: \${$contname}");
2039 0           return '';
2040             }
2041              
2042 0 0 0       if ($give_up_if_still_deferred && $url =~ /^\{!!WMDEFER_content_url:/)
2043             {
2044 0           $self->fail ("unable to get URL for content item: \${$contname}");
2045 0           return '';
2046             }
2047 0           return $url;
2048             }
2049              
2050             sub make_deferred_url {
2051 0     0 0   my ($self, $contname) = @_;
2052 0           return '{!!WMDEFER_content_url:'.$contname.'}';
2053             }
2054              
2055             # -------------------------------------------------------------------------
2056              
2057             sub depend_check ($$$) {
2058 0     0 0   my ($self, $fname, $outfname) = @_;
2059              
2060 0           my @deps = $self->getcache()->get_content_deps($fname);
2061 0           my $foundadep = 0;
2062 0           my $needrebuild = 0;
2063              
2064 0           my @s = stat $outfname;
2065 0 0 0       if ($#deps >= 0 && -f _) {
2066 0           my $outmod = $s[9];
2067 0           foreach my $dep (@deps) {
2068 0 0 0       next unless (defined $dep && $dep ne '');
2069 0           $foundadep = 1;
2070 0 0         if (!$self->check_content_dep ($dep, $fname, $outmod)) {
2071 0           $needrebuild = 1;
2072             }
2073             }
2074             }
2075              
2076 0 0 0       if ($foundadep && !$needrebuild) {
2077 0           return 1; # dependencies found, and we're OK for all of them
2078             } else {
2079 0           return 0; # no prior dependencies recorded, must rebuild
2080             }
2081             }
2082              
2083             sub check_content_dep ($$$$) {
2084 0     0 0   my ($self, $dep, $fname, $outmod) = @_;
2085              
2086 0 0         if ($fname eq $SUBST_EVAL) {
2087 0           dbg ("subst from eval code (always rebuilt)");
2088 0           return 0;
2089             }
2090 0 0         if ($fname eq $SUBST_DEP_IGNORE) { return 1; }
  0            
2091              
2092 0 0         if ($dep eq $SUBST_EVAL) {
2093 0           dbg ("$fname depends on eval code (always rebuilt)");
2094 0           return 0;
2095             }
2096 0 0         if ($dep eq $SUBST_DEP_IGNORE) { return 1; }
  0            
2097              
2098 0           my $prevmod = $self->getcache()->get_modtime ($dep);
2099 0 0         if (!defined $prevmod) { return 0; }
  0            
2100 0   0       $prevmod ||= 0;
2101              
2102 0           my $nowmod = $self->cached_get_location_modtime ($dep);
2103 0 0         if (!defined $nowmod) { return 0; }
  0            
2104              
2105 0 0 0       if ($DEBUG > 1 && $dep ne $SUBST_DEP_IGNORE) {
2106 0           my $prevsecs = $self->{now} - $prevmod;
2107 0           my $nowsecs = $self->{now} - $nowmod;
2108 0           dbg ("$fname depends on $dep ($nowsecs secs old, previous: $prevsecs)");
2109             }
2110              
2111 0 0         if ($nowmod > $prevmod) { return 0; }
  0            
2112              
2113             # if the dependency file is newer than the output file,
2114             # we always need to rebuild. This is really a sanity check
2115 0 0 0       if (defined $outmod && $nowmod > $outmod) { return 0; }
  0            
2116 0           return 1;
2117             }
2118              
2119             # -------------------------------------------------------------------------
2120              
2121             sub clean_html {
2122 0     0 0   my ($self, $txtptr, $fname, $features) = @_;
2123              
2124 0 0         if ($features !~ /\S/) { return $$txtptr; }
  0            
2125              
2126 0 0         if (!defined $self->{htmlcleaner}) {
2127 0 0         if (!eval '
2128             use HTML::WebMake::HTMLCleaner;
2129             $self->{htmlcleaner} = new HTML::WebMake::HTMLCleaner($self);
2130             1;')
2131             {
2132 0           warn "HTMLCleaner load failed: $@\n";
2133 0           warn "HTMLCleaner load failed -- not cleaning HTML.\n";
2134 0           $self->{htmlcleaner} = { 'loadfailed' => 1 };
2135 0           return $$txtptr;
2136             }
2137             }
2138 0 0         if ($self->{htmlcleaner}->{loadfailed}) { return $$txtptr; }
  0            
2139              
2140 0           $self->{htmlcleaner}->select_features ($features);
2141 0           $self->{htmlcleaner}->clean ($txtptr, $fname);
2142             }
2143              
2144             # -------------------------------------------------------------------------
2145              
2146             =item $ok = $f->can_build($fname);
2147              
2148             Returns 1 if WebMake can build the named file, 0 otherwise.
2149              
2150             =cut
2151              
2152             sub can_build {
2153 0     0 1   my ($self, $fname) = @_;
2154              
2155 0           return (defined $self->{outs}->{$fname});
2156             }
2157              
2158             # -------------------------------------------------------------------------
2159              
2160             =item $num_failures = $f->finish();
2161              
2162             Finish with a WebMake object and dispose of its internal open files etc.
2163             Returns the number of serious failure conditions that occurred (files that
2164             could not be created, etc.).
2165              
2166             =cut
2167              
2168             sub finish {
2169 0     0 1   my ($self) = @_;
2170              
2171 0 0         if (defined $self->{cache}) {
2172 0           $self->{cache}->untie();
2173             }
2174 0           $self->{failures};
2175             }
2176              
2177             # -------------------------------------------------------------------------
2178              
2179             sub quicktxt2html {
2180 0     0 0   my $txt = join ('',@_);
2181              
2182 0 0         if ($HTML_LOGGING) {
2183 0           $txt =~ s/&/&/gs;
2184 0           $txt =~ s/
2185 0           $txt =~ s/>/>/gs;
2186 0           $txt =~ s/\n/
\n/gs;
2187             }
2188              
2189 0           return $txt;
2190             }
2191              
2192             sub dbg {
2193 0 0   0 0   if ($DEBUG > 0) {
2194 0           my @now = localtime(time);
2195 0 0         if ($DEBUG > 1) {
2196 0           printf STDOUT ("%02d:%02d:%02d debug: %s\n",
2197             $now[2], $now[1], $now[0], quicktxt2html(@_));
2198             } else {
2199 0           printf STDOUT ("debug: %s\n", quicktxt2html(@_));
2200             }
2201             }
2202             }
2203              
2204             sub dbg2 {
2205 0 0   0 0   if ($DEBUG > 1) { dbg(@_); }
  0            
2206             }
2207              
2208             sub vrb {
2209 0 0   0 0   if ($VERBOSE) {
2210 0           print STDOUT "webmake: ".quicktxt2html(@_, "\n");
2211             }
2212             }
2213              
2214             sub fail {
2215 0     0 0   my $self = shift;
2216 0           warn "webmake: error: ".quicktxt2html(@_, "\n");
2217 0           $self->{failures}++;
2218             }
2219              
2220             # intended for use with -MCarp=verbose
2221             sub stacktrace {
2222 0     0 0   carp join ("\n", @_);
2223             }
2224              
2225             1;
2226              
2227             __END__