# Copyright 2009 Kevin Ryde # This file is part of Perl-Critic-Pulp. # Perl-Critic-Pulp is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, or (at your option) any later # version. # # Perl-Critic-Pulp is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License along # with Perl-Critic-Pulp. If not, see . package Pod::MinimumVersion; use 5.005; use strict; use warnings; use List::Util; use version; use vars qw($VERSION @CHECKS); $VERSION = 22; use constant DEBUG => 0; sub new { my ($class, %self) = @_; $self{'want_reports'} ||= 'one_per_version'; return bless \%self, $class; } sub minimum_version { my ($self) = @_; my $report = $self->minimum_report || return undef; return $report->{'version'}; } sub minimum_report { my ($self) = @_; if (! exists $self->{'minimum_report'}) { $self->{'minimum_report'} = List::Util::reduce {$a->{'version'} > $b->{'version'} ? $a : $b} $self->reports; } return $self->{'minimum_report'}; } sub reports { my ($self) = @_; $self->analyze; return @{$self->{'reports'} || []}; } sub analyze { my ($self) = @_; return if $self->{'analyzed'}; $self->{'analyzed'} = 1; if (DEBUG) { print "MinVer analyze\n"; } my %checks; foreach my $elem (@CHECKS) { my ($func, $command, $version) = @$elem; next if ($self->{'above_version'} && $version <= $self->{'above_version'}); push @{$checks{$command}}, $func; } return if (! %checks); my $parser = Pod::MinimumVersion::Parser->new (pmv => $self, checks => \%checks); if (exists $self->{'string'}) { $parser->parse_from_string ("$self->{'string'}"); } elsif (exists $self->{'filehandle'}) { $parser->parse_from_filehandle ($self->{'filehandle'}); } elsif (exists $self->{'filename'}) { $parser->parse_from_file ($self->{'filename'}); } } #------------------------------------------------------------------------------ # 5.005 { my $v5005 = version->new('5.005'); # L display alternative new in 5.005 # push @CHECKS, [ \&_check_link_display_text, 'interior_sequence', $v5005 ]; sub _check_link_display_text { my ($self, $command, $arg, $seq_obj) = @_; if ($command eq 'L' && $arg =~ /\|/) { $self->report ('link_display_text', $v5005, $seq_obj, 'Display text L link'); } } } #------------------------------------------------------------------------------ # 5.006 { my $v5006 = version->new('5.006'); push @CHECKS, [ \&_check_double_angles, 'interior_sequence', $v5006 ]; sub _check_double_angles { my ($self, $command, $arg, $seq_obj) = @_; if ($seq_obj->left_delimiter =~ /^<report ('double_angles', $v5006, $seq_obj, 'Double angle brackets C<< foo >>'); } } } #------------------------------------------------------------------------------ # 5.008 { my $v5008 = version->new('5.008'); # =head3 and =head4 new in 5.8.0 push @CHECKS, [ \&_check_head34, 'command', $v5008 ]; my %head34 = (head3 => 1, head4 => 1); sub _check_head34 { my ($self, $command, $text, $para_obj) = @_; if ($head34{$command}) { $self->report ('head34', $v5008, $para_obj, "=$command command"); } } # E and E documented in 5.6.0, but Pod::Man only has them in # 5.8.0, so rate them as a 5008 feature # # E is in Pod::Man of 5.8.0, though not documented explicitly # push @CHECKS, [ \&_check_E_5008, 'interior_sequence', $v5008 ]; my %E_5008 = (apos => 1, sol => 1, verbar => 1); sub _check_E_5008 { my ($self, $command, $arg, $seq_obj) = @_; if ($command eq 'E' && $E_5008{$arg}) { $self->report ('E_5008', $v5008, $seq_obj, "E<$arg> escape"); } } # L urls new in 5.8.0 # # In 5.6 and earlier the "/" is interpreted as a section, so from # L you get something bad like # # the section on "/foo.com/index.html" in the http: manpage # # Crib note: a "|" display text part is not allowed with a url, according # to perlpodspec of perl 5.10.0 under the "Authors wanting to link to a # particular (absolute) URL" bullet point. So no need to watch for that # in applying this test. # push @CHECKS, [ \&_check_link_url, 'interior_sequence', $v5008 ]; sub _check_link_url { my ($self, $command, $arg, $seq_obj) = @_; # this regexp as recommended by perlpodspec of perl 5.10.0 if ($command eq 'L' && $arg =~ m/\A\w+:[^:\s]\S*\z/) { $self->report ('link_url', $v5008, $seq_obj, 'URL in L<> link'); } } } #------------------------------------------------------------------------------ # 5.010 { my $v5010 = version->new('5.010'); # =encoding documented in 5.8.0, but Pod::Man doesn't recognise it until # 5.10.0, so rate it as a 5010 feature # push @CHECKS, [ \&_check_encoding, 'command', $v5010 ]; sub _check_encoding { my ($self, $command, $text, $para_obj) = @_; if ($command eq 'encoding') { $self->report ('encoding', $v5010, $para_obj, '=encoding command'); } } } #------------------------------------------------------------------------------ sub report { my ($self, $name, $version, $pod_obj, $why) = @_; if ($self->{'want_reports'} eq 'one_per_check') { return if ($self->{'seen'}->{$name}++); } if ($self->{'want_reports'} eq 'one_per_version') { return if ($self->{'seen'}->{$version}++); } my ($filename, $linenum) = $pod_obj->file_line; if (defined $self->{'filename'}) { $filename = $self->{'filename'}; } push @{$self->{'reports'}}, Pod::MinimumVersion::Report->new (filename => $filename, name => $name, linenum => $linenum, version => $version, why => $why); } package Pod::MinimumVersion::Report; use strict; use warnings; use overload '""' => \&as_string; sub new { my ($class, %self) = @_; return bless \%self, $class; } # not sure about this ... sub as_string { my ($self) = @_; return "$self->{'filename'}:$self->{'linenum'}: $self->{'version'} due to $self->{'why'}"; } package Pod::MinimumVersion::Parser; use strict; use warnings; use base 'Pod::Parser'; use constant DEBUG => 0; # sub begin_input { # print "begin_input\n"; # } # sub end_input { # print "end_input\n"; # } sub parse_from_string { my ($self, $str) = @_; require IO::String; my $fh = IO::String->new ($str); $self->{_INFILE} = "(string)"; return $self->parse_from_filehandle ($fh); } sub command { my $self = shift; my ($command, $text, $linenum, $paraobj) = @_; if (DEBUG) { print "command: $command -- ", (defined $text ? $text : 'undef'), "\n"; } if ($command eq 'for' && $text =~ /^\s*Pod::MinimumVersion\s+use\s+(v?[0-9._]+)/) { $self->{'pmv'}->{'for_version'} = version->new($1); } foreach my $func (@{$self->{'checks'}->{'command'}}) { $func->($self->{'pmv'}, $command, $text, $paraobj); } return ''; } sub verbatim { return ''; } sub textblock { my ($self, $text, $linenum, $paraobj) = @_; if (DEBUG) { print "textblock\n"; } return $self->interpolate ($text, $linenum); } sub interior_sequence { my ($self, $command, $arg, $seq_obj) = @_; if (DEBUG) { print "interior: $command -- $arg seq=$seq_obj\n"; print " raw_text ", $seq_obj->raw_text, "\n"; print " left ", $seq_obj->left_delimiter, "\n"; if (my $outer = $seq_obj->nested) { print " nested ", $outer->cmd_name, "\n"; } } # J<> from Pod::MultiLang -- doubled C<<>> or L<|display> are allowed # ENHANCE-ME: might prefer to make parse_tree() not descend into J<> at # all, but it doesn't seem setup for that my $outer; if ($command eq 'J' || (($outer = $seq_obj->nested) && $outer->cmd_name eq 'J')) { return ''; } foreach my $func (@{$self->{'checks'}->{'interior_sequence'}}) { $func->($self->{'pmv'}, $command, $arg, $seq_obj); } return ''; } 1; __END__ =head1 NAME Pod::MinimumVersion - Perl version for POD directives used =head1 SYNPOSIS use Pod::MinimumVersion; my $pmv = Pod::MinimumVersion->new (filename => '/some/foo.pl'); print $pmv->minimum_version,"\n"; print $pmv->reports; =head1 DESCRIPTION B C parses the POD in a Perl script, module, or document, and reports what version of Perl is required to process the directives in it with C etc. =head1 CHECKS The following POD features are identified. =over 4 =item * LEdisplay text|targetE display part, new in 5.005. =item * CEE foo EE double-angles, new in 5.6.0. =item * C<=head3> and C<=head4>, new in 5.8.0. =item * LEhttp://some.where.comE, new in 5.8.0. (Prior versions take the "/" as a "section" part, giving very poor output.) =item * EEaposE, EEsolE, EEverbarE chars, new in 5.8.0. (Documented in 5.6.0, but pod2man doesn't recognise them until 5.8.) =item * C<=encoding> command, new in 5.10.0. (Documented in 5.8.0, but pod2man doesn't recognise it until 5.10.) =back =head1 FUNCTIONS =over 4 =item C<< $pmv = Pod::MinimumVersion->new (key => value, ...) >> Create and return a new C object which will analyze a document. The document is supplied as one of filehandle => $fh, string => 'something', filename => '/my/dir/foo.pod', For C and C, a C can be supplied too to give a name in the reports. The handle or string is what's actually read though. The C option lets you set a Perl version you use, so reports are only about features above that level. above_version => '5.006', =item C<< $version = $pmv->minimum_version () >> =item C<< $report = $pmv->minimum_report () >> Return the minimum Perl required for the document in C<$pmv>. C returns a C object (see L). C returns a C object described below (L). =item C<< @reports = $pmv->reports () >> Return a list of C objects concerning the document in C<$pmv>. These multiple reports let you identify multiple places that a particular Perl is required. With the C option the reports are still only about things higher than that. The report from C and C is simply the highest Perl among these multiple reports. =back =head1 REPORT OBJECTS A C object holds a location within a document and a reason that a particular Perl is needed at that point. The hash fields are filename string linenum integer, with 1 for the first line version version object why string =over 4 =item C<$str = $report-Eas_string> Return a formatted string for the report. Currently this is in GNU file:line style, simply :: due to =back =head1 SEE ALSO L, L, L =head1 HOME PAGE http://user42.tuxfamily.org/perl-critic-pulp/index.html =head1 COPYRIGHT Copyright 2009 Kevin Ryde Perl-Critic-Pulp is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Perl-Critic-Pulp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Perl-Critic-Pulp. If not, see . =cut