xref: /openbsd-src/gnu/usr.bin/perl/cpan/Pod-Simple/lib/Pod/Simple/HTMLBatch.pm (revision ff0e7be1ebbcc809ea8ad2b6dafe215824da9e46)
1
2require 5;
3package Pod::Simple::HTMLBatch;
4use strict;
5use vars qw( $VERSION $HTML_RENDER_CLASS $HTML_EXTENSION
6 $CSS $JAVASCRIPT $SLEEPY $SEARCH_CLASS @ISA
7);
8$VERSION = '3.43';
9@ISA = ();  # Yup, we're NOT a subclass of Pod::Simple::HTML!
10
11# TODO: nocontents stylesheets. Strike some of the color variations?
12
13use Pod::Simple::HTML ();
14BEGIN {*esc = \&Pod::Simple::HTML::esc }
15use File::Spec ();
16
17use Pod::Simple::Search;
18$SEARCH_CLASS ||= 'Pod::Simple::Search';
19
20BEGIN {
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
35Pod::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
52sub 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
70sub 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.</p>\n\n</body></html>\n",
98    esc(
99      ref($new),
100      eval {$new->VERSION} || $VERSION,
101      $], scalar(gmtime($ENV{SOURCE_DATE_EPOCH} || time)),
102  )));
103
104  return $new;
105}
106
107# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
108
109sub 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
119sub 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
145sub _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
198sub _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
210sub _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
221sub _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 STDERR "%s %sb < $infile %s %sb\n",
274     $outfile, -s $outfile, $infile, -s $infile
275  ;
276
277  undef($page);
278  return $retval;
279}
280
281# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
282sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
283
284# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
285
286sub 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 STDERR "Noting @$c[-1]\n";
299  }
300  return;
301}
302
303#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
304
305sub 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
331sub _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
357sub _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
382sub _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
396sub _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
431sub _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
440sub 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 STDERR "  Making $dir\n";
450    mkdir $dir, 0777
451     or die "Can't mkdir $dir: $!\nAborting"
452    ;
453  }
454  return;
455}
456
457#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
458
459sub 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
480sub 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
496sub 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
512sub url_up_to_contents {
513  my($self, $depth) = @_;
514  --$depth;
515  return join '/', ('..') x $depth, esc($self->contents_file);
516}
517
518#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
519
520sub 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
530sub 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 STDERR "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 STDERR "Modules found (name => path):\n";
549    foreach my $m (sort {lc($a) cmp lc($b)} keys %$m2p) {
550      print STDERR "  $m  $$m2p{$m}\n";
551    }
552    print STDERR "(total ",     scalar(keys %$m2p), ")\n\n";
553  } elsif( DEBUG ) {
554    print STDERR      "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
564sub _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 STDERR "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
577sub 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
599sub _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 STDERR "Noting $$chunk[0] as a file I'll create.\n";
612    } else {
613      DEBUG > 5 and print STDERR "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 STDERR "Wrote $outfile\n";
624  }
625
626  return;
627}
628
629# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
630
631sub _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# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
659sub _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# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
670sub _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  for (my ($outfile, $variation) = each %{{
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 $this_css = join "\n",
730      "/* This file is autogenerated.  Do not edit.  $outfile */\n",
731      "\@import url(\"./_$variation.css\");",
732      ".indexgroup { display: none; }",
733      "\n",
734    ;
735    my $name = $outfile;
736    $name =~ tr/-_/  /;
737    $self->add_css( "_$outfile.css", 0, $name, 0, 0, \$this_css);
738  }
739
740  return;
741}
742
743sub _color_negate {
744  my $x = lc $_[0];
745  $x =~ tr[0123456789abcdef]
746          [fedcba9876543210];
747  return $x;
748}
749
750#===========================================================================
751
752sub add_javascript {
753  my($self, $url, $content_type, $_code) = @_;
754  return unless $url;
755  push  @{ $self->_javascript_wad }, [
756    $url, $content_type || 'text/javascript', $_code
757  ];
758  return;
759}
760
761sub _spray_javascript {
762  my($self, $outdir) = @_;
763  return unless $self->javascript_flurry();
764  $self->_gen_javascript_wad();
765
766  my $lol = $self->_javascript_wad;
767  foreach my $script (@$lol) {
768    my $url = $script->[0];
769    my $outfile;
770
771    if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) {
772      $outfile = $self->filespecsys->catfile( $outdir, "$1" );
773      DEBUG > 5 and print STDERR "Noting $$script[0] as a file I'll create.\n";
774    } else {
775      DEBUG > 5 and print STDERR "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 STDERR "Wrote $outfile\n";
786  }
787
788  return;
789}
790
791sub _gen_javascript_wad {
792  my $self = $_[0];
793  my $js_code = $self->_javascript || return;
794  $self->add_javascript( "_podly.js", 0, \$js_code);
795  return;
796}
797
798sub _javascript_wad_to_markup {
799  my($self, $depth) = @_;
800
801  my @scripts  = @{ $self->_javascript_wad || return '' };
802  return '' unless @scripts;
803
804  my $out = '';
805
806  --$depth;
807  my $uplink = $depth ? ('../' x $depth) : '';
808
809  foreach my $s (@scripts) {
810    next unless $s and @$s;
811
812    my( $url1, $url2, $type, $media) = (
813      $self->_maybe_uplink( $s->[0], $uplink ),
814      esc(grep !ref($_), @$s)
815    );
816
817    $out .= qq{<script type="$type" src="$url1$url2"></script>\n};
818  }
819  return $out;
820}
821
822#===========================================================================
823
824sub _css_template { return $CSS }
825sub _javascript   { return $JAVASCRIPT }
826
827$CSS = <<'EOCSS';
828/* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */
829
830@media all { .hide { display: none; } }
831
832@media print {
833  .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none }
834
835  * {
836    border-color: black !important;
837    color: black !important;
838    background-color: transparent !important;
839    background-image: none !important;
840  }
841
842  dl.superindex > dd  {
843    word-spacing: .6em;
844  }
845}
846
847@media aural, braille, embossed {
848  div.indexgroup  { display: none; }  /* Too noisy, don't you think? */
849  dl.superindex > dt:before { content: "Group ";  }
850  dl.superindex > dt:after  { content: " contains:"; }
851  .backlinktop    a:before  { content: "Back to contents"; }
852  .backlinkbottom a:before  { content: "Back to contents"; }
853}
854
855@media aural {
856  dl.superindex > dt  { pause-before: 600ms; }
857}
858
859@media screen, tty, tv, projection {
860  .noscreen { display: none; }
861
862  a:link    { color: #7070ff; text-decoration: underline; }
863  a:visited { color: #e030ff; text-decoration: underline; }
864  a:active  { color: #800000; text-decoration: underline; }
865  body.contentspage a            { text-decoration: none; }
866  a.u { color: #fff !important; text-decoration: none; }
867
868  body.pod {
869    margin: 0 5px;
870    color:            #fff;
871    background-color: #000;
872  }
873
874  body.pod h1, body.pod h2, body.pod h3,
875  body.pod h4, body.pod h5, body.pod h6  {
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  body.pod h5  { border-top-color: #010; }
889  body.pod h6  { border-top-color: #010; }
890
891  p.backlinktop + h1 { border-top: none; margin-top: 0em;  }
892  p.backlinktop + h2 { border-top: none; margin-top: 0em;  }
893  p.backlinktop + h3 { border-top: none; margin-top: 0em;  }
894  p.backlinktop + h4 { border-top: none; margin-top: 0em;  }
895  p.backlinktop + h5 { border-top: none; margin-top: 0em;  }
896  p.backlinktop + h6 { border-top: none; margin-top: 0em;  }
897
898  body.pod dt {
899    font-size: 105%; /* just a wee bit more than normal */
900  }
901
902  .indexgroup { font-size: 80%; }
903
904  .backlinktop,   .backlinkbottom    {
905    margin-left:  -5px;
906    margin-right: -5px;
907    background-color:         #040;
908    border-top:    thin solid #050;
909    border-bottom: thin solid #050;
910  }
911
912  .backlinktop a, .backlinkbottom a  {
913    text-decoration: none;
914    color: #080;
915    background-color:  #000;
916    border: thin solid #0d0;
917  }
918  .backlinkbottom { margin-bottom: 0; padding-bottom: 0; }
919  .backlinktop    { margin-top:    0; padding-top:    0; }
920
921  body.contentspage {
922    color:            #fff;
923    background-color: #000;
924  }
925
926  body.contentspage h1  {
927    color:            #0d0;
928    margin-left: 1em;
929    margin-right: 1em;
930    text-indent: -.9em;
931    font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
932    font-weight: normal;
933    border-top:    thin solid #fff;
934    border-bottom: thin solid #fff;
935    text-align: center;
936  }
937
938  dl.superindex > dt  {
939    font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
940    font-weight: normal;
941    font-size: 90%;
942    margin-top: .45em;
943    /* margin-bottom: -.15em; */
944  }
945  dl.superindex > dd  {
946    word-spacing: .6em;    /* most important rule here! */
947  }
948  dl.superindex > a:link  {
949    text-decoration: none;
950    color: #fff;
951  }
952
953  .contentsfooty {
954    border-top: thin solid #999;
955    font-size: 90%;
956  }
957
958}
959
960/* The End */
961
962EOCSS
963
964#==========================================================================
965
966$JAVASCRIPT = <<'EOJAVASCRIPT';
967
968// From http://www.alistapart.com/articles/alternate/
969
970function setActiveStyleSheet(title) {
971  var i, a, main;
972  for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
973    if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
974      a.disabled = true;
975      if(a.getAttribute("title") == title) a.disabled = false;
976    }
977  }
978}
979
980function getActiveStyleSheet() {
981  var i, a;
982  for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
983    if(   a.getAttribute("rel").indexOf("style") != -1
984       && a.getAttribute("title")
985       && !a.disabled
986       ) return a.getAttribute("title");
987  }
988  return null;
989}
990
991function getPreferredStyleSheet() {
992  var i, a;
993  for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
994    if(   a.getAttribute("rel").indexOf("style") != -1
995       && a.getAttribute("rel").indexOf("alt") == -1
996       && a.getAttribute("title")
997       ) return a.getAttribute("title");
998  }
999  return null;
1000}
1001
1002function createCookie(name,value,days) {
1003  if (days) {
1004    var date = new Date();
1005    date.setTime(date.getTime()+(days*24*60*60*1000));
1006    var expires = "; expires="+date.toGMTString();
1007  }
1008  else expires = "";
1009  document.cookie = name+"="+value+expires+"; path=/";
1010}
1011
1012function readCookie(name) {
1013  var nameEQ = name + "=";
1014  var ca = document.cookie.split(';');
1015  for(var i=0  ;  i < ca.length  ;  i++) {
1016    var c = ca[i];
1017    while (c.charAt(0)==' ') c = c.substring(1,c.length);
1018    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
1019  }
1020  return null;
1021}
1022
1023window.onload = function(e) {
1024  var cookie = readCookie("style");
1025  var title = cookie ? cookie : getPreferredStyleSheet();
1026  setActiveStyleSheet(title);
1027}
1028
1029window.onunload = function(e) {
1030  var title = getActiveStyleSheet();
1031  createCookie("style", title, 365);
1032}
1033
1034var cookie = readCookie("style");
1035var title = cookie ? cookie : getPreferredStyleSheet();
1036setActiveStyleSheet(title);
1037
1038// The End
1039
1040EOJAVASCRIPT
1041
1042# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10431;
1044__END__
1045
1046
1047=head1 NAME
1048
1049Pod::Simple::HTMLBatch - convert several Pod files to several HTML files
1050
1051=head1 SYNOPSIS
1052
1053  perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out
1054
1055
1056=head1 DESCRIPTION
1057
1058This module is used for running batch-conversions of a lot of HTML
1059documents
1060
1061This class is NOT a subclass of Pod::Simple::HTML
1062(nor of bad old Pod::Html) -- although it uses
1063Pod::Simple::HTML for doing the conversion of each document.
1064
1065The normal use of this class is like so:
1066
1067  use Pod::Simple::HTMLBatch;
1068  my $batchconv = Pod::Simple::HTMLBatch->new;
1069  $batchconv->some_option( some_value );
1070  $batchconv->some_other_option( some_other_value );
1071  $batchconv->batch_convert( \@search_dirs, $output_dir );
1072
1073=head2 FROM THE COMMAND LINE
1074
1075Note that this class also provides
1076(but does not export) the function Pod::Simple::HTMLBatch::go.
1077This is basically just a shortcut for C<<
1078Pod::Simple::HTMLBatch->batch_convert(@ARGV) >>.
1079It's meant to be handy for calling from the command line.
1080
1081However, the shortcut requires that you specify exactly two command-line
1082arguments, C<indirs> and C<outdir>.
1083
1084Example:
1085
1086  % mkdir out_html
1087  % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go @INC out_html
1088      (to convert the pod from Perl's @INC
1089       files under the directory ./out_html)
1090
1091(Note that the command line there contains a literal atsign-I-N-C.  This
1092is handled as a special case by batch_convert, in order to save you having
1093to enter the odd-looking "" as the first command-line parameter when you
1094mean "just use whatever's in @INC".)
1095
1096Example:
1097
1098  % mkdir ../seekrut
1099  % chmod og-rx ../seekrut
1100  % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go . ../seekrut
1101      (to convert the pod under the current dir into HTML
1102       files under the directory ./seekrut)
1103
1104Example:
1105
1106  % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs .
1107      (to convert all pod from happydocs into the current directory)
1108
1109
1110
1111=head1 MAIN METHODS
1112
1113=over
1114
1115=item $batchconv = Pod::Simple::HTMLBatch->new;
1116
1117This creates a new batch converter.  The method doesn't take parameters.
1118To change the converter's attributes, use the L<"/ACCESSOR METHODS">
1119below.
1120
1121=item $batchconv->batch_convert( I<indirs>, I<outdir> );
1122
1123This searches the directories given in I<indirs> and writes
1124HTML files for each of these to a corresponding directory
1125in I<outdir>.  The directory I<outdir> must exist.
1126
1127=item $batchconv->batch_convert( undef    , ...);
1128
1129=item $batchconv->batch_convert( q{@INC}, ...);
1130
1131These two values for I<indirs> specify that the normal Perl @INC
1132
1133=item $batchconv->batch_convert( \@dirs , ...);
1134
1135This specifies that the input directories are the items in
1136the arrayref C<\@dirs>.
1137
1138=item $batchconv->batch_convert( "somedir" , ...);
1139
1140This specifies that the director "somedir" is the input.
1141(This can be an absolute or relative path, it doesn't matter.)
1142
1143A common value you might want would be just "." for the current
1144directory:
1145
1146     $batchconv->batch_convert( "." , ...);
1147
1148
1149=item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1150
1151This specifies that you want the dirs "somedir", "someother", and "also"
1152scanned, just as if you'd passed the arrayref
1153C<[qw( somedir someother also)]>.  Note that a ":"-separator is normal
1154under Unix, but Under MSWin, you'll need C<'somedir;someother;also'>
1155instead, since the pathsep on MSWin is ";" instead of ":".  (And
1156I<that> is because ":" often comes up in paths, like
1157C<"c:/perl/lib">.)
1158
1159(Exactly what separator character should be used, is gotten from
1160C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1161
1162=item $batchconv->batch_convert( ... , undef );
1163
1164This specifies that you want the HTML output to go into the current
1165directory.
1166
1167(Note that a missing or undefined value means a different thing in
1168the first slot than in the second.  That's so that C<batch_convert()>
1169with no arguments (or undef arguments) means "go from @INC, into
1170the current directory.)
1171
1172=item $batchconv->batch_convert( ... , 'somedir' );
1173
1174This specifies that you want the HTML output to go into the
1175directory 'somedir'.
1176(This can be an absolute or relative path, it doesn't matter.)
1177
1178=back
1179
1180
1181Note that you can also call C<batch_convert> as a class method,
1182like so:
1183
1184  Pod::Simple::HTMLBatch->batch_convert( ... );
1185
1186That is just short for this:
1187
1188  Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1189
1190That is, it runs a conversion with default options, for
1191whatever inputdirs and output dir you specify.
1192
1193
1194=head2 ACCESSOR METHODS
1195
1196The following are all accessor methods -- that is, they don't do anything
1197on their own, but just alter the contents of the conversion object,
1198which comprises the options for this particular batch conversion.
1199
1200We show the "put" form of the accessors below (i.e., the syntax you use
1201for setting the accessor to a specific value).  But you can also
1202call each method with no parameters to get its current value.  For
1203example, C<< $self->contents_file() >> returns the current value of
1204the contents_file attribute.
1205
1206=over
1207
1208
1209=item $batchconv->verbose( I<nonnegative_integer> );
1210
1211This controls how verbose to be during batch conversion, as far as
1212notes to STDOUT (or whatever is C<select>'d) about how the conversion
1213is going.  If 0, no progress information is printed.
1214If 1 (the default value), some progress information is printed.
1215Higher values print more information.
1216
1217
1218=item $batchconv->index( I<true-or-false> );
1219
1220This controls whether or not each HTML page is liable to have a little
1221table of contents at the top (which we call an "index" for historical
1222reasons).  This is true by default.
1223
1224
1225=item $batchconv->contents_file( I<filename> );
1226
1227If set, should be the name of a file (in the output directory)
1228to write the HTML index to.  The default value is "index.html".
1229If you set this to a false value, no contents file will be written.
1230
1231=item $batchconv->contents_page_start( I<HTML_string> );
1232
1233This specifies what string should be put at the beginning of
1234the contents page.
1235The default is a string more or less like this:
1236
1237  <html>
1238  <head><title>Perl Documentation</title></head>
1239  <body class='contentspage'>
1240  <h1>Perl Documentation</h1>
1241
1242=item $batchconv->contents_page_end( I<HTML_string> );
1243
1244This specifies what string should be put at the end of the contents page.
1245The default is a string more or less like this:
1246
1247  <p class='contentsfooty'>Generated by
1248  Pod::Simple::HTMLBatch v3.01 under Perl v5.008
1249  <br >At Fri May 14 22:26:42 2004 GMT,
1250  which is Fri May 14 14:26:42 2004 local time.</p>
1251
1252
1253
1254=item $batchconv->add_css( $url );
1255
1256TODO
1257
1258=item $batchconv->add_javascript( $url );
1259
1260TODO
1261
1262=item $batchconv->css_flurry( I<true-or-false> );
1263
1264If true (the default value), we autogenerate some CSS files in the
1265output directory, and set our HTML files to use those.
1266TODO: continue
1267
1268=item $batchconv->javascript_flurry( I<true-or-false> );
1269
1270If true (the default value), we autogenerate a JavaScript in the
1271output directory, and set our HTML files to use it.  Currently,
1272the JavaScript is used only to get the browser to remember what
1273stylesheet it prefers.
1274TODO: continue
1275
1276=item $batchconv->no_contents_links( I<true-or-false> );
1277
1278TODO
1279
1280=item $batchconv->html_render_class( I<classname> );
1281
1282This sets what class is used for rendering the files.
1283The default is "Pod::Simple::HTML".  If you set it to something else,
1284it should probably be a subclass of Pod::Simple::HTML, and you should
1285C<require> or C<use> that class so that's it's loaded before
1286Pod::Simple::HTMLBatch tries loading it.
1287
1288=item $batchconv->search_class( I<classname> );
1289
1290This sets what class is used for searching for the files.
1291The default is "Pod::Simple::Search".  If you set it to something else,
1292it should probably be a subclass of Pod::Simple::Search, and you should
1293C<require> or C<use> that class so that's it's loaded before
1294Pod::Simple::HTMLBatch tries loading it.
1295
1296=back
1297
1298
1299
1300
1301=head1 NOTES ON CUSTOMIZATION
1302
1303TODO
1304
1305  call add_css($someurl) to add stylesheet as alternate
1306  call add_css($someurl,1) to add as primary stylesheet
1307
1308  call add_javascript
1309
1310  subclass Pod::Simple::HTML and set $batchconv->html_render_class to
1311    that classname
1312  and maybe override
1313    $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
1314  or maybe override
1315    $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
1316  subclass Pod::Simple::Search and set $batchconv->search_class to
1317    that classname
1318
1319
1320=head1 SEE ALSO
1321
1322L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1323
1324=head1 SUPPORT
1325
1326Questions or discussion about POD and Pod::Simple should be sent to the
1327pod-people@perl.org mail list. Send an empty email to
1328pod-people-subscribe@perl.org to subscribe.
1329
1330This module is managed in an open GitHub repository,
1331L<https://github.com/perl-pod/pod-simple/>. Feel free to fork and contribute, or
1332to clone L<git://github.com/perl-pod/pod-simple.git> and send patches!
1333
1334Patches against Pod::Simple are welcome. Please send bug reports to
1335<bug-pod-simple@rt.cpan.org>.
1336
1337=head1 COPYRIGHT AND DISCLAIMERS
1338
1339Copyright (c) 2002 Sean M. Burke.
1340
1341This library is free software; you can redistribute it and/or modify it
1342under the same terms as Perl itself.
1343
1344This program is distributed in the hope that it will be useful, but
1345without any warranty; without even the implied warranty of
1346merchantability or fitness for a particular purpose.
1347
1348=head1 AUTHOR
1349
1350Pod::Simple was created by Sean M. Burke <sburke@cpan.org>.
1351But don't bother him, he's retired.
1352
1353Pod::Simple is maintained by:
1354
1355=over
1356
1357=item * Allison Randal C<allison@perl.org>
1358
1359=item * Hans Dieter Pearcey C<hdp@cpan.org>
1360
1361=item * David E. Wheeler C<dwheeler@cpan.org>
1362
1363=back
1364
1365=cut
1366