Upgrade to Pod::Simple 3.13 from CPAN to fix a regression (in 3.08) that broke html...
[p5sagit/p5-mst-13.2.git] / cpan / Pod-Simple / lib / Pod / Simple / HTMLBatch.pm
CommitLineData
351625bd 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);
433cf6b4 8$VERSION = '3.13';
351625bd 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 ();
16use UNIVERSAL ();
17 # "Isn't the Universe an amazing place? I wouldn't live anywhere else!"
18
19use Pod::Simple::Search;
20$SEARCH_CLASS ||= 'Pod::Simple::Search';
21
22BEGIN {
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
37Pod::Simple::_accessorize( __PACKAGE__,
38 'verbose', # how verbose to be during batch conversion
39 'html_render_class', # what class to use to render
9d65762f 40 'search_class', # what to use to search for POD documents
351625bd 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
54sub 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
72sub new {
73 my $new = bless {}, ref($_[0]) || $_[0];
74 $new->html_render_class($HTML_RENDER_CLASS);
9d65762f 75 $new->search_class($SEARCH_CLASS);
351625bd 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
111sub 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
121sub 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
147sub _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
200sub _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
212sub _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
223sub _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:
351625bd 251 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
252 if $page->can('batch_mode_page_object_init');
a242eeb4 253 # Init for the index (TOC), too.
254 $self->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
255 if $self->can('batch_mode_page_object_init');
351625bd 256
257 # Now get busy...
258 $self->makepath($outdir => \@namelets);
259
260 $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module");
261
262 if( $retval = $page->parse_from_file($infile, $outfile) ) {
263 ++ $self->{"__batch_conv_page_count"} ;
264 $self->note_for_contents_file( \@namelets, $infile, $outfile );
265 } else {
266 $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false.");
267 }
268
269 $page->batch_mode_page_object_kill($self, $module, $infile, $outfile, $depth)
270 if $page->can('batch_mode_page_object_kill');
271 # The following isn't a typo. Note that it switches $self and $page.
272 $self->batch_mode_page_object_kill($page, $module, $infile, $outfile, $depth)
273 if $self->can('batch_mode_page_object_kill');
274
275 DEBUG > 4 and printf "%s %sb < $infile %s %sb\n",
276 $outfile, -s $outfile, $infile, -s $infile
277 ;
278
279 undef($page);
280 return $retval;
281}
282
283# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
284sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
285
286# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
287
288sub note_for_contents_file {
289 my($self, $namelets, $infile, $outfile) = @_;
290
291 # I think the infile and outfile parts are never used. -- SMB
292 # But it's handy to have them around for debugging.
293
294 if( $self->contents_file ) {
295 my $c = $self->_contents();
296 push @$c,
297 [ join("::", @$namelets), $infile, $outfile, $namelets ]
298 # 0 1 2 3
299 ;
300 DEBUG > 3 and print "Noting @$c[-1]\n";
301 }
302 return;
303}
304
305#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
306
307sub write_contents_file {
308 my($self, $outdir) = @_;
309 my $outfile = $self->_contents_filespec($outdir) || return;
310
311 $self->muse("Preparing list of modules for ToC");
312
313 my($toplevel, # maps toplevelbit => [all submodules]
314 $toplevel_form_freq, # ends up being 'foo' => 'Foo'
315 ) = $self->_prep_contents_breakdown;
316
317 my $Contents = eval { $self->_wopen($outfile) };
318 if( $Contents ) {
319 $self->muse( "Writing contents file $outfile" );
320 } else {
321 warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all";
322 return;
323 }
324
325 $self->_write_contents_start( $Contents, $outfile, );
326 $self->_write_contents_middle( $Contents, $outfile, $toplevel, $toplevel_form_freq );
327 $self->_write_contents_end( $Contents, $outfile, );
328 return $outfile;
329}
330
331# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
332
333sub _write_contents_start {
334 my($self, $Contents, $outfile) = @_;
335 my $starter = $self->contents_page_start || '';
336
337 {
338 my $css_wad = $self->_css_wad_to_markup(1);
339 if( $css_wad ) {
340 $starter =~ s{(</head>)}{\n$css_wad\n$1}i; # otherwise nevermind
341 }
342
343 my $javascript_wad = $self->_javascript_wad_to_markup(1);
344 if( $javascript_wad ) {
345 $starter =~ s{(</head>)}{\n$javascript_wad\n$1}i; # otherwise nevermind
346 }
347 }
348
349 unless(print $Contents $starter, "<dl class='superindex'>\n" ) {
350 warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
351 close($Contents);
352 return 0;
353 }
354 return 1;
355}
356
357# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
358
359sub _write_contents_middle {
360 my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_;
361
362 foreach my $t (sort keys %$toplevel2submodules) {
363 my @downlines = sort {$a->[-1] cmp $b->[-1]}
364 @{ $toplevel2submodules->{$t} };
365
366 printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n],
367 esc( $t, $toplevel_form_freq->{$t} )
368 ;
369
370 my($path, $name);
371 foreach my $e (@downlines) {
372 $name = $e->[0];
373 $path = join( "/", '.', esc( @{$e->[3]} ) )
374 . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION);
375 print $Contents qq{ <a href="$path">}, esc($name), "</a>&nbsp;&nbsp;\n";
376 }
377 print $Contents "</dd>\n\n";
378 }
379 return 1;
380}
381
382# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
383
384sub _write_contents_end {
385 my($self, $Contents, $outfile) = @_;
386 unless(
387 print $Contents "</dl>\n",
388 $self->contents_page_end || '',
389 ) {
390 warn "Couldn't write to $outfile: $!";
391 }
392 close($Contents) or warn "Couldn't close $outfile: $!";
393 return 1;
394}
395
396# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
397
398sub _prep_contents_breakdown {
399 my($self) = @_;
400 my $contents = $self->_contents;
401 my %toplevel; # maps lctoplevelbit => [all submodules]
402 my %toplevel_form_freq; # ends up being 'foo' => 'Foo'
403 # (mapping anycase forms to most freq form)
404
405 foreach my $entry (@$contents) {
406 my $toplevel =
407 $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs'
408 # group all the perlwhatever docs together
409 : $entry->[3][0] # normal case
410 ;
411 ++$toplevel_form_freq{ lc $toplevel }{ $toplevel };
412 push @{ $toplevel{ lc $toplevel } }, $entry;
413 push @$entry, lc($entry->[0]); # add a sort-order key to the end
414 }
415
416 foreach my $toplevel (sort keys %toplevel) {
417 my $fgroup = $toplevel_form_freq{$toplevel};
418 $toplevel_form_freq{$toplevel} =
419 (
420 sort { $fgroup->{$b} <=> $fgroup->{$a} or $a cmp $b }
421 keys %$fgroup
422 # This hash is extremely unlikely to have more than 4 members, so this
423 # sort isn't so very wasteful
424 )[0];
425 }
426
427 return(\%toplevel, \%toplevel_form_freq) if wantarray;
428 return \%toplevel;
429}
430
431# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
432
433sub _contents_filespec {
434 my($self, $outdir) = @_;
435 my $outfile = $self->contents_file;
436 return unless $outfile;
437 return $self->filespecsys->catfile( $outdir, $outfile );
438}
439
440#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
441
442sub makepath {
443 my($self, $outdir, $namelets) = @_;
444 return unless @$namelets > 1;
445 for my $i (0 .. ($#$namelets - 1)) {
446 my $dir = $self->filespecsys->catdir( $outdir, @$namelets[0 .. $i] );
447 if(-e $dir) {
448 die "$dir exists but not as a directory!?" unless -d $dir;
449 next;
450 }
451 DEBUG > 3 and print " Making $dir\n";
452 mkdir $dir, 0777
453 or die "Can't mkdir $dir: $!\nAborting"
454 ;
455 }
456 return;
457}
458
459#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
460
461sub batch_mode_page_object_init {
462 my $self = shift;
463 my($page, $module, $infile, $outfile, $depth) = @_;
464
465 # TODO: any further options to percolate onto this new object here?
466
467 $page->default_title($module);
468 $page->index( $self->index );
469
470 $page->html_css( $self-> _css_wad_to_markup($depth) );
471 $page->html_javascript( $self->_javascript_wad_to_markup($depth) );
472
473 $self->add_header_backlink($page, $module, $infile, $outfile, $depth);
474 $self->add_footer_backlink($page, $module, $infile, $outfile, $depth);
475
476
477 return $self;
478}
479
480# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
481
482sub add_header_backlink {
483 my $self = shift;
484 return if $self->no_contents_links;
485 my($page, $module, $infile, $outfile, $depth) = @_;
486 $page->html_header_after_title( join '',
487 $page->html_header_after_title || '',
488
489 qq[<p class="backlinktop"><b><a name="___top" href="],
490 $self->url_up_to_contents($depth),
491 qq[" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>\n],
492 )
493 if $self->contents_file
494 ;
495 return;
496}
497
498sub add_footer_backlink {
499 my $self = shift;
500 return if $self->no_contents_links;
501 my($page, $module, $infile, $outfile, $depth) = @_;
502 $page->html_footer( join '',
503 qq[<p class="backlinkbottom"><b><a name="___bottom" href="],
504 $self->url_up_to_contents($depth),
505 qq[" title="All Documents">&lt;&lt;</a></b></p>\n],
506
507 $page->html_footer || '',
508 )
509 if $self->contents_file
510 ;
511 return;
512}
513
514sub url_up_to_contents {
515 my($self, $depth) = @_;
516 --$depth;
517 return join '/', ('..') x $depth, esc($self->contents_file);
518}
519
520#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
521
522sub find_all_pods {
523 my($self, $dirs) = @_;
524 # You can override find_all_pods in a subclass if you want to
525 # do extra filtering or whatnot. But for the moment, we just
526 # pass to modnames2paths:
527 return $self->modnames2paths($dirs);
528}
529
530#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
531
532sub modnames2paths { # return a hashref mapping modulenames => paths
533 my($self, $dirs) = @_;
534
535 my $m2p;
536 {
9d65762f 537 my $search = $self->search_class->new;
351625bd 538 DEBUG and print "Searching via $search\n";
539 $search->verbose(1) if DEBUG > 10;
540 $search->progress( $self->progress->copy->goal(0) ) if $self->progress;
541 $search->shadows(0); # don't bother noting shadowed files
542 $search->inc( $dirs ? 0 : 1 );
543 $search->survey( $dirs ? @$dirs : () );
544 $m2p = $search->name2path;
545 die "What, no name2path?!" unless $m2p;
546 }
547
548 $self->muse("That's odd... no modules found!") unless keys %$m2p;
549 if( DEBUG > 4 ) {
550 print "Modules found (name => path):\n";
551 foreach my $m (sort {lc($a) cmp lc($b)} keys %$m2p) {
552 print " $m $$m2p{$m}\n";
553 }
554 print "(total ", scalar(keys %$m2p), ")\n\n";
555 } elsif( DEBUG ) {
556 print "Found ", scalar(keys %$m2p), " modules.\n";
557 }
558 $self->muse( "Found ", scalar(keys %$m2p), " modules." );
559
560 # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref
561 return $m2p;
562}
563
564#===========================================================================
565
566sub _wopen {
567 # this is abstracted out so that the daemon class can override it
568 my($self, $outpath) = @_;
569 require Symbol;
570 my $out_fh = Symbol::gensym();
571 DEBUG > 5 and print "Write-opening to $outpath\n";
572 return $out_fh if open($out_fh, "> $outpath");
573 require Carp;
574 Carp::croak("Can't write-open $outpath: $!");
575}
576
577#==========================================================================
578
579sub add_css {
580 my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_;
581 return unless $url;
582 unless($name) {
583 # cook up a reasonable name based on the URL
584 $name = $url;
585 if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) {
586 $name = $1;
587 $name =~ s/\.css//i;
588 }
589 }
590 $media ||= 'all';
591 $content_type ||= 'text/css';
592
593 my $bunch = [$url, $name, $content_type, $media, $_code];
594 if($is_default) { unshift @{ $self->_css_wad }, $bunch }
595 else { push @{ $self->_css_wad }, $bunch }
596 return;
597}
598
599# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
600
601sub _spray_css {
602 my($self, $outdir) = @_;
603
604 return unless $self->css_flurry();
605 $self->_gen_css_wad();
606
607 my $lol = $self->_css_wad;
608 foreach my $chunk (@$lol) {
609 my $url = $chunk->[0];
610 my $outfile;
611 if( ref($chunk->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.css$)} ) {
69473a20 612 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
351625bd 613 DEBUG > 5 and print "Noting $$chunk[0] as a file I'll create.\n";
614 } else {
615 DEBUG > 5 and print "OK, noting $$chunk[0] as an external CSS.\n";
616 # Requires no further attention.
617 next;
618 }
619
620 #$self->muse( "Writing autogenerated CSS file $outfile" );
621 my $Cssout = $self->_wopen($outfile);
622 print $Cssout ${$chunk->[-1]}
623 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
624 close($Cssout);
625 DEBUG > 5 and print "Wrote $outfile\n";
626 }
627
628 return;
629}
630
631# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
632
633sub _css_wad_to_markup {
634 my($self, $depth) = @_;
635
636 my @css = @{ $self->_css_wad || return '' };
637 return '' unless @css;
638
639 my $rel = 'stylesheet';
640 my $out = '';
641
642 --$depth;
643 my $uplink = $depth ? ('../' x $depth) : '';
644
645 foreach my $chunk (@css) {
646 next unless $chunk and @$chunk;
647
648 my( $url1, $url2, $title, $type, $media) = (
649 $self->_maybe_uplink( $chunk->[0], $uplink ),
650 esc(grep !ref($_), @$chunk)
651 );
652
653 $out .= qq{<link rel="$rel" title="$title" type="$type" href="$url1$url2" media="$media" >\n};
654
655 $rel = 'alternate stylesheet'; # alternates = all non-first iterations
656 }
657 return $out;
658}
659
660# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
661sub _maybe_uplink {
662 # if the given URL looks relative, return the given uplink string --
663 # otherwise return emptystring
664 my($self, $url, $uplink) = @_;
665 ($url =~ m{^\./} or $url !~ m{[/\:]} )
666 ? $uplink
667 : ''
668 # qualify it, if/as needed
669}
670
671# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
672sub _gen_css_wad {
673 my $self = $_[0];
674 my $css_template = $self->_css_template;
675 foreach my $variation (
676
677 # Commented out for sake of concision:
678 #
679 # 011n=black_with_red_on_white
680 # 001n=black_with_yellow_on_white
681 # 101n=black_with_green_on_white
682 # 110=white_with_yellow_on_black
683 # 010=white_with_green_on_black
684 # 011=white_with_blue_on_black
685 # 100=white_with_red_on_black
9d65762f 686 '110n=blkbluw', # black_with_blue_on_white
687 '010n=blkmagw', # black_with_magenta_on_white
688 '100n=blkcynw', # black_with_cyan_on_white
689 '101=whtprpk', # white_with_purple_on_black
690 '001=whtnavk', # white_with_navy_blue_on_black
691 '010a=grygrnk', # grey_with_green_on_black
692 '010b=whtgrng', # white_with_green_on_grey
693 '101an=blkgrng', # black_with_green_on_grey
694 '101bn=grygrnw', # grey_with_green_on_white
695 ) {
351625bd 696
697 my $outname = $variation;
698 my($flipmode, @swap) = ( ($4 || ''), $1,$2,$3)
699 if $outname =~ s/^([012])([012])([[012])([a-z]*)=?//s;
700 @swap = () if '010' eq join '', @swap; # 010 is a swop-no-op!
701
702 my $this_css =
703 "/* This file is autogenerated. Do not edit. $variation */\n\n"
704 . $css_template;
705
706 # Only look at three-digitty colors, for now at least.
707 if( $flipmode =~ m/n/ ) {
708 $this_css =~ s/(#[0-9a-fA-F]{3})\b/_color_negate($1)/eg;
709 $this_css =~ s/\bthin\b/medium/g;
710 }
711 $this_css =~ s<#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b>
712 < join '', '#', ($1,$2,$3)[@swap] >eg if @swap;
713
714 if( $flipmode =~ m/a/)
715 { $this_css =~ s/#fff\b/#999/gi } # black -> dark grey
716 elsif($flipmode =~ m/b/)
717 { $this_css =~ s/#000\b/#666/gi } # white -> light grey
718
719 my $name = $outname;
720 $name =~ tr/-_/ /;
721 $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
722 }
723
724 # Now a few indexless variations:
9d65762f 725 foreach my $variation (
726 'blkbluw', # black_with_blue_on_white
727 'whtpurk', # white_with_purple_on_black
728 'whtgrng', # white_with_green_on_grey
729 'grygrnw', # grey_with_green_on_white
730 ) {
731 my $outname = "$variation\_";
351625bd 732 my $this_css = join "\n",
733 "/* This file is autogenerated. Do not edit. $outname */\n",
734 "\@import url(\"./_$variation.css\");",
735 ".indexgroup { display: none; }",
736 "\n",
737 ;
738 my $name = $outname;
739 $name =~ tr/-_/ /;
9d65762f 740 $self->add_css( "$outname.css", 0, $name, 0, 0, \$this_css);
351625bd 741 }
742
743 return;
744}
745
746sub _color_negate {
747 my $x = lc $_[0];
748 $x =~ tr[0123456789abcdef]
749 [fedcba9876543210];
750 return $x;
751}
752
753#===========================================================================
754
755sub add_javascript {
756 my($self, $url, $content_type, $_code) = @_;
757 return unless $url;
758 push @{ $self->_javascript_wad }, [
759 $url, $content_type || 'text/javascript', $_code
760 ];
761 return;
762}
763
764sub _spray_javascript {
765 my($self, $outdir) = @_;
766 return unless $self->javascript_flurry();
767 $self->_gen_javascript_wad();
768
769 my $lol = $self->_javascript_wad;
770 foreach my $script (@$lol) {
771 my $url = $script->[0];
772 my $outfile;
773
774 if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) {
69473a20 775 $outfile = $self->filespecsys->catfile( $outdir, "$1" );
351625bd 776 DEBUG > 5 and print "Noting $$script[0] as a file I'll create.\n";
777 } else {
778 DEBUG > 5 and print "OK, noting $$script[0] as an external JavaScript.\n";
779 next;
780 }
781
782 #$self->muse( "Writing JavaScript file $outfile" );
783 my $Jsout = $self->_wopen($outfile);
784
785 print $Jsout ${$script->[-1]}
786 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
787 close($Jsout);
788 DEBUG > 5 and print "Wrote $outfile\n";
789 }
790
791 return;
792}
793
794sub _gen_javascript_wad {
795 my $self = $_[0];
796 my $js_code = $self->_javascript || return;
797 $self->add_javascript( "_podly.js", 0, \$js_code);
798 return;
799}
800
801sub _javascript_wad_to_markup {
802 my($self, $depth) = @_;
803
804 my @scripts = @{ $self->_javascript_wad || return '' };
805 return '' unless @scripts;
806
807 my $out = '';
808
809 --$depth;
810 my $uplink = $depth ? ('../' x $depth) : '';
811
812 foreach my $s (@scripts) {
813 next unless $s and @$s;
814
815 my( $url1, $url2, $type, $media) = (
816 $self->_maybe_uplink( $s->[0], $uplink ),
817 esc(grep !ref($_), @$s)
818 );
819
820 $out .= qq{<script type="$type" src="$url1$url2"></script>\n};
821 }
822 return $out;
823}
824
825#===========================================================================
826
827sub _css_template { return $CSS }
828sub _javascript { return $JAVASCRIPT }
829
830$CSS = <<'EOCSS';
831/* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */
832
833@media all { .hide { display: none; } }
834
835@media print {
836 .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none }
837
838 * {
839 border-color: black !important;
840 color: black !important;
841 background-color: transparent !important;
842 background-image: none !important;
843 }
844
845 dl.superindex > dd {
846 word-spacing: .6em;
847 }
848}
849
850@media aural, braille, embossed {
851 div.indexgroup { display: none; } /* Too noisy, don't you think? */
852 dl.superindex > dt:before { content: "Group "; }
853 dl.superindex > dt:after { content: " contains:"; }
854 .backlinktop a:before { content: "Back to contents"; }
855 .backlinkbottom a:before { content: "Back to contents"; }
856}
857
858@media aural {
859 dl.superindex > dt { pause-before: 600ms; }
860}
861
862@media screen, tty, tv, projection {
863 .noscreen { display: none; }
864
865 a:link { color: #7070ff; text-decoration: underline; }
866 a:visited { color: #e030ff; text-decoration: underline; }
867 a:active { color: #800000; text-decoration: underline; }
868 body.contentspage a { text-decoration: none; }
869 a.u { color: #fff !important; text-decoration: none; }
870
871 body.pod {
872 margin: 0 5px;
873 color: #fff;
874 background-color: #000;
875 }
876
877 body.pod h1, body.pod h2, body.pod h3, body.pod h4 {
878 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
879 font-weight: normal;
880 margin-top: 1.2em;
881 margin-bottom: .1em;
882 border-top: thin solid transparent;
883 /* margin-left: -5px; border-left: 2px #7070ff solid; padding-left: 3px; */
884 }
885
886 body.pod h1 { border-top-color: #0a0; }
887 body.pod h2 { border-top-color: #080; }
888 body.pod h3 { border-top-color: #040; }
889 body.pod h4 { 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
896 body.pod dt {
897 font-size: 105%; /* just a wee bit more than normal */
898 }
899
900 .indexgroup { font-size: 80%; }
901
902 .backlinktop, .backlinkbottom {
903 margin-left: -5px;
904 margin-right: -5px;
905 background-color: #040;
906 border-top: thin solid #050;
907 border-bottom: thin solid #050;
908 }
909
910 .backlinktop a, .backlinkbottom a {
911 text-decoration: none;
912 color: #080;
913 background-color: #000;
914 border: thin solid #0d0;
915 }
916 .backlinkbottom { margin-bottom: 0; padding-bottom: 0; }
917 .backlinktop { margin-top: 0; padding-top: 0; }
918
919 body.contentspage {
920 color: #fff;
921 background-color: #000;
922 }
923
924 body.contentspage h1 {
925 color: #0d0;
926 margin-left: 1em;
927 margin-right: 1em;
928 text-indent: -.9em;
929 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
930 font-weight: normal;
931 border-top: thin solid #fff;
932 border-bottom: thin solid #fff;
933 text-align: center;
934 }
935
936 dl.superindex > dt {
937 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
938 font-weight: normal;
939 font-size: 90%;
940 margin-top: .45em;
941 /* margin-bottom: -.15em; */
942 }
943 dl.superindex > dd {
944 word-spacing: .6em; /* most important rule here! */
945 }
946 dl.superindex > a:link {
947 text-decoration: none;
948 color: #fff;
949 }
950
951 .contentsfooty {
952 border-top: thin solid #999;
953 font-size: 90%;
954 }
955
956}
957
958/* The End */
959
960EOCSS
961
962#==========================================================================
963
964$JAVASCRIPT = <<'EOJAVASCRIPT';
965
966// From http://www.alistapart.com/articles/alternate/
967
968function setActiveStyleSheet(title) {
969 var i, a, main;
970 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
971 if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
972 a.disabled = true;
973 if(a.getAttribute("title") == title) a.disabled = false;
974 }
975 }
976}
977
978function getActiveStyleSheet() {
979 var i, a;
980 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
981 if( a.getAttribute("rel").indexOf("style") != -1
982 && a.getAttribute("title")
983 && !a.disabled
984 ) return a.getAttribute("title");
985 }
986 return null;
987}
988
989function getPreferredStyleSheet() {
990 var i, a;
991 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) {
992 if( a.getAttribute("rel").indexOf("style") != -1
993 && a.getAttribute("rel").indexOf("alt") == -1
994 && a.getAttribute("title")
995 ) return a.getAttribute("title");
996 }
997 return null;
998}
999
1000function createCookie(name,value,days) {
1001 if (days) {
1002 var date = new Date();
1003 date.setTime(date.getTime()+(days*24*60*60*1000));
1004 var expires = "; expires="+date.toGMTString();
1005 }
1006 else expires = "";
1007 document.cookie = name+"="+value+expires+"; path=/";
1008}
1009
1010function readCookie(name) {
1011 var nameEQ = name + "=";
1012 var ca = document.cookie.split(';');
1013 for(var i=0 ; i < ca.length ; i++) {
1014 var c = ca[i];
1015 while (c.charAt(0)==' ') c = c.substring(1,c.length);
1016 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
1017 }
1018 return null;
1019}
1020
1021window.onload = function(e) {
1022 var cookie = readCookie("style");
1023 var title = cookie ? cookie : getPreferredStyleSheet();
1024 setActiveStyleSheet(title);
1025}
1026
1027window.onunload = function(e) {
1028 var title = getActiveStyleSheet();
1029 createCookie("style", title, 365);
1030}
1031
1032var cookie = readCookie("style");
1033var title = cookie ? cookie : getPreferredStyleSheet();
1034setActiveStyleSheet(title);
1035
1036// The End
1037
1038EOJAVASCRIPT
1039
1040# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10411;
1042__END__
1043
1044
1045=head1 NAME
1046
1047Pod::Simple::HTMLBatch - convert several Pod files to several HTML files
1048
1049=head1 SYNOPSIS
1050
1051 perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out
1052
1053
1054=head1 DESCRIPTION
1055
1056This module is used for running batch-conversions of a lot of HTML
1057documents
1058
1059This class is NOT a subclass of Pod::Simple::HTML
1060(nor of bad old Pod::Html) -- although it uses
1061Pod::Simple::HTML for doing the conversion of each document.
1062
1063The normal use of this class is like so:
1064
1065 use Pod::Simple::HTMLBatch;
1066 my $batchconv = Pod::Simple::HTMLBatch->new;
1067 $batchconv->some_option( some_value );
1068 $batchconv->some_other_option( some_other_value );
1069 $batchconv->batch_convert( \@search_dirs, $output_dir );
1070
1071=head2 FROM THE COMMAND LINE
1072
1073Note that this class also provides
1074(but does not export) the function Pod::Simple::HTMLBatch::go.
1075This is basically just a shortcut for C<<
1076Pod::Simple::HTMLBatch->batch_convert(@ARGV) >>.
1077It's meant to be handy for calling from the command line.
1078
1079However, the shortcut requires that you specify exactly two command-line
1080arguments, C<indirs> and C<outdir>.
1081
1082Example:
1083
1084 % mkdir out_html
1085 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go @INC out_html
1086 (to convert the pod from Perl's @INC
1087 files under the directory ../htmlversion)
1088
1089(Note that the command line there contains a literal atsign-I-N-C. This
1090is handled as a special case by batch_convert, in order to save you having
1091to enter the odd-looking "" as the first command-line parameter when you
1092mean "just use whatever's in @INC".)
1093
1094Example:
1095
1096 % mkdir ../seekrut
1097 % chmod og-rx ../seekrut
1098 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go . ../htmlversion
1099 (to convert the pod under the current dir into HTML
1100 files under the directory ../htmlversion)
1101
1102Example:
1103
1104 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs .
1105 (to convert all pod from happydocs into the current directory)
1106
1107
1108
1109=head1 MAIN METHODS
1110
1111=over
1112
1113=item $batchconv = Pod::Simple::HTMLBatch->new;
1114
1115This TODO
1116
1117
1118=item $batchconv->batch_convert( I<indirs>, I<outdir> );
1119
1120this TODO
1121
1122=item $batchconv->batch_convert( undef , ...);
1123
1124=item $batchconv->batch_convert( q{@INC}, ...);
1125
1126These two values for I<indirs> specify that the normal Perl @INC
1127
1128=item $batchconv->batch_convert( \@dirs , ...);
1129
1130This specifies that the input directories are the items in
1131the arrayref C<\@dirs>.
1132
1133=item $batchconv->batch_convert( "somedir" , ...);
1134
1135This specifies that the director "somedir" is the input.
1136(This can be an absolute or relative path, it doesn't matter.)
1137
1138A common value you might want would be just "." for the current
1139directory:
1140
1141 $batchconv->batch_convert( "." , ...);
1142
1143
1144=item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1145
1146This specifies that you want the dirs "somedir", "somother", and "also"
1147scanned, just as if you'd passed the arrayref
1148C<[qw( somedir someother also)]>. Note that a ":"-separator is normal
1149under Unix, but Under MSWin, you'll need C<'somedir;someother;also'>
1150instead, since the pathsep on MSWin is ";" instead of ":". (And
1151I<that> is because ":" often comes up in paths, like
1152C<"c:/perl/lib">.)
1153
1154(Exactly what separator character should be used, is gotten from
1155C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1156
1157=item $batchconv->batch_convert( ... , undef );
1158
1159This specifies that you want the HTML output to go into the current
1160directory.
1161
1162(Note that a missing or undefined value means a different thing in
1163the first slot than in the second. That's so that C<batch_convert()>
1164with no arguments (or undef arguments) means "go from @INC, into
1165the current directory.)
1166
1167=item $batchconv->batch_convert( ... , 'somedir' );
1168
1169This specifies that you want the HTML output to go into the
1170directory 'somedir'.
1171(This can be an absolute or relative path, it doesn't matter.)
1172
1173=back
1174
1175
1176Note that you can also call C<batch_convert> as a class method,
1177like so:
1178
1179 Pod::Simple::HTMLBatch->batch_convert( ... );
1180
1181That is just short for this:
1182
1183 Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1184
1185That is, it runs a conversion with default options, for
1186whatever inputdirs and output dir you specify.
1187
1188
1189=head2 ACCESSOR METHODS
1190
1191The following are all accessor methods -- that is, they don't do anything
1192on their own, but just alter the contents of the conversion object,
1193which comprises the options for this particular batch conversion.
1194
1195We show the "put" form of the accessors below (i.e., the syntax you use
1196for setting the accessor to a specific value). But you can also
1197call each method with no parameters to get its current value. For
1198example, C<< $self->contents_file() >> returns the current value of
1199the contents_file attribute.
1200
1201=over
1202
1203
1204=item $batchconv->verbose( I<nonnegative_integer> );
1205
1206This controls how verbose to be during batch conversion, as far as
1207notes to STDOUT (or whatever is C<select>'d) about how the conversion
1208is going. If 0, no progress information is printed.
1209If 1 (the default value), some progress information is printed.
1210Higher values print more information.
1211
1212
1213=item $batchconv->index( I<true-or-false> );
1214
1215This controls whether or not each HTML page is liable to have a little
1216table of contents at the top (which we call an "index" for historical
1217reasons). This is true by default.
1218
1219
1220=item $batchconv->contents_file( I<filename> );
1221
1222If set, should be the name of a file (in the output directory)
1223to write the HTML index to. The default value is "index.html".
1224If you set this to a false value, no contents file will be written.
1225
1226=item $batchconv->contents_page_start( I<HTML_string> );
1227
1228This specifies what string should be put at the beginning of
1229the contents page.
1230The default is a string more or less like this:
1231
1232 <html>
1233 <head><title>Perl Documentation</title></head>
1234 <body class='contentspage'>
1235 <h1>Perl Documentation</h1>
1236
1237=item $batchconv->contents_page_end( I<HTML_string> );
1238
1239This specifies what string should be put at the end of the contents page.
1240The default is a string more or less like this:
1241
1242 <p class='contentsfooty'>Generated by
1243 Pod::Simple::HTMLBatch v3.01 under Perl v5.008
1244 <br >At Fri May 14 22:26:42 2004 GMT,
1245 which is Fri May 14 14:26:42 2004 local time.</p>
1246
1247
1248
1249=item $batchconv->add_css( $url );
1250
1251TODO
1252
1253=item $batchconv->add_javascript( $url );
1254
1255TODO
1256
1257=item $batchconv->css_flurry( I<true-or-false> );
1258
1259If true (the default value), we autogenerate some CSS files in the
1260output directory, and set our HTML files to use those.
1261TODO: continue
1262
1263=item $batchconv->javascript_flurry( I<true-or-false> );
1264
1265If true (the default value), we autogenerate a JavaScript in the
1266output directory, and set our HTML files to use it. Currently,
1267the JavaScript is used only to get the browser to remember what
1268stylesheet it prefers.
1269TODO: continue
1270
1271=item $batchconv->no_contents_links( I<true-or-false> );
1272
1273TODO
1274
1275=item $batchconv->html_render_class( I<classname> );
1276
1277This sets what class is used for rendering the files.
9d65762f 1278The default is "Pod::Simple::HTML". If you set it to something else,
1279it should probably be a subclass of Pod::Simple::HTML, and you should
1280C<require> or C<use> that class so that's it's loaded before
1281Pod::Simple::HTMLBatch tries loading it.
1282
1283=item $batchconv->search_class( I<classname> );
1284
1285This sets what class is used for searching for the files.
351625bd 1286The default is "Pod::Simple::Search". If you set it to something else,
1287it should probably be a subclass of Pod::Simple::Search, and you should
1288C<require> or C<use> that class so that's it's loaded before
1289Pod::Simple::HTMLBatch tries loading it.
1290
1291=back
1292
1293
1294
1295
1296=head1 NOTES ON CUSTOMIZATION
1297
1298TODO
1299
1300 call add_css($someurl) to add stylesheet as alternate
1301 call add_css($someurl,1) to add as primary stylesheet
1302
1303 call add_javascript
1304
1305 subclass Pod::Simple::HTML and set $batchconv->html_render_class to
1306 that classname
1307 and maybe override
1308 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
1309 or maybe override
1310 $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
9d65762f 1311 subclass Pod::Simple::Search and set $batchconv->search_class to
1312 that classname
351625bd 1313
1314
1315
1316=head1 ASK ME!
1317
1318If you want to do some kind of big pod-to-HTML version with some
1319particular kind of option that you don't see how to achieve using this
1320module, email me (C<sburke@cpan.org>) and I'll probably have a good idea
1321how to do it. For reasons of concision and energetic laziness, some
1322methods and options in this module (and the dozen modules it depends on)
1323are undocumented; but one of those undocumented bits might be just what
1324you're looking for.
1325
1326
1327=head1 SEE ALSO
1328
1329L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1330
a242eeb4 1331=head1 SUPPORT
351625bd 1332
a242eeb4 1333Questions or discussion about POD and Pod::Simple should be sent to the
1334pod-people@perl.org mail list. Send an empty email to
1335pod-people-subscribe@perl.org to subscribe.
351625bd 1336
a242eeb4 1337This module is managed in an open GitHub repository,
1338L<http://github.com/theory/pod-simple/>. Feel free to fork and contribute, or
1339to clone L<git://github.com/theory/pod-simple.git> and send patches!
1340
1341Patches against Pod::Simple are welcome. Please send bug reports to
1342<bug-pod-simple@rt.cpan.org>.
351625bd 1343
1344=head1 COPYRIGHT AND DISCLAIMERS
1345
433cf6b4 1346Copyright (c) 2002 Sean M. Burke.
351625bd 1347
1348This library is free software; you can redistribute it and/or modify it
1349under the same terms as Perl itself.
1350
1351This program is distributed in the hope that it will be useful, but
1352without any warranty; without even the implied warranty of
1353merchantability or fitness for a particular purpose.
1354
1355=head1 AUTHOR
1356
a242eeb4 1357Pod::Simple was created by Sean M. Burke <sburke@cpan.org>.
1358But don't bother him, he's retired.
351625bd 1359
a242eeb4 1360Pod::Simple is maintained by:
1361
1362=over
351625bd 1363
a242eeb4 1364=item * Allison Randal C<allison@perl.org>
351625bd 1365
a242eeb4 1366=item * Hans Dieter Pearcey C<hdp@cpan.org>
351625bd 1367
a242eeb4 1368=item * David E. Wheeler C<dwheeler@cpan.org>
1369
1370=back
1371
1372=cut