This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Update Pod-Simple to CPAN version 3.28
[perl5.git] / cpan / Pod-Simple / lib / 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.28';
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
17 use Pod::Simple::Search;
18 $SEARCH_CLASS ||= 'Pod::Simple::Search';
19
20 BEGIN {
21   if(defined &DEBUG) { } # no-op
22   elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG }
23   else { *DEBUG = sub () {0}; }
24 }
25
26 $SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i;
27 # flag to occasionally sleep for $SLEEPY - 1 seconds.
28
29 $HTML_RENDER_CLASS ||= "Pod::Simple::HTML";
30
31 #
32 # Methods beginning with "_" are particularly internal and possibly ugly.
33 #
34
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',
44
45  'css_flurry', '_css_wad', 'javascript_flurry', '_javascript_wad',
46  'no_contents_links', # set to true to suppress automatic adding of << links.
47  '_contents',
48 );
49
50 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
51 # Just so we can run from the command line more easily
52 sub go {
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__, 
56   );
57   
58   if(defined($ARGV[1]) and length($ARGV[1])) {
59     my $d = $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";
63   }
64   
65   __PACKAGE__->batch_convert(@ARGV);
66 }
67 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
68
69
70 sub new {
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);
75   $new->_contents([]);
76   
77   $new->index(1);
78
79   $new->       _css_wad([]);         $new->css_flurry(1);
80   $new->_javascript_wad([]);  $new->javascript_flurry(1);
81   
82   $new->contents_file(
83     'index' . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION)
84   );
85   
86   $new->contents_page_start( join "\n", grep $_,
87     $Pod::Simple::HTML::Doctype_decl,
88     "<html><head>",
89     "<title>Perl Documentation</title>",
90     $Pod::Simple::HTML::Content_decl,
91     "</head>",
92     "\n<body class='contentspage'>\n<h1>Perl Documentation</h1>\n"
93   ); # override if you need a different title
94   
95   
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",
98     esc(
99       ref($new),
100       eval {$new->VERSION} || $VERSION,
101       $], scalar(gmtime), scalar(localtime), 
102   )));
103
104   return $new;
105 }
106
107 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
108
109 sub muse {
110   my $self = shift;
111   if($self->verbose) {
112     print 'T+', int(time() - $self->{'_batch_start_time'}), "s: ", @_, "\n";
113   }
114   return 1;
115 }
116
117 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
118
119 sub batch_convert {
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
123
124   if(!defined($dirs)  or  $dirs eq ''  or  $dirs eq '@INC' ) {
125     $dirs = '';
126   } elsif(ref $dirs) {
127     # OK, it's an explicit set of dirs to scan, specified as an arrayref.
128   } else {
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!)
132     require Config;
133     my $ps = quotemeta( $Config::Config{'path_sep'} || ":" );
134     $dirs = [ grep length($_), split qr/$ps/, $dirs ];
135   }
136
137   $outdir = $self->filespecsys->curdir
138    unless defined $outdir and length $outdir;
139
140   $self->_batch_convert_main($dirs, $outdir);
141 }
142
143 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
144
145 sub _batch_convert_main {
146   my($self, $dirs, $outdir) = @_;
147   # $dirs is either false, or an arrayref.    
148   # $outdir is a pathspec.
149   
150   $self->{'_batch_start_time'} ||= time();
151
152   $self->muse( "= ", scalar(localtime) );
153   $self->muse( "Starting batch conversion to \"$outdir\"" );
154
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
162     );
163     $self->progress($progress);
164   }
165   
166   if($dirs) {
167     $self->muse(scalar(@$dirs), " dirs to scan: @$dirs");
168   } else {
169     $self->muse("Scanning \@INC.  This could take a minute or two.");
170   }
171   my $mod2path = $self->find_all_pods($dirs ? $dirs : ());
172   $self->muse("Done scanning.");
173
174   my $total = keys %$mod2path;
175   unless($total) {
176     $self->muse("No pod found.  Aborting batch conversion.\n");
177     return $self;
178   }
179
180   $progress and $progress->goal($total);
181   $self->muse("Now converting pod files to HTML.",
182     ($total > 25) ? "  This will take a while more." : ()
183   );
184
185   $self->_spray_css(        $outdir );
186   $self->_spray_javascript( $outdir );
187
188   $self->_do_all_batch_conversions($mod2path, $outdir);
189
190   $progress and $progress->done(sprintf (
191     "Done converting %d files.",  $self->{"__batch_conv_page_count"}
192   ));
193   return $self->_batch_convert_finish($outdir);
194   return $self;
195 }
196
197
198 sub _do_all_batch_conversions {
199   my($self, $mod2path, $outdir) = @_;
200   $self->{"__batch_conv_page_count"} = 0;
201
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;
205   }
206
207   return;
208 }
209
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!");
216   return;
217 }
218
219 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
220
221 sub _do_one_batch_conversion {
222   my($self, $module, $mod2path, $outdir, $outfile) = @_;
223
224   my $retval;
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
231     
232   $outfile  ||= do {
233     my @n = @namelets;
234     $n[-1] .= $HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION;
235     $self->filespecsys->catfile( $outdir, @n );
236   };
237
238   my $progress = $self->progress;
239
240   my $page = $self->html_render_class->new;
241   if(DEBUG > 5) {
242     $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: ",
243       ref($page), " render ($depth) $module => $outfile");
244   } elsif(DEBUG > 2) {
245     $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: $module => $outfile")
246   }
247
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');
254     
255   # Now get busy...
256   $self->makepath($outdir => \@namelets);
257
258   $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module");
259
260   if( $retval = $page->parse_from_file($infile, $outfile) ) {
261     ++ $self->{"__batch_conv_page_count"} ;
262     $self->note_for_contents_file( \@namelets, $infile, $outfile );
263   } else {
264     $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false.");
265   }
266
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');
272     
273   DEBUG > 4 and printf "%s %sb < $infile %s %sb\n",
274      $outfile, -s $outfile, $infile, -s $infile
275   ;
276
277   undef($page);
278   return $retval;
279 }
280
281 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
282 sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
283
284 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
285
286 sub note_for_contents_file {
287   my($self, $namelets, $infile, $outfile) = @_;
288
289   # I think the infile and outfile parts are never used. -- SMB
290   # But it's handy to have them around for debugging.
291
292   if( $self->contents_file ) {
293     my $c = $self->_contents();
294     push @$c,
295      [ join("::", @$namelets), $infile, $outfile, $namelets ]
296      #            0               1         2         3
297     ;
298     DEBUG > 3 and print "Noting @$c[-1]\n";
299   }
300   return;
301 }
302
303 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
304
305 sub write_contents_file {
306   my($self, $outdir) = @_;
307   my $outfile  = $self->_contents_filespec($outdir) || return;
308
309   $self->muse("Preparing list of modules for ToC");
310
311   my($toplevel,           # maps  toplevelbit => [all submodules]
312      $toplevel_form_freq, # ends up being  'foo' => 'Foo'
313     ) = $self->_prep_contents_breakdown;
314
315   my $Contents = eval { $self->_wopen($outfile) };
316   if( $Contents ) {
317     $self->muse( "Writing contents file $outfile" );
318   } else {
319     warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all";
320     return;
321   }
322
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, );
326   return $outfile;
327 }
328
329 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
330
331 sub _write_contents_start {
332   my($self, $Contents, $outfile) = @_;
333   my $starter = $self->contents_page_start || '';
334   
335   {
336     my $css_wad = $self->_css_wad_to_markup(1);
337     if( $css_wad ) {
338       $starter =~ s{(</head>)}{\n$css_wad\n$1}i;  # otherwise nevermind
339     }
340     
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
344     }
345   }
346
347   unless(print $Contents $starter, "<dl class='superindex'>\n" ) {
348     warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
349     close($Contents);
350     return 0;
351   }
352   return 1;
353 }
354
355 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
356
357 sub _write_contents_middle {
358   my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_;
359
360   foreach my $t (sort keys %$toplevel2submodules) {
361     my @downlines = sort {$a->[-1] cmp $b->[-1]}
362                           @{ $toplevel2submodules->{$t} };
363     
364     printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n],
365       esc( $t, $toplevel_form_freq->{$t} )
366     ;
367     
368     my($path, $name);
369     foreach my $e (@downlines) {
370       $name = $e->[0];
371       $path = join( "/", '.', esc( @{$e->[3]} ) )
372         . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION);
373       print $Contents qq{  <a href="$path">}, esc($name), "</a>&nbsp;&nbsp;\n";
374     }
375     print $Contents "</dd>\n\n";
376   }
377   return 1;
378 }
379
380 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
381
382 sub _write_contents_end {
383   my($self, $Contents, $outfile) = @_;
384   unless(
385     print $Contents "</dl>\n",
386       $self->contents_page_end || '',
387   ) {
388     warn "Couldn't write to $outfile: $!";
389   }
390   close($Contents) or warn "Couldn't close $outfile: $!";
391   return 1;
392 }
393
394 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
395
396 sub _prep_contents_breakdown {
397   my($self) = @_;
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)
402   
403   foreach my $entry (@$contents) {
404     my $toplevel = 
405       $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs'
406           # group all the perlwhatever docs together
407       : $entry->[3][0] # normal case
408     ;
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
412   }
413
414   foreach my $toplevel (sort keys %toplevel) {
415     my $fgroup = $toplevel_form_freq{$toplevel};
416     $toplevel_form_freq{$toplevel} =
417     (
418       sort { $fgroup->{$b} <=> $fgroup->{$a}  or  $a cmp $b }
419         keys %$fgroup
420       # This hash is extremely unlikely to have more than 4 members, so this
421       # sort isn't so very wasteful
422     )[0];
423   }
424
425   return(\%toplevel, \%toplevel_form_freq) if wantarray;
426   return \%toplevel;
427 }
428
429 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
430
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 );
436 }
437
438 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
439
440 sub makepath {
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] );
445     if(-e $dir) {
446       die "$dir exists but not as a directory!?" unless -d $dir;
447       next;
448     }
449     DEBUG > 3 and print "  Making $dir\n";
450     mkdir $dir, 0777
451      or die "Can't mkdir $dir: $!\nAborting"
452     ;
453   }
454   return;
455 }
456
457 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
458
459 sub batch_mode_page_object_init {
460   my $self = shift;
461   my($page, $module, $infile, $outfile, $depth) = @_;
462   
463   # TODO: any further options to percolate onto this new object here?
464
465   $page->default_title($module);
466   $page->index( $self->index );
467
468   $page->html_css(        $self->       _css_wad_to_markup($depth) );
469   $page->html_javascript( $self->_javascript_wad_to_markup($depth) );
470
471   $self->add_header_backlink($page, $module, $infile, $outfile, $depth);
472   $self->add_footer_backlink($page, $module, $infile, $outfile, $depth);
473
474
475   return $self;
476 }
477
478 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
479
480 sub add_header_backlink {
481   my $self = shift;
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 || '',
486
487     qq[<p class="backlinktop"><b><a name="___top" href="],
488     $self->url_up_to_contents($depth),
489     qq[" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>\n],
490   )
491    if $self->contents_file
492   ;
493   return;
494 }
495
496 sub add_footer_backlink {
497   my $self = shift;
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">&lt;&lt;</a></b></p>\n],
504     
505     $page->html_footer || '',
506   )
507    if $self->contents_file
508   ;
509   return;
510 }
511
512 sub url_up_to_contents {
513   my($self, $depth) = @_;
514   --$depth;
515   return join '/', ('..') x $depth, esc($self->contents_file);
516 }
517
518 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
519
520 sub find_all_pods {
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);
526 }
527
528 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
529
530 sub modnames2paths { # return a hashref mapping modulenames => paths
531   my($self, $dirs) = @_;
532
533   my $m2p;
534   {
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;
544   }
545
546   $self->muse("That's odd... no modules found!") unless keys %$m2p;
547   if( DEBUG > 4 ) {
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";
551     }
552     print "(total ",     scalar(keys %$m2p), ")\n\n";
553   } elsif( DEBUG ) {
554     print      "Found ", scalar(keys %$m2p), " modules.\n";
555   }
556   $self->muse( "Found ", scalar(keys %$m2p), " modules." );
557   
558   # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref
559   return $m2p;
560 }
561
562 #===========================================================================
563
564 sub _wopen {
565   # this is abstracted out so that the daemon class can override it
566   my($self, $outpath) = @_;
567   require Symbol;
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");
571   require Carp;  
572   Carp::croak("Can't write-open $outpath: $!");
573 }
574
575 #==========================================================================
576
577 sub add_css {
578   my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_;
579   return unless $url;
580   unless($name) {
581     # cook up a reasonable name based on the URL
582     $name = $url;
583     if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) {
584       $name = $1;
585       $name =~ s/\.css//i;
586     }
587   }
588   $media        ||= 'all';
589   $content_type ||= 'text/css';
590   
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 }
594   return;
595 }
596
597 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
598
599 sub _spray_css {
600   my($self, $outdir) = @_;
601
602   return unless $self->css_flurry();
603   $self->_gen_css_wad();
604
605   my $lol = $self->_css_wad;
606   foreach my $chunk (@$lol) {
607     my $url = $chunk->[0];
608     my $outfile;
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";
612     } else {
613       DEBUG > 5 and print "OK, noting $$chunk[0] as an external CSS.\n";
614       # Requires no further attention.
615       next;
616     }
617     
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";
622     close($Cssout);
623     DEBUG > 5 and print "Wrote $outfile\n";
624   }
625
626   return;
627 }
628
629 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
630
631 sub _css_wad_to_markup {
632   my($self, $depth) = @_;
633   
634   my @css  = @{ $self->_css_wad || return '' };
635   return '' unless @css;
636   
637   my $rel = 'stylesheet';
638   my $out = '';
639
640   --$depth;
641   my $uplink = $depth ? ('../' x $depth) : '';
642
643   foreach my $chunk (@css) {
644     next unless $chunk and @$chunk;
645
646     my( $url1, $url2, $title, $type, $media) = (
647       $self->_maybe_uplink( $chunk->[0], $uplink ),
648       esc(grep !ref($_), @$chunk)
649     );
650
651     $out .= qq{<link rel="$rel" title="$title" type="$type" href="$url1$url2" media="$media" >\n};
652
653     $rel = 'alternate stylesheet'; # alternates = all non-first iterations
654   }
655   return $out;
656 }
657
658 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
659 sub _maybe_uplink {
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{[/\:]} )
664     ? $uplink
665     : ''
666     # qualify it, if/as needed
667 }
668
669 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
670 sub _gen_css_wad {
671   my $self = $_[0];
672   my $css_template = $self->_css_template;
673   foreach my $variation (
674
675    # Commented out for sake of concision:
676    #
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
693   ) {
694
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!
699   
700     my $this_css =
701       "/* This file is autogenerated.  Do not edit.  $variation */\n\n"
702       . $css_template;
703
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;
708     }
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;
711
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
716
717     my $name = $outname;    
718     $name =~ tr/-_/  /;
719     $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
720   }
721
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
728   ) {
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; }",
734       "\n",
735     ;
736     my $name = $outname;    
737     $name =~ tr/-_/  /;
738     $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
739   }
740
741   return;
742 }
743
744 sub _color_negate {
745   my $x = lc $_[0];
746   $x =~ tr[0123456789abcdef]
747           [fedcba9876543210];
748   return $x;
749 }
750
751 #===========================================================================
752
753 sub add_javascript {
754   my($self, $url, $content_type, $_code) = @_;
755   return unless $url;
756   push  @{ $self->_javascript_wad }, [
757     $url, $content_type || 'text/javascript', $_code
758   ];
759   return;
760 }
761
762 sub _spray_javascript {
763   my($self, $outdir) = @_;
764   return unless $self->javascript_flurry();
765   $self->_gen_javascript_wad();
766
767   my $lol = $self->_javascript_wad;
768   foreach my $script (@$lol) {
769     my $url = $script->[0];
770     my $outfile;
771     
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";
775     } else {
776       DEBUG > 5 and print "OK, noting $$script[0] as an external JavaScript.\n";
777       next;
778     }
779     
780     #$self->muse( "Writing JavaScript file $outfile" );
781     my $Jsout = $self->_wopen($outfile);
782
783     print $Jsout ${$script->[-1]}
784      or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
785     close($Jsout);
786     DEBUG > 5 and print "Wrote $outfile\n";
787   }
788
789   return;
790 }
791
792 sub _gen_javascript_wad {
793   my $self = $_[0];
794   my $js_code = $self->_javascript || return;
795   $self->add_javascript( "_podly.js", 0, \$js_code);
796   return;
797 }
798
799 sub _javascript_wad_to_markup {
800   my($self, $depth) = @_;
801   
802   my @scripts  = @{ $self->_javascript_wad || return '' };
803   return '' unless @scripts;
804   
805   my $out = '';
806
807   --$depth;
808   my $uplink = $depth ? ('../' x $depth) : '';
809
810   foreach my $s (@scripts) {
811     next unless $s and @$s;
812
813     my( $url1, $url2, $type, $media) = (
814       $self->_maybe_uplink( $s->[0], $uplink ),
815       esc(grep !ref($_), @$s)
816     );
817
818     $out .= qq{<script type="$type" src="$url1$url2"></script>\n};
819   }
820   return $out;
821 }
822
823 #===========================================================================
824
825 sub _css_template { return $CSS }
826 sub _javascript   { return $JAVASCRIPT }
827
828 $CSS = <<'EOCSS';
829 /* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */
830
831 @media all { .hide { display: none; } }
832
833 @media print {
834   .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none }
835
836   * {
837     border-color: black !important;
838     color: black !important;
839     background-color: transparent !important;
840     background-image: none !important;
841   }
842
843   dl.superindex > dd  {
844     word-spacing: .6em;
845   }
846 }
847
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"; }
854 }
855
856 @media aural {
857   dl.superindex > dt  { pause-before: 600ms; }
858 }
859
860 @media screen, tty, tv, projection {
861   .noscreen { display: none; }
862
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; }
868
869   body.pod {
870     margin: 0 5px;
871     color:            #fff;
872     background-color: #000;
873   }
874
875   body.pod h1, body.pod h2, body.pod h3, body.pod h4  {
876     font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
877     font-weight: normal;
878     margin-top: 1.2em;
879     margin-bottom: .1em;
880     border-top: thin solid transparent;
881     /* margin-left: -5px;  border-left: 2px #7070ff solid;  padding-left: 3px; */
882   }
883   
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; }
888
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;  }
893
894   body.pod dt {
895     font-size: 105%; /* just a wee bit more than normal */
896   }
897
898   .indexgroup { font-size: 80%; }
899
900   .backlinktop,   .backlinkbottom    {
901     margin-left:  -5px;
902     margin-right: -5px;
903     background-color:         #040;
904     border-top:    thin solid #050;
905     border-bottom: thin solid #050;
906   }
907   
908   .backlinktop a, .backlinkbottom a  {
909     text-decoration: none;
910     color: #080;
911     background-color:  #000;
912     border: thin solid #0d0;
913   }
914   .backlinkbottom { margin-bottom: 0; padding-bottom: 0; }
915   .backlinktop    { margin-top:    0; padding-top:    0; }
916
917   body.contentspage {
918     color:            #fff;
919     background-color: #000;
920   }
921   
922   body.contentspage h1  {
923     color:            #0d0;
924     margin-left: 1em;
925     margin-right: 1em;
926     text-indent: -.9em;
927     font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
928     font-weight: normal;
929     border-top:    thin solid #fff;
930     border-bottom: thin solid #fff;
931     text-align: center;
932   }
933
934   dl.superindex > dt  {
935     font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
936     font-weight: normal;
937     font-size: 90%;
938     margin-top: .45em;
939     /* margin-bottom: -.15em; */
940   }
941   dl.superindex > dd  {
942     word-spacing: .6em;    /* most important rule here! */
943   }
944   dl.superindex > a:link  {
945     text-decoration: none;
946     color: #fff;
947   }
948
949   .contentsfooty {
950     border-top: thin solid #999;
951     font-size: 90%;
952   }
953   
954 }
955
956 /* The End */
957
958 EOCSS
959
960 #==========================================================================
961
962 $JAVASCRIPT = <<'EOJAVASCRIPT';
963
964 // From http://www.alistapart.com/articles/alternate/
965
966 function setActiveStyleSheet(title) {
967   var i, a, main;
968   for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
969     if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
970       a.disabled = true;
971       if(a.getAttribute("title") == title) a.disabled = false;
972     }
973   }
974 }
975
976 function getActiveStyleSheet() {
977   var i, a;
978   for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
979     if(   a.getAttribute("rel").indexOf("style") != -1
980        && a.getAttribute("title")
981        && !a.disabled
982        ) return a.getAttribute("title");
983   }
984   return null;
985 }
986
987 function getPreferredStyleSheet() {
988   var i, a;
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");
994   }
995   return null;
996 }
997
998 function createCookie(name,value,days) {
999   if (days) {
1000     var date = new Date();
1001     date.setTime(date.getTime()+(days*24*60*60*1000));
1002     var expires = "; expires="+date.toGMTString();
1003   }
1004   else expires = "";
1005   document.cookie = name+"="+value+expires+"; path=/";
1006 }
1007
1008 function readCookie(name) {
1009   var nameEQ = name + "=";
1010   var ca = document.cookie.split(';');
1011   for(var i=0  ;  i < ca.length  ;  i++) {
1012     var c = ca[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);
1015   }
1016   return null;
1017 }
1018
1019 window.onload = function(e) {
1020   var cookie = readCookie("style");
1021   var title = cookie ? cookie : getPreferredStyleSheet();
1022   setActiveStyleSheet(title);
1023 }
1024
1025 window.onunload = function(e) {
1026   var title = getActiveStyleSheet();
1027   createCookie("style", title, 365);
1028 }
1029
1030 var cookie = readCookie("style");
1031 var title = cookie ? cookie : getPreferredStyleSheet();
1032 setActiveStyleSheet(title);
1033
1034 // The End
1035
1036 EOJAVASCRIPT
1037
1038 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1039 1;
1040 __END__
1041
1042
1043 =head1 NAME
1044
1045 Pod::Simple::HTMLBatch - convert several Pod files to several HTML files
1046
1047 =head1 SYNOPSIS
1048
1049   perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out
1050
1051
1052 =head1 DESCRIPTION
1053
1054 This module is used for running batch-conversions of a lot of HTML
1055 documents 
1056
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.
1060
1061 The normal use of this class is like so:
1062
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 );
1068
1069 =head2 FROM THE COMMAND LINE
1070
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.
1076
1077 However, the shortcut requires that you specify exactly two command-line
1078 arguments, C<indirs> and C<outdir>.
1079
1080 Example:
1081
1082   % mkdir out_html
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)
1086
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".)
1091
1092 Example:
1093
1094   % mkdir ../seekrut
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)
1099
1100 Example:
1101
1102   % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs .
1103       (to convert all pod from happydocs into the current directory)
1104
1105
1106
1107 =head1 MAIN METHODS
1108
1109 =over
1110
1111 =item $batchconv = Pod::Simple::HTMLBatch->new;
1112
1113 This TODO
1114
1115
1116 =item $batchconv->batch_convert( I<indirs>, I<outdir> );
1117
1118 this TODO
1119
1120 =item $batchconv->batch_convert( undef    , ...);
1121
1122 =item $batchconv->batch_convert( q{@INC}, ...);
1123
1124 These two values for I<indirs> specify that the normal Perl @INC
1125
1126 =item $batchconv->batch_convert( \@dirs , ...);
1127
1128 This specifies that the input directories are the items in
1129 the arrayref C<\@dirs>.
1130
1131 =item $batchconv->batch_convert( "somedir" , ...);
1132
1133 This specifies that the director "somedir" is the input.
1134 (This can be an absolute or relative path, it doesn't matter.)
1135
1136 A common value you might want would be just "." for the current
1137 directory:
1138
1139      $batchconv->batch_convert( "." , ...);
1140
1141
1142 =item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1143
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
1150 C<"c:/perl/lib">.)
1151
1152 (Exactly what separator character should be used, is gotten from
1153 C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1154
1155 =item $batchconv->batch_convert( ... , undef );
1156
1157 This specifies that you want the HTML output to go into the current
1158 directory.
1159
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.)
1164
1165 =item $batchconv->batch_convert( ... , 'somedir' );
1166
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.)
1170
1171 =back
1172
1173
1174 Note that you can also call C<batch_convert> as a class method,
1175 like so:
1176
1177   Pod::Simple::HTMLBatch->batch_convert( ... );
1178
1179 That is just short for this:
1180
1181   Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1182
1183 That is, it runs a conversion with default options, for
1184 whatever inputdirs and output dir you specify.
1185
1186
1187 =head2 ACCESSOR METHODS
1188
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.
1192
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.
1198
1199 =over
1200
1201
1202 =item $batchconv->verbose( I<nonnegative_integer> );
1203
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.
1209
1210
1211 =item $batchconv->index( I<true-or-false> );
1212
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.
1216
1217
1218 =item $batchconv->contents_file( I<filename> );
1219
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.
1223
1224 =item $batchconv->contents_page_start( I<HTML_string> );
1225
1226 This specifies what string should be put at the beginning of
1227 the contents page.
1228 The default is a string more or less like this:
1229
1230   <html>
1231   <head><title>Perl Documentation</title></head>
1232   <body class='contentspage'>
1233   <h1>Perl Documentation</h1>
1234
1235 =item $batchconv->contents_page_end( I<HTML_string> );
1236
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:
1239
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>
1244
1245
1246
1247 =item $batchconv->add_css( $url );
1248
1249 TODO
1250
1251 =item $batchconv->add_javascript( $url );
1252
1253 TODO
1254
1255 =item $batchconv->css_flurry( I<true-or-false> );
1256
1257 If true (the default value), we autogenerate some CSS files in the
1258 output directory, and set our HTML files to use those.
1259 TODO: continue
1260
1261 =item $batchconv->javascript_flurry( I<true-or-false> );
1262
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.
1267 TODO: continue
1268
1269 =item $batchconv->no_contents_links( I<true-or-false> );
1270
1271 TODO
1272
1273 =item $batchconv->html_render_class( I<classname> );
1274
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.
1280
1281 =item $batchconv->search_class( I<classname> );
1282
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.
1288
1289 =back
1290
1291
1292
1293
1294 =head1 NOTES ON CUSTOMIZATION
1295
1296 TODO
1297
1298   call add_css($someurl) to add stylesheet as alternate
1299   call add_css($someurl,1) to add as primary stylesheet
1300
1301   call add_javascript
1302
1303   subclass Pod::Simple::HTML and set $batchconv->html_render_class to
1304     that classname
1305   and maybe override
1306     $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
1307   or maybe override
1308     $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
1309   subclass Pod::Simple::Search and set $batchconv->search_class to
1310     that classname
1311
1312
1313
1314 =head1 ASK ME!
1315
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
1322 you're looking for.
1323
1324
1325 =head1 SEE ALSO
1326
1327 L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1328
1329 =head1 SUPPORT
1330
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.
1334
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!
1338
1339 Patches against Pod::Simple are welcome. Please send bug reports to
1340 <bug-pod-simple@rt.cpan.org>.
1341
1342 =head1 COPYRIGHT AND DISCLAIMERS
1343
1344 Copyright (c) 2002 Sean M. Burke.
1345
1346 This library is free software; you can redistribute it and/or modify it
1347 under the same terms as Perl itself.
1348
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.
1352
1353 =head1 AUTHOR
1354
1355 Pod::Simple was created by Sean M. Burke <sburke@cpan.org>.
1356 But don't bother him, he's retired.
1357
1358 Pod::Simple is maintained by:
1359
1360 =over
1361
1362 =item * Allison Randal C<allison@perl.org>
1363
1364 =item * Hans Dieter Pearcey C<hdp@cpan.org>
1365
1366 =item * David E. Wheeler C<dwheeler@cpan.org>
1367
1368 =back
1369
1370 =cut