3 package Pod::Simple::HTMLBatch;
5 use vars qw( $VERSION $HTML_RENDER_CLASS $HTML_EXTENSION
6 $CSS $JAVASCRIPT $SLEEPY $SEARCH_CLASS @ISA
9 @ISA = (); # Yup, we're NOT a subclass of Pod::Simple::HTML!
11 # TODO: nocontents stylesheets. Strike some of the color variations?
13 use Pod::Simple::HTML ();
14 BEGIN {*esc = \&Pod::Simple::HTML::esc }
17 # "Isn't the Universe an amazing place? I wouldn't live anywhere else!"
19 use Pod::Simple::Search;
20 $SEARCH_CLASS ||= 'Pod::Simple::Search';
23 if(defined &DEBUG) { } # no-op
24 elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG }
25 else { *DEBUG = sub () {0}; }
28 $SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i;
29 # flag to occasionally sleep for $SLEEPY - 1 seconds.
31 $HTML_RENDER_CLASS ||= "Pod::Simple::HTML";
34 # Methods beginning with "_" are particularly internal and possibly ugly.
37 Pod::Simple::_accessorize( __PACKAGE__,
38 'verbose', # how verbose to be during batch conversion
39 'html_render_class', # what class to use to render
40 'search_class', # what to use to search for POD documents
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',
47 'css_flurry', '_css_wad', 'javascript_flurry', '_javascript_wad',
48 'no_contents_links', # set to true to suppress automatic adding of << links.
52 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
53 # Just so we can run from the command line more easily
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__,
60 if(defined($ARGV[1]) and length($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";
67 __PACKAGE__->batch_convert(@ARGV);
69 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
73 my $new = bless {}, ref($_[0]) || $_[0];
74 $new->html_render_class($HTML_RENDER_CLASS);
75 $new->search_class($SEARCH_CLASS);
76 $new->verbose(1 + DEBUG);
81 $new-> _css_wad([]); $new->css_flurry(1);
82 $new->_javascript_wad([]); $new->javascript_flurry(1);
85 'index' . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION)
88 $new->contents_page_start( join "\n", grep $_,
89 $Pod::Simple::HTML::Doctype_decl,
91 "<title>Perl Documentation</title>",
92 $Pod::Simple::HTML::Content_decl,
94 "\n<body class='contentspage'>\n<h1>Perl Documentation</h1>\n"
95 ); # override if you need a different title
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",
102 eval {$new->VERSION} || $VERSION,
103 $], scalar(gmtime), scalar(localtime),
109 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
114 print 'T+', int(time() - $self->{'_batch_start_time'}), "s: ", @_, "\n";
119 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
126 if(!defined($dirs) or $dirs eq '' or $dirs eq '@INC' ) {
129 # OK, it's an explicit set of dirs to scan, specified as an arrayref.
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!)
135 my $ps = quotemeta( $Config::Config{'path_sep'} || ":" );
136 $dirs = [ grep length($_), split qr/$ps/, $dirs ];
139 $outdir = $self->filespecsys->curdir
140 unless defined $outdir and length $outdir;
142 $self->_batch_convert_main($dirs, $outdir);
145 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
147 sub _batch_convert_main {
148 my($self, $dirs, $outdir) = @_;
149 # $dirs is either false, or an arrayref.
150 # $outdir is a pathspec.
152 $self->{'_batch_start_time'} ||= time();
154 $self->muse( "= ", scalar(localtime) );
155 $self->muse( "Starting batch conversion to \"$outdir\"" );
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
165 $self->progress($progress);
169 $self->muse(scalar(@$dirs), " dirs to scan: @$dirs");
171 $self->muse("Scanning \@INC. This could take a minute or two.");
173 my $mod2path = $self->find_all_pods($dirs ? $dirs : ());
174 $self->muse("Done scanning.");
176 my $total = keys %$mod2path;
178 $self->muse("No pod found. Aborting batch conversion.\n");
182 $progress and $progress->goal($total);
183 $self->muse("Now converting pod files to HTML.",
184 ($total > 25) ? " This will take a while more." : ()
187 $self->_spray_css( $outdir );
188 $self->_spray_javascript( $outdir );
190 $self->_do_all_batch_conversions($mod2path, $outdir);
192 $progress and $progress->done(sprintf (
193 "Done converting %d files.", $self->{"__batch_conv_page_count"}
195 return $self->_batch_convert_finish($outdir);
200 sub _do_all_batch_conversions {
201 my($self, $mod2path, $outdir) = @_;
202 $self->{"__batch_conv_page_count"} = 0;
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;
212 sub _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!");
221 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
223 sub _do_one_batch_conversion {
224 my($self, $module, $mod2path, $outdir, $outfile) = @_;
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
236 $n[-1] .= $HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION;
237 $self->filespecsys->catfile( $outdir, @n );
240 my $progress = $self->progress;
242 my $page = $self->html_render_class->new;
244 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: ",
245 ref($page), " render ($depth) $module => $outfile");
247 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: $module => $outfile")
250 # Give each class a chance to init the converter:
251 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
252 if $page->can('batch_mode_page_object_init');
255 $self->makepath($outdir => \@namelets);
257 $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module");
259 if( $retval = $page->parse_from_file($infile, $outfile) ) {
260 ++ $self->{"__batch_conv_page_count"} ;
261 $self->note_for_contents_file( \@namelets, $infile, $outfile );
263 $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false.");
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');
272 DEBUG > 4 and printf "%s %sb < $infile %s %sb\n",
273 $outfile, -s $outfile, $infile, -s $infile
280 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
281 sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
283 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
285 sub note_for_contents_file {
286 my($self, $namelets, $infile, $outfile) = @_;
288 # I think the infile and outfile parts are never used. -- SMB
289 # But it's handy to have them around for debugging.
291 if( $self->contents_file ) {
292 my $c = $self->_contents();
294 [ join("::", @$namelets), $infile, $outfile, $namelets ]
297 DEBUG > 3 and print "Noting @$c[-1]\n";
302 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
304 sub write_contents_file {
305 my($self, $outdir) = @_;
306 my $outfile = $self->_contents_filespec($outdir) || return;
308 $self->muse("Preparing list of modules for ToC");
310 my($toplevel, # maps toplevelbit => [all submodules]
311 $toplevel_form_freq, # ends up being 'foo' => 'Foo'
312 ) = $self->_prep_contents_breakdown;
314 my $Contents = eval { $self->_wopen($outfile) };
316 $self->muse( "Writing contents file $outfile" );
318 warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all";
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, );
328 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
330 sub _write_contents_start {
331 my($self, $Contents, $outfile) = @_;
332 my $starter = $self->contents_page_start || '';
335 my $css_wad = $self->_css_wad_to_markup(1);
337 $starter =~ s{(</head>)}{\n$css_wad\n$1}i; # otherwise nevermind
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
346 unless(print $Contents $starter, "<dl class='superindex'>\n" ) {
347 warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
354 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
356 sub _write_contents_middle {
357 my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_;
359 foreach my $t (sort keys %$toplevel2submodules) {
360 my @downlines = sort {$a->[-1] cmp $b->[-1]}
361 @{ $toplevel2submodules->{$t} };
363 printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n],
364 esc( $t, $toplevel_form_freq->{$t} )
368 foreach my $e (@downlines) {
370 $path = join( "/", '.', esc( @{$e->[3]} ) )
371 . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION);
372 print $Contents qq{ <a href="$path">}, esc($name), "</a> \n";
374 print $Contents "</dd>\n\n";
379 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
381 sub _write_contents_end {
382 my($self, $Contents, $outfile) = @_;
384 print $Contents "</dl>\n",
385 $self->contents_page_end || '',
387 warn "Couldn't write to $outfile: $!";
389 close($Contents) or warn "Couldn't close $outfile: $!";
393 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
395 sub _prep_contents_breakdown {
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)
402 foreach my $entry (@$contents) {
404 $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs'
405 # group all the perlwhatever docs together
406 : $entry->[3][0] # normal case
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
413 foreach my $toplevel (sort keys %toplevel) {
414 my $fgroup = $toplevel_form_freq{$toplevel};
415 $toplevel_form_freq{$toplevel} =
417 sort { $fgroup->{$b} <=> $fgroup->{$a} or $a cmp $b }
419 # This hash is extremely unlikely to have more than 4 members, so this
420 # sort isn't so very wasteful
424 return(\%toplevel, \%toplevel_form_freq) if wantarray;
428 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
430 sub _contents_filespec {
431 my($self, $outdir) = @_;
432 my $outfile = $self->contents_file;
433 return unless $outfile;
434 return $self->filespecsys->catfile( $outdir, $outfile );
437 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
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] );
445 die "$dir exists but not as a directory!?" unless -d $dir;
448 DEBUG > 3 and print " Making $dir\n";
450 or die "Can't mkdir $dir: $!\nAborting"
456 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
458 sub batch_mode_page_object_init {
460 my($page, $module, $infile, $outfile, $depth) = @_;
462 # TODO: any further options to percolate onto this new object here?
464 $page->default_title($module);
465 $page->index( $self->index );
467 $page->html_css( $self-> _css_wad_to_markup($depth) );
468 $page->html_javascript( $self->_javascript_wad_to_markup($depth) );
470 $self->add_header_backlink($page, $module, $infile, $outfile, $depth);
471 $self->add_footer_backlink($page, $module, $infile, $outfile, $depth);
477 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
479 sub add_header_backlink {
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 || '',
486 qq[<p class="backlinktop"><b><a name="___top" href="],
487 $self->url_up_to_contents($depth),
488 qq[" accesskey="1" title="All Documents"><<</a></b></p>\n],
490 if $self->contents_file
495 sub add_footer_backlink {
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"><<</a></b></p>\n],
504 $page->html_footer || '',
506 if $self->contents_file
511 sub url_up_to_contents {
512 my($self, $depth) = @_;
514 return join '/', ('..') x $depth, esc($self->contents_file);
517 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
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);
527 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
529 sub modnames2paths { # return a hashref mapping modulenames => paths
530 my($self, $dirs) = @_;
534 my $search = $self->search_class->new;
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;
545 $self->muse("That's odd... no modules found!") unless keys %$m2p;
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";
551 print "(total ", scalar(keys %$m2p), ")\n\n";
553 print "Found ", scalar(keys %$m2p), " modules.\n";
555 $self->muse( "Found ", scalar(keys %$m2p), " modules." );
557 # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref
561 #===========================================================================
564 # this is abstracted out so that the daemon class can override it
565 my($self, $outpath) = @_;
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");
571 Carp::croak("Can't write-open $outpath: $!");
574 #==========================================================================
577 my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_;
580 # cook up a reasonable name based on the URL
582 if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) {
588 $content_type ||= 'text/css';
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 }
596 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
599 my($self, $outdir) = @_;
601 return unless $self->css_flurry();
602 $self->_gen_css_wad();
604 my $lol = $self->_css_wad;
605 foreach my $chunk (@$lol) {
606 my $url = $chunk->[0];
608 if( ref($chunk->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.css$)} ) {
609 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
610 DEBUG > 5 and print "Noting $$chunk[0] as a file I'll create.\n";
612 DEBUG > 5 and print "OK, noting $$chunk[0] as an external CSS.\n";
613 # Requires no further attention.
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";
622 DEBUG > 5 and print "Wrote $outfile\n";
628 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
630 sub _css_wad_to_markup {
631 my($self, $depth) = @_;
633 my @css = @{ $self->_css_wad || return '' };
634 return '' unless @css;
636 my $rel = 'stylesheet';
640 my $uplink = $depth ? ('../' x $depth) : '';
642 foreach my $chunk (@css) {
643 next unless $chunk and @$chunk;
645 my( $url1, $url2, $title, $type, $media) = (
646 $self->_maybe_uplink( $chunk->[0], $uplink ),
647 esc(grep !ref($_), @$chunk)
650 $out .= qq{<link rel="$rel" title="$title" type="$type" href="$url1$url2" media="$media" >\n};
652 $rel = 'alternate stylesheet'; # alternates = all non-first iterations
657 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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{[/\:]} )
665 # qualify it, if/as needed
668 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
671 my $css_template = $self->_css_template;
672 foreach my $variation (
674 # Commented out for sake of concision:
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
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
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!
700 "/* This file is autogenerated. Do not edit. $variation */\n\n"
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;
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;
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
718 $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
721 # Now a few indexless variations:
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
728 my $outname = "$variation\_";
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; }",
737 $self->add_css( "$outname.css", 0, $name, 0, 0, \$this_css);
745 $x =~ tr[0123456789abcdef]
750 #===========================================================================
753 my($self, $url, $content_type, $_code) = @_;
755 push @{ $self->_javascript_wad }, [
756 $url, $content_type || 'text/javascript', $_code
761 sub _spray_javascript {
762 my($self, $outdir) = @_;
763 return unless $self->javascript_flurry();
764 $self->_gen_javascript_wad();
766 my $lol = $self->_javascript_wad;
767 foreach my $script (@$lol) {
768 my $url = $script->[0];
771 if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) {
772 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
773 DEBUG > 5 and print "Noting $$script[0] as a file I'll create.\n";
775 DEBUG > 5 and print "OK, noting $$script[0] as an external JavaScript.\n";
779 #$self->muse( "Writing JavaScript file $outfile" );
780 my $Jsout = $self->_wopen($outfile);
782 print $Jsout ${$script->[-1]}
783 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
785 DEBUG > 5 and print "Wrote $outfile\n";
791 sub _gen_javascript_wad {
793 my $js_code = $self->_javascript || return;
794 $self->add_javascript( "_podly.js", 0, \$js_code);
798 sub _javascript_wad_to_markup {
799 my($self, $depth) = @_;
801 my @scripts = @{ $self->_javascript_wad || return '' };
802 return '' unless @scripts;
807 my $uplink = $depth ? ('../' x $depth) : '';
809 foreach my $s (@scripts) {
810 next unless $s and @$s;
812 my( $url1, $url2, $type, $media) = (
813 $self->_maybe_uplink( $s->[0], $uplink ),
814 esc(grep !ref($_), @$s)
817 $out .= qq{<script type="$type" src="$url1$url2"></script>\n};
822 #===========================================================================
824 sub _css_template { return $CSS }
825 sub _javascript { return $JAVASCRIPT }
828 /* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */
830 @media all { .hide { display: none; } }
833 .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none }
836 border-color: black !important;
837 color: black !important;
838 background-color: transparent !important;
839 background-image: none !important;
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"; }
856 dl.superindex > dt { pause-before: 600ms; }
859 @media screen, tty, tv, projection {
860 .noscreen { display: none; }
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; }
871 background-color: #000;
874 body.pod h1, body.pod h2, body.pod h3, body.pod h4 {
875 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
879 border-top: thin solid transparent;
880 /* margin-left: -5px; border-left: 2px #7070ff solid; padding-left: 3px; */
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; }
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; }
894 font-size: 105%; /* just a wee bit more than normal */
897 .indexgroup { font-size: 80%; }
899 .backlinktop, .backlinkbottom {
902 background-color: #040;
903 border-top: thin solid #050;
904 border-bottom: thin solid #050;
907 .backlinktop a, .backlinkbottom a {
908 text-decoration: none;
910 background-color: #000;
911 border: thin solid #0d0;
913 .backlinkbottom { margin-bottom: 0; padding-bottom: 0; }
914 .backlinktop { margin-top: 0; padding-top: 0; }
918 background-color: #000;
921 body.contentspage h1 {
926 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
928 border-top: thin solid #fff;
929 border-bottom: thin solid #fff;
934 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
938 /* margin-bottom: -.15em; */
941 word-spacing: .6em; /* most important rule here! */
943 dl.superindex > a:link {
944 text-decoration: none;
949 border-top: thin solid #999;
959 #==========================================================================
961 $JAVASCRIPT = <<'EOJAVASCRIPT';
963 // From http://www.alistapart.com/articles/alternate/
965 function setActiveStyleSheet(title) {
967 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
968 if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
970 if(a.getAttribute("title") == title) a.disabled = false;
975 function getActiveStyleSheet() {
977 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
978 if( a.getAttribute("rel").indexOf("style") != -1
979 && a.getAttribute("title")
981 ) return a.getAttribute("title");
986 function getPreferredStyleSheet() {
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");
997 function createCookie(name,value,days) {
999 var date = new Date();
1000 date.setTime(date.getTime()+(days*24*60*60*1000));
1001 var expires = "; expires="+date.toGMTString();
1004 document.cookie = name+"="+value+expires+"; path=/";
1007 function readCookie(name) {
1008 var nameEQ = name + "=";
1009 var ca = document.cookie.split(';');
1010 for(var i=0 ; i < ca.length ; 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);
1018 window.onload = function(e) {
1019 var cookie = readCookie("style");
1020 var title = cookie ? cookie : getPreferredStyleSheet();
1021 setActiveStyleSheet(title);
1024 window.onunload = function(e) {
1025 var title = getActiveStyleSheet();
1026 createCookie("style", title, 365);
1029 var cookie = readCookie("style");
1030 var title = cookie ? cookie : getPreferredStyleSheet();
1031 setActiveStyleSheet(title);
1037 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1044 Pod::Simple::HTMLBatch - convert several Pod files to several HTML files
1048 perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out
1053 This module is used for running batch-conversions of a lot of HTML
1056 This class is NOT a subclass of Pod::Simple::HTML
1057 (nor of bad old Pod::Html) -- although it uses
1058 Pod::Simple::HTML for doing the conversion of each document.
1060 The normal use of this class is like so:
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 );
1068 =head2 FROM THE COMMAND LINE
1070 Note that this class also provides
1071 (but does not export) the function Pod::Simple::HTMLBatch::go.
1072 This is basically just a shortcut for C<<
1073 Pod::Simple::HTMLBatch->batch_convert(@ARGV) >>.
1074 It's meant to be handy for calling from the command line.
1076 However, the shortcut requires that you specify exactly two command-line
1077 arguments, C<indirs> and C<outdir>.
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)
1086 (Note that the command line there contains a literal atsign-I-N-C. This
1087 is handled as a special case by batch_convert, in order to save you having
1088 to enter the odd-looking "" as the first command-line parameter when you
1089 mean "just use whatever's in @INC".)
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)
1101 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs .
1102 (to convert all pod from happydocs into the current directory)
1110 =item $batchconv = Pod::Simple::HTMLBatch->new;
1115 =item $batchconv->batch_convert( I<indirs>, I<outdir> );
1119 =item $batchconv->batch_convert( undef , ...);
1121 =item $batchconv->batch_convert( q{@INC}, ...);
1123 These two values for I<indirs> specify that the normal Perl @INC
1125 =item $batchconv->batch_convert( \@dirs , ...);
1127 This specifies that the input directories are the items in
1128 the arrayref C<\@dirs>.
1130 =item $batchconv->batch_convert( "somedir" , ...);
1132 This specifies that the director "somedir" is the input.
1133 (This can be an absolute or relative path, it doesn't matter.)
1135 A common value you might want would be just "." for the current
1138 $batchconv->batch_convert( "." , ...);
1141 =item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1143 This specifies that you want the dirs "somedir", "somother", and "also"
1144 scanned, just as if you'd passed the arrayref
1145 C<[qw( somedir someother also)]>. Note that a ":"-separator is normal
1146 under Unix, but Under MSWin, you'll need C<'somedir;someother;also'>
1147 instead, since the pathsep on MSWin is ";" instead of ":". (And
1148 I<that> is because ":" often comes up in paths, like
1151 (Exactly what separator character should be used, is gotten from
1152 C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1154 =item $batchconv->batch_convert( ... , undef );
1156 This specifies that you want the HTML output to go into the current
1159 (Note that a missing or undefined value means a different thing in
1160 the first slot than in the second. That's so that C<batch_convert()>
1161 with no arguments (or undef arguments) means "go from @INC, into
1162 the current directory.)
1164 =item $batchconv->batch_convert( ... , 'somedir' );
1166 This specifies that you want the HTML output to go into the
1167 directory 'somedir'.
1168 (This can be an absolute or relative path, it doesn't matter.)
1173 Note that you can also call C<batch_convert> as a class method,
1176 Pod::Simple::HTMLBatch->batch_convert( ... );
1178 That is just short for this:
1180 Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1182 That is, it runs a conversion with default options, for
1183 whatever inputdirs and output dir you specify.
1186 =head2 ACCESSOR METHODS
1188 The following are all accessor methods -- that is, they don't do anything
1189 on their own, but just alter the contents of the conversion object,
1190 which comprises the options for this particular batch conversion.
1192 We show the "put" form of the accessors below (i.e., the syntax you use
1193 for setting the accessor to a specific value). But you can also
1194 call each method with no parameters to get its current value. For
1195 example, C<< $self->contents_file() >> returns the current value of
1196 the contents_file attribute.
1201 =item $batchconv->verbose( I<nonnegative_integer> );
1203 This controls how verbose to be during batch conversion, as far as
1204 notes to STDOUT (or whatever is C<select>'d) about how the conversion
1205 is going. If 0, no progress information is printed.
1206 If 1 (the default value), some progress information is printed.
1207 Higher values print more information.
1210 =item $batchconv->index( I<true-or-false> );
1212 This controls whether or not each HTML page is liable to have a little
1213 table of contents at the top (which we call an "index" for historical
1214 reasons). This is true by default.
1217 =item $batchconv->contents_file( I<filename> );
1219 If set, should be the name of a file (in the output directory)
1220 to write the HTML index to. The default value is "index.html".
1221 If you set this to a false value, no contents file will be written.
1223 =item $batchconv->contents_page_start( I<HTML_string> );
1225 This specifies what string should be put at the beginning of
1227 The default is a string more or less like this:
1230 <head><title>Perl Documentation</title></head>
1231 <body class='contentspage'>
1232 <h1>Perl Documentation</h1>
1234 =item $batchconv->contents_page_end( I<HTML_string> );
1236 This specifies what string should be put at the end of the contents page.
1237 The default is a string more or less like this:
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>
1246 =item $batchconv->add_css( $url );
1250 =item $batchconv->add_javascript( $url );
1254 =item $batchconv->css_flurry( I<true-or-false> );
1256 If true (the default value), we autogenerate some CSS files in the
1257 output directory, and set our HTML files to use those.
1260 =item $batchconv->javascript_flurry( I<true-or-false> );
1262 If true (the default value), we autogenerate a JavaScript in the
1263 output directory, and set our HTML files to use it. Currently,
1264 the JavaScript is used only to get the browser to remember what
1265 stylesheet it prefers.
1268 =item $batchconv->no_contents_links( I<true-or-false> );
1272 =item $batchconv->html_render_class( I<classname> );
1274 This sets what class is used for rendering the files.
1275 The default is "Pod::Simple::HTML". If you set it to something else,
1276 it should probably be a subclass of Pod::Simple::HTML, and you should
1277 C<require> or C<use> that class so that's it's loaded before
1278 Pod::Simple::HTMLBatch tries loading it.
1280 =item $batchconv->search_class( I<classname> );
1282 This sets what class is used for searching for the files.
1283 The default is "Pod::Simple::Search". If you set it to something else,
1284 it should probably be a subclass of Pod::Simple::Search, and you should
1285 C<require> or C<use> that class so that's it's loaded before
1286 Pod::Simple::HTMLBatch tries loading it.
1293 =head1 NOTES ON CUSTOMIZATION
1297 call add_css($someurl) to add stylesheet as alternate
1298 call add_css($someurl,1) to add as primary stylesheet
1302 subclass Pod::Simple::HTML and set $batchconv->html_render_class to
1305 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
1307 $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
1308 subclass Pod::Simple::Search and set $batchconv->search_class to
1315 If you want to do some kind of big pod-to-HTML version with some
1316 particular kind of option that you don't see how to achieve using this
1317 module, email me (C<sburke@cpan.org>) and I'll probably have a good idea
1318 how to do it. For reasons of concision and energetic laziness, some
1319 methods and options in this module (and the dozen modules it depends on)
1320 are undocumented; but one of those undocumented bits might be just what
1326 L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1331 =head1 COPYRIGHT AND DISCLAIMERS
1333 Copyright (c) 2004 Sean M. Burke. All rights reserved.
1335 This library is free software; you can redistribute it and/or modify it
1336 under the same terms as Perl itself.
1338 This program is distributed in the hope that it will be useful, but
1339 without any warranty; without even the implied warranty of
1340 merchantability or fitness for a particular purpose.
1344 Sean M. Burke C<sburke@cpan.org>