This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Fix Win32 build
[perl5.git] / lib / Pod / ParseUtils.pm
1 #############################################################################
2 # Pod/ParseUtils.pm -- helpers for POD parsing and conversion
3 #
4 # Copyright (C) 1999-2000 by Marek Rouchal. All rights reserved.
5 # This file is part of "PodParser". PodParser is free software;
6 # you can redistribute it and/or modify it under the same terms
7 # as Perl itself.
8 #############################################################################
9
10 package Pod::ParseUtils;
11 use strict;
12
13 use vars qw($VERSION);
14 $VERSION = '1.36'; ## Current version of this package
15 require  5.005;    ## requires this Perl version or later
16
17 =head1 NAME
18
19 Pod::ParseUtils - helpers for POD parsing and conversion
20
21 =head1 SYNOPSIS
22
23   use Pod::ParseUtils;
24
25   my $list = new Pod::List;
26   my $link = Pod::Hyperlink->new('Pod::Parser');
27
28 =head1 DESCRIPTION
29
30 B<Pod::ParseUtils> contains a few object-oriented helper packages for
31 POD parsing and processing (i.e. in POD formatters and translators).
32
33 =cut
34
35 #-----------------------------------------------------------------------------
36 # Pod::List
37 #
38 # class to hold POD list info (=over, =item, =back)
39 #-----------------------------------------------------------------------------
40
41 package Pod::List;
42
43 use Carp;
44
45 =head2 Pod::List
46
47 B<Pod::List> can be used to hold information about POD lists
48 (written as =over ... =item ... =back) for further processing.
49 The following methods are available:
50
51 =over 4
52
53 =item Pod::List-E<gt>new()
54
55 Create a new list object. Properties may be specified through a hash
56 reference like this:
57
58   my $list = Pod::List->new({ -start => $., -indent => 4 });
59
60 See the individual methods/properties for details.
61
62 =cut
63
64 sub new {
65     my $this = shift;
66     my $class = ref($this) || $this;
67     my %params = @_;
68     my $self = {%params};
69     bless $self, $class;
70     $self->initialize();
71     return $self;
72 }
73
74 sub initialize {
75     my $self = shift;
76     $self->{-file} ||= 'unknown';
77     $self->{-start} ||= 'unknown';
78     $self->{-indent} ||= 4; # perlpod: "should be the default"
79     $self->{_items} = [];
80     $self->{-type} ||= '';
81 }
82
83 =item $list-E<gt>file()
84
85 Without argument, retrieves the file name the list is in. This must
86 have been set before by either specifying B<-file> in the B<new()>
87 method or by calling the B<file()> method with a scalar argument.
88
89 =cut
90
91 # The POD file name the list appears in
92 sub file {
93    return (@_ > 1) ? ($_[0]->{-file} = $_[1]) : $_[0]->{-file};
94 }
95
96 =item $list-E<gt>start()
97
98 Without argument, retrieves the line number where the list started.
99 This must have been set before by either specifying B<-start> in the
100 B<new()> method or by calling the B<start()> method with a scalar
101 argument.
102
103 =cut
104
105 # The line in the file the node appears
106 sub start {
107    return (@_ > 1) ? ($_[0]->{-start} = $_[1]) : $_[0]->{-start};
108 }
109
110 =item $list-E<gt>indent()
111
112 Without argument, retrieves the indent level of the list as specified
113 in C<=over n>. This must have been set before by either specifying
114 B<-indent> in the B<new()> method or by calling the B<indent()> method
115 with a scalar argument.
116
117 =cut
118
119 # indent level
120 sub indent {
121    return (@_ > 1) ? ($_[0]->{-indent} = $_[1]) : $_[0]->{-indent};
122 }
123
124 =item $list-E<gt>type()
125
126 Without argument, retrieves the list type, which can be an arbitrary value,
127 e.g. C<OL>, C<UL>, ... when thinking the HTML way.
128 This must have been set before by either specifying
129 B<-type> in the B<new()> method or by calling the B<type()> method
130 with a scalar argument.
131
132 =cut
133
134 # The type of the list (UL, OL, ...)
135 sub type {
136    return (@_ > 1) ? ($_[0]->{-type} = $_[1]) : $_[0]->{-type};
137 }
138
139 =item $list-E<gt>rx()
140
141 Without argument, retrieves a regular expression for simplifying the 
142 individual item strings once the list type has been determined. Usage:
143 E.g. when converting to HTML, one might strip the leading number in
144 an ordered list as C<E<lt>OLE<gt>> already prints numbers itself.
145 This must have been set before by either specifying
146 B<-rx> in the B<new()> method or by calling the B<rx()> method
147 with a scalar argument.
148
149 =cut
150
151 # The regular expression to simplify the items
152 sub rx {
153    return (@_ > 1) ? ($_[0]->{-rx} = $_[1]) : $_[0]->{-rx};
154 }
155
156 =item $list-E<gt>item()
157
158 Without argument, retrieves the array of the items in this list.
159 The items may be represented by any scalar.
160 If an argument has been given, it is pushed on the list of items.
161
162 =cut
163
164 # The individual =items of this list
165 sub item {
166     my ($self,$item) = @_;
167     if(defined $item) {
168         push(@{$self->{_items}}, $item);
169         return $item;
170     }
171     else {
172         return @{$self->{_items}};
173     }
174 }
175
176 =item $list-E<gt>parent()
177
178 Without argument, retrieves information about the parent holding this
179 list, which is represented as an arbitrary scalar.
180 This must have been set before by either specifying
181 B<-parent> in the B<new()> method or by calling the B<parent()> method
182 with a scalar argument.
183
184 =cut
185
186 # possibility for parsers/translators to store information about the
187 # lists's parent object
188 sub parent {
189    return (@_ > 1) ? ($_[0]->{-parent} = $_[1]) : $_[0]->{-parent};
190 }
191
192 =item $list-E<gt>tag()
193
194 Without argument, retrieves information about the list tag, which can be
195 any scalar.
196 This must have been set before by either specifying
197 B<-tag> in the B<new()> method or by calling the B<tag()> method
198 with a scalar argument.
199
200 =back
201
202 =cut
203
204 # possibility for parsers/translators to store information about the
205 # list's object
206 sub tag {
207    return (@_ > 1) ? ($_[0]->{-tag} = $_[1]) : $_[0]->{-tag};
208 }
209
210 #-----------------------------------------------------------------------------
211 # Pod::Hyperlink
212 #
213 # class to manipulate POD hyperlinks (L<>)
214 #-----------------------------------------------------------------------------
215
216 package Pod::Hyperlink;
217
218 =head2 Pod::Hyperlink
219
220 B<Pod::Hyperlink> is a class for manipulation of POD hyperlinks. Usage:
221
222   my $link = Pod::Hyperlink->new('alternative text|page/"section in page"');
223
224 The B<Pod::Hyperlink> class is mainly designed to parse the contents of the
225 C<LE<lt>...E<gt>> sequence, providing a simple interface for accessing the
226 different parts of a POD hyperlink for further processing. It can also be
227 used to construct hyperlinks.
228
229 =over 4
230
231 =item Pod::Hyperlink-E<gt>new()
232
233 The B<new()> method can either be passed a set of key/value pairs or a single
234 scalar value, namely the contents of a C<LE<lt>...E<gt>> sequence. An object
235 of the class C<Pod::Hyperlink> is returned. The value C<undef> indicates a
236 failure, the error message is stored in C<$@>.
237
238 =cut
239
240 use Carp;
241
242 sub new {
243     my $this = shift;
244     my $class = ref($this) || $this;
245     my $self = +{};
246     bless $self, $class;
247     $self->initialize();
248     if(defined $_[0]) {
249         if(ref($_[0])) {
250             # called with a list of parameters
251             %$self = %{$_[0]};
252             $self->_construct_text();
253         }
254         else {
255             # called with L<> contents
256             return unless($self->parse($_[0]));
257         }
258     }
259     return $self;
260 }
261
262 sub initialize {
263     my $self = shift;
264     $self->{-line} ||= 'undef';
265     $self->{-file} ||= 'undef';
266     $self->{-page} ||= '';
267     $self->{-node} ||= '';
268     $self->{-alttext} ||= '';
269     $self->{-type} ||= 'undef';
270     $self->{_warnings} = [];
271 }
272
273 =item $link-E<gt>parse($string)
274
275 This method can be used to (re)parse a (new) hyperlink, i.e. the contents
276 of a C<LE<lt>...E<gt>> sequence. The result is stored in the current object.
277 Warnings are stored in the B<warnings> property.
278 E.g. sections like C<LE<lt>open(2)E<gt>> are deprecated, as they do not point
279 to Perl documents. C<LE<lt>DBI::foo(3p)E<gt>> is wrong as well, the manpage
280 section can simply be dropped.
281
282 =cut
283
284 sub parse {
285     my $self = shift;
286     local($_) = $_[0];
287     # syntax check the link and extract destination
288     my ($alttext,$page,$node,$type,$quoted) = (undef,'','','',0);
289
290     $self->{_warnings} = [];
291
292     # collapse newlines with whitespace
293     s/\s*\n+\s*/ /g;
294
295     # strip leading/trailing whitespace
296     if(s/^[\s\n]+//) {
297         $self->warning('ignoring leading whitespace in link');
298     }
299     if(s/[\s\n]+$//) {
300         $self->warning('ignoring trailing whitespace in link');
301     }
302     unless(length($_)) {
303         _invalid_link('empty link');
304         return;
305     }
306
307     ## Check for different possibilities. This is tedious and error-prone
308     # we match all possibilities (alttext, page, section/item)
309     #warn "DEBUG: link=$_\n";
310
311     # only page
312     # problem: a lot of people use (), or (1) or the like to indicate
313     # man page sections. But this collides with L<func()> that is supposed
314     # to point to an internal funtion...
315     my $page_rx = '[\w.-]+(?:::[\w.-]+)*(?:[(](?:\d\w*|)[)]|)';
316     # page name only
317     if(/^($page_rx)$/o) {
318         $page = $1;
319         $type = 'page';
320     }
321     # alttext, page and "section"
322     elsif(m{^(.*?)\s*[|]\s*($page_rx)\s*/\s*"(.+)"$}o) {
323         ($alttext, $page, $node) = ($1, $2, $3);
324         $type = 'section';
325         $quoted = 1; #... therefore | and / are allowed
326     }
327     # alttext and page
328     elsif(/^(.*?)\s*[|]\s*($page_rx)$/o) {
329         ($alttext, $page) = ($1, $2);
330         $type = 'page';
331     }
332     # alttext and "section"
333     elsif(m{^(.*?)\s*[|]\s*(?:/\s*|)"(.+)"$}) {
334         ($alttext, $node) = ($1,$2);
335         $type = 'section';
336         $quoted = 1;
337     }
338     # page and "section"
339     elsif(m{^($page_rx)\s*/\s*"(.+)"$}o) {
340         ($page, $node) = ($1, $2);
341         $type = 'section';
342         $quoted = 1;
343     }
344     # page and item
345     elsif(m{^($page_rx)\s*/\s*(.+)$}o) {
346         ($page, $node) = ($1, $2);
347         $type = 'item';
348     }
349     # only "section"
350     elsif(m{^/?"(.+)"$}) {
351         $node = $1;
352         $type = 'section';
353         $quoted = 1;
354     }
355     # only item
356     elsif(m{^\s*/(.+)$}) {
357         $node = $1;
358         $type = 'item';
359     }
360
361     # non-standard: Hyperlink with alt-text - doesn't remove protocol prefix, maybe it should?
362     elsif(/^ \s* (.*?) \s* [|] \s* (\w+:[^:\s] [^\s|]*?) \s* $/ix) {
363       ($alttext,$node) = ($1,$2);
364       $type = 'hyperlink';
365     }
366
367     # non-standard: Hyperlink
368     elsif(/^(\w+:[^:\s]\S*)$/i) {
369         $node = $1;
370         $type = 'hyperlink';
371     }
372     # alttext, page and item
373     elsif(m{^(.*?)\s*[|]\s*($page_rx)\s*/\s*(.+)$}o) {
374         ($alttext, $page, $node) = ($1, $2, $3);
375         $type = 'item';
376     }
377     # alttext and item
378     elsif(m{^(.*?)\s*[|]\s*/(.+)$}) {
379         ($alttext, $node) = ($1,$2);
380     }
381     # must be an item or a "malformed" section (without "")
382     else {
383         $node = $_;
384         $type = 'item';
385     }
386     # collapse whitespace in nodes
387     $node =~ s/\s+/ /gs;
388
389     # empty alternative text expands to node name
390     if(defined $alttext) {
391         if(!length($alttext)) {
392           $alttext = $node || $page;
393         }
394     }
395     else {
396         $alttext = '';
397     }
398
399     if($page =~ /[(]\w*[)]$/) {
400         $self->warning("(section) in '$page' deprecated");
401     }
402     if(!$quoted && $node =~ m{[|/]} && $type ne 'hyperlink') {
403         $self->warning("node '$node' contains non-escaped | or /");
404     }
405     if($alttext =~ m{[|/]}) {
406         $self->warning("alternative text '$node' contains non-escaped | or /");
407     }
408     $self->{-page} = $page;
409     $self->{-node} = $node;
410     $self->{-alttext} = $alttext;
411     #warn "DEBUG: page=$page section=$section item=$item alttext=$alttext\n";
412     $self->{-type} = $type;
413     $self->_construct_text();
414     1;
415 }
416
417 sub _construct_text {
418     my $self = shift;
419     my $alttext = $self->alttext();
420     my $type = $self->type();
421     my $section = $self->node();
422     my $page = $self->page();
423     my $page_ext = '';
424     $page =~ s/([(]\w*[)])$// && ($page_ext = $1);
425     if($alttext) {
426         $self->{_text} = $alttext;
427     }
428     elsif($type eq 'hyperlink') {
429         $self->{_text} = $section;
430     }
431     else {
432         $self->{_text} = ($section || '') .
433             (($page && $section) ? ' in ' : '') .
434             "$page$page_ext";
435     }
436     # for being marked up later
437     # use the non-standard markers P<> and Q<>, so that the resulting
438     # text can be parsed by the translators. It's their job to put
439     # the correct hypertext around the linktext
440     if($alttext) {
441         $self->{_markup} = "Q<$alttext>";
442     }
443     elsif($type eq 'hyperlink') {
444         $self->{_markup} = "Q<$section>";
445     }
446     else {
447         $self->{_markup} = (!$section ? '' : "Q<$section>") .
448             ($page ? ($section ? ' in ':'') . "P<$page>$page_ext" : '');
449     }
450 }
451
452 =item $link-E<gt>markup($string)
453
454 Set/retrieve the textual value of the link. This string contains special
455 markers C<PE<lt>E<gt>> and C<QE<lt>E<gt>> that should be expanded by the
456 translator's interior sequence expansion engine to the
457 formatter-specific code to highlight/activate the hyperlink. The details
458 have to be implemented in the translator.
459
460 =cut
461
462 #' retrieve/set markuped text
463 sub markup {
464     return (@_ > 1) ? ($_[0]->{_markup} = $_[1]) : $_[0]->{_markup};
465 }
466
467 =item $link-E<gt>text()
468
469 This method returns the textual representation of the hyperlink as above,
470 but without markers (read only). Depending on the link type this is one of
471 the following alternatives (the + and * denote the portions of the text
472 that are marked up):
473
474   +perl+                    L<perl>
475   *$|* in +perlvar+         L<perlvar/$|>
476   *OPTIONS* in +perldoc+    L<perldoc/"OPTIONS">
477   *DESCRIPTION*             L<"DESCRIPTION">
478
479 =cut
480
481 # The complete link's text
482 sub text {
483     return $_[0]->{_text};
484 }
485
486 =item $link-E<gt>warning()
487
488 After parsing, this method returns any warnings encountered during the
489 parsing process.
490
491 =cut
492
493 # Set/retrieve warnings
494 sub warning {
495     my $self = shift;
496     if(@_) {
497         push(@{$self->{_warnings}}, @_);
498         return @_;
499     }
500     return @{$self->{_warnings}};
501 }
502
503 =item $link-E<gt>file()
504
505 =item $link-E<gt>line()
506
507 Just simple slots for storing information about the line and the file
508 the link was encountered in. Has to be filled in manually.
509
510 =cut
511
512 # The line in the file the link appears
513 sub line {
514     return (@_ > 1) ? ($_[0]->{-line} = $_[1]) : $_[0]->{-line};
515 }
516
517 # The POD file name the link appears in
518 sub file {
519     return (@_ > 1) ? ($_[0]->{-file} = $_[1]) : $_[0]->{-file};
520 }
521
522 =item $link-E<gt>page()
523
524 This method sets or returns the POD page this link points to.
525
526 =cut
527
528 # The POD page the link appears on
529 sub page {
530     if (@_ > 1) {
531         $_[0]->{-page} = $_[1];
532         $_[0]->_construct_text();
533     }
534     return $_[0]->{-page};
535 }
536
537 =item $link-E<gt>node()
538
539 As above, but the destination node text of the link.
540
541 =cut
542
543 # The link destination
544 sub node {
545     if (@_ > 1) {
546         $_[0]->{-node} = $_[1];
547         $_[0]->_construct_text();
548     }
549     return $_[0]->{-node};
550 }
551
552 =item $link-E<gt>alttext()
553
554 Sets or returns an alternative text specified in the link.
555
556 =cut
557
558 # Potential alternative text
559 sub alttext {
560     if (@_ > 1) {
561         $_[0]->{-alttext} = $_[1];
562         $_[0]->_construct_text();
563     }
564     return $_[0]->{-alttext};
565 }
566
567 =item $link-E<gt>type()
568
569 The node type, either C<section> or C<item>. As an unofficial type,
570 there is also C<hyperlink>, derived from e.g. C<LE<lt>http://perl.comE<gt>>
571
572 =cut
573
574 # The type: item or headn
575 sub type {
576     return (@_ > 1) ? ($_[0]->{-type} = $_[1]) : $_[0]->{-type};
577 }
578
579 =item $link-E<gt>link()
580
581 Returns the link as contents of C<LE<lt>E<gt>>. Reciprocal to B<parse()>.
582
583 =back
584
585 =cut
586
587 # The link itself
588 sub link {
589     my $self = shift;
590     my $link = $self->page() || '';
591     if($self->node()) {
592         my $node = $self->node();
593         $node =~ s/\|/E<verbar>/g;
594         $node =~ s{/}{E<sol>}g;
595         if($self->type() eq 'section') {
596             $link .= ($link ? '/' : '') . '"' . $node . '"';
597         }
598         elsif($self->type() eq 'hyperlink') {
599             $link = $self->node();
600         }
601         else { # item
602             $link .= '/' . $node;
603         }
604     }
605     if($self->alttext()) {
606         my $text = $self->alttext();
607         $text =~ s/\|/E<verbar>/g;
608         $text =~ s{/}{E<sol>}g;
609         $link = "$text|$link";
610     }
611     return $link;
612 }
613
614 sub _invalid_link {
615     my ($msg) = @_;
616     # this sets @_
617     #eval { die "$msg\n" };
618     #chomp $@;
619     $@ = $msg; # this seems to work, too!
620     return;
621 }
622
623 #-----------------------------------------------------------------------------
624 # Pod::Cache
625 #
626 # class to hold POD page details
627 #-----------------------------------------------------------------------------
628
629 package Pod::Cache;
630
631 =head2 Pod::Cache
632
633 B<Pod::Cache> holds information about a set of POD documents,
634 especially the nodes for hyperlinks.
635 The following methods are available:
636
637 =over 4
638
639 =item Pod::Cache-E<gt>new()
640
641 Create a new cache object. This object can hold an arbitrary number of
642 POD documents of class Pod::Cache::Item.
643
644 =cut
645
646 sub new {
647     my $this = shift;
648     my $class = ref($this) || $this;
649     my $self = [];
650     bless $self, $class;
651     return $self;
652 }
653
654 =item $cache-E<gt>item()
655
656 Add a new item to the cache. Without arguments, this method returns a
657 list of all cache elements.
658
659 =cut
660
661 sub item {
662     my ($self,%param) = @_;
663     if(%param) {
664         my $item = Pod::Cache::Item->new(%param);
665         push(@$self, $item);
666         return $item;
667     }
668     else {
669         return @{$self};
670     }
671 }
672
673 =item $cache-E<gt>find_page($name)
674
675 Look for a POD document named C<$name> in the cache. Returns the
676 reference to the corresponding Pod::Cache::Item object or undef if
677 not found.
678
679 =back
680
681 =cut
682
683 sub find_page {
684     my ($self,$page) = @_;
685     foreach(@$self) {
686         if($_->page() eq $page) {
687             return $_;
688         }
689     }
690     return;
691 }
692
693 package Pod::Cache::Item;
694
695 =head2 Pod::Cache::Item
696
697 B<Pod::Cache::Item> holds information about individual POD documents,
698 that can be grouped in a Pod::Cache object.
699 It is intended to hold information about the hyperlink nodes of POD
700 documents.
701 The following methods are available:
702
703 =over 4
704
705 =item Pod::Cache::Item-E<gt>new()
706
707 Create a new object.
708
709 =cut
710
711 sub new {
712     my $this = shift;
713     my $class = ref($this) || $this;
714     my %params = @_;
715     my $self = {%params};
716     bless $self, $class;
717     $self->initialize();
718     return $self;
719 }
720
721 sub initialize {
722     my $self = shift;
723     $self->{-nodes} = [] unless(defined $self->{-nodes});
724 }
725
726 =item $cacheitem-E<gt>page()
727
728 Set/retrieve the POD document name (e.g. "Pod::Parser").
729
730 =cut
731
732 # The POD page
733 sub page {
734    return (@_ > 1) ? ($_[0]->{-page} = $_[1]) : $_[0]->{-page};
735 }
736
737 =item $cacheitem-E<gt>description()
738
739 Set/retrieve the POD short description as found in the C<=head1 NAME>
740 section.
741
742 =cut
743
744 # The POD description, taken out of NAME if present
745 sub description {
746    return (@_ > 1) ? ($_[0]->{-description} = $_[1]) : $_[0]->{-description};
747 }
748
749 =item $cacheitem-E<gt>path()
750
751 Set/retrieve the POD file storage path.
752
753 =cut
754
755 # The file path
756 sub path {
757    return (@_ > 1) ? ($_[0]->{-path} = $_[1]) : $_[0]->{-path};
758 }
759
760 =item $cacheitem-E<gt>file()
761
762 Set/retrieve the POD file name.
763
764 =cut
765
766 # The POD file name
767 sub file {
768    return (@_ > 1) ? ($_[0]->{-file} = $_[1]) : $_[0]->{-file};
769 }
770
771 =item $cacheitem-E<gt>nodes()
772
773 Add a node (or a list of nodes) to the document's node list. Note that
774 the order is kept, i.e. start with the first node and end with the last.
775 If no argument is given, the current list of nodes is returned in the
776 same order the nodes have been added.
777 A node can be any scalar, but usually is a pair of node string and
778 unique id for the C<find_node> method to work correctly.
779
780 =cut
781
782 # The POD nodes
783 sub nodes {
784     my ($self,@nodes) = @_;
785     if(@nodes) {
786         push(@{$self->{-nodes}}, @nodes);
787         return @nodes;
788     }
789     else {
790         return @{$self->{-nodes}};
791     }
792 }
793
794 =item $cacheitem-E<gt>find_node($name)
795
796 Look for a node or index entry named C<$name> in the object.
797 Returns the unique id of the node (i.e. the second element of the array
798 stored in the node array) or undef if not found.
799
800 =cut
801
802 sub find_node {
803     my ($self,$node) = @_;
804     my @search;
805     push(@search, @{$self->{-nodes}}) if($self->{-nodes});
806     push(@search, @{$self->{-idx}}) if($self->{-idx});
807     foreach(@search) {
808         if($_->[0] eq $node) {
809             return $_->[1]; # id
810         }
811     }
812     return;
813 }
814
815 =item $cacheitem-E<gt>idx()
816
817 Add an index entry (or a list of them) to the document's index list. Note that
818 the order is kept, i.e. start with the first node and end with the last.
819 If no argument is given, the current list of index entries is returned in the
820 same order the entries have been added.
821 An index entry can be any scalar, but usually is a pair of string and
822 unique id.
823
824 =back
825
826 =cut
827
828 # The POD index entries
829 sub idx {
830     my ($self,@idx) = @_;
831     if(@idx) {
832         push(@{$self->{-idx}}, @idx);
833         return @idx;
834     }
835     else {
836         return @{$self->{-idx}};
837     }
838 }
839
840 =head1 AUTHOR
841
842 Please report bugs using L<http://rt.cpan.org>.
843
844 Marek Rouchal E<lt>marekr@cpan.orgE<gt>, borrowing
845 a lot of things from L<pod2man> and L<pod2roff> as well as other POD
846 processing tools by Tom Christiansen, Brad Appleton and Russ Allbery.
847
848 =head1 SEE ALSO
849
850 L<pod2man>, L<pod2roff>, L<Pod::Parser>, L<Pod::Checker>,
851 L<pod2html>
852
853 =cut
854
855 1;