Add built local::lib
[catagits/Gitalist.git] / local-lib5 / lib / perl5 / Pod / Simple / HTMLBatch.pm
1
2 require 5;
3 package Pod::Simple::HTMLBatch;
4 use strict;
5 use 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
13 use Pod::Simple::HTML ();
14 BEGIN {*esc = \&Pod::Simple::HTML::esc }
15 use File::Spec ();
16 use UNIVERSAL ();
17   # "Isn't the Universe an amazing place?  I wouldn't live anywhere else!"
18
19 use Pod::Simple::Search;
20 $SEARCH_CLASS ||= 'Pod::Simple::Search';
21
22 BEGIN {
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
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',
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
54 sub 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
72 sub new {
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);
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
111 sub 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
121 sub 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
147 sub _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
200 sub _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
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!");
218   return;
219 }
220
221 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
222
223 sub _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:
251   $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
252    if $page->can('batch_mode_page_object_init');
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 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
281 sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
282
283 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
284
285 sub 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
304 sub 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
330 sub _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
356 sub _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
381 sub _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
395 sub _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
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 );
435 }
436
437 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
438
439 sub 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
458 sub 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
479 sub 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
495 sub 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
511 sub url_up_to_contents {
512   my($self, $depth) = @_;
513   --$depth;
514   return join '/', ('..') x $depth, esc($self->contents_file);
515 }
516
517 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
518
519 sub 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
529 sub modnames2paths { # return a hashref mapping modulenames => paths
530   my($self, $dirs) = @_;
531
532   my $m2p;
533   {
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;
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
563 sub _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
576 sub 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
598 sub _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$)} ) {
609       $outfile = $self->filespecsys->catfile( $outdir, "$1" );
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
630 sub _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 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
658 sub _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 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
669 sub _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
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   ) {
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:
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\_";
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/-_/  /;
737     $self->add_css( "$outname.css", 0, $name, 0, 0, \$this_css);
738   }
739
740   return;
741 }
742
743 sub _color_negate {
744   my $x = lc $_[0];
745   $x =~ tr[0123456789abcdef]
746           [fedcba9876543210];
747   return $x;
748 }
749
750 #===========================================================================
751
752 sub 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
761 sub _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$)} ) {
772       $outfile = $self->filespecsys->catfile( $outdir, "$1" );
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
791 sub _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
798 sub _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
824 sub _css_template { return $CSS }
825 sub _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
957 EOCSS
958
959 #==========================================================================
960
961 $JAVASCRIPT = <<'EOJAVASCRIPT';
962
963 // From http://www.alistapart.com/articles/alternate/
964
965 function 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
975 function 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
986 function 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
997 function 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
1007 function 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
1018 window.onload = function(e) {
1019   var cookie = readCookie("style");
1020   var title = cookie ? cookie : getPreferredStyleSheet();
1021   setActiveStyleSheet(title);
1022 }
1023
1024 window.onunload = function(e) {
1025   var title = getActiveStyleSheet();
1026   createCookie("style", title, 365);
1027 }
1028
1029 var cookie = readCookie("style");
1030 var title = cookie ? cookie : getPreferredStyleSheet();
1031 setActiveStyleSheet(title);
1032
1033 // The End
1034
1035 EOJAVASCRIPT
1036
1037 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1038 1;
1039 __END__
1040
1041
1042 =head1 NAME
1043
1044 Pod::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
1053 This module is used for running batch-conversions of a lot of HTML
1054 documents 
1055
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.
1059
1060 The 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
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.
1075
1076 However, the shortcut requires that you specify exactly two command-line
1077 arguments, C<indirs> and C<outdir>.
1078
1079 Example:
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
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".)
1090
1091 Example:
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
1099 Example:
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
1112 This TODO
1113
1114
1115 =item $batchconv->batch_convert( I<indirs>, I<outdir> );
1116
1117 this TODO
1118
1119 =item $batchconv->batch_convert( undef    , ...);
1120
1121 =item $batchconv->batch_convert( q{@INC}, ...);
1122
1123 These two values for I<indirs> specify that the normal Perl @INC
1124
1125 =item $batchconv->batch_convert( \@dirs , ...);
1126
1127 This specifies that the input directories are the items in
1128 the arrayref C<\@dirs>.
1129
1130 =item $batchconv->batch_convert( "somedir" , ...);
1131
1132 This specifies that the director "somedir" is the input.
1133 (This can be an absolute or relative path, it doesn't matter.)
1134
1135 A common value you might want would be just "." for the current
1136 directory:
1137
1138      $batchconv->batch_convert( "." , ...);
1139
1140
1141 =item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1142
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
1149 C<"c:/perl/lib">.)
1150
1151 (Exactly what separator character should be used, is gotten from
1152 C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1153
1154 =item $batchconv->batch_convert( ... , undef );
1155
1156 This specifies that you want the HTML output to go into the current
1157 directory.
1158
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.)
1163
1164 =item $batchconv->batch_convert( ... , 'somedir' );
1165
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.)
1169
1170 =back
1171
1172
1173 Note that you can also call C<batch_convert> as a class method,
1174 like so:
1175
1176   Pod::Simple::HTMLBatch->batch_convert( ... );
1177
1178 That is just short for this:
1179
1180   Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1181
1182 That is, it runs a conversion with default options, for
1183 whatever inputdirs and output dir you specify.
1184
1185
1186 =head2 ACCESSOR METHODS
1187
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.
1191
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.
1197
1198 =over
1199
1200
1201 =item $batchconv->verbose( I<nonnegative_integer> );
1202
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.
1208
1209
1210 =item $batchconv->index( I<true-or-false> );
1211
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.
1215
1216
1217 =item $batchconv->contents_file( I<filename> );
1218
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.
1222
1223 =item $batchconv->contents_page_start( I<HTML_string> );
1224
1225 This specifies what string should be put at the beginning of
1226 the contents page.
1227 The 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
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:
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
1248 TODO
1249
1250 =item $batchconv->add_javascript( $url );
1251
1252 TODO
1253
1254 =item $batchconv->css_flurry( I<true-or-false> );
1255
1256 If true (the default value), we autogenerate some CSS files in the
1257 output directory, and set our HTML files to use those.
1258 TODO: continue
1259
1260 =item $batchconv->javascript_flurry( I<true-or-false> );
1261
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.
1266 TODO: continue
1267
1268 =item $batchconv->no_contents_links( I<true-or-false> );
1269
1270 TODO
1271
1272 =item $batchconv->html_render_class( I<classname> );
1273
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.
1279
1280 =item $batchconv->search_class( I<classname> );
1281
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.
1287
1288 =back
1289
1290
1291
1292
1293 =head1 NOTES ON CUSTOMIZATION
1294
1295 TODO
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)
1308   subclass Pod::Simple::Search and set $batchconv->search_class to
1309     that classname
1310
1311
1312
1313 =head1 ASK ME!
1314
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
1321 you're looking for.
1322
1323
1324 =head1 SEE ALSO
1325
1326 L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1327
1328
1329
1330
1331 =head1 COPYRIGHT AND DISCLAIMERS
1332
1333 Copyright (c) 2004 Sean M. Burke.  All rights reserved.
1334
1335 This library is free software; you can redistribute it and/or modify it
1336 under the same terms as Perl itself.
1337
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.
1341
1342 =head1 AUTHOR
1343
1344 Sean M. Burke C<sburke@cpan.org>
1345
1346 =cut
1347
1348
1349