Bring Pod::Simple up to 3.09 as on CPAN.
[p5sagit/p5-mst-13.2.git] / cpan / Pod-Simple / lib / Pod / Simple / HTMLBatch.pm
CommitLineData
351625bd 1
2require 5;
3package Pod::Simple::HTMLBatch;
4use strict;
5use vars qw( $VERSION $HTML_RENDER_CLASS $HTML_EXTENSION
6 $CSS $JAVASCRIPT $SLEEPY $SEARCH_CLASS @ISA
7);
8$VERSION = '3.02';
9@ISA = (); # Yup, we're NOT a subclass of Pod::Simple::HTML!
10
11# TODO: nocontents stylesheets. Strike some of the color variations?
12
13use Pod::Simple::HTML ();
14BEGIN {*esc = \&Pod::Simple::HTML::esc }
15use File::Spec ();
16use UNIVERSAL ();
17 # "Isn't the Universe an amazing place? I wouldn't live anywhere else!"
18
19use Pod::Simple::Search;
20$SEARCH_CLASS ||= 'Pod::Simple::Search';
21
22BEGIN {
23 if(defined &DEBUG) { } # no-op
24 elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG }
25 else { *DEBUG = sub () {0}; }
26}
27
28$SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i;
29# flag to occasionally sleep for $SLEEPY - 1 seconds.
30
31$HTML_RENDER_CLASS ||= "Pod::Simple::HTML";
32
33#
34# Methods beginning with "_" are particularly internal and possibly ugly.
35#
36
37Pod::Simple::_accessorize( __PACKAGE__,
38 'verbose', # how verbose to be during batch conversion
39 'html_render_class', # what class to use to render
9d65762f 40 'search_class', # what to use to search for POD documents
351625bd 41 'contents_file', # If set, should be the name of a file (in current directory)
42 # to write the list of all modules to
43 'index', # will set $htmlpage->index(...) to this (true or false)
44 'progress', # progress object
45 'contents_page_start', 'contents_page_end',
46
47 'css_flurry', '_css_wad', 'javascript_flurry', '_javascript_wad',
48 'no_contents_links', # set to true to suppress automatic adding of << links.
49 '_contents',
50);
51
52# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
53# Just so we can run from the command line more easily
54sub go {
55 @ARGV == 2 or die sprintf(
56 "Usage: perl -M%s -e %s:go indirs outdir\n (or use \"\@INC\" for indirs)\n",
57 __PACKAGE__, __PACKAGE__,
58 );
59
60 if(defined($ARGV[1]) and length($ARGV[1])) {
61 my $d = $ARGV[1];
62 -e $d or die "I see no output directory named \"$d\"\nAborting";
63 -d $d or die "But \"$d\" isn't a directory!\nAborting";
64 -w $d or die "Directory \"$d\" isn't writeable!\nAborting";
65 }
66
67 __PACKAGE__->batch_convert(@ARGV);
68}
69# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
70
71
72sub new {
73 my $new = bless {}, ref($_[0]) || $_[0];
74 $new->html_render_class($HTML_RENDER_CLASS);
9d65762f 75 $new->search_class($SEARCH_CLASS);
351625bd 76 $new->verbose(1 + DEBUG);
77 $new->_contents([]);
78
79 $new->index(1);
80
81 $new-> _css_wad([]); $new->css_flurry(1);
82 $new->_javascript_wad([]); $new->javascript_flurry(1);
83
84 $new->contents_file(
85 'index' . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION)
86 );
87
88 $new->contents_page_start( join "\n", grep $_,
89 $Pod::Simple::HTML::Doctype_decl,
90 "<html><head>",
91 "<title>Perl Documentation</title>",
92 $Pod::Simple::HTML::Content_decl,
93 "</head>",
94 "\n<body class='contentspage'>\n<h1>Perl Documentation</h1>\n"
95 ); # override if you need a different title
96
97
98 $new->contents_page_end( sprintf(
99 "\n\n<p class='contentsfooty'>Generated by %s v%s under Perl v%s\n<br >At %s GMT, which is %s local time.</p>\n\n</body></html>\n",
100 esc(
101 ref($new),
102 eval {$new->VERSION} || $VERSION,
103 $], scalar(gmtime), scalar(localtime),
104 )));
105
106 return $new;
107}
108
109# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
110
111sub muse {
112 my $self = shift;
113 if($self->verbose) {
114 print 'T+', int(time() - $self->{'_batch_start_time'}), "s: ", @_, "\n";
115 }
116 return 1;
117}
118
119# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
120
121sub batch_convert {
122 my($self, $dirs, $outdir) = @_;
123 $self ||= __PACKAGE__; # tolerate being called as an optionless function
124 $self = $self->new unless ref $self; # tolerate being used as a class method
125
126 if(!defined($dirs) or $dirs eq '' or $dirs eq '@INC' ) {
127 $dirs = '';
128 } elsif(ref $dirs) {
129 # OK, it's an explicit set of dirs to scan, specified as an arrayref.
130 } else {
131 # OK, it's an explicit set of dirs to scan, specified as a
132 # string like "/thing:/also:/whatever/perl" (":"-delim, as usual)
133 # or, under MSWin, like "c:/thing;d:/also;c:/whatever/perl" (";"-delim!)
134 require Config;
135 my $ps = quotemeta( $Config::Config{'path_sep'} || ":" );
136 $dirs = [ grep length($_), split qr/$ps/, $dirs ];
137 }
138
139 $outdir = $self->filespecsys->curdir
140 unless defined $outdir and length $outdir;
141
142 $self->_batch_convert_main($dirs, $outdir);
143}
144
145# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
146
147sub _batch_convert_main {
148 my($self, $dirs, $outdir) = @_;
149 # $dirs is either false, or an arrayref.
150 # $outdir is a pathspec.
151
152 $self->{'_batch_start_time'} ||= time();
153
154 $self->muse( "= ", scalar(localtime) );
155 $self->muse( "Starting batch conversion to \"$outdir\"" );
156
157 my $progress = $self->progress;
158 if(!$progress and $self->verbose > 0 and $self->verbose() <= 5) {
159 require Pod::Simple::Progress;
160 $progress = Pod::Simple::Progress->new(
161 ($self->verbose < 2) ? () # Default omission-delay
162 : ($self->verbose == 2) ? 1 # Reduce the omission-delay
163 : 0 # Eliminate the omission-delay
164 );
165 $self->progress($progress);
166 }
167
168 if($dirs) {
169 $self->muse(scalar(@$dirs), " dirs to scan: @$dirs");
170 } else {
171 $self->muse("Scanning \@INC. This could take a minute or two.");
172 }
173 my $mod2path = $self->find_all_pods($dirs ? $dirs : ());
174 $self->muse("Done scanning.");
175
176 my $total = keys %$mod2path;
177 unless($total) {
178 $self->muse("No pod found. Aborting batch conversion.\n");
179 return $self;
180 }
181
182 $progress and $progress->goal($total);
183 $self->muse("Now converting pod files to HTML.",
184 ($total > 25) ? " This will take a while more." : ()
185 );
186
187 $self->_spray_css( $outdir );
188 $self->_spray_javascript( $outdir );
189
190 $self->_do_all_batch_conversions($mod2path, $outdir);
191
192 $progress and $progress->done(sprintf (
193 "Done converting %d files.", $self->{"__batch_conv_page_count"}
194 ));
195 return $self->_batch_convert_finish($outdir);
196 return $self;
197}
198
199
200sub _do_all_batch_conversions {
201 my($self, $mod2path, $outdir) = @_;
202 $self->{"__batch_conv_page_count"} = 0;
203
204 foreach my $module (sort {lc($a) cmp lc($b)} keys %$mod2path) {
205 $self->_do_one_batch_conversion($module, $mod2path, $outdir);
206 sleep($SLEEPY - 1) if $SLEEPY;
207 }
208
209 return;
210}
211
212sub _batch_convert_finish {
213 my($self, $outdir) = @_;
214 $self->write_contents_file($outdir);
215 $self->muse("Done with batch conversion. $$self{'__batch_conv_page_count'} files done.");
216 $self->muse( "= ", scalar(localtime) );
217 $self->progress and $self->progress->done("All done!");
218 return;
219}
220
221# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
222
223sub _do_one_batch_conversion {
224 my($self, $module, $mod2path, $outdir, $outfile) = @_;
225
226 my $retval;
227 my $total = scalar keys %$mod2path;
228 my $infile = $mod2path->{$module};
229 my @namelets = grep m/\S/, split "::", $module;
230 # this can stick around in the contents LoL
231 my $depth = scalar @namelets;
232 die "Contentless thingie?! $module $infile" unless @namelets; #sanity
233
234 $outfile ||= do {
235 my @n = @namelets;
236 $n[-1] .= $HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION;
237 $self->filespecsys->catfile( $outdir, @n );
238 };
239
240 my $progress = $self->progress;
241
242 my $page = $self->html_render_class->new;
243 if(DEBUG > 5) {
244 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: ",
245 ref($page), " render ($depth) $module => $outfile");
246 } elsif(DEBUG > 2) {
247 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: $module => $outfile")
248 }
249
250 # Give each class a chance to init the converter:
351625bd 251 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
252 if $page->can('batch_mode_page_object_init');
351625bd 253
254 # Now get busy...
255 $self->makepath($outdir => \@namelets);
256
257 $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module");
258
259 if( $retval = $page->parse_from_file($infile, $outfile) ) {
260 ++ $self->{"__batch_conv_page_count"} ;
261 $self->note_for_contents_file( \@namelets, $infile, $outfile );
262 } else {
263 $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false.");
264 }
265
266 $page->batch_mode_page_object_kill($self, $module, $infile, $outfile, $depth)
267 if $page->can('batch_mode_page_object_kill');
268 # The following isn't a typo. Note that it switches $self and $page.
269 $self->batch_mode_page_object_kill($page, $module, $infile, $outfile, $depth)
270 if $self->can('batch_mode_page_object_kill');
271
272 DEBUG > 4 and printf "%s %sb < $infile %s %sb\n",
273 $outfile, -s $outfile, $infile, -s $infile
274 ;
275
276 undef($page);
277 return $retval;
278}
279
280# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
281sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
282
283# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
284
285sub note_for_contents_file {
286 my($self, $namelets, $infile, $outfile) = @_;
287
288 # I think the infile and outfile parts are never used. -- SMB
289 # But it's handy to have them around for debugging.
290
291 if( $self->contents_file ) {
292 my $c = $self->_contents();
293 push @$c,
294 [ join("::", @$namelets), $infile, $outfile, $namelets ]
295 # 0 1 2 3
296 ;
297 DEBUG > 3 and print "Noting @$c[-1]\n";
298 }
299 return;
300}
301
302#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
303
304sub write_contents_file {
305 my($self, $outdir) = @_;
306 my $outfile = $self->_contents_filespec($outdir) || return;
307
308 $self->muse("Preparing list of modules for ToC");
309
310 my($toplevel, # maps toplevelbit => [all submodules]
311 $toplevel_form_freq, # ends up being 'foo' => 'Foo'
312 ) = $self->_prep_contents_breakdown;
313
314 my $Contents = eval { $self->_wopen($outfile) };
315 if( $Contents ) {
316 $self->muse( "Writing contents file $outfile" );
317 } else {
318 warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all";
319 return;
320 }
321
322 $self->_write_contents_start( $Contents, $outfile, );
323 $self->_write_contents_middle( $Contents, $outfile, $toplevel, $toplevel_form_freq );
324 $self->_write_contents_end( $Contents, $outfile, );
325 return $outfile;
326}
327
328# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
329
330sub _write_contents_start {
331 my($self, $Contents, $outfile) = @_;
332 my $starter = $self->contents_page_start || '';
333
334 {
335 my $css_wad = $self->_css_wad_to_markup(1);
336 if( $css_wad ) {
337 $starter =~ s{(</head>)}{\n$css_wad\n$1}i; # otherwise nevermind
338 }
339
340 my $javascript_wad = $self->_javascript_wad_to_markup(1);
341 if( $javascript_wad ) {
342 $starter =~ s{(</head>)}{\n$javascript_wad\n$1}i; # otherwise nevermind
343 }
344 }
345
346 unless(print $Contents $starter, "<dl class='superindex'>\n" ) {
347 warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
348 close($Contents);
349 return 0;
350 }
351 return 1;
352}
353
354# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
355
356sub _write_contents_middle {
357 my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_;
358
359 foreach my $t (sort keys %$toplevel2submodules) {
360 my @downlines = sort {$a->[-1] cmp $b->[-1]}
361 @{ $toplevel2submodules->{$t} };
362
363 printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n],
364 esc( $t, $toplevel_form_freq->{$t} )
365 ;
366
367 my($path, $name);
368 foreach my $e (@downlines) {
369 $name = $e->[0];
370 $path = join( "/", '.', esc( @{$e->[3]} ) )
371 . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION);
372 print $Contents qq{ <a href="$path">}, esc($name), "</a>&nbsp;&nbsp;\n";
373 }
374 print $Contents "</dd>\n\n";
375 }
376 return 1;
377}
378
379# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
380
381sub _write_contents_end {
382 my($self, $Contents, $outfile) = @_;
383 unless(
384 print $Contents "</dl>\n",
385 $self->contents_page_end || '',
386 ) {
387 warn "Couldn't write to $outfile: $!";
388 }
389 close($Contents) or warn "Couldn't close $outfile: $!";
390 return 1;
391}
392
393# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
394
395sub _prep_contents_breakdown {
396 my($self) = @_;
397 my $contents = $self->_contents;
398 my %toplevel; # maps lctoplevelbit => [all submodules]
399 my %toplevel_form_freq; # ends up being 'foo' => 'Foo'
400 # (mapping anycase forms to most freq form)
401
402 foreach my $entry (@$contents) {
403 my $toplevel =
404 $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs'
405 # group all the perlwhatever docs together
406 : $entry->[3][0] # normal case
407 ;
408 ++$toplevel_form_freq{ lc $toplevel }{ $toplevel };
409 push @{ $toplevel{ lc $toplevel } }, $entry;
410 push @$entry, lc($entry->[0]); # add a sort-order key to the end
411 }
412
413 foreach my $toplevel (sort keys %toplevel) {
414 my $fgroup = $toplevel_form_freq{$toplevel};
415 $toplevel_form_freq{$toplevel} =
416 (
417 sort { $fgroup->{$b} <=> $fgroup->{$a} or $a cmp $b }
418 keys %$fgroup
419 # This hash is extremely unlikely to have more than 4 members, so this
420 # sort isn't so very wasteful
421 )[0];
422 }
423
424 return(\%toplevel, \%toplevel_form_freq) if wantarray;
425 return \%toplevel;
426}
427
428# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
429
430sub _contents_filespec {
431 my($self, $outdir) = @_;
432 my $outfile = $self->contents_file;
433 return unless $outfile;
434 return $self->filespecsys->catfile( $outdir, $outfile );
435}
436
437#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
438
439sub makepath {
440 my($self, $outdir, $namelets) = @_;
441 return unless @$namelets > 1;
442 for my $i (0 .. ($#$namelets - 1)) {
443 my $dir = $self->filespecsys->catdir( $outdir, @$namelets[0 .. $i] );
444 if(-e $dir) {
445 die "$dir exists but not as a directory!?" unless -d $dir;
446 next;
447 }
448 DEBUG > 3 and print " Making $dir\n";
449 mkdir $dir, 0777
450 or die "Can't mkdir $dir: $!\nAborting"
451 ;
452 }
453 return;
454}
455
456#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
457
458sub batch_mode_page_object_init {
459 my $self = shift;
460 my($page, $module, $infile, $outfile, $depth) = @_;
461
462 # TODO: any further options to percolate onto this new object here?
463
464 $page->default_title($module);
465 $page->index( $self->index );
466
467 $page->html_css( $self-> _css_wad_to_markup($depth) );
468 $page->html_javascript( $self->_javascript_wad_to_markup($depth) );
469
470 $self->add_header_backlink($page, $module, $infile, $outfile, $depth);
471 $self->add_footer_backlink($page, $module, $infile, $outfile, $depth);
472
473
474 return $self;
475}
476
477# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
478
479sub add_header_backlink {
480 my $self = shift;
481 return if $self->no_contents_links;
482 my($page, $module, $infile, $outfile, $depth) = @_;
483 $page->html_header_after_title( join '',
484 $page->html_header_after_title || '',
485
486 qq[<p class="backlinktop"><b><a name="___top" href="],
487 $self->url_up_to_contents($depth),
488 qq[" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>\n],
489 )
490 if $self->contents_file
491 ;
492 return;
493}
494
495sub add_footer_backlink {
496 my $self = shift;
497 return if $self->no_contents_links;
498 my($page, $module, $infile, $outfile, $depth) = @_;
499 $page->html_footer( join '',
500 qq[<p class="backlinkbottom"><b><a name="___bottom" href="],
501 $self->url_up_to_contents($depth),
502 qq[" title="All Documents">&lt;&lt;</a></b></p>\n],
503
504 $page->html_footer || '',
505 )
506 if $self->contents_file
507 ;
508 return;
509}
510
511sub url_up_to_contents {
512 my($self, $depth) = @_;
513 --$depth;
514 return join '/', ('..') x $depth, esc($self->contents_file);
515}
516
517#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
518
519sub find_all_pods {
520 my($self, $dirs) = @_;
521 # You can override find_all_pods in a subclass if you want to
522 # do extra filtering or whatnot. But for the moment, we just
523 # pass to modnames2paths:
524 return $self->modnames2paths($dirs);
525}
526
527#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
528
529sub modnames2paths { # return a hashref mapping modulenames => paths
530 my($self, $dirs) = @_;
531
532 my $m2p;
533 {
9d65762f 534 my $search = $self->search_class->new;
351625bd 535 DEBUG and print "Searching via $search\n";
536 $search->verbose(1) if DEBUG > 10;
537 $search->progress( $self->progress->copy->goal(0) ) if $self->progress;
538 $search->shadows(0); # don't bother noting shadowed files
539 $search->inc( $dirs ? 0 : 1 );
540 $search->survey( $dirs ? @$dirs : () );
541 $m2p = $search->name2path;
542 die "What, no name2path?!" unless $m2p;
543 }
544
545 $self->muse("That's odd... no modules found!") unless keys %$m2p;
546 if( DEBUG > 4 ) {
547 print "Modules found (name => path):\n";
548 foreach my $m (sort {lc($a) cmp lc($b)} keys %$m2p) {
549 print " $m $$m2p{$m}\n";
550 }
551 print "(total ", scalar(keys %$m2p), ")\n\n";
552 } elsif( DEBUG ) {
553 print "Found ", scalar(keys %$m2p), " modules.\n";
554 }
555 $self->muse( "Found ", scalar(keys %$m2p), " modules." );
556
557 # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref
558 return $m2p;
559}
560
561#===========================================================================
562
563sub _wopen {
564 # this is abstracted out so that the daemon class can override it
565 my($self, $outpath) = @_;
566 require Symbol;
567 my $out_fh = Symbol::gensym();
568 DEBUG > 5 and print "Write-opening to $outpath\n";
569 return $out_fh if open($out_fh, "> $outpath");
570 require Carp;
571 Carp::croak("Can't write-open $outpath: $!");
572}
573
574#==========================================================================
575
576sub add_css {
577 my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_;
578 return unless $url;
579 unless($name) {
580 # cook up a reasonable name based on the URL
581 $name = $url;
582 if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) {
583 $name = $1;
584 $name =~ s/\.css//i;
585 }
586 }
587 $media ||= 'all';
588 $content_type ||= 'text/css';
589
590 my $bunch = [$url, $name, $content_type, $media, $_code];
591 if($is_default) { unshift @{ $self->_css_wad }, $bunch }
592 else { push @{ $self->_css_wad }, $bunch }
593 return;
594}
595
596# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
597
598sub _spray_css {
599 my($self, $outdir) = @_;
600
601 return unless $self->css_flurry();
602 $self->_gen_css_wad();
603
604 my $lol = $self->_css_wad;
605 foreach my $chunk (@$lol) {
606 my $url = $chunk->[0];
607 my $outfile;
608 if( ref($chunk->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.css$)} ) {
69473a20 609 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
351625bd 610 DEBUG > 5 and print "Noting $$chunk[0] as a file I'll create.\n";
611 } else {
612 DEBUG > 5 and print "OK, noting $$chunk[0] as an external CSS.\n";
613 # Requires no further attention.
614 next;
615 }
616
617 #$self->muse( "Writing autogenerated CSS file $outfile" );
618 my $Cssout = $self->_wopen($outfile);
619 print $Cssout ${$chunk->[-1]}
620 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
621 close($Cssout);
622 DEBUG > 5 and print "Wrote $outfile\n";
623 }
624
625 return;
626}
627
628# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
629
630sub _css_wad_to_markup {
631 my($self, $depth) = @_;
632
633 my @css = @{ $self->_css_wad || return '' };
634 return '' unless @css;
635
636 my $rel = 'stylesheet';
637 my $out = '';
638
639 --$depth;
640 my $uplink = $depth ? ('../' x $depth) : '';
641
642 foreach my $chunk (@css) {
643 next unless $chunk and @$chunk;
644
645 my( $url1, $url2, $title, $type, $media) = (
646 $self->_maybe_uplink( $chunk->[0], $uplink ),
647 esc(grep !ref($_), @$chunk)
648 );
649
650 $out .= qq{<link rel="$rel" title="$title" type="$type" href="$url1$url2" media="$media" >\n};
651
652 $rel = 'alternate stylesheet'; # alternates = all non-first iterations
653 }
654 return $out;
655}
656
657# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
658sub _maybe_uplink {
659 # if the given URL looks relative, return the given uplink string --
660 # otherwise return emptystring
661 my($self, $url, $uplink) = @_;
662 ($url =~ m{^\./} or $url !~ m{[/\:]} )
663 ? $uplink
664 : ''
665 # qualify it, if/as needed
666}
667
668# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
669sub _gen_css_wad {
670 my $self = $_[0];
671 my $css_template = $self->_css_template;
672 foreach my $variation (
673
674 # Commented out for sake of concision:
675 #
676 # 011n=black_with_red_on_white
677 # 001n=black_with_yellow_on_white
678 # 101n=black_with_green_on_white
679 # 110=white_with_yellow_on_black
680 # 010=white_with_green_on_black
681 # 011=white_with_blue_on_black
682 # 100=white_with_red_on_black
9d65762f 683 '110n=blkbluw', # black_with_blue_on_white
684 '010n=blkmagw', # black_with_magenta_on_white
685 '100n=blkcynw', # black_with_cyan_on_white
686 '101=whtprpk', # white_with_purple_on_black
687 '001=whtnavk', # white_with_navy_blue_on_black
688 '010a=grygrnk', # grey_with_green_on_black
689 '010b=whtgrng', # white_with_green_on_grey
690 '101an=blkgrng', # black_with_green_on_grey
691 '101bn=grygrnw', # grey_with_green_on_white
692 ) {
351625bd 693
694 my $outname = $variation;
695 my($flipmode, @swap) = ( ($4 || ''), $1,$2,$3)
696 if $outname =~ s/^([012])([012])([[012])([a-z]*)=?//s;
697 @swap = () if '010' eq join '', @swap; # 010 is a swop-no-op!
698
699 my $this_css =
700 "/* This file is autogenerated. Do not edit. $variation */\n\n"
701 . $css_template;
702
703 # Only look at three-digitty colors, for now at least.
704 if( $flipmode =~ m/n/ ) {
705 $this_css =~ s/(#[0-9a-fA-F]{3})\b/_color_negate($1)/eg;
706 $this_css =~ s/\bthin\b/medium/g;
707 }
708 $this_css =~ s<#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b>
709 < join '', '#', ($1,$2,$3)[@swap] >eg if @swap;
710
711 if( $flipmode =~ m/a/)
712 { $this_css =~ s/#fff\b/#999/gi } # black -> dark grey
713 elsif($flipmode =~ m/b/)
714 { $this_css =~ s/#000\b/#666/gi } # white -> light grey
715
716 my $name = $outname;
717 $name =~ tr/-_/ /;
718 $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
719 }
720
721 # Now a few indexless variations:
9d65762f 722 foreach my $variation (
723 'blkbluw', # black_with_blue_on_white
724 'whtpurk', # white_with_purple_on_black
725 'whtgrng', # white_with_green_on_grey
726 'grygrnw', # grey_with_green_on_white
727 ) {
728 my $outname = "$variation\_";
351625bd 729 my $this_css = join "\n",
730 "/* This file is autogenerated. Do not edit. $outname */\n",
731 "\@import url(\"./_$variation.css\");",
732 ".indexgroup { display: none; }",
733 "\n",
734 ;
735 my $name = $outname;
736 $name =~ tr/-_/ /;
9d65762f 737 $self->add_css( "$outname.css", 0, $name, 0, 0, \$this_css);
351625bd 738 }
739
740 return;
741}
742
743sub _color_negate {
744 my $x = lc $_[0];
745 $x =~ tr[0123456789abcdef]
746 [fedcba9876543210];
747 return $x;
748}
749
750#===========================================================================
751
752sub add_javascript {
753 my($self, $url, $content_type, $_code) = @_;
754 return unless $url;
755 push @{ $self->_javascript_wad }, [
756 $url, $content_type || 'text/javascript', $_code
757 ];
758 return;
759}
760
761sub _spray_javascript {
762 my($self, $outdir) = @_;
763 return unless $self->javascript_flurry();
764 $self->_gen_javascript_wad();
765
766 my $lol = $self->_javascript_wad;
767 foreach my $script (@$lol) {
768 my $url = $script->[0];
769 my $outfile;
770
771 if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) {
69473a20 772 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
351625bd 773 DEBUG > 5 and print "Noting $$script[0] as a file I'll create.\n";
774 } else {
775 DEBUG > 5 and print "OK, noting $$script[0] as an external JavaScript.\n";
776 next;
777 }
778
779 #$self->muse( "Writing JavaScript file $outfile" );
780 my $Jsout = $self->_wopen($outfile);
781
782 print $Jsout ${$script->[-1]}
783 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
784 close($Jsout);
785 DEBUG > 5 and print "Wrote $outfile\n";
786 }
787
788 return;
789}
790
791sub _gen_javascript_wad {
792 my $self = $_[0];
793 my $js_code = $self->_javascript || return;
794 $self->add_javascript( "_podly.js", 0, \$js_code);
795 return;
796}
797
798sub _javascript_wad_to_markup {
799 my($self, $depth) = @_;
800
801 my @scripts = @{ $self->_javascript_wad || return '' };
802 return '' unless @scripts;
803
804 my $out = '';
805
806 --$depth;
807 my $uplink = $depth ? ('../' x $depth) : '';
808
809 foreach my $s (@scripts) {
810 next unless $s and @$s;
811
812 my( $url1, $url2, $type, $media) = (
813 $self->_maybe_uplink( $s->[0], $uplink ),
814 esc(grep !ref($_), @$s)
815 );
816
817 $out .= qq{<script type="$type" src="$url1$url2"></script>\n};
818 }
819 return $out;
820}
821
822#===========================================================================
823
824sub _css_template { return $CSS }
825sub _javascript { return $JAVASCRIPT }
826
827$CSS = <<'EOCSS';
828/* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */
829
830@media all { .hide { display: none; } }
831
832@media print {
833 .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none }
834
835 * {
836 border-color: black !important;
837 color: black !important;
838 background-color: transparent !important;
839 background-image: none !important;
840 }
841
842 dl.superindex > dd {
843 word-spacing: .6em;
844 }
845}
846
847@media aural, braille, embossed {
848 div.indexgroup { display: none; } /* Too noisy, don't you think? */
849 dl.superindex > dt:before { content: "Group "; }
850 dl.superindex > dt:after { content: " contains:"; }
851 .backlinktop a:before { content: "Back to contents"; }
852 .backlinkbottom a:before { content: "Back to contents"; }
853}
854
855@media aural {
856 dl.superindex > dt { pause-before: 600ms; }
857}
858
859@media screen, tty, tv, projection {
860 .noscreen { display: none; }
861
862 a:link { color: #7070ff; text-decoration: underline; }
863 a:visited { color: #e030ff; text-decoration: underline; }
864 a:active { color: #800000; text-decoration: underline; }
865 body.contentspage a { text-decoration: none; }
866 a.u { color: #fff !important; text-decoration: none; }
867
868 body.pod {
869 margin: 0 5px;
870 color: #fff;
871 background-color: #000;
872 }
873
874 body.pod h1, body.pod h2, body.pod h3, body.pod h4 {
875 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
876 font-weight: normal;
877 margin-top: 1.2em;
878 margin-bottom: .1em;
879 border-top: thin solid transparent;
880 /* margin-left: -5px; border-left: 2px #7070ff solid; padding-left: 3px; */
881 }
882
883 body.pod h1 { border-top-color: #0a0; }
884 body.pod h2 { border-top-color: #080; }
885 body.pod h3 { border-top-color: #040; }
886 body.pod h4 { border-top-color: #010; }
887
888 p.backlinktop + h1 { border-top: none; margin-top: 0em; }
889 p.backlinktop + h2 { border-top: none; margin-top: 0em; }
890 p.backlinktop + h3 { border-top: none; margin-top: 0em; }
891 p.backlinktop + h4 { border-top: none; margin-top: 0em; }
892
893 body.pod dt {
894 font-size: 105%; /* just a wee bit more than normal */
895 }
896
897 .indexgroup { font-size: 80%; }
898
899 .backlinktop, .backlinkbottom {
900 margin-left: -5px;
901 margin-right: -5px;
902 background-color: #040;
903 border-top: thin solid #050;
904 border-bottom: thin solid #050;
905 }
906
907 .backlinktop a, .backlinkbottom a {
908 text-decoration: none;
909 color: #080;
910 background-color: #000;
911 border: thin solid #0d0;
912 }
913 .backlinkbottom { margin-bottom: 0; padding-bottom: 0; }
914 .backlinktop { margin-top: 0; padding-top: 0; }
915
916 body.contentspage {
917 color: #fff;
918 background-color: #000;
919 }
920
921 body.contentspage h1 {
922 color: #0d0;
923 margin-left: 1em;
924 margin-right: 1em;
925 text-indent: -.9em;
926 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
927 font-weight: normal;
928 border-top: thin solid #fff;
929 border-bottom: thin solid #fff;
930 text-align: center;
931 }
932
933 dl.superindex > dt {
934 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
935 font-weight: normal;
936 font-size: 90%;
937 margin-top: .45em;
938 /* margin-bottom: -.15em; */
939 }
940 dl.superindex > dd {
941 word-spacing: .6em; /* most important rule here! */
942 }
943 dl.superindex > a:link {
944 text-decoration: none;
945 color: #fff;
946 }
947
948 .contentsfooty {
949 border-top: thin solid #999;
950 font-size: 90%;
951 }
952
953}
954
955/* The End */
956
957EOCSS
958
959#==========================================================================
960
961$JAVASCRIPT = <<'EOJAVASCRIPT';
962
963// From http://www.alistapart.com/articles/alternate/
964
965function setActiveStyleSheet(title) {
966 var i, a, main;
967 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
968 if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
969 a.disabled = true;
970 if(a.getAttribute("title") == title) a.disabled = false;
971 }
972 }
973}
974
975function getActiveStyleSheet() {
976 var i, a;
977 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
978 if( a.getAttribute("rel").indexOf("style") != -1
979 && a.getAttribute("title")
980 && !a.disabled
981 ) return a.getAttribute("title");
982 }
983 return null;
984}
985
986function getPreferredStyleSheet() {
987 var i, a;
988 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
989 if( a.getAttribute("rel").indexOf("style") != -1
990 && a.getAttribute("rel").indexOf("alt") == -1
991 && a.getAttribute("title")
992 ) return a.getAttribute("title");
993 }
994 return null;
995}
996
997function createCookie(name,value,days) {
998 if (days) {
999 var date = new Date();
1000 date.setTime(date.getTime()+(days*24*60*60*1000));
1001 var expires = "; expires="+date.toGMTString();
1002 }
1003 else expires = "";
1004 document.cookie = name+"="+value+expires+"; path=/";
1005}
1006
1007function readCookie(name) {
1008 var nameEQ = name + "=";
1009 var ca = document.cookie.split(';');
1010 for(var i=0 ; i < ca.length ; i++) {
1011 var c = ca[i];
1012 while (c.charAt(0)==' ') c = c.substring(1,c.length);
1013 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
1014 }
1015 return null;
1016}
1017
1018window.onload = function(e) {
1019 var cookie = readCookie("style");
1020 var title = cookie ? cookie : getPreferredStyleSheet();
1021 setActiveStyleSheet(title);
1022}
1023
1024window.onunload = function(e) {
1025 var title = getActiveStyleSheet();
1026 createCookie("style", title, 365);
1027}
1028
1029var cookie = readCookie("style");
1030var title = cookie ? cookie : getPreferredStyleSheet();
1031setActiveStyleSheet(title);
1032
1033// The End
1034
1035EOJAVASCRIPT
1036
1037# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10381;
1039__END__
1040
1041
1042=head1 NAME
1043
1044Pod::Simple::HTMLBatch - convert several Pod files to several HTML files
1045
1046=head1 SYNOPSIS
1047
1048 perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out
1049
1050
1051=head1 DESCRIPTION
1052
1053This module is used for running batch-conversions of a lot of HTML
1054documents
1055
1056This class is NOT a subclass of Pod::Simple::HTML
1057(nor of bad old Pod::Html) -- although it uses
1058Pod::Simple::HTML for doing the conversion of each document.
1059
1060The normal use of this class is like so:
1061
1062 use Pod::Simple::HTMLBatch;
1063 my $batchconv = Pod::Simple::HTMLBatch->new;
1064 $batchconv->some_option( some_value );
1065 $batchconv->some_other_option( some_other_value );
1066 $batchconv->batch_convert( \@search_dirs, $output_dir );
1067
1068=head2 FROM THE COMMAND LINE
1069
1070Note that this class also provides
1071(but does not export) the function Pod::Simple::HTMLBatch::go.
1072This is basically just a shortcut for C<<
1073Pod::Simple::HTMLBatch->batch_convert(@ARGV) >>.
1074It's meant to be handy for calling from the command line.
1075
1076However, the shortcut requires that you specify exactly two command-line
1077arguments, C<indirs> and C<outdir>.
1078
1079Example:
1080
1081 % mkdir out_html
1082 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go @INC out_html
1083 (to convert the pod from Perl's @INC
1084 files under the directory ../htmlversion)
1085
1086(Note that the command line there contains a literal atsign-I-N-C. This
1087is handled as a special case by batch_convert, in order to save you having
1088to enter the odd-looking "" as the first command-line parameter when you
1089mean "just use whatever's in @INC".)
1090
1091Example:
1092
1093 % mkdir ../seekrut
1094 % chmod og-rx ../seekrut
1095 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go . ../htmlversion
1096 (to convert the pod under the current dir into HTML
1097 files under the directory ../htmlversion)
1098
1099Example:
1100
1101 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs .
1102 (to convert all pod from happydocs into the current directory)
1103
1104
1105
1106=head1 MAIN METHODS
1107
1108=over
1109
1110=item $batchconv = Pod::Simple::HTMLBatch->new;
1111
1112This TODO
1113
1114
1115=item $batchconv->batch_convert( I<indirs>, I<outdir> );
1116
1117this TODO
1118
1119=item $batchconv->batch_convert( undef , ...);
1120
1121=item $batchconv->batch_convert( q{@INC}, ...);
1122
1123These two values for I<indirs> specify that the normal Perl @INC
1124
1125=item $batchconv->batch_convert( \@dirs , ...);
1126
1127This specifies that the input directories are the items in
1128the arrayref C<\@dirs>.
1129
1130=item $batchconv->batch_convert( "somedir" , ...);
1131
1132This specifies that the director "somedir" is the input.
1133(This can be an absolute or relative path, it doesn't matter.)
1134
1135A common value you might want would be just "." for the current
1136directory:
1137
1138 $batchconv->batch_convert( "." , ...);
1139
1140
1141=item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1142
1143This specifies that you want the dirs "somedir", "somother", and "also"
1144scanned, just as if you'd passed the arrayref
1145C<[qw( somedir someother also)]>. Note that a ":"-separator is normal
1146under Unix, but Under MSWin, you'll need C<'somedir;someother;also'>
1147instead, since the pathsep on MSWin is ";" instead of ":". (And
1148I<that> is because ":" often comes up in paths, like
1149C<"c:/perl/lib">.)
1150
1151(Exactly what separator character should be used, is gotten from
1152C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1153
1154=item $batchconv->batch_convert( ... , undef );
1155
1156This specifies that you want the HTML output to go into the current
1157directory.
1158
1159(Note that a missing or undefined value means a different thing in
1160the first slot than in the second. That's so that C<batch_convert()>
1161with no arguments (or undef arguments) means "go from @INC, into
1162the current directory.)
1163
1164=item $batchconv->batch_convert( ... , 'somedir' );
1165
1166This specifies that you want the HTML output to go into the
1167directory 'somedir'.
1168(This can be an absolute or relative path, it doesn't matter.)
1169
1170=back
1171
1172
1173Note that you can also call C<batch_convert> as a class method,
1174like so:
1175
1176 Pod::Simple::HTMLBatch->batch_convert( ... );
1177
1178That is just short for this:
1179
1180 Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1181
1182That is, it runs a conversion with default options, for
1183whatever inputdirs and output dir you specify.
1184
1185
1186=head2 ACCESSOR METHODS
1187
1188The following are all accessor methods -- that is, they don't do anything
1189on their own, but just alter the contents of the conversion object,
1190which comprises the options for this particular batch conversion.
1191
1192We show the "put" form of the accessors below (i.e., the syntax you use
1193for setting the accessor to a specific value). But you can also
1194call each method with no parameters to get its current value. For
1195example, C<< $self->contents_file() >> returns the current value of
1196the contents_file attribute.
1197
1198=over
1199
1200
1201=item $batchconv->verbose( I<nonnegative_integer> );
1202
1203This controls how verbose to be during batch conversion, as far as
1204notes to STDOUT (or whatever is C<select>'d) about how the conversion
1205is going. If 0, no progress information is printed.
1206If 1 (the default value), some progress information is printed.
1207Higher values print more information.
1208
1209
1210=item $batchconv->index( I<true-or-false> );
1211
1212This controls whether or not each HTML page is liable to have a little
1213table of contents at the top (which we call an "index" for historical
1214reasons). This is true by default.
1215
1216
1217=item $batchconv->contents_file( I<filename> );
1218
1219If set, should be the name of a file (in the output directory)
1220to write the HTML index to. The default value is "index.html".
1221If you set this to a false value, no contents file will be written.
1222
1223=item $batchconv->contents_page_start( I<HTML_string> );
1224
1225This specifies what string should be put at the beginning of
1226the contents page.
1227The default is a string more or less like this:
1228
1229 <html>
1230 <head><title>Perl Documentation</title></head>
1231 <body class='contentspage'>
1232 <h1>Perl Documentation</h1>
1233
1234=item $batchconv->contents_page_end( I<HTML_string> );
1235
1236This specifies what string should be put at the end of the contents page.
1237The default is a string more or less like this:
1238
1239 <p class='contentsfooty'>Generated by
1240 Pod::Simple::HTMLBatch v3.01 under Perl v5.008
1241 <br >At Fri May 14 22:26:42 2004 GMT,
1242 which is Fri May 14 14:26:42 2004 local time.</p>
1243
1244
1245
1246=item $batchconv->add_css( $url );
1247
1248TODO
1249
1250=item $batchconv->add_javascript( $url );
1251
1252TODO
1253
1254=item $batchconv->css_flurry( I<true-or-false> );
1255
1256If true (the default value), we autogenerate some CSS files in the
1257output directory, and set our HTML files to use those.
1258TODO: continue
1259
1260=item $batchconv->javascript_flurry( I<true-or-false> );
1261
1262If true (the default value), we autogenerate a JavaScript in the
1263output directory, and set our HTML files to use it. Currently,
1264the JavaScript is used only to get the browser to remember what
1265stylesheet it prefers.
1266TODO: continue
1267
1268=item $batchconv->no_contents_links( I<true-or-false> );
1269
1270TODO
1271
1272=item $batchconv->html_render_class( I<classname> );
1273
1274This sets what class is used for rendering the files.
9d65762f 1275The default is "Pod::Simple::HTML". If you set it to something else,
1276it should probably be a subclass of Pod::Simple::HTML, and you should
1277C<require> or C<use> that class so that's it's loaded before
1278Pod::Simple::HTMLBatch tries loading it.
1279
1280=item $batchconv->search_class( I<classname> );
1281
1282This sets what class is used for searching for the files.
351625bd 1283The default is "Pod::Simple::Search". If you set it to something else,
1284it should probably be a subclass of Pod::Simple::Search, and you should
1285C<require> or C<use> that class so that's it's loaded before
1286Pod::Simple::HTMLBatch tries loading it.
1287
1288=back
1289
1290
1291
1292
1293=head1 NOTES ON CUSTOMIZATION
1294
1295TODO
1296
1297 call add_css($someurl) to add stylesheet as alternate
1298 call add_css($someurl,1) to add as primary stylesheet
1299
1300 call add_javascript
1301
1302 subclass Pod::Simple::HTML and set $batchconv->html_render_class to
1303 that classname
1304 and maybe override
1305 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
1306 or maybe override
1307 $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
9d65762f 1308 subclass Pod::Simple::Search and set $batchconv->search_class to
1309 that classname
351625bd 1310
1311
1312
1313=head1 ASK ME!
1314
1315If you want to do some kind of big pod-to-HTML version with some
1316particular kind of option that you don't see how to achieve using this
1317module, email me (C<sburke@cpan.org>) and I'll probably have a good idea
1318how to do it. For reasons of concision and energetic laziness, some
1319methods and options in this module (and the dozen modules it depends on)
1320are undocumented; but one of those undocumented bits might be just what
1321you're looking for.
1322
1323
1324=head1 SEE ALSO
1325
1326L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1327
1328
1329
1330
1331=head1 COPYRIGHT AND DISCLAIMERS
1332
1333Copyright (c) 2004 Sean M. Burke. All rights reserved.
1334
1335This library is free software; you can redistribute it and/or modify it
1336under the same terms as Perl itself.
1337
1338This program is distributed in the hope that it will be useful, but
1339without any warranty; without even the implied warranty of
1340merchantability or fitness for a particular purpose.
1341
1342=head1 AUTHOR
1343
1344Sean M. Burke C<sburke@cpan.org>
1345
1346=cut
1347
1348
1349