#!/usr/bin/perl

package main;

use strict;
use warnings;
use Pod::Usage;
use Getopt::Long;
use English qw(-no_match_vars);
use Perl::Review::Utils;
use Perl::Review;

#---------------------------------------------------------------
# Begin script

my %opts   = get_options();               #Side-affects @ARGV
my $source = get_input( shift @ARGV );    #Only one arg allowed
my $review = Perl::Review->new(
    -priority => $opts{priority},
    -profile  => $opts{profile}
);
my @violations = $review->review_code($source);
my $status     = print_report(@violations);
exit $status;

#----------------------------------------------------------------
# Begin subroutines

sub get_options {
    my %opts      = ();
    my @opt_specs = qw(priority=s profile=s noprofile help|? man);
    GetOptions( \%opts, @opt_specs ) or pod2usage(1);    #Exits
    pod2usage(1) if $opts{help};                         #Exits
    pod2usage(2) if $opts{man};                          #Exits

    #Sanity checks
    if ( $opts{noprofile} && $opts{profile} ) {
        my $msg = 'Cannot use -noprofile with -profile';
        pod2usage( -exitstatus => 1, -message => $msg ); #Exits
    }

    if ( @ARGV > 1 ) {
        my $msg = 'Only one file at a time please';
        pod2usage( -exitstatus => 1, -message => $msg ); #Exits
    }

    #Override profile, if -noprofile
    $opts{profile} = $EMPTY if $opts{noprofile};

    #All good!
    return %opts;
}

sub get_input {
    return shift if (@_);                                #Reading from file
    my $code_string = do { local $RS; <STDIN> };         #Slurp mode
    return \$code_string;    #Convert to SCALAR ref
}

sub print_report {
    # Sort violations lexically
    my @violations = sort by_location @_;
    print "$_\n" for @violations;
    return scalar @violations;
}

sub by_location {
    return $a->location->[0] <=> $b->location->[0]
      || $a->location->[1] <=> $b->location->[1];
}

__END__

=head1 NAME

perlreview - Critique Perl souce code

=head1 SYNOPSIS

 perlreview [options] FILE  #Read from FILE
 perlreview [options]       #Read from STDIN

=head1 DESCRIPTION

C<perlreview> is the executable front-end to the L<Perl::Review>
engine. The L<Perl::Review> distribution includes several policies
based on the coding standards outlined in Damian Conway's book B<Perl
Best Practices>.  I highly recommend that you get a copy!

=head1 ARGUMENTS

The only argument is the path to the file you wish to analyze.  No
more than one file can be specified at a time.  If the file is not
specified, then the input is read from STDIN.

=head1 OPTIONS

Option names can be abbreviated to uniqueness, and can be stated with
singe or double dashes, and option values can be separated from the
option name by a space or '=' (a la L<Getopt::Long>).

=over 8

=item -priority N

Sets the the maximum priority value of Policies that should be loaded
from the configuration file. 1 is the "highest" priority, and all
numbers larger than 1 have "lower" priority.  Only Policies that have
been configured with a priority value less than or equal to N will not
be applied.  For a given C<-profile>, increasing the priority value
will result in more violations.  See L<"CONFIGURATION"> for more
details.

=item -profile FILE

Tells perlreview to use profile named by FILE rather than looking
for the default file at F<.perlreviewrc> in your home directory.  See
L<"CONFIGURATION"> for more information.

=item -noprofile

By default, perlreview looks in several directores for a configuration
file named F<.perlreviewrc>.  The C<-noprofile> option tells
perlreview not to load any configuration file, thus defaulting to its
factory setup, which means that all the Policy modules that are
distributed with L<Perl::Review> will be loaded.

=back

=head1 CONFIGURATION

The default configuration file is called F<.perlreviewrc> and it lives
in your home directory.  If this file does not exist and the
C<-profile> option is not given to the constructor, perlreview
defaults to its factory setup, which means that all the policies that
are distributed with L<Perl::Review> will be applied.

The format of the configuration file is a series of named sections
that contain key-value pairs separated by ':' or '='.  Comments
should start with '#' and can be placed on a separate line or after
the name-value pairing if you desire.  The general recipe is a series
of sections like this:

    [PolicyModuleName]
    priority = 1
    arg1 = value1
    arg2 = value2

The C<PolicyModuleName> should be the name of module that implements
the policy you want to use.  The module should be a subclass of
L<Perl::Review::Policy>.  For brevity, you can ommit the
C<'Perl::Review::Policy'> part of the module name.

The C<priority> should be the level of importance you wish to assign
to the policy module.  1 is the highest priority level, and all
numbers greater than 1 have increasingly lower priority.  Only those
policies with a priority less than or equal to the C<-priority> value
given on the command-line.  The priority can be an arbitrarily large
positive integer.  If the priority is not defined, it defaults to 1.

The remaining key-value pairs are configuration parameters for that
specific Policy and will be passed into the constructor of the
L<Perl::Review::Policy> subclass.  The constructors for most Policy
modules do not support arguments, and those that do should have
reasonable defaults.  See the documentation on the appropriate Policy
module for more details.

By default, all the policies that are distributed with C<Perl::Review>
are applied.  Rather than assign priority levels to each one, you can
simply "turn off" a Policy by appending a '-' to the name of the
module in the config file.  In this manner, the Policy will never be
applied, regardless of the C<-priority> option given at the
command-line.


A sample configuration might look like this:

    #--------------------------------------------------------------
    # These are really important, so always apply them

    [RequirePackageStricture]
    priority = 1

    [RequirePackageWarnings]
    priority = 1

    #--------------------------------------------------------------
    # These are less important, so only apply when asked

    [ProhibitOneArgumentBless]
    priority = 2

    [ProhibitDoWhileLoops]
    priority = 2

    #--------------------------------------------------------------
    # I don't agree with these, so never apply them

    [-ProhibitMixedCaseVars]
    [-ProhibitMixedCaseSubs]

=head1 THE POLICIES

The following Policy modules are distributed with Perl::Review.
Policy modules have been categorized according to the table of
contents in Damian Conway's book B<Perl Best Practices>.  Since most
coding standards take the form "do this..." or "don't do that...", I
have adopted the convention of naming each module C<RequireSomething>
or C<ProhibitSomething>.  See the documentation of each module for
it's specific details.

L<Perl::Review::Policy::BuiltinFunctions::ProhibitStringyEval>

L<Perl::Review::Policy::BuiltinFunctions::ProhibitStringyGrep>       

L<Perl::Review::Policy::BuiltinFunctions::ProhibitStringyMap>

L<Perl::Review::Policy::CodeLayout::RequireTidyCode>

L<Perl::Review::Policy::ControlStructures::ProhibitPostfixControls>

L<Perl::Review::Policy::InputOutput::ProhibitBacktickOperators>

L<Perl::Review::Policy::Modules::ProhibitMultiplePackages>

L<Perl::Review::Policy::Modules::ProhibitRequireStatements> 

L<Perl::Review::Policy::Modules::ProhibitSpecificModules>

L<Perl::Review::Policy::Modules::ProhibitUnpackagedCode>

L<Perl::Review::Policy::NamingConventions::ProhibitMixedCaseSubs>

L<Perl::Review::Policy::NamingConventions::ProhibitMixedCaseVars>

L<Perl::Review::Policy::Subroutines::ProhibitSubroutinePrototypes>

L<Perl::Review::Policy::TestingAndDebugging::RequirePackageStricture>

L<Perl::Review::Policy::TestingAndDebugging::RequirePackageWarnings>

L<Perl::Review::Policy::ValuesAndExpressions::ProhibitConstantPragma>

L<Perl::Review::Policy::ValuesAndExpressions::ProhibitEmptyQuotes>

L<Perl::Review::Policy::ValuesAndExpressions::ProhibitInterpolationOfLiterals>

L<Perl::Review::Policy::ValuesAndExpressions::ProhibitNoisyQuotes>

L<Perl::Review::Policy::ValuesAndExpressions::RequireInterpolationOfMetachars>

L<Perl::Review::Policy::ValuesAndExpressions::RequireQuotedHeredocTerminator>

L<Perl::Review::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator>

L<Perl::Review::Policy::Variables::ProhibitLocalVars>

L<Perl::Review::Policy::Variables::ProhibitPackageVars>

L<Perl::Review::Policy::Variables::ProhibitPunctuationVars>

=head1 EDITOR INTEGRATION

For ease-of-use, perlreview can be integrated with your favorite
editor.  C<emacs> users can put the following code in your F<.emacs>
configuration file:

  (defun perlreview ()
    (interactive)
    (shell-command-on-region (point) (mark) "perlreview"))

  (global-set-key "\C-xpr" 'perlreview) 

Pressing "Control-x p r" will run C<perlreview> on the current region
and the output will appear in a separate buffer.  My E-Lisp skills are
pretty weak, so I'd appreciate any tips for improvement on this.
Also, C<vi> fans are welcome to submit similar code and I'll publish
it here.

=head1 AUTHOR

Jeffrey Ryan Thalhammer <thaljef@cpan.org>

=head1 COPYRIGHT

Copyright (c) 2005 Jeffrey Ryan Thalhammer.  All rights reserved.

This program is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.  The full text of this license
can be found in the LICENSE file included with this module.




