The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 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 <http://www.gnu.org/licenses/>.

package Perl::Critic::Pulp::PodParser;
use 5.006;
use strict;
use warnings;
use Perl::Critic::Pulp::Utils;
use base 'Pod::Parser';

our $VERSION = 95;

# uncomment this to run the ### lines
# use Smart::Comments;


# sub new {
#   my $class = shift;
#   ### Pulp-PodParser new()
#   my $self = $class->SUPER::new (@_);
#   return $self;
# }
sub initialize {
  my ($self) = @_;
  ### initialize() ...

  # empty violations for violations() to return before a parse
  $self->{'violations'} = [];
  $self->{'in_begin'} = '';
  $self->errorsub ('error_handler'); # method name

  # Note: The violations list is never cleared.  Might like to do so at the
  # start of a new a pod document, though this parser is only ever used on a
  # single document and then discarded.  begin_input() and begin_pod() are
  # no good as they're invoked for each chunk fed in by parse_from_elem().
}

sub error_handler {
  my ($self, $errmsg) = @_;
  ### error_handler() ...
  return 1;  # error handled

  # Don't think it's the place of this policy to report pod parse errors.
  # Maybe within sections a policy is operating on, on the basis that could
  # affect the goodness of its checks, but better leave it all to podchecker
  # or other perlcritic policies.
  #
  #   my $policy = $self->{'policy'};
  #   my $elem   = $self->{'elem'};
  #   push @{$self->{'violations'}},
  #     $policy->violation ("Pod::Parser $errmsg", '', $elem);
}

sub parse_from_elem {
  my ($self, $elem) = @_;
  ### Pulp-PodParser parse_from_elem(): ref($elem)

  my $elems = ($elem->can('find')
               ? $elem->find ('PPI::Token::Pod')
               : [ $elem ])
    || return;  # find() returns false if nothing found
  foreach my $pod (@$elems) {
    ### pod chunk at linenum: $pod->line_number
    $self->{'elem'} = $pod;
    $self->parse_from_string ($pod->content);
  }
}

# this is generic except for holding onto $str ready for violation override
sub parse_from_string {
  my ($self, $str) = @_;
  $self->{'str'} = $str;
  require IO::String;
  my $fh = IO::String->new ($str);
  $self->parse_from_filehandle ($fh);
}

sub command {
  my ($self, $command, $text, $linenum) = @_;
  if ($command eq 'begin') {
    push @{$self->{'in_begin_stack'}}, $self->{'in_begin'};
    if ($text =~ /^:/) {
      # "=begin :foo" is ordinary POD
      $self->{'in_begin'} = '';
    } elsif ($text =~ /(\w+)/) {
      $self->{'in_begin'} = $1;  # first word only
    } else {
      # "=begin" with no word chars ...
      $self->{'in_begin'} = '';
    }
    ### in_begin: $self->{'in_begin'}

  } elsif ($command eq 'end') {
    $self->{'in_begin'} = pop @{$self->{'in_begin_stack'}};
    if (! defined $self->{'in_begin'}) {
      $self->{'in_begin'} = '';
    }
    ### pop to in_begin: $self->{'in_begin'}
  }
}
use constant verbatim => '';
use constant textblock => '';

sub violation_at_linenum {
  my ($self, $message, $linenum) = @_;
  ### violation on elem: ref($self->{'elem'})

  my $policy = $self->{'policy'};
  ### policy: ref($policy)
  my $violation = $policy->violation ($message, '', $self->{'elem'});

  # fix dodgy Perl::Critic::Policy 1.108 violation() ending up with caller
  # package not given $policy
  if ($violation->policy eq __PACKAGE__
      && defined $violation->{'_policy'}
      && $violation->{'_policy'} eq __PACKAGE__) {
    $violation->{'_policy'} = ref($policy);
  }

  Perl::Critic::Pulp::Utils::_violation_override_linenum
      ($violation, $self->{'str'}, $linenum);
  ### $violation
  push @{$self->{'violations'}}, $violation;
}

sub violation_at_linenum_and_textpos {
  my ($self, $message, $linenum, $text, $pos) = @_;
  ### violation_at_linenum_and_textpos()
  ### $message
  ### $linenum
  ### $pos

  my $part = substr($text,0,$pos);
  $linenum += ($part =~ tr/\n//);
  $self->violation_at_linenum ($message, $linenum);
}

# return list of violation objects (possibly empty)
sub violations {
  my ($self) = @_;
  return @{$self->{'violations'}};
}

#------------------------------------------------------------------------------
# This not documented yet.  Might prefer to split it out for separate use too.
#
# Not sure about padding to make the column right.  Usually good, but
# perhaps not always.  Maybe should offset a column by examining
# $paraobj->cmd_prefix() and $paraobj->cmd_name().

{
  my %command_non_text = (for   => 1,
                          begin => 1,
                          end   => 1,
                          cut   => 1);

  # The parameters are as per the command() method of Pod::Parser.
  # If $command contains text style markup then call $self->textblock() on
  # its text.
  # All commands except =for, =begin, =end and =cut have marked-up text.
  # Eg. =head2 C<blah blah>
  #
  sub command_as_textblock {
    my ($self, $command, $text, $linenum, $paraobj) = @_;
    ### command: $command
    ### $text

    # $text can be undef if =foo with no newline at end-of-file
    if (defined $text && ! $command_non_text{$command}) {
      # padded to make the column number right, the leading spaces do no harm
      # for this policy
      $self->textblock ((' ' x (length($command)+1)) . $text,
                        $linenum,
                        $paraobj);
    }
    return '';
  }
}

1;
__END__

=for stopwords perlcritic Ryde

=head1 NAME

Perl::Critic::Pulp::PodParser - shared POD parsing code for the Pulp perlcritic add-on

=head1 SYNOPSIS

 use base 'Perl::Critic::Pulp::PodParser';

=head1 DESCRIPTION

This is only meant for internal use yet.

It's some shared parse-from-element, error suppression, no output, violation
accumulation and violation line number things for POD parsing in policies.

=head1 SEE ALSO

L<Perl::Critic::Pulp>

=head1 HOME PAGE

L<http://user42.tuxfamily.org/perl-critic-pulp/index.html>

=head1 COPYRIGHT

Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 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 <http://www.gnu.org/licenses/>.

=cut