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 use Pod::Simple::Search;
18 $SEARCH_CLASS ||= 'Pod::Simple::Search';
21 if(defined &DEBUG) { } # no-op
22 elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG }
23 else { *DEBUG = sub () {0}; }
26 $SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i;
27 # flag to occasionally sleep for $SLEEPY - 1 seconds.
29 $HTML_RENDER_CLASS ||= "Pod::Simple::HTML";
32 # Methods beginning with "_" are particularly internal and possibly ugly.
35 Pod::Simple::_accessorize( __PACKAGE__,
36 'verbose', # how verbose to be during batch conversion
37 'html_render_class', # what class to use to render
38 'search_class', # what to use to search for POD documents
39 'contents_file', # If set, should be the name of a file (in current directory)
40 # to write the list of all modules to
41 'index', # will set $htmlpage->index(...) to this (true or false)
42 'progress', # progress object
43 'contents_page_start', 'contents_page_end',
45 'css_flurry', '_css_wad', 'javascript_flurry', '_javascript_wad',
46 'no_contents_links', # set to true to suppress automatic adding of << links.
50 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
51 # Just so we can run from the command line more easily
53 @ARGV == 2 or die sprintf(
54 "Usage: perl -M%s -e %s:go indirs outdir\n (or use \"\@INC\" for indirs)\n",
55 __PACKAGE__, __PACKAGE__,
58 if(defined($ARGV[1]) and length($ARGV[1])) {
60 -e $d or die "I see no output directory named \"$d\"\nAborting";
61 -d $d or die "But \"$d\" isn't a directory!\nAborting";
62 -w $d or die "Directory \"$d\" isn't writeable!\nAborting";
65 __PACKAGE__->batch_convert(@ARGV);
67 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
71 my $new = bless {}, ref($_[0]) || $_[0];
72 $new->html_render_class($HTML_RENDER_CLASS);
73 $new->search_class($SEARCH_CLASS);
74 $new->verbose(1 + DEBUG);
79 $new-> _css_wad([]); $new->css_flurry(1);
80 $new->_javascript_wad([]); $new->javascript_flurry(1);
83 'index' . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION)
86 $new->contents_page_start( join "\n", grep $_,
87 $Pod::Simple::HTML::Doctype_decl,
89 "<title>Perl Documentation</title>",
90 $Pod::Simple::HTML::Content_decl,
92 "\n<body class='contentspage'>\n<h1>Perl Documentation</h1>\n"
93 ); # override if you need a different title
96 $new->contents_page_end( sprintf(
97 "\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 eval {$new->VERSION} || $VERSION,
101 $], scalar(gmtime), scalar(localtime),
107 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
112 print 'T+', int(time() - $self->{'_batch_start_time'}), "s: ", @_, "\n";
117 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
120 my($self, $dirs, $outdir) = @_;
121 $self ||= __PACKAGE__; # tolerate being called as an optionless function
122 $self = $self->new unless ref $self; # tolerate being used as a class method
124 if(!defined($dirs) or $dirs eq '' or $dirs eq '@INC' ) {
127 # OK, it's an explicit set of dirs to scan, specified as an arrayref.
129 # OK, it's an explicit set of dirs to scan, specified as a
130 # string like "/thing:/also:/whatever/perl" (":"-delim, as usual)
131 # or, under MSWin, like "c:/thing;d:/also;c:/whatever/perl" (";"-delim!)
133 my $ps = quotemeta( $Config::Config{'path_sep'} || ":" );
134 $dirs = [ grep length($_), split qr/$ps/, $dirs ];
137 $outdir = $self->filespecsys->curdir
138 unless defined $outdir and length $outdir;
140 $self->_batch_convert_main($dirs, $outdir);
143 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
145 sub _batch_convert_main {
146 my($self, $dirs, $outdir) = @_;
147 # $dirs is either false, or an arrayref.
148 # $outdir is a pathspec.
150 $self->{'_batch_start_time'} ||= time();
152 $self->muse( "= ", scalar(localtime) );
153 $self->muse( "Starting batch conversion to \"$outdir\"" );
155 my $progress = $self->progress;
156 if(!$progress and $self->verbose > 0 and $self->verbose() <= 5) {
157 require Pod::Simple::Progress;
158 $progress = Pod::Simple::Progress->new(
159 ($self->verbose < 2) ? () # Default omission-delay
160 : ($self->verbose == 2) ? 1 # Reduce the omission-delay
161 : 0 # Eliminate the omission-delay
163 $self->progress($progress);
167 $self->muse(scalar(@$dirs), " dirs to scan: @$dirs");
169 $self->muse("Scanning \@INC. This could take a minute or two.");
171 my $mod2path = $self->find_all_pods($dirs ? $dirs : ());
172 $self->muse("Done scanning.");
174 my $total = keys %$mod2path;
176 $self->muse("No pod found. Aborting batch conversion.\n");
180 $progress and $progress->goal($total);
181 $self->muse("Now converting pod files to HTML.",
182 ($total > 25) ? " This will take a while more." : ()
185 $self->_spray_css( $outdir );
186 $self->_spray_javascript( $outdir );
188 $self->_do_all_batch_conversions($mod2path, $outdir);
190 $progress and $progress->done(sprintf (
191 "Done converting %d files.", $self->{"__batch_conv_page_count"}
193 return $self->_batch_convert_finish($outdir);
198 sub _do_all_batch_conversions {
199 my($self, $mod2path, $outdir) = @_;
200 $self->{"__batch_conv_page_count"} = 0;
202 foreach my $module (sort {lc($a) cmp lc($b)} keys %$mod2path) {
203 $self->_do_one_batch_conversion($module, $mod2path, $outdir);
204 sleep($SLEEPY - 1) if $SLEEPY;
210 sub _batch_convert_finish {
211 my($self, $outdir) = @_;
212 $self->write_contents_file($outdir);
213 $self->muse("Done with batch conversion. $$self{'__batch_conv_page_count'} files done.");
214 $self->muse( "= ", scalar(localtime) );
215 $self->progress and $self->progress->done("All done!");
219 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
221 sub _do_one_batch_conversion {
222 my($self, $module, $mod2path, $outdir, $outfile) = @_;
225 my $total = scalar keys %$mod2path;
226 my $infile = $mod2path->{$module};
227 my @namelets = grep m/\S/, split "::", $module;
228 # this can stick around in the contents LoL
229 my $depth = scalar @namelets;
230 die "Contentless thingie?! $module $infile" unless @namelets; #sanity
234 $n[-1] .= $HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION;
235 $self->filespecsys->catfile( $outdir, @n );
238 my $progress = $self->progress;
240 my $page = $self->html_render_class->new;
242 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: ",
243 ref($page), " render ($depth) $module => $outfile");
245 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: $module => $outfile")
248 # Give each class a chance to init the converter:
249 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
250 if $page->can('batch_mode_page_object_init');
251 # Init for the index (TOC), too.
252 $self->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
253 if $self->can('batch_mode_page_object_init');
256 $self->makepath($outdir => \@namelets);
258 $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module");
260 if( $retval = $page->parse_from_file($infile, $outfile) ) {
261 ++ $self->{"__batch_conv_page_count"} ;
262 $self->note_for_contents_file( \@namelets, $infile, $outfile );
264 $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false.");
267 $page->batch_mode_page_object_kill($self, $module, $infile, $outfile, $depth)
268 if $page->can('batch_mode_page_object_kill');
269 # The following isn't a typo. Note that it switches $self and $page.
270 $self->batch_mode_page_object_kill($page, $module, $infile, $outfile, $depth)
271 if $self->can('batch_mode_page_object_kill');
273 DEBUG > 4 and printf "%s %sb < $infile %s %sb\n",
274 $outfile, -s $outfile, $infile, -s $infile
281 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
282 sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
284 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
286 sub note_for_contents_file {
287 my($self, $namelets, $infile, $outfile) = @_;
289 # I think the infile and outfile parts are never used. -- SMB
290 # But it's handy to have them around for debugging.
292 if( $self->contents_file ) {
293 my $c = $self->_contents();
295 [ join("::", @$namelets), $infile, $outfile, $namelets ]
298 DEBUG > 3 and print "Noting @$c[-1]\n";
303 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
305 sub write_contents_file {
306 my($self, $outdir) = @_;
307 my $outfile = $self->_contents_filespec($outdir) || return;
309 $self->muse("Preparing list of modules for ToC");
311 my($toplevel, # maps toplevelbit => [all submodules]
312 $toplevel_form_freq, # ends up being 'foo' => 'Foo'
313 ) = $self->_prep_contents_breakdown;
315 my $Contents = eval { $self->_wopen($outfile) };
317 $self->muse( "Writing contents file $outfile" );
319 warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all";
323 $self->_write_contents_start( $Contents, $outfile, );
324 $self->_write_contents_middle( $Contents, $outfile, $toplevel, $toplevel_form_freq );
325 $self->_write_contents_end( $Contents, $outfile, );
329 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
331 sub _write_contents_start {
332 my($self, $Contents, $outfile) = @_;
333 my $starter = $self->contents_page_start || '';
336 my $css_wad = $self->_css_wad_to_markup(1);
338 $starter =~ s{(</head>)}{\n$css_wad\n$1}i; # otherwise nevermind
341 my $javascript_wad = $self->_javascript_wad_to_markup(1);
342 if( $javascript_wad ) {
343 $starter =~ s{(</head>)}{\n$javascript_wad\n$1}i; # otherwise nevermind
347 unless(print $Contents $starter, "<dl class='superindex'>\n" ) {
348 warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
355 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
357 sub _write_contents_middle {
358 my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_;
360 foreach my $t (sort keys %$toplevel2submodules) {
361 my @downlines = sort {$a->[-1] cmp $b->[-1]}
362 @{ $toplevel2submodules->{$t} };
364 printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n],
365 esc( $t, $toplevel_form_freq->{$t} )
369 foreach my $e (@downlines) {
371 $path = join( "/", '.', esc( @{$e->[3]} ) )
372 . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION);
373 print $Contents qq{ <a href="$path">}, esc($name), "</a> \n";
375 print $Contents "</dd>\n\n";
380 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
382 sub _write_contents_end {
383 my($self, $Contents, $outfile) = @_;
385 print $Contents "</dl>\n",
386 $self->contents_page_end || '',
388 warn "Couldn't write to $outfile: $!";
390 close($Contents) or warn "Couldn't close $outfile: $!";
394 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
396 sub _prep_contents_breakdown {
398 my $contents = $self->_contents;
399 my %toplevel; # maps lctoplevelbit => [all submodules]
400 my %toplevel_form_freq; # ends up being 'foo' => 'Foo'
401 # (mapping anycase forms to most freq form)
403 foreach my $entry (@$contents) {
405 $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs'
406 # group all the perlwhatever docs together
407 : $entry->[3][0] # normal case
409 ++$toplevel_form_freq{ lc $toplevel }{ $toplevel };
410 push @{ $toplevel{ lc $toplevel } }, $entry;
411 push @$entry, lc($entry->[0]); # add a sort-order key to the end
414 foreach my $toplevel (sort keys %toplevel) {
415 my $fgroup = $toplevel_form_freq{$toplevel};
416 $toplevel_form_freq{$toplevel} =
418 sort { $fgroup->{$b} <=> $fgroup->{$a} or $a cmp $b }
420 # This hash is extremely unlikely to have more than 4 members, so this
421 # sort isn't so very wasteful
425 return(\%toplevel, \%toplevel_form_freq) if wantarray;
429 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
431 sub _contents_filespec {
432 my($self, $outdir) = @_;
433 my $outfile = $self->contents_file;
434 return unless $outfile;
435 return $self->filespecsys->catfile( $outdir, $outfile );
438 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
441 my($self, $outdir, $namelets) = @_;
442 return unless @$namelets > 1;
443 for my $i (0 .. ($#$namelets - 1)) {
444 my $dir = $self->filespecsys->catdir( $outdir, @$namelets[0 .. $i] );
446 die "$dir exists but not as a directory!?" unless -d $dir;
449 DEBUG > 3 and print " Making $dir\n";
451 or die "Can't mkdir $dir: $!\nAborting"
457 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
459 sub batch_mode_page_object_init {
461 my($page, $module, $infile, $outfile, $depth) = @_;
463 # TODO: any further options to percolate onto this new object here?
465 $page->default_title($module);
466 $page->index( $self->index );
468 $page->html_css( $self-> _css_wad_to_markup($depth) );
469 $page->html_javascript( $self->_javascript_wad_to_markup($depth) );
471 $self->add_header_backlink($page, $module, $infile, $outfile, $depth);
472 $self->add_footer_backlink($page, $module, $infile, $outfile, $depth);
478 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
480 sub add_header_backlink {
482 return if $self->no_contents_links;
483 my($page, $module, $infile, $outfile, $depth) = @_;
484 $page->html_header_after_title( join '',
485 $page->html_header_after_title || '',
487 qq[<p class="backlinktop"><b><a name="___top" href="],
488 $self->url_up_to_contents($depth),
489 qq[" accesskey="1" title="All Documents"><<</a></b></p>\n],
491 if $self->contents_file
496 sub add_footer_backlink {
498 return if $self->no_contents_links;
499 my($page, $module, $infile, $outfile, $depth) = @_;
500 $page->html_footer( join '',
501 qq[<p class="backlinkbottom"><b><a name="___bottom" href="],
502 $self->url_up_to_contents($depth),
503 qq[" title="All Documents"><<</a></b></p>\n],
505 $page->html_footer || '',
507 if $self->contents_file
512 sub url_up_to_contents {
513 my($self, $depth) = @_;
515 return join '/', ('..') x $depth, esc($self->contents_file);
518 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
521 my($self, $dirs) = @_;
522 # You can override find_all_pods in a subclass if you want to
523 # do extra filtering or whatnot. But for the moment, we just
524 # pass to modnames2paths:
525 return $self->modnames2paths($dirs);
528 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
530 sub modnames2paths { # return a hashref mapping modulenames => paths
531 my($self, $dirs) = @_;
535 my $search = $self->search_class->new;
536 DEBUG and print "Searching via $search\n";
537 $search->verbose(1) if DEBUG > 10;
538 $search->progress( $self->progress->copy->goal(0) ) if $self->progress;
539 $search->shadows(0); # don't bother noting shadowed files
540 $search->inc( $dirs ? 0 : 1 );
541 $search->survey( $dirs ? @$dirs : () );
542 $m2p = $search->name2path;
543 die "What, no name2path?!" unless $m2p;
546 $self->muse("That's odd... no modules found!") unless keys %$m2p;
548 print "Modules found (name => path):\n";
549 foreach my $m (sort {lc($a) cmp lc($b)} keys %$m2p) {
550 print " $m $$m2p{$m}\n";
552 print "(total ", scalar(keys %$m2p), ")\n\n";
554 print "Found ", scalar(keys %$m2p), " modules.\n";
556 $self->muse( "Found ", scalar(keys %$m2p), " modules." );
558 # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref
562 #===========================================================================
565 # this is abstracted out so that the daemon class can override it
566 my($self, $outpath) = @_;
568 my $out_fh = Symbol::gensym();
569 DEBUG > 5 and print "Write-opening to $outpath\n";
570 return $out_fh if open($out_fh, "> $outpath");
572 Carp::croak("Can't write-open $outpath: $!");
575 #==========================================================================
578 my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_;
581 # cook up a reasonable name based on the URL
583 if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) {
589 $content_type ||= 'text/css';
591 my $bunch = [$url, $name, $content_type, $media, $_code];
592 if($is_default) { unshift @{ $self->_css_wad }, $bunch }
593 else { push @{ $self->_css_wad }, $bunch }
597 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
600 my($self, $outdir) = @_;
602 return unless $self->css_flurry();
603 $self->_gen_css_wad();
605 my $lol = $self->_css_wad;
606 foreach my $chunk (@$lol) {
607 my $url = $chunk->[0];
609 if( ref($chunk->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.css$)} ) {
610 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
611 DEBUG > 5 and print "Noting $$chunk[0] as a file I'll create.\n";
613 DEBUG > 5 and print "OK, noting $$chunk[0] as an external CSS.\n";
614 # Requires no further attention.
618 #$self->muse( "Writing autogenerated CSS file $outfile" );
619 my $Cssout = $self->_wopen($outfile);
620 print $Cssout ${$chunk->[-1]}
621 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
623 DEBUG > 5 and print "Wrote $outfile\n";
629 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
631 sub _css_wad_to_markup {
632 my($self, $depth) = @_;
634 my @css = @{ $self->_css_wad || return '' };
635 return '' unless @css;
637 my $rel = 'stylesheet';
641 my $uplink = $depth ? ('../' x $depth) : '';
643 foreach my $chunk (@css) {
644 next unless $chunk and @$chunk;
646 my( $url1, $url2, $title, $type, $media) = (
647 $self->_maybe_uplink( $chunk->[0], $uplink ),
648 esc(grep !ref($_), @$chunk)
651 $out .= qq{<link rel="$rel" title="$title" type="$type" href="$url1$url2" media="$media" >\n};
653 $rel = 'alternate stylesheet'; # alternates = all non-first iterations
658 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
660 # if the given URL looks relative, return the given uplink string --
661 # otherwise return emptystring
662 my($self, $url, $uplink) = @_;
663 ($url =~ m{^\./} or $url !~ m{[/\:]} )
666 # qualify it, if/as needed
669 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
672 my $css_template = $self->_css_template;
673 foreach my $variation (
675 # Commented out for sake of concision:
677 # 011n=black_with_red_on_white
678 # 001n=black_with_yellow_on_white
679 # 101n=black_with_green_on_white
680 # 110=white_with_yellow_on_black
681 # 010=white_with_green_on_black
682 # 011=white_with_blue_on_black
683 # 100=white_with_red_on_black
684 '110n=blkbluw', # black_with_blue_on_white
685 '010n=blkmagw', # black_with_magenta_on_white
686 '100n=blkcynw', # black_with_cyan_on_white
687 '101=whtprpk', # white_with_purple_on_black
688 '001=whtnavk', # white_with_navy_blue_on_black
689 '010a=grygrnk', # grey_with_green_on_black
690 '010b=whtgrng', # white_with_green_on_grey
691 '101an=blkgrng', # black_with_green_on_grey
692 '101bn=grygrnw', # grey_with_green_on_white
695 my $outname = $variation;
696 my($flipmode, @swap) = ( ($4 || ''), $1,$2,$3)
697 if $outname =~ s/^([012])([012])([[012])([a-z]*)=?//s;
698 @swap = () if '010' eq join '', @swap; # 010 is a swop-no-op!
701 "/* This file is autogenerated. Do not edit. $variation */\n\n"
704 # Only look at three-digitty colors, for now at least.
705 if( $flipmode =~ m/n/ ) {
706 $this_css =~ s/(#[0-9a-fA-F]{3})\b/_color_negate($1)/eg;
707 $this_css =~ s/\bthin\b/medium/g;
709 $this_css =~ s<#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b>
710 < join '', '#', ($1,$2,$3)[@swap] >eg if @swap;
712 if( $flipmode =~ m/a/)
713 { $this_css =~ s/#fff\b/#999/gi } # black -> dark grey
714 elsif($flipmode =~ m/b/)
715 { $this_css =~ s/#000\b/#666/gi } # white -> light grey
719 $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
722 # Now a few indexless variations:
723 foreach my $variation (
724 'blkbluw', # black_with_blue_on_white
725 'whtpurk', # white_with_purple_on_black
726 'whtgrng', # white_with_green_on_grey
727 'grygrnw', # grey_with_green_on_white
729 my $outname = $variation;
730 my $this_css = join "\n",
731 "/* This file is autogenerated. Do not edit. $outname */\n",
732 "\@import url(\"./_$variation.css\");",
733 ".indexgroup { display: none; }",
738 $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
746 $x =~ tr[0123456789abcdef]
751 #===========================================================================
754 my($self, $url, $content_type, $_code) = @_;
756 push @{ $self->_javascript_wad }, [
757 $url, $content_type || 'text/javascript', $_code
762 sub _spray_javascript {
763 my($self, $outdir) = @_;
764 return unless $self->javascript_flurry();
765 $self->_gen_javascript_wad();
767 my $lol = $self->_javascript_wad;
768 foreach my $script (@$lol) {
769 my $url = $script->[0];
772 if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) {
773 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
774 DEBUG > 5 and print "Noting $$script[0] as a file I'll create.\n";
776 DEBUG > 5 and print "OK, noting $$script[0] as an external JavaScript.\n";
780 #$self->muse( "Writing JavaScript file $outfile" );
781 my $Jsout = $self->_wopen($outfile);
783 print $Jsout ${$script->[-1]}
784 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
786 DEBUG > 5 and print "Wrote $outfile\n";
792 sub _gen_javascript_wad {
794 my $js_code = $self->_javascript || return;
795 $self->add_javascript( "_podly.js", 0, \$js_code);
799 sub _javascript_wad_to_markup {
800 my($self, $depth) = @_;
802 my @scripts = @{ $self->_javascript_wad || return '' };
803 return '' unless @scripts;
808 my $uplink = $depth ? ('../' x $depth) : '';
810 foreach my $s (@scripts) {
811 next unless $s and @$s;
813 my( $url1, $url2, $type, $media) = (
814 $self->_maybe_uplink( $s->[0], $uplink ),
815 esc(grep !ref($_), @$s)
818 $out .= qq{<script type="$type" src="$url1$url2"></script>\n};
823 #===========================================================================
825 sub _css_template { return $CSS }
826 sub _javascript { return $JAVASCRIPT }
829 /* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */
831 @media all { .hide { display: none; } }
834 .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none }
837 border-color: black !important;
838 color: black !important;
839 background-color: transparent !important;
840 background-image: none !important;
848 @media aural, braille, embossed {
849 div.indexgroup { display: none; } /* Too noisy, don't you think? */
850 dl.superindex > dt:before { content: "Group "; }
851 dl.superindex > dt:after { content: " contains:"; }
852 .backlinktop a:before { content: "Back to contents"; }
853 .backlinkbottom a:before { content: "Back to contents"; }
857 dl.superindex > dt { pause-before: 600ms; }
860 @media screen, tty, tv, projection {
861 .noscreen { display: none; }
863 a:link { color: #7070ff; text-decoration: underline; }
864 a:visited { color: #e030ff; text-decoration: underline; }
865 a:active { color: #800000; text-decoration: underline; }
866 body.contentspage a { text-decoration: none; }
867 a.u { color: #fff !important; text-decoration: none; }
872 background-color: #000;
875 body.pod h1, body.pod h2, body.pod h3, body.pod h4 {
876 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
880 border-top: thin solid transparent;
881 /* margin-left: -5px; border-left: 2px #7070ff solid; padding-left: 3px; */
884 body.pod h1 { border-top-color: #0a0; }
885 body.pod h2 { border-top-color: #080; }
886 body.pod h3 { border-top-color: #040; }
887 body.pod h4 { border-top-color: #010; }
889 p.backlinktop + h1 { border-top: none; margin-top: 0em; }
890 p.backlinktop + h2 { border-top: none; margin-top: 0em; }
891 p.backlinktop + h3 { border-top: none; margin-top: 0em; }
892 p.backlinktop + h4 { border-top: none; margin-top: 0em; }
895 font-size: 105%; /* just a wee bit more than normal */
898 .indexgroup { font-size: 80%; }
900 .backlinktop, .backlinkbottom {
903 background-color: #040;
904 border-top: thin solid #050;
905 border-bottom: thin solid #050;
908 .backlinktop a, .backlinkbottom a {
909 text-decoration: none;
911 background-color: #000;
912 border: thin solid #0d0;
914 .backlinkbottom { margin-bottom: 0; padding-bottom: 0; }
915 .backlinktop { margin-top: 0; padding-top: 0; }
919 background-color: #000;
922 body.contentspage h1 {
927 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
929 border-top: thin solid #fff;
930 border-bottom: thin solid #fff;
935 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
939 /* margin-bottom: -.15em; */
942 word-spacing: .6em; /* most important rule here! */
944 dl.superindex > a:link {
945 text-decoration: none;
950 border-top: thin solid #999;
960 #==========================================================================
962 $JAVASCRIPT = <<'EOJAVASCRIPT';
964 // From http://www.alistapart.com/articles/alternate/
966 function setActiveStyleSheet(title) {
968 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
969 if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
971 if(a.getAttribute("title") == title) a.disabled = false;
976 function getActiveStyleSheet() {
978 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
979 if( a.getAttribute("rel").indexOf("style") != -1
980 && a.getAttribute("title")
982 ) return a.getAttribute("title");
987 function getPreferredStyleSheet() {
989 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
990 if( a.getAttribute("rel").indexOf("style") != -1
991 && a.getAttribute("rel").indexOf("alt") == -1
992 && a.getAttribute("title")
993 ) return a.getAttribute("title");
998 function createCookie(name,value,days) {
1000 var date = new Date();
1001 date.setTime(date.getTime()+(days*24*60*60*1000));
1002 var expires = "; expires="+date.toGMTString();
1005 document.cookie = name+"="+value+expires+"; path=/";
1008 function readCookie(name) {
1009 var nameEQ = name + "=";
1010 var ca = document.cookie.split(';');
1011 for(var i=0 ; i < ca.length ; i++) {
1013 while (c.charAt(0)==' ') c = c.substring(1,c.length);
1014 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
1019 window.onload = function(e) {
1020 var cookie = readCookie("style");
1021 var title = cookie ? cookie : getPreferredStyleSheet();
1022 setActiveStyleSheet(title);
1025 window.onunload = function(e) {
1026 var title = getActiveStyleSheet();
1027 createCookie("style", title, 365);
1030 var cookie = readCookie("style");
1031 var title = cookie ? cookie : getPreferredStyleSheet();
1032 setActiveStyleSheet(title);
1038 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1045 Pod::Simple::HTMLBatch - convert several Pod files to several HTML files
1049 perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out
1054 This module is used for running batch-conversions of a lot of HTML
1057 This class is NOT a subclass of Pod::Simple::HTML
1058 (nor of bad old Pod::Html) -- although it uses
1059 Pod::Simple::HTML for doing the conversion of each document.
1061 The normal use of this class is like so:
1063 use Pod::Simple::HTMLBatch;
1064 my $batchconv = Pod::Simple::HTMLBatch->new;
1065 $batchconv->some_option( some_value );
1066 $batchconv->some_other_option( some_other_value );
1067 $batchconv->batch_convert( \@search_dirs, $output_dir );
1069 =head2 FROM THE COMMAND LINE
1071 Note that this class also provides
1072 (but does not export) the function Pod::Simple::HTMLBatch::go.
1073 This is basically just a shortcut for C<<
1074 Pod::Simple::HTMLBatch->batch_convert(@ARGV) >>.
1075 It's meant to be handy for calling from the command line.
1077 However, the shortcut requires that you specify exactly two command-line
1078 arguments, C<indirs> and C<outdir>.
1083 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go @INC out_html
1084 (to convert the pod from Perl's @INC
1085 files under the directory ./out_html)
1087 (Note that the command line there contains a literal atsign-I-N-C. This
1088 is handled as a special case by batch_convert, in order to save you having
1089 to enter the odd-looking "" as the first command-line parameter when you
1090 mean "just use whatever's in @INC".)
1095 % chmod og-rx ../seekrut
1096 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go . ../seekrut
1097 (to convert the pod under the current dir into HTML
1098 files under the directory ./seekrut)
1102 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs .
1103 (to convert all pod from happydocs into the current directory)
1111 =item $batchconv = Pod::Simple::HTMLBatch->new;
1116 =item $batchconv->batch_convert( I<indirs>, I<outdir> );
1120 =item $batchconv->batch_convert( undef , ...);
1122 =item $batchconv->batch_convert( q{@INC}, ...);
1124 These two values for I<indirs> specify that the normal Perl @INC
1126 =item $batchconv->batch_convert( \@dirs , ...);
1128 This specifies that the input directories are the items in
1129 the arrayref C<\@dirs>.
1131 =item $batchconv->batch_convert( "somedir" , ...);
1133 This specifies that the director "somedir" is the input.
1134 (This can be an absolute or relative path, it doesn't matter.)
1136 A common value you might want would be just "." for the current
1139 $batchconv->batch_convert( "." , ...);
1142 =item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1144 This specifies that you want the dirs "somedir", "someother", and "also"
1145 scanned, just as if you'd passed the arrayref
1146 C<[qw( somedir someother also)]>. Note that a ":"-separator is normal
1147 under Unix, but Under MSWin, you'll need C<'somedir;someother;also'>
1148 instead, since the pathsep on MSWin is ";" instead of ":". (And
1149 I<that> is because ":" often comes up in paths, like
1152 (Exactly what separator character should be used, is gotten from
1153 C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1155 =item $batchconv->batch_convert( ... , undef );
1157 This specifies that you want the HTML output to go into the current
1160 (Note that a missing or undefined value means a different thing in
1161 the first slot than in the second. That's so that C<batch_convert()>
1162 with no arguments (or undef arguments) means "go from @INC, into
1163 the current directory.)
1165 =item $batchconv->batch_convert( ... , 'somedir' );
1167 This specifies that you want the HTML output to go into the
1168 directory 'somedir'.
1169 (This can be an absolute or relative path, it doesn't matter.)
1174 Note that you can also call C<batch_convert> as a class method,
1177 Pod::Simple::HTMLBatch->batch_convert( ... );
1179 That is just short for this:
1181 Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1183 That is, it runs a conversion with default options, for
1184 whatever inputdirs and output dir you specify.
1187 =head2 ACCESSOR METHODS
1189 The following are all accessor methods -- that is, they don't do anything
1190 on their own, but just alter the contents of the conversion object,
1191 which comprises the options for this particular batch conversion.
1193 We show the "put" form of the accessors below (i.e., the syntax you use
1194 for setting the accessor to a specific value). But you can also
1195 call each method with no parameters to get its current value. For
1196 example, C<< $self->contents_file() >> returns the current value of
1197 the contents_file attribute.
1202 =item $batchconv->verbose( I<nonnegative_integer> );
1204 This controls how verbose to be during batch conversion, as far as
1205 notes to STDOUT (or whatever is C<select>'d) about how the conversion
1206 is going. If 0, no progress information is printed.
1207 If 1 (the default value), some progress information is printed.
1208 Higher values print more information.
1211 =item $batchconv->index( I<true-or-false> );
1213 This controls whether or not each HTML page is liable to have a little
1214 table of contents at the top (which we call an "index" for historical
1215 reasons). This is true by default.
1218 =item $batchconv->contents_file( I<filename> );
1220 If set, should be the name of a file (in the output directory)
1221 to write the HTML index to. The default value is "index.html".
1222 If you set this to a false value, no contents file will be written.
1224 =item $batchconv->contents_page_start( I<HTML_string> );
1226 This specifies what string should be put at the beginning of
1228 The default is a string more or less like this:
1231 <head><title>Perl Documentation</title></head>
1232 <body class='contentspage'>
1233 <h1>Perl Documentation</h1>
1235 =item $batchconv->contents_page_end( I<HTML_string> );
1237 This specifies what string should be put at the end of the contents page.
1238 The default is a string more or less like this:
1240 <p class='contentsfooty'>Generated by
1241 Pod::Simple::HTMLBatch v3.01 under Perl v5.008
1242 <br >At Fri May 14 22:26:42 2004 GMT,
1243 which is Fri May 14 14:26:42 2004 local time.</p>
1247 =item $batchconv->add_css( $url );
1251 =item $batchconv->add_javascript( $url );
1255 =item $batchconv->css_flurry( I<true-or-false> );
1257 If true (the default value), we autogenerate some CSS files in the
1258 output directory, and set our HTML files to use those.
1261 =item $batchconv->javascript_flurry( I<true-or-false> );
1263 If true (the default value), we autogenerate a JavaScript in the
1264 output directory, and set our HTML files to use it. Currently,
1265 the JavaScript is used only to get the browser to remember what
1266 stylesheet it prefers.
1269 =item $batchconv->no_contents_links( I<true-or-false> );
1273 =item $batchconv->html_render_class( I<classname> );
1275 This sets what class is used for rendering the files.
1276 The default is "Pod::Simple::HTML". If you set it to something else,
1277 it should probably be a subclass of Pod::Simple::HTML, and you should
1278 C<require> or C<use> that class so that's it's loaded before
1279 Pod::Simple::HTMLBatch tries loading it.
1281 =item $batchconv->search_class( I<classname> );
1283 This sets what class is used for searching for the files.
1284 The default is "Pod::Simple::Search". If you set it to something else,
1285 it should probably be a subclass of Pod::Simple::Search, and you should
1286 C<require> or C<use> that class so that's it's loaded before
1287 Pod::Simple::HTMLBatch tries loading it.
1294 =head1 NOTES ON CUSTOMIZATION
1298 call add_css($someurl) to add stylesheet as alternate
1299 call add_css($someurl,1) to add as primary stylesheet
1303 subclass Pod::Simple::HTML and set $batchconv->html_render_class to
1306 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
1308 $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
1309 subclass Pod::Simple::Search and set $batchconv->search_class to
1316 If you want to do some kind of big pod-to-HTML version with some
1317 particular kind of option that you don't see how to achieve using this
1318 module, email me (C<sburke@cpan.org>) and I'll probably have a good idea
1319 how to do it. For reasons of concision and energetic laziness, some
1320 methods and options in this module (and the dozen modules it depends on)
1321 are undocumented; but one of those undocumented bits might be just what
1327 L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1331 Questions or discussion about POD and Pod::Simple should be sent to the
1332 pod-people@perl.org mail list. Send an empty email to
1333 pod-people-subscribe@perl.org to subscribe.
1335 This module is managed in an open GitHub repository,
1336 L<https://github.com/theory/pod-simple/>. Feel free to fork and contribute, or
1337 to clone L<git://github.com/theory/pod-simple.git> and send patches!
1339 Patches against Pod::Simple are welcome. Please send bug reports to
1340 <bug-pod-simple@rt.cpan.org>.
1342 =head1 COPYRIGHT AND DISCLAIMERS
1344 Copyright (c) 2002 Sean M. Burke.
1346 This library is free software; you can redistribute it and/or modify it
1347 under the same terms as Perl itself.
1349 This program is distributed in the hope that it will be useful, but
1350 without any warranty; without even the implied warranty of
1351 merchantability or fitness for a particular purpose.
1355 Pod::Simple was created by Sean M. Burke <sburke@cpan.org>.
1356 But don't bother him, he's retired.
1358 Pod::Simple is maintained by:
1362 =item * Allison Randal C<allison@perl.org>
1364 =item * Hans Dieter Pearcey C<hdp@cpan.org>
1366 =item * David E. Wheeler C<dwheeler@cpan.org>