#!/usr/bin/perl -w

###############################################################################
# Runs krazy over the source code                                             #
# Copyright 2005-2017 by Allen Winter <winter@kde.org>                        #
#                                                                             #
# This program 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 2 of the License, or           #
# (at your option) any later version.                                         #
#                                                                             #
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,     #
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.               #
#                                                                             #
###############################################################################

use strict;
use File::Basename;
use Getopt::Long;
use Cwd 'abs_path';
use FindBin qw($Bin);
use lib "$Bin/../lib";
use Krazy::Utils;

my ($Prog) = 'krazy2all';
my
 $VERSION = '2.994';    #split line so MakeMaker can find the version here

my ($help)      = '';
my ($version)   = '';
my ($list)      = '';
my ($listtypes) = '';
my ($listtype)  = '';
my ($listsets)  = '';
my ($listset)   = '';
my ($verbose)   = '';
my ($ignorerc)  = '';
my ($configf)   = '';
my ($dryrun)    = '';
my ($brief)     = '';
my ($nobrief)   = '';
my ($quiet)     = '';
my ($priority)  = '';
my ($strict)    = '';
my ($check)     = '';
my ($checksets) = '';
my ($exclude)   = '';
my ($extra)     = '';
my ($inctypes)  = '';
my ($exctypes)  = '';
my ($export)    = '';
my ($title)     = '';
my ($cms)       = '';
my ($topdir)    = '';
my ($outfile)   = '';

exit 1
  if ( !GetOptions(
       'help'           => \$help,
       'version'        => \$version,
       'list'           => \$list,
       'list-types'     => \$listtypes,
       'list-type=s'    => \$listtype,
       'list-sets'      => \$listsets,
       'list-set=s'     => \$listset,
       'dry-run'        => \$dryrun,
       'ignorerc'       => \$ignorerc,
       'config=s'       => \$configf,
       'verbose'        => \$verbose,
       'brief'          => \$brief,
       'no-brief'       => \$nobrief,
       'quiet'          => \$quiet,
       'priority=s'     => \$priority,
       'strict=s'       => \$strict,
       'check=s'        => \$check,
       'check-sets=s'   => \$checksets,
       'exclude=s'      => \$exclude,
       'extra=s'        => \$extra,
       'types=s'        => \$inctypes,
       'exclude-types=s'=> \$exctypes,
       'export=s'       => \$export,
       'title=s'        => \$title,
       'cms=s'          => \$cms,
       'topdir=s'       => \$topdir,
       'outfile=s'      => \$outfile
      )
     );

&Help(0)   if ($#ARGV >= 0); # no positional args allowed
&Help(0)   if ($help);
&Version() if ($version);

if ($list) {
  system("krazy2 --list");
  exit 0;
}

if ($listtypes) {
  system("krazy2 --list-types");
  exit 0;
}

if ($listtype) {
  system("krazy2 --list-type=$listtype");
  exit 0;
}

if ($listsets) {
  system("krazy2 --list-sets");
  exit 0;
}

if ($listset) {
  system("krazy2 --list-set=$listset");
  exit 0;
}

# Options to pass to Krazy
my ($opts) = "";
$opts .= "--dry-run "                 if ($dryrun);
$opts .= "--ignorerc "                if ($ignorerc);
$opts .= "--config=$configf "         if ($configf);
$opts .= "--brief "                   if ($brief);
$opts .= "--no-brief "                if ($nobrief);
$opts .= "--quiet "                   if ($quiet);
$opts .= "--verbose "                 if ($verbose);
$opts .= "--check=$check "            if ($check);
$opts .= "--check-sets=$checksets "   if ($checksets);
$opts .= "--exclude=$exclude "        if ($exclude);
$opts .= "--extra=$extra "            if ($extra);
$opts .= "--types=$inctypes "         if ($inctypes);
$opts .= "--exclude-types=$exctypes " if ($exctypes);
$opts .= "--export=$export "          if ($export);
$opts .= "--title=\"$title\" "        if ($title);
$opts .= "--cms=$cms "                if ($cms);
$opts .= "--priority=$priority "      if ($priority);
$opts .= "--strict=$strict "          if ($strict);
$opts .= "--explain "                 if ($export ne "textlist" && $export ne "textedit");

my($cwd) = abs_path();

# Set the top-level directory for finding files to process
my($top) = $cwd;
if ($topdir) {
  $top = glob($topdir); # expand ~
  $top = abs_path($top);
}
if (!defined($top) || ! -e $top) {
  &userError("The specified topdir directory \"$topdir\" does not exist.");
}
if (! -r $top) {
  &userError("You do not have read access to topdir \"$top\".");
}
$top =~ s:\+:\\\+:g;  #escape '+'

# Set the default config file name, which might be found under the topdir
if ($top ne $cwd) {
  if (! $configf) {
    my($projConfig) = $top . "/.krazy";
    if (-e $projConfig) {
      $opts .= "--config=$projConfig ";
    }
  }
}

# Set the output file, stdout by default
my($outf) = '';
if ($outfile) {
  $outfile = glob($outfile); # expand ~
  $outf = abs_path($outfile);
  if (!defined($outf)) {
    $outf = $outfile;
  }
}
if ($outf && ! -w dirname($outf)) {
  &userError("You do not have access to write output file \"$outfile\".");
}

# Find the files to process
my($files) = &findFiles($top);
if ($top eq $cwd) {
  $files =~ s+^$top+\.+gm;
}
$files =~ s+\\n+\'\\n\'+g;

# Pipe the file list to krazy2 for the actual processing work
if ($outf) {
  open(P, "| krazy2 $opts - > $outf");
} else {
  open(P, "| krazy2 $opts -");
}
print P "$files\n";
close(P);

if (($? >> 8) > 0) {
  exit 1;
} else {
  exit 0;
}

# Help function: print help message and exit.
sub Help {
  my ($keepgoing) = @_;
  &Version();
  print "Run krazy2 on all the source in the current working directory\n\n";
  print "Usage: $Prog [OPTION]\n";
  print "  --help       display help message and exit\n";
  print "  --version    display version information and exit\n";
  print "  --list       print a list of all the checker programs\n";
  print "  --list-types print a list of all the supported file types\n";
  print "  --list-type <type>\n";
  print "               print the list of available checkers for the specified file typet\n";
  print "  --list-sets  print a list of all the check-sets\n";
  print "  --list-set <set>\n";
  print "               print the list of available checkers for the specified check-set\n";
  print "  --priority <low|normal|high|important|all>\n";
  print "               report only issues with the specified priorities:\n";
  print "                 low -> print low priority issues only\n";
  print "                 normal -> print normal priority issues only\n";
  print "                 high -> print high priority issues only\n";
  print "                 important -> print issues with normal or high priority\n";
  print "                 all -> print all issues (default)\n";
  print "               use priorities to help put the issues in fix-first order.\n";
  print "  --strict <normal|super|all>\n";
  print "               report only issues matching the strictness level:\n";
  print "                 normal -> print non-super strict issues only\n";
  print "                 super -> print super strict issues only\n";
  print "                 all -> print all issues (default)\n";
  print "               use strictness to help filter out issues of less importance\n";
  print "  --check <prog[,prog1,prog2,...,progN]>\n";
  print "               run the specified sanity checker program(s) only\n";
  print "  --check-sets <set[,set1,set2,...,setN]>\n";
  print "               run the checker programs belonging to the specified set(s) only\n";
  print "  --exclude <prog[,prog1,prog2,...,progN]>\n";
  print "               do NOT run the specified checker program(s)\n";
  print "  --extra <prog[,prog1,prog2,...,progN]>:\n";
  print "               add the specified \"extra\" program(s) to the checker list\n";
  print "  --types <type[,type1,type2,...,typeN]>:\n";
  print "               check the specified file type(s) only\n";
  print "  --exclude-types <type[,type1,type2,...,typeN]>:\n";
  print "               do NOT check the specified file type(s)\n";
  print "  --export <text|textlist|textedit|xml>\n";
  print "               output in one of the following formats:\n";
  print "                 text (default)\n";
  print "                 textlist -> plain old text, 1 offending file-per-line\n";
  print "                 textedit -> text formatted for IDEs, 1 issue-per-line\n";
  print "                 xml -> XML formatted\n";
  print "  --title:     give the output a project title\n";
  print "  --cms        component/module/subdir triple for XML\n";
  print "  --topdir <dir>\n";
  print "               find files to process recursively in the specified dir\n";
  print "               rather than the current working directory.\n";
  print "  --outfile <file>\n";
  print "               write the output to the specified file instead of standard output\n";
  print "  --ignorerc:  ignore .krazy files\n";
  print "  --config <krazyrc>\n";
  print "               read settings from the specified config file\n";
  print "  --dry-run    don't execute the checks; only show what would be run\n";
  print "  --brief:     print only checks with at least 1 issue\n";
  print "  --no-brief:  print the result of all checks i.e, the opposite of brief (default)\n";
  print "  --quiet      suppress all output messages\n";
  print "  --verbose    print the offending content for each file processed\n";
  print "\n";
  exit 0 if !$keepgoing;
}

# Version function: print the version number and exit.
sub Version {
  print "$Prog, version $VERSION\n";
  exit 0 if $version;
}

__END__

#==============================================================================

=head1 NAME

krazy2all - Sanity checks source code.

=head1 SYNOPSIS

krazy2all [options]

=head1 DESCRIPTION

krazy2all finds all supported files within the current directory tree
and runs the krazy2 checker on all of them, producing 1 concise report
in the specified export format.

=head1 OPTIONS

=over 4

=item B<--help>

Print help message and exit.

=item B<--version>

Print version information and exit.

=item B<--list>

Print a list of all available checker programs and exit.

=item B<--list-types>

Print a list of the support file types and exit.

=item B<--list-type> <type>

Print the list of available checkers for the specified file type and exit.

=item B<--list-sets>

Print a list of all the check-sets and exit.

=item B<--list-set> <set>

Print the list of available checkers for the specified set and exit.

=item B<--priority> <low|normal|high|important|all>

Tell each checker program to report issues with the specified priority only.
This option is useful to help put issues into "fix-first" order.

Supported priorites are:
     low -> print low priority issues only
     normal -> print normal priority issues only
     high -> print high priority issues only
     important -> print issues with normal or high priority
     all -> print all issues (default)

=item B<--strict> <normal|super|all>

Tell each checker program to report issues match the strictness level only.
Use this option to help filter out issues of less importance.

Support strictness levels are:
     normal -> print non-super strict issues only
     super -> print super strict issues only
     all -> print all issues (default)

=item B<--ignorerc>

Ignore .krazy files.

=item B<--config> <krazyrc>

Read settings from the specified krazyrc configuration file only.
All other configuration files will be ignored, including those
found in the user's home directory or in the current working directory.

=item B<--dry-run>

With this option the checker programs aren't run; instead, the command line
for each check that would be run is printed.

=item B<--check> <prog[,prog1,prog2,...,progN]>

Run the specified checker program(s) only.
You may not combine this option with B<--check-sets>.

Use the B<--list> option to show the list of available checkers.

=item B<--check-sets> <set[,set1,set2,...,setN]>

Run the checker programs belonging to the specified set(s) only.
You may not combine this option with B<--check>.

Use the B<--list-sets> option to see the list of available check-sets and programs.

=item B<--exclude> <prog[,prog1,prog2,...,progN]>

Do B<NOT> run the specified checker program(s).

=item B<--extra> <prog[,prog1,prog2,...,progN]>

Add the specified "extra" program(s) to the list of checkers to run.
Use the --list option to show the list of available "extra" checkers; they
will be marked with the tag [EXTRA] by the checker description line.

=item B<--types> <type[,type1,type2,...,typeN]>

Check the specified file type(s) only.

=item B<--exclude-types> <type[,type1,type2,...,typeN]>

Do NOT check the specified file type(s).

=item B<--export> <text|textlist|textedit|xml>

Output in one of the following formats:
     text (default)
     textlist -> plain old text, 1 offending file-per-line
     textedit -> text formatted for IDEs, 1 issue-per-line (file:line:issue)
     xml -> XML formatted

=item B<--title>

Give the output report a project title.

=item B<--cms>

An acronym for "component/module/subdir".  Used to write the breadcrumbs line
in the XML output.  Must be a slash-delimited triple containing the component,
module, and subdir which is being scanned.

=item B<--topdir> <dir>

Find files to process recursively in the specified dir rather than the
current working directory.  The krazyrc configuration file is also
searched for in this location, unless explicitly set otherwise
using the B<--config> or B<--ignorerc> options.

=item B<--outfile> <file>

Write the output to the file specified instead of standard output.

=item B<--brief>

Only print the output for checkers that have at least 1 issue.

=item B<--no-brief>

Print the result of all checks i.e, the opposite of B<--brief>.
This is the default output setting.

=item B<--quiet>

Suppress all output messages.

=item B<--verbose>

Print the offending content for each file processed

=back

=head1 ENVIRONMENT

KRAZY_PLUGIN_PATH - this is a colon-separated list of paths which is
searched when locating plugins. By default, plugins are searched for in
the path F<$TOP/lib/krazy2/krazy-plugins:krazy-plugins>.

KRAZY_EXTRA_PATH - this is a colon-separated list of paths which is
searched when locating "extra" plugins. By default, the "extras" are searched
for in the path F<$TOP/lib/krazy2/krazy-extras:krazy-extras>.

KRAZY_SET_PATH - this is a colon-separated list of paths which is
searched when locating check-sets. By default, the sets are searched
for in the path F<$TOP/lib/krazy2/krazy-sets:krazy-sets>.

where $TOP is the top-level installation directory (eg. F</usr/local>, F</usr/local/Krazy2>)

=head1 EXIT STATUS

In normal operation, krazy2all exits with status=1 if at least 1 issue
is encountered; else when no issues are found the exit status=0.

If a command line option was incorrectly provided, krazy2all exits with
status=1.

If krazy2all was envoked with the B<--help>, B<--version>  B<--list>
or B<--list-types> options it will exit with status=0.

=head1 COPYRIGHT

Copyright (c) 2005-2017 by Allen Winter <winter@kde.org>

This program 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 2 of the License, or
(at your option) any later version.

This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

=head1 FILES

F<.krazy> (see krazyrc(3))

krazy2all looks up the current working directory tree for a F<.krazy> file
in the project subdirectory.  Only F<.krazy> files found at the project
subdirectory level are read.

For example, if the current working dir is
F</my/kde/trunk/KDE/kdepimlibs/kcal/versit>, then krazy2all will look for
F</my/kde/trunk/KDE/kdepimlibs/kcal/.krazy>, since kcal is the project
within the kdepimlibs module.

If the current working dir is not within a KDE module, or if the current
working dir is above a project subdir, then a F<.krazy> file will not be read.


=head1 SEE ALSO

krazyrc(3), krazy2(1), krazy2xml(1)

=head1 AUTHORS

Allen Winter, <winter@kde.org>

=cut
