package Pod::Checker;
use vars qw($VERSION);
-$VERSION = 1.097; ## Current version of this package
+$VERSION = 1.098; ## Current version of this package
require 5.004; ## requires this Perl version or later
use Pod::ParseUtils; ## for hyperlinks and lists
$syntax_okay = podchecker($filepath, $outputpath, %options);
my $checker = new Pod::Checker %options;
+ $checker->parse_from_file($filepath, \*STDERR);
=head1 OPTIONS/ARGUMENTS
additional features they wish to see in B<Pod::Checker> and B<podchecker>
and verify that the checks are consistent with L<perlpod>.
-The following checks are preformed:
+The following checks are currently preformed:
=over 4
=item *
-Unknown '=xxxx' commands, unknown 'X<...>' interior-sequences,
+Unknown '=xxxx' commands, unknown 'XE<lt>...E<gt>' interior-sequences,
and unterminated interior sequences.
=item *
=back
-=head2 Additional Features
-
-While checking, this module collects document properties, e.g. the nodes
-for hyperlinks (C<=headX>, C<=item>). POD translators can use this feature
-to syntax-check and get the nodes in a first pass before actually starting
-to convert. This is expensive in terms of execution time, but allows for
-very robust conversions.
-
=head1 DIAGNOSTICS
=head2 Errors
The C<ZE<lt>E<gt>> sequence is supposed to be empty.
+=item * empty XE<lt>E<gt>
+
+The index entry specified contains nothing but whitespace.
+
=item * Spurious text after =pod / =cut
The commands C<=pod> and C<=cut> do not take any arguments.
I<[T.B.D.]>
-=head1 AUTHOR
+=head1 INTERFACE
-Brad Appleton E<lt>bradapp@enteract.comE<gt> (initial version),
-Marek Rouchal E<lt>marek@saftsack.fs.uni-bayreuth.deE<gt>
-
-Based on code for B<Pod::Text::pod2text()> written by
-Tom Christiansen E<lt>tchrist@mox.perl.comE<gt>
+While checking, this module collects document properties, e.g. the nodes
+for hyperlinks (C<=headX>, C<=item>) and index entries (C<XE<lt>E<gt>>).
+POD translators can use this feature to syntax-check and get the nodes in
+a first pass before actually starting to convert. This is expensive in terms
+of execution time, but allows for very robust conversions.
=cut
## Now check the pod document for errors
$checker->parse_from_file($infile, $outfile);
-
+
## Return the number of errors found
return $checker->num_errors();
}
$self->{_have_begin} = ''; # stores =begin
$self->{_links} = []; # stack for internal hyperlinks
$self->{_nodes} = []; # stack for =head/=item nodes
+ $self->{_index} = []; # text in X<>
# print warnings?
$self->{-warnings} = 1 unless(defined $self->{-warnings});
$self->{_current_head1} = ''; # the current =head1 block
}
+##################################
+
+=over 4
+
+=item C<$checker-E<gt>poderror( @args )>
+
+=item C<$checker-E<gt>poderror( {%opts}, @args )>
+
+Internal method for printing errors and warnings. If no options are
+given, simply prints "@_". The following options are recognized and used
+to form the output:
+
+ -msg
+
+A message to print prior to C<@args>.
+
+ -line
+
+The line number the error occurred in.
+
+ -file
+
+The file (name) the error occurred in.
+
+ -severity
+
+The error level, should be 'WARNING' or 'ERROR'.
+
+=cut
+
# Invoked as $self->poderror( @args ), or $self->poderror( {%opts}, @args )
sub poderror {
my $self = shift;
if($self->{-warnings} || !%opts || $opts{-severity} ne 'WARNING');
}
-# set/retrieve the number of errors found
+##################################
+
+=item C<$checker-E<gt>num_errors()>
+
+Set (if argument specified) and retrieve the number of errors found.
+
+=cut
+
sub num_errors {
return (@_ > 1) ? ($_[0]->{_NUM_ERRORS} = $_[1]) : $_[0]->{_NUM_ERRORS};
}
-# set and/or retrieve canonical name of POD
+##################################
+
+=item C<$checker-E<gt>name()>
+
+Set (if argument specified) and retrieve the canonical name of POD as
+found in the C<=head1 NAME> section.
+
+=cut
+
sub name {
return (@_ > 1 && $_[1]) ?
($_[0]->{-name} = $_[1]) : $_[0]->{-name};
}
-# set/return nodes of the current POD
+##################################
+
+=item C<$checker-E<gt>node()>
+
+Add (if argument specified) and retrieve the nodes (as defined by C<=headX>
+and C<=item>) of the current POD. The nodes are returned in the order of
+their occurence. They consist of plain text, each piece of whitespace is
+collapsed to a single blank.
+
+=cut
+
sub node {
my ($self,$text) = @_;
if(defined $text) {
# add node, order important!
push(@{$self->{_nodes}}, $text);
# keep also a uniqueness counter
- $self->{_unique_nodes}->{$text}++;
+ $self->{_unique_nodes}->{$text}++ if($text !~ /^\s*$/s);
return $text;
}
@{$self->{_nodes}};
}
+##################################
+
+=item C<$checker-E<gt>idx()>
+
+Add (if argument specified) and retrieve the index entries (as defined by
+C<XE<lt>E<gt>>) of the current POD. They consist of plain text, each piece
+of whitespace is collapsed to a single blank.
+
+=cut
+
+# set/return index entries of current POD
+sub idx {
+ my ($self,$text) = @_;
+ if(defined $text) {
+ $text =~ s/\s+$//s; # strip trailing whitespace
+ $text =~ s/\s+/ /gs; # collapse whitespace
+ # add node, order important!
+ push(@{$self->{_index}}, $text);
+ # keep also a uniqueness counter
+ $self->{_unique_nodes}->{$text}++ if($text !~ /^\s*$/s);
+ return $text;
+ }
+ @{$self->{_index}};
+}
+
+##################################
+
+=item C<$checker-E<gt>hyperlink()>
+
+Add (if argument specified) and retrieve the hyperlinks (as defined by
+C<LE<lt>E<gt>>) of the current POD. They consist of an 2-item array: line
+number and C<Pod::Hyperlink> object.
+
+=back
+
+=cut
+
# set/return hyperlinks of the current POD
sub hyperlink {
my $self = shift;
}
}
foreach($self->hyperlink()) {
- my $line = '';
- s/^(\d+):// && ($line = $1);
- if($_ && !$nodes{$_}) {
- $self->poderror({ -line => $line, -file => $infile,
- -severity => 'ERROR',
- -msg => "unresolved internal link '$_'"});
+ my ($line,$link) = @$_;
+ # _TODO_ what if there is a link to the page itself by the name,
+ # e.g. in Tk::Pod : L<Tk::Pod/"DESCRIPTION">
+ if($link->node() && !$link->page() && $link->type() ne 'hyperlink') {
+ my $node = $self->_check_ptree($self->parse_text($link->node(),
+ $line), $line, $infile, 'L');
+ if($node && !$nodes{$node}) {
+ $self->poderror({ -line => $line || '', -file => $infile,
+ -severity => 'ERROR',
+ -msg => "unresolved internal link '$node'"});
+ }
}
}
+
+ # check the internal nodes for uniqueness. This pertains to
+ # =headX, =item and X<...>
foreach(grep($self->{_unique_nodes}->{$_} > 1,
keys %{$self->{_unique_nodes}})) {
$self->poderror({ -line => '-', -file => $infile,
}
}
elsif($cmd =~ /^head(\d+)/) {
+ # check whether the previous =head section had some contents
if(defined $self->{_commands_in_head} &&
$self->{_commands_in_head} == 0 &&
defined $self->{_last_head} &&
# check the link text
$text .= $self->_check_ptree($self->parse_text($link->text(),
$line), $line, $file, "$nestlist$cmd");
- my $node = '';
- # remember internal link
- # _TODO_ what if there is a link to the page itself by the name,
- # e.g. in Tk::Pod : L<Tk::Pod/"DESCRIPTION">
- if($link->node() && !$link->page() && $link->type() ne 'hyperlink') {
- $node = $self->_check_ptree($self->parse_text($link->node(),
- $line), $line, $file, "$nestlist$cmd");
- $self->hyperlink("$line:$node") if($node);
- }
+ # remember link
+ $self->hyperlink([$line,$link]);
}
elsif($cmd =~ /[BCFIS]/) {
# add the guts
-msg => "Nonempty Z<>"});
}
}
- else { # X<>
- # check, but add nothing to $text
- $self->_check_ptree($contents, $line, $file, "$nestlist$cmd");
+ elsif($cmd eq 'X') {
+ my $idx = $self->_check_ptree($contents, $line, $file, "$nestlist$cmd");
+ if($idx =~ /^\s*$/s) {
+ $self->poderror({ -line => $line, -file => $file,
+ -severity => 'ERROR',
+ -msg => "Empty X<>"});
+ }
+ else {
+ # remember this node
+ $self->idx($idx);
+ }
+ }
+ else {
+ # not reached
+ die "internal error";
}
}
$text;
}
-# _TODO_ overloadable methods for BC..Z<...> expansion?
-
# process a block of verbatim text
sub verbatim {
## Nothing particular to check
1;
+__END__
+
+=head1 AUTHOR
+
+Brad Appleton E<lt>bradapp@enteract.comE<gt> (initial version),
+Marek Rouchal E<lt>marek@saftsack.fs.uni-bayreuth.deE<gt>
+
+Based on code for B<Pod::Text::pod2text()> written by
+Tom Christiansen E<lt>tchrist@mox.perl.comE<gt>
+
+=cut
+
($alttext, $page, $node) = ($1, $2, $3);
$type = 'section';
}
+ # alttext and page
+ elsif(m!^(.+?)\s*[|]\s*($page_rx)$!o) {
+ ($alttext, $page) = ($1, $2);
+ $type = 'page';
+ }
+ # alttext and "section"
+ elsif(m!^(.+?)\s*[|]\s*(?:/\s*|)"(.+)"$!) {
+ ($alttext, $node) = ($1,$2);
+ $type = 'section';
+ }
# page and "section"
elsif(m!^($page_rx)\s*/\s*"(.+)"$!o) {
($page, $node) = ($1, $2);
($alttext, $page, $node) = ($1, $2, $3);
$type = 'item';
}
- # alttext and page
- elsif(m!^(.+?)\s*[|]\s*($page_rx)$!o) {
- ($alttext, $page) = ($1, $2);
- $type = 'page';
- }
- # alttext and "section"
- elsif(m!^(.+?)\s*[|]\s*(?:/\s*|)"(.+)"$!) {
- ($alttext, $node) = ($1,$2);
- $type = 'section';
- }
# alttext and item
elsif(m!^(.+?)\s*[|]\s*/(.+)$!) {
($alttext, $node) = ($1,$2);
=item find_node($name)
-Look for a node named C<$name> in the object's node list. Returns the
-unique id of the node (i.e. the second element of the array stored in
-the node arry) or undef if not found.
+Look for a node or index entry named C<$name> in the object.
+Returns the unique id of the node (i.e. the second element of the array
+stored in the node arry) or undef if not found.
=back
sub find_node {
my ($self,$node) = @_;
- foreach(@{$self->{-nodes}}) {
+ my @search;
+ push(@search, @{$self->{-nodes}}) if($self->{-nodes});
+ push(@search, @{$self->{-idx}}) if($self->{-idx});
+ foreach(@search) {
if($_->[0] eq $node) {
return $_->[1]; # id
}
undef;
}
+=item idx()
+
+Add an index entry (or a list of them) to the document's index list. Note that
+the order is kept, i.e. start with the first node and end with the last.
+If no argument is given, the current list of index entries is returned in the
+same order the entries have been added.
+An index entry can be any scalar, but usually is a pair of string and
+unique id.
+
+=cut
+
+# The POD index entries
+sub idx {
+ my ($self,@idx) = @_;
+ if(@idx) {
+ push(@{$self->{-idx}}, @idx);
+ return @idx;
+ }
+ else {
+ return @{$self->{-idx}};
+ }
+}
=head1 AUTHOR
-*** ERROR: Unknown command 'unknown1' at line 21 in file pod/poderrs.t
-*** ERROR: Unknown interior-sequence 'Q' at line 25 in file pod/poderrs.t
-*** ERROR: Unknown interior-sequence 'A' at line 26 in file pod/poderrs.t
-*** ERROR: Unknown interior-sequence 'Y' at line 27 in file pod/poderrs.t
-*** ERROR: Unknown interior-sequence 'V' at line 27 in file pod/poderrs.t
-*** WARNING: unterminated B<...> at line 31 in file pod/poderrs.t
-*** WARNING: unterminated I<...> at line 30 in file pod/poderrs.t
-*** WARNING: unterminated C<...> at line 33 in file pod/poderrs.t
-*** WARNING: line containing nothing but whitespace in paragraph at line 41 in file pod/poderrs.t
-*** ERROR: =item without previous =over at line 48 in file pod/poderrs.t
-*** ERROR: =back without previous =over at line 52 in file pod/poderrs.t
-*** ERROR: =over on line 56 without closing =back (at head2) at line 60 in file pod/poderrs.t
-*** ERROR: =end without =begin at line 62 in file pod/poderrs.t
-*** ERROR: Nested =begin's (first at line 66:html) at line 68 in file pod/poderrs.t
-*** ERROR: =end without =begin at line 72 in file pod/poderrs.t
-*** ERROR: nested commands C<...C<...>...> at line 76 in file pod/poderrs.t
-*** ERROR: garbled entity E<alea iacta est> at line 80 in file pod/poderrs.t
-*** ERROR: garbled entity E<C<auml>> at line 81 in file pod/poderrs.t
-*** ERROR: garbled entity E<abcI<bla>> at line 82 in file pod/poderrs.t
-*** WARNING: collapsing newlines to blanks at line 92 in file pod/poderrs.t
-*** ERROR: malformed link L<> : empty link at line 94 in file pod/poderrs.t
-*** WARNING: ignoring leading whitespace in link at line 100 in file pod/poderrs.t
-*** WARNING: ignoring trailing whitespace in link at line 100 in file pod/poderrs.t
-*** ERROR: Spurious character(s) after =back at line 106 in file pod/poderrs.t
-*** WARNING: No items in =over (at line 114) / =back list at line 116 in file pod/poderrs.t
-*** WARNING: 2 unescaped <> in paragraph at line 118 in file pod/poderrs.t
-*** ERROR: unresolved internal link 'begin or begin' at line 86 in file pod/poderrs.t
-*** ERROR: unresolved internal link 'end with begin' at line 87 in file pod/poderrs.t
-*** ERROR: unresolved internal link 'OoPs' at line 88 in file pod/poderrs.t
-*** ERROR: unresolved internal link 'abc def' at line 92 in file pod/poderrs.t
-*** ERROR: unresolved internal link 'passwd(5)' at line 99 in file pod/poderrs.t
+*** ERROR: Unknown command 'unknown1' at line 25 in file pod/poderrs.t
+*** ERROR: Unknown interior-sequence 'Q' at line 29 in file pod/poderrs.t
+*** ERROR: Unknown interior-sequence 'A' at line 30 in file pod/poderrs.t
+*** ERROR: Unknown interior-sequence 'Y' at line 31 in file pod/poderrs.t
+*** ERROR: Unknown interior-sequence 'V' at line 31 in file pod/poderrs.t
+*** WARNING: unterminated B<...> at line 35 in file pod/poderrs.t
+*** WARNING: unterminated I<...> at line 34 in file pod/poderrs.t
+*** WARNING: unterminated C<...> at line 37 in file pod/poderrs.t
+*** WARNING: line containing nothing but whitespace in paragraph at line 45 in file pod/poderrs.t
+*** ERROR: =item without previous =over at line 52 in file pod/poderrs.t
+*** ERROR: =back without previous =over at line 56 in file pod/poderrs.t
+*** ERROR: =over on line 60 without closing =back (at head2) at line 64 in file pod/poderrs.t
+*** ERROR: =end without =begin at line 66 in file pod/poderrs.t
+*** ERROR: Nested =begin's (first at line 70:html) at line 72 in file pod/poderrs.t
+*** ERROR: =end without =begin at line 76 in file pod/poderrs.t
+*** ERROR: nested commands C<...C<...>...> at line 80 in file pod/poderrs.t
+*** ERROR: garbled entity E<alea iacta est> at line 84 in file pod/poderrs.t
+*** ERROR: garbled entity E<C<auml>> at line 85 in file pod/poderrs.t
+*** ERROR: garbled entity E<abcI<bla>> at line 86 in file pod/poderrs.t
+*** WARNING: collapsing newlines to blanks at line 96 in file pod/poderrs.t
+*** ERROR: malformed link L<> : empty link at line 98 in file pod/poderrs.t
+*** WARNING: ignoring leading whitespace in link at line 104 in file pod/poderrs.t
+*** WARNING: ignoring trailing whitespace in link at line 104 in file pod/poderrs.t
+*** ERROR: Spurious character(s) after =back at line 110 in file pod/poderrs.t
+*** WARNING: No items in =over (at line 118) / =back list at line 120 in file pod/poderrs.t
+*** WARNING: 2 unescaped <> in paragraph at line 122 in file pod/poderrs.t
+*** ERROR: unresolved internal link 'begin or begin' at line 90 in file pod/poderrs.t
+*** ERROR: unresolved internal link 'end with begin' at line 91 in file pod/poderrs.t
+*** ERROR: unresolved internal link 'OoPs' at line 92 in file pod/poderrs.t
+*** ERROR: unresolved internal link 'abc def' at line 96 in file pod/poderrs.t
+*** ERROR: unresolved internal link 'passwd(5)' at line 103 in file pod/poderrs.t
*** WARNING: multiple occurence of link target 'oops' at line - in file pod/poderrs.t
pod/poderrs.t has 22 pod syntax errors.