#!/usr/bin/perl -w

###############################################################################
# Sanity checks for your KDE source code                                      #
# Copyright 2005-2010 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.               #
#                                                                             #
###############################################################################

# Plugin-based: simply put your checker somewhere in $KRAZY_PLUGIN_PATH.
# the checker program can be written in the language of your choice,
# but it must follow the following rules:
#
# Plugin Rules:
#   1. must accept the following optional command line args:
#        --krazy:     a flag indicating the plugin was called by this krazy.
#        --help:      print one-line help message and exit
#        --version:   print one-line version information and exit
#        --priority:  print issues with the specified priority only
#        --strict:    print issues according to the specified strictness
#        --explain:   print an explanation with solving instructions
#        --quiet:     suppress all output messages
#        --verbose:   print the offending content for each file processed
#        --installed: file is to be installed.
#   2. must require one command line argument which is the file to test
#   3. must exit with status 0, and print "okay" if the file passes the test
#   4. must exit with non-zero status (=total failures) if file fails the test
#   5. if issues are encountered, the final line printed should be "ISSUES=N", where N
#      is the total number of issues found.  Krazy2 assumes this is the last line of
#      output, even if the plugin prints more information after the "ISSUES=N" line.
#   6. must print a string to standard output showing line number(s) that
#      fail the test.
#   7. the plugin should be a quick test of a source code file
#   8. the --explain option must print an explanation of why the offending
#      code is a problem, along with instructions on how to fix the code.
#
# Program options:
#   --help:         display help message and exit
#   --version:      display version information and exit
#   --list:         print a list of all the checker programs
#   --list-types:   print a list of all the supported file types
#   --list-sets:    print a list of all the checker sets
#   --explain:      if issues found, print an explanation along with
#                   solving instructions at the end of each test
#   --check <prog[,prog1,prog2,...,progN]>:
#                   run the specified checker program(s) only
#   --check-sets <set[,set1,set2,...,setN]>:
#                   run the checker programs belonging to the specified set(s) only
#   --exclude <prog[,prog1,prog2,...,progN]>:
#                   do NOT run the specified checker program(s)
#   --extra <prog[,prog1,prog2,...,progN]>:
#                   add the specified "extra" program(s) to the list of checkers
#   --types <type[,type1,type2,...,typeN]>:
#                   check the specified file type(s) only
#   --exclude-types <type[,type1,type2,...,typeN]>:
#                   do NOT check the specified file type(s)
#   --priority <low|normal|high|important|all>
#                   report only issues with the specified priorities:
#                     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)
#                   use priorities to help put the issues in fix-first order.
#   --strict <normal|super|all>
#                   report only issues matching the strictness level:
#                     normal -> print non-super strict issues only
#                     super -> print super strict issues only
#                     all -> print all issues (default)
#                   use strictness to help filter out issues of less importance
#   --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
#                     xml -> XML formatted
#   --title:        give the output a project title.
#   --rev:          subversion revision number
#   --ignorerc:     ignore .krazy files
#   --config <krazyrc> read settings from the specified config file
#   --dry-run:      don't execute the checks; only show what would be run
#   --brief:        print only checks with at least 1 issue
#   --no-brief:     print the result of all checks i.e, the opposite of brief (default)
#   --quiet:        suppress all output messages
#   --verbose:      print the offending content for each file processed
#
use strict;
use Getopt::Long;
use Env qw (HOME KRAZY_PLUGIN_PATH KRAZY_EXTRA_PATH KRAZY_SET_PATH);
use File::Basename;
use File::Find;
use Text::Wrap;
use HTML::Entities;
use File::Basename;
use Cwd;
use Cwd 'abs_path';
use Tie::IxHash;
use FindBin qw($Bin);
use lib "$Bin/../lib";
use Krazy::Config;
use Krazy::Utils;

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

my ($help)      = '';
my ($version)   = '';
my ($explain)   = '';
my ($list)      = '';
my ($listtypes) = '';
my ($listsets)  = '';
my ($brief)     = '';
my ($nobrief)   = '';
my ($quiet)     = '';
my ($verbose)   = '';
my ($dryrun)    = '';
my ($ignorerc)  = '';
my ($configf)   = '';
my ($only)      = '';
my ($checksets) = '';
my ($exclude)   = '';
my ($inctypes)  = '';
my ($exctypes)  = '';
my ($extra)     = '';
my ($priority)  = '';
my ($strict)    = '';
my ($output)    = '';
my ($skip)      = "/////";            # hopefully a regex that matches nothing
my ($export)    = '';
my ($title)     = "$Prog Analysis";
my ($cms)       = "KDE";
my ($rev)       = '';

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

&Help() if ($help);
if ( !$list && !$listtypes && !$listsets && $#ARGV < 0 ) { &Help(); exit 0; }
&Version() if ($version);

if ($only && $checksets) {
  die "May not combine the options --check and --check-sets... exiting\n";
}

if ($priority && !&validatePriorityType($priority)) {
  die "Bad priority level \"$priority\" specified... exiting\n";
}

if ($strict && !&validateStrictType($strict)) {
  die "Bad strictness level \"$strict\" specified... exiting\n";
}

if ($export && !&validateExportType($export)) {
  die "Unsupported export type \"$export\"... exiting\n";
}

my ($KRAZYBINPATH) = dirname( abs_path($0) );
my ($KRAZYPATH)    = dirname($KRAZYBINPATH);
my ($CWD)          = getcwd;
# the following, just in case kdebase-foo are symlinks
$CWD =~ s+/kdebase/apps/+/kdebase-apps/+;
$CWD =~ s+/kdebase/runtime/+/kdebase-runtime/+;
$CWD =~ s+/kdebase/workspace/+/kdebase-workspace/+;
$CWD =~ s+/kdepim/runtime/+/kdepim-runtime/+;

# Checkers hash has a list of all the plugins, by file type we support.
tie my (%Checkers),  "Tie::IxHash";
tie my (%oCheckers), "Tie::IxHash";    # the "only" list
tie my (%eCheckers), "Tie::IxHash";    # the "exclude" list
tie my (%xCheckers), "Tie::IxHash";    # the "extras" list
tie my (%pCheckers), "Tie::IxHash";    # the final list
&initCheckers();

# Sets hash
tie my (%Sets), "Tie::IxHash";
&initSets();

######################################################################
# This section builds the list of checker programs and checker sets. #
######################################################################
$KRAZY_PLUGIN_PATH =
  "$KRAZYPATH/lib64/krazy2/krazy-plugins:" .
  "$KRAZYPATH/lib/krazy2/krazy-plugins:"
  if ( !$KRAZY_PLUGIN_PATH );
$KRAZY_EXTRA_PATH =
  "$KRAZYPATH/lib64/krazy2/krazy-extras:" .
  "$KRAZYPATH/lib/krazy2/krazy-extras:"
  if ( !$KRAZY_EXTRA_PATH );
$KRAZY_SET_PATH =
  "$KRAZYPATH/lib64/krazy2/krazy-sets:" .
  "$KRAZYPATH/lib/krazy2/krazy-sets:"
  if ( !$KRAZY_SET_PATH );

my (@sanity_paths,@set_paths);

# Generate an array of paths to search for plugins
my ($sp);
for $sp ( split( /:/, $KRAZY_PLUGIN_PATH ) ) {
  push( @sanity_paths, abs_path $sp ) if ( -d $sp );
}
@sanity_paths = deDupe(@sanity_paths);
if ( $#sanity_paths < 0 ) {
  print "No plugin paths found.\n";
  print "Please check your KRAZY_PLUGIN_PATH environment variable... exiting\n";
  exit 1;
}

# Generate a hash of arrays containing the plugin names for each supported type
my ( $type, @tpaths, @tmp );
foreach $type ( keys %Checkers ) {
  splice @tpaths;
  for $sp (@sanity_paths) {
    if ( -d "$sp/$type" ) {
      push( @tpaths, "$sp/$type" );
    }
  }
  if ( $#tpaths >= 0 ) {
    @tmp = ();
    find( \&buildPluginList, @tpaths );

    sub buildPluginList {
      -x && !-d && push( @tmp, $File::Find::name );
    }
    push @{ $Checkers{$type} }, @tmp;
  }
}

# Generate an array of paths to search for checker sets
my ($setp);
for $setp ( split( /:/, $KRAZY_SET_PATH ) ) {
  push( @set_paths, abs_path $setp ) if ( -d $setp );
}
@set_paths = deDupe(@set_paths);
if ( $#set_paths < 0 ) {
  print "No checker set paths found.\n";
  print "Please check your KRAZY_SET_PATH environment variable... exiting\n";
  exit 1;
}

# Generate a hash of arrays for the checker sets
my($set);
foreach $set ( keys %Sets ) {
  splice @tpaths;
  for $setp (@set_paths) {
    if ( -d "$setp/$set" ) {
      push( @tpaths, "$setp/$set" );
    }
  }
  if ( $#tpaths >= 0 ) {
    @tmp = ();
    find( \&buildSetList, @tpaths );

    sub buildSetList {
      -x && !-d && push( @tmp, $File::Find::name );
    }
    push @{ $Sets{$set} }, @tmp;
  }
}

# Generate a hash of arrays containing the extra plugins for each supported type
my ( $xp, @extra_paths );
for $xp ( split( /:/, $KRAZY_EXTRA_PATH ) ) {
  push( @extra_paths, abs_path $xp ) if ( -d $xp );
}
@extra_paths = deDupe(@extra_paths);
foreach $type ( keys %xCheckers ) {
  splice @tpaths;
  for $xp (@extra_paths) {
    if ( -d "$xp/$type" ) {
      push( @tpaths, "$xp/$type" );
    }
  }
  if ( $#tpaths >= 0 ) {
    @tmp = ();
    find( \&buildExtraList, @tpaths );

    sub buildExtraList {
      -x && !-d && push( @tmp, $File::Find::name );
    }
    push @{ $xCheckers{$type} }, @tmp;
  }
}
###############################################################
# Invoke the --list options, which all exit after completing. #
###############################################################
# Print the list of available checker programs and exit.
&List() if $list;

# Print the list of supported file types and exit.
&ListTypes() if $listtypes;

# Print the list of checker sets and exit.
&ListSets() if $listsets;

###############################################
# This section parses the krazy config files. #
###############################################
# Parse .krazy file(s)
my(%ds,%pds);
$ds{'EXTRA'}        = $pds{'EXTRA'} = "";
$ds{'CHECK'}        = $pds{'CHECK'} = "";
$ds{'CHECKSETS'}    = $pds{'CHECKSETS'} = "";
$ds{'EXCLUDE'}      = $pds{'EXCLUDE'} = "";
$ds{'TYPES'}        = $pds{'TYPES'} = "";
$ds{'EXCLUDETYPES'} = $pds{'EXCLUDETYPES'} = "";
$ds{'SKIPREGEX'}    = $pds{'SKIPREGEX'} = "";
$ds{'PRIORITY'}     = $pds{'PRIORITY'} = "";
$ds{'STRICT'}       = $pds{'STRICT'} = "";
$ds{'OUTPUT'}       = $pds{'OUTPUT'} = "";
$ds{'EXPORT'}       = $pds{'EXPORT'} = "";

if ( !$ignorerc ) {
  my ($rcfile);

  if ($configf) {
    $configf = glob($configf);
    # read directives from the specified config file
    if (! -f $configf) {
      print "Specified config file \"$configf\" does not exist or is not readable.\n";
      exit 1;
    } else {
      %ds = &ParseKrazyRC($configf);
    }
  } else {
    # read directives from the user' .krazy in their home dir.
    $rcfile = "";
    if ( defined($HOME) && $HOME ) { # just in case $HOME isn't set
      $rcfile = $HOME . "/.krazy";
    }
    if ($rcfile && -f $rcfile) {
      %ds = &ParseKrazyRC($rcfile);
    }

    # read directives from the project's .krazy
    $rcfile = "";
    my ($projpath) = &topProject($CWD);
    if ($projpath) {
      $rcfile = $projpath . "/.krazy";
    } else {
      if (!&topModule($CWD)) {
        #not in a KDE project dir, so use the one in cwd
        $rcfile = $CWD . "/.krazy";
      }
    }
    if ($rcfile && -f $rcfile) {
      %pds = &ParseKrazyRC($rcfile);
    }
  }

  #Merge the directives from the possible 2 .krazy files
  $ds{'EXCLUDE'}      = &addCommaSeparated( $ds{'EXCLUDE'},     $pds{'EXCLUDE'});
  $ds{'CHECK'}        = &addCommaSeparated( $ds{'CHECK'},       $pds{'CHECK'});
  $ds{'CHECKSETS'}    = &addCommaSeparated( $ds{'CHECKSETS'},   $pds{'CHECKSETS'});
  $ds{'EXTRA'}        = &addCommaSeparated( $ds{'EXTRA'},       $pds{'EXTRA'});
  $ds{'TYPES'}        = &addCommaSeparated( $ds{'TYPES'},       $pds{'TYPES'});
  $ds{'EXCLUDETYPES'} = &addCommaSeparated( $ds{'EXCLUDETYPES'},$pds{'EXCLUDETYPES'});
  $ds{'SKIPREGEX'}    = &addRegEx(          $ds{'SKIPREGEX'},   $pds{'SKIPREGEX'});

  #override directives from the possible 2 .krazy files
  $ds{'PRIORITY'} = $pds{'PRIORITY'} if ($pds{'PRIORITY'});
  $ds{'STRICT'}   = $pds{'STRICT'}   if ($pds{'STRICT'});
  $ds{'OUTPUT'}   = $pds{'OUTPUT'}   if ($pds{'OUTPUT'});
  $ds{'EXPORT'}   = $pds{'EXPORT'}   if ($pds{'EXPORT'});
}

# override .krazy file settings with command line settings
$extra     = $ds{'EXTRA'}        if (!$extra     && $ds{'EXTRA'});
$only      = $ds{'CHECK'}        if (!$only      && $ds{'CHECK'});
$checksets = $ds{'CHECKSETS'}    if (!$checksets && $ds{'CHECKSETS'});
$exclude   = $ds{'EXCLUDE'}      if (!$exclude   && $ds{'EXCLUDE'});
$inctypes  = $ds{'TYPES'}        if (!$inctypes  && $ds{'TYPES'});
$exctypes  = $ds{'EXCLUDETYPES'} if (!$exctypes  && $ds{'EXCLUDETYPES'});
$skip      = $ds{'SKIPREGEX'}    if (               $ds{'SKIPREGEX'});
$priority  = $ds{'PRIORITY'}     if (!$priority  && $ds{'PRIORITY'});
$strict    = $ds{'STRICT'}       if (!$strict    && $ds{'STRICT'});
$output    = $ds{'OUTPUT'}       if (               $ds{'OUTPUT'});
$export    = $ds{'EXPORT'}       if (!$export    && $ds{'EXPORT'});

# fallback defaults
$export = "text" if (!$export);
$quiet = 1 if ( !$quiet && $output eq "quiet" );
$brief = 1 if ( !$brief && $output eq "brief" );
$brief = 0 if ( $nobrief );

# if ($verbose) {
#    print "\nDirectives:\n";
#    print "CHECK $only\n"          if ($only);
#    print "EXCLUDE $exclude\n"     if ($exclude);
#    print "EXTRA $extra\n"         if ($extra);
#    print "TYPES $inctypes\n"      if ($inctypes);
#    print "EXTRATYPES $exctypes\n" if ($exctypes);
#    print "SKIP $skip\n"           if ($skip);
#    print "PRIORITY $priority\n"   if ($priority);
#    print "STRICT $strict\n"       if ($strict);
#    print "OUTPUT $output\n"       if ($output);
#    print "EXPORT $export\n"       if ($export);
# }

###########################################################################
# Now, we munge together all the options to include and exclude checkers, #
# and support checkset and extra checkers.                                #
###########################################################################

# Generate a list of all the checker programs in the specified sets.
my($prog,@set_progs);
if ($checksets) {
  foreach $set ( split( ",", $checksets ) ) {
    if (!defined($Sets{$set})) {
      print "No such check set \"$set\".\n";
      print "Run $Prog --list-sets to see the list of available check sets.\n";
      exit 1;
    }
    foreach $prog ( @{ $Sets{$set} } ) {
      push( @set_progs, &basename($prog));
    }
  }
  @set_progs = deDupe(@set_progs);
}

# Now subtract away the checkers that aren't found in the check sets.
if ($checksets) {
  my ($tprog,$sprog);
  foreach $type ( keys %Checkers ) {
    splice @tmp;
    foreach $tprog ( @{ $Checkers{$type} } ) {
      foreach $sprog ( @set_progs) {
        my($st,$sp) = split("-",$sprog);
        next unless ($st eq $type);
        if ( &basename($tprog) eq $sp ) {
          push( @tmp, $tprog);
          last;
        }
      }
    }
    @{ $Checkers{$type} } = @tmp;
  }
}

my ($p);
## process extras
if ($extra) {
  my (@extra_progs) = split( ",", $extra );
  @extra_progs = deDupe(@extra_progs);
  my ( $i, $x );
  for $x (@extra_progs) {

    # Make sure specified extra program is in the list
    my ($found) = 0;
    foreach $type ( keys %xCheckers ) {
      for $p ( sort @{ $xCheckers{$type} } ) {
        if ( &basename($p) eq $x ) {
          $found = 1;
          push( @{ $Checkers{$type} }, $p );
          last;
        }
      }
    }
    if ( !$found ) {
      print "Extra checker \"$x\" not found.\n";
      print "Run $Prog --list to see the list of available checker programs.\n";
      exit 1;
    }
  }
}

# Remove file types from the Checkers hash, according to the types command line arg
if ($inctypes) {
  my (@only_types) = split( ",", $inctypes );
  @only_types = deDupe(@only_types);
  my ($t, $o);
  for $t (@only_types) {
    if (!defined($Checkers{$t})) {
      print "The file type \"$t\" is not supported.\n";
      print "Run $Prog --list-types to see the list of supported file types.\n";
      exit 1;
    }
  }
  foreach $type ( keys %Checkers ) {
    my ($found) = 0;
    foreach $o (@only_types) {
      if ( $type eq $o ) {
        $found = 1;
        last;
      }
    }
    if (!$found) {
      @{ $Checkers{$type} } = ();
      @{ $xCheckers{$type} } = ();
    }
  }
}

# Remove file types from the Checkers hash, according to the exclude-types command line arg
if ($exctypes) {
  my (@exclude_types) = split( ",", $exctypes );
  @exclude_types = deDupe(@exclude_types);
  my ($t, $o);
  for $t (@exclude_types) {
    if (!defined($Checkers{$t})) {
      print "The file type \"$t\" is not supported.\n";
      print "Run $Prog --list-types to see the list of supported file types.\n";
      exit 1;
    }
  }
  foreach $type ( keys %Checkers ) {
    my ($found) = 0;
    foreach $o (@exclude_types) {
      if ( $type eq $o ) {
        $found = 1;
        last;
      }
    }
    if ($found) {
      @{ $Checkers{$type} } = ();
      @{ $xCheckers{$type} } = ();
    }
  }
}

# If only, then run the specified check program only
if ($only) {
  my (@only_progs) = split( ",", $only );
  @only_progs = deDupe(@only_progs);
  my ( $i, $o );
  for $o (@only_progs) {

    # Make sure specified only program is in the list
    my ($found) = 0;
    foreach $type ( keys %Checkers ) {
      for $p ( sort @{ $Checkers{$type} } ) {
        if ( &basename($p) eq $o ) {
          $found = 1;
          push( @{ $oCheckers{$type} }, $p );
          last;
        }
      }
    }
    if (!$found) {
      # ok, maybe an extra checker was specified
      foreach $type ( keys %xCheckers ) {
        for $p ( sort @{ $xCheckers{$type} } ) {
          if ( &basename($p) eq $o ) {
            $found = 1;
            push( @{ $oCheckers{$type} }, $p );
            last;
          }
        }
      }
    }
    if ( !$found ) {
      if ( !$checksets) {
        print "No such checker \"$o\".\n";
        print "Run $Prog --list to see the list of available checker programs.\n";
      } else {
        print "No such checker \"$o\" in the check-sets \"$checksets\".\n";
        print "Run $Prog --list-sets to see the list of available check sets and programs.\n";
      }
      exit 1;
    }
  }
} else {
  foreach $type ( keys %Checkers ) {
    for $p ( sort @{ $Checkers{$type} } ) {
      push( @{ $oCheckers{$type} }, $p );
    }
  }
}

## process program exclusions
if ($exclude) {
  my (@exclude_progs) = split( ",", $exclude );
  @exclude_progs = deDupe(@exclude_progs);
  my ( $i, $e );
  foreach $e (@exclude_progs) {

    # Make sure specified exclude program is in the list
    my ($found) = 0;
    foreach $type ( keys %Checkers ) {
      for $p ( sort @{ $Checkers{$type} } ) {
        if ( &basename($p) eq $e ) {
          $found = 1;
          push( @{ $eCheckers{$type} }, $p );
          last;
        }
      }
    }
    if ( !$found ) {
      if ( !$checksets) {
        print "No such checker \"$e\" to exclude.\n";
        print "Run $Prog --list to see the list of available checker programs.\n";
      } else {
        print "No such checker \"$e\" to exclude in the check-sets \"$checksets\".\n";
        print "Run $Prog --list-sets to see the list of available check sets and programs.\n";
      }
      exit 1;
    }
  }
}

# finally, remove the "excluded" checkers from the "only" checkers
my ($nprogs) = 0;
foreach $type ( keys %Checkers ) {
  for $p ( sort @{ $oCheckers{$type} } ) {
    my ($q);
    my ($found) = 0;
    for $q ( sort @{ $eCheckers{$type} } ) {
      if ( &basename($p) eq &basename($q) ) {
        $found = 1;
        last;
      }
    }
    if ( !$found ) {
      push( @{ $pCheckers{$type} }, $p );
      $nprogs++;    #count the total number of checkers, over all filetypes
    }
  }
}

if ( $nprogs == 0 ) {
  print "No checker programs to run... exiting\n";
  exit 1;
}

#####################################################################
# This section runs each checker program for each specified file,   #
# collecting the output and exit status into a hash on the checker. #
#####################################################################

# Options to pass to the checker programs
my ($opts) = "--krazy "; #always passed
$priority = "all" if ( !$priority );
$opts .= "--priority $priority " if ($priority);
$strict = "all" if ( !$strict );
$opts .= "--strict $strict " if ($strict);
$opts .= "--quiet " if ($quiet);
$opts .= "--verbose " if ($verbose);

# create the list of files to process
my (@allfiles) = ();
if ( $ARGV[0] eq "-" ) {
  # read the file list from stdin
  while ( defined( $_ = <STDIN> ) ) {
    chomp($_);
    push( @allfiles, $_ ) if ( $_ && $_ !~ m/^#/ );
  }
} else {
  # files were provided on the command line
  @allfiles = @ARGV;
}

# quick run through all the files, eliminating types we don't need
my ( $f, $absf, $ftype );
my (@types) = ();
for $f (@allfiles) {
  if ( !-f $f ) {
    print STDERR "Cannot access file $f\n";
    next;
  }
  if ( $f =~ m+$skip+ ) {
    print STDERR "skipping $f\n" if ($verbose);
    next;
  }
  $ftype = fileType($f);
  if ( $ftype eq "" ) {
    print STDERR "Unsupported file type for $f... skipping\n";
    next;
  }
  my ($t);
  my ($found) = 0;
  if ( $#types >= 0 ) {
    for $t (@types) {
      if ( $t eq $ftype ) {
        $found = 1;
        last;
      }
    }
  }
  push( @types, $ftype ) if ( !$found );
}

my ($insopt);
my ($overall_status) = 0;
my ($num_checkers)   = 0;
my ($use, %result, $pid, %status);
my ($insp) = $KRAZYPATH . "/lib64/krazy2/krazy-helpers/getInstalledHeaders.pl";
  $insp = $KRAZYPATH . "/lib/krazy2/krazy-helpers/getInstalledHeaders.pl" if (! -f $insp);
my ($dirf);
for $ftype (@types) {
  if ( defined( $pCheckers{$ftype} ) ) {
    for $p ( sort @{ $pCheckers{$ftype} } ) {
      my ($bp) = &basename($p);
      if(!$quiet) {
        if(!$brief) {
          print STDERR "=>$ftype/$bp test in-progress.";
        } else {
          print STDERR "." unless ($export =~ m/text[a-z]+/);
        }
      }
      $result{$p} = "";
      my ($nf) = 0;
      $num_checkers++;
      for $f (@allfiles) {
        next if ( $f =~ m+$skip+ );
        next unless ( fileType($f) eq $ftype );

        $nf++;

        # skip the following files because they are auto-generated but do not
        # contain text that can be tested to determine that situation.
        next if ( $f =~ m/la\.all_cpp\.cpp$/ );

        # skip auto-generated files: test the first 5 lines for known signatures
        open( F, "<$f" ) || die "Couldn't open $f";
        my (@c) = <F>;
        my ($tt) = join '', @c[ 0 .. ( $#c > 5 ? 5 : $#c ) ];
        close(F);
        next
          if ( $tt =~
     /(All changes made in this file will be lost|All changes made to it will be lost|DO NOT EDIT|DO NOT delete this file|[Gg]enerated by|uicgenerated)|Bison parser|define BISON_/
             );

        # use the helper program to determine if this file is to be installed
        $insopt = "";
        $absf   = abs_path($f);
        $dirf   = dirname($absf);
        open( INS, "$insp $dirf |" ) or print STDERR "Cannot run: $insp\n";
        while (<INS>) {
          chomp($_);
          if ( $absf eq $_ ) {
            $insopt = "--installed";
            last;
          }
        }
        close(INS);
  
        # run the checker, concatentating the output
        if ( !$dryrun ) {
          $pid = open( SANE, "$p $opts $insopt \'$f\' 2>/dev/null |" )
            or print STDERR "Cannot run: &basename($p)\n";
          my($issues) = -1;
          while (<SANE>) {
            chomp($_);
            if($_ =~ m/^ISSUES=(\d+)/) {
              $issues = $1;
              last;
            } else {
              $result{$p} .= "\t" . $f . ": " . $_ . "\n"
                unless ( $_ =~ m+[Oo][Kk][Aa][Yy]$+ || $_ =~ m+[Nn]/[Aa]+ );
            }
          }
          close(SANE);
          if ($issues < 0) {
            #maybe the checker didn't print the ISSUES=N line, so use the old exit status
            $issues = $? >> 8;
          }
          $status{$p} += $issues;
        } else {
          print "$p $opts $insopt $f\n";
        }
        print STDERR "." unless ( $nf % 10 || $quiet || $export =~ m/text[a-z]+/);
      } # foreach file to process
      if ( $nf > 0 ) {
        $overall_status += $status{$p} if ( defined( $status{$p} ) );
        print STDERR "done\n" unless ($quiet || $brief);
      }
    } # foreach Checker of this type
  } # if any Checker is defined for file of this type
} # foreach type of file

###############################
# This section prints results #
###############################
if ( !$quiet ) {

  &printHeader( $overall_status, $num_checkers, $#allfiles + 1 );

  my ($st,$bp);
  my ($cline, $rline);
  for $ftype (@types) {
    next unless ( $#{ $pCheckers{$ftype} } >= 0 );
    $st = 0;
    my ($pth) = ${ $pCheckers{$ftype} }[0];
    &printFType( $pth, $ftype ) if (!$brief || $export eq "xml");
    for $p ( sort @{ $pCheckers{$ftype} } ) {
      $st++;
      $use = `$p --help 2>/dev/null`;
      chomp($use);
      $use = "no description available" if ( length($use) < 4 );
      $cline = '';
      $cline .= "$st. " if ( $export eq "text" );
      $bp = &basename($p);
      $cline .= "$use [$bp]...";
      if ( defined( $status{$p} ) && $status{$p} > 0 ) {
        my ($si) = ( $status{$p} > 1 ? "issues" : "issue" );
        $rline = "$status{$p} $si found";
      } else {
        $rline = "Ok!";
        $cline = "" if ($brief); #so printCheck() will print nothing
      }
      &printCheck( $cline, $rline );
      if (defined($status{$p}) && $status{$p} > 0 && length($result{$p}) > 0) {
        printOOPS( $result{$p}, $cline );
        if ($explain) {
          my ($use) = `$p --explain 2>/dev/null`;
          chomp($use);
          $use = "(no explanation available)" if ( length($use) < 4 );
          &printExplain( $use );
        }
      }
      print "\n" if ($cline ne "" && $export ne "textlist" && $export ne "textedit");
      print "      </check>\n" if ( $cline ne "" && $export eq "xml" );
    }

    print "    </file-type>\n" if ( $export eq "xml" );
  }
  &printFooter();
}

# This program exits with a sum of all issues for each file processed.
exit $overall_status;

#==============================================================================
# Help function: print help message and exit.
sub Help {
  &Version();
  print "A KDE source code sanitizer.\n\n";
  print "Usage: $Prog [OPTION] [FILES]\n\n";
  print "When FILE is - read a list of files (1 file per line) from standard input.\n\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-sets  print a list of all the checker sets\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 "  --explain    print explanations with solving instructions\n";
  print "  --check <prog[,prog1,prog2,...,progN]>\n";
  print "               run the specified 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 "  --rev:       subversion revision number\n";
  print "  --ignorerc:  ignore .krazy files\n";
  print "  --config <krazyrc>\n";
  print "               read settings from the specified configfile\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 $help;
}

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

# Initialize the Checkers hash
sub initCheckers {
  %Checkers = (
    'c++'       => [],
    'desktop'   => [],
    'designer'  => [],
    'kconfigxt' => [],
    'messages'  => [],
    'kpartgui'  => [],
    'tips'      => [],
    'qml'       => [],
    'qdoc'      => [],
  );
  my ($type);
  foreach $type ( keys %Checkers ) {

    # add in these extra checkers
    @{ $xCheckers{$type} } = ();

    # then, check these only from the list of all available
    @{ $oCheckers{$type} } = ();

    # except, exclude these
    @{ $eCheckers{$type} } = ();

    # and here is the final list of checkers
    @{ $pCheckers{$type} } = ();
  }
}

# Initialize the Sets hash
sub initSets {
  %Sets = (
    'c++' => [],
    'qt4' => [],
    'kde4' => [],
    'foss' => [],
  );
  my ($set);
  foreach $set (keys %Sets ) {
    @{ $Sets{$set} } = ();
  }
}

# printList function: print a formatted list of the checker programs provided.
sub printList {
  my ( $isextra, @list ) = @_;

  my ($prog);
  for $prog ( sort @list ) {
    $use = "";
    $use = "[EXTRA] " if ($isextra && $export ne "xml");
    $use .= `$prog --help 2>/dev/null`;
    chomp($use);
    $use = "(no description available)" if ( length($use) < 4 );

    if ( $export eq "xml" ) {
      $version = `$prog --version 2>/dev/null`;
      chomp($version);
      if ($version =~ m/.*(\d\.\d(\.\d)?).*/) {
        $version = $1;
      }

      print "  " if ( $isextra );
      print "      <plugin name=\"" . &basename($prog) . "\"";
      print " short-desc=\"" . encode_entities($use) . "\"";
      print " version=\"" . $version . "\"";
      if ( $explain ) {
        print ">\n";
      } else {
        print "/>\n";
      }
    } else {
      printf( "%18.18s: %s\n", &basename($prog), $use );
    }

    if ($explain) {
      $use = `$prog --explain 2>/dev/null`;
      chomp( $use );
      $use = "(no explanation available)" if ( length($use) < 4 );
      printExplain( $use, $isextra );
    }

   print "  " if ( $isextra && $export eq "xml");
   print "      </plugin>\n" if ( $export eq "xml" && $explain );
  }
}

# List function: print a list of all checker programs available to run.
sub List {
  my ( $prog, $use, $type );

  if ( $export =~ "xml" ) {
    &printXmlHeader();
    print "  <file-types>\n";
  } else {
    print "Available KDE source code sanitizer checks:\n";
  }

  foreach $type ( keys %Checkers ) {
    my ($pth) = ${ $Checkers{$type} }[0];
    &printFType( dirname($pth), $type );
    if ( $#{ $Checkers{$type} } >= 0 ) {
      &printList( 0, @{ $Checkers{$type} } );
    } else {
      printf( "%18.18s\n", "(none)" );
    }
    if ( $#{ $xCheckers{$type} } >= 0 ) {
      print "      <extra>\n" if ( $export eq "xml" );
      &printList( 1, @{ $xCheckers{$type} } );
      print "      </extra>\n" if ( $export eq "xml" );
    }

    print "    </file-type>" if ( $export eq "xml" );
    print "\n";
  }

  &printFooter();
  exit 0 if $list;
}

# ListTypes function: print a list of supported file types.
sub ListTypes {
  my ( $prog, $use, $type );

  &printXmlHeader() if ( $export =~ "xml" );

  print "Supported file types:\n";

  foreach $type ( keys %Checkers ) {
    my($desc) = &fileTypeDesc($type);
    printf( "%12.12s: %s", $type, $desc );
    print "\n";
  }

  &printFooter();
  exit 0 if $listtypes;
}

# ListSets function: print a list of checker sets.
sub ListSets {
  my ( $prog, $prprog, $use, $type );

  &printXmlHeader() if ( $export =~ "xml" );

  print "List of available checker sets:\n";

  foreach $set ( keys %Sets ) {
    my($desc) = &textDescription(dirname(${ $Sets{$set} }[0]));
    print "== $set: $desc ==\n";
    my($prog,$use);
    foreach $prog (sort @{ $Sets{$set} }) {
      $use = "";
      $use .= `$prog --help 2>/dev/null`;
      chomp($use);
      $use = "(no description available)" if ( length($use) < 4 );

      $prprog = &basename($prog);
#      $prprog =~ s/^.*-//;
      printf( "%20.20s: %s\n", $prprog, $use );
    }
    print "\n";
  }

  &printFooter();
  exit 0 if $listsets;
}

# printXmlHeader function: print the header string for the XML export type.
sub printXmlHeader {
  print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
  if ($list) {
    print "<tool name=\"krazy2\" version=\"$VERSION\">\n";
  } else {
    print "<tool-result tool=\"krazy2\" version=\"$VERSION\">\n";
  }
}

# printHeader function: print the header string, according to export type.
sub printHeader {
  my ( $num_issues, $num_checks, $num_files ) = @_;
  my ( $component, $module, $subdir ) = split( "/", $cms );

  if ($export =~ "xml") {
    &printXmlHeader();

    print "  <global>\n";
    print "    <date value=\"", asOf(), "\" />\n";
    print "    <processed-files value=\"$num_files\" />\n";
    print "    <svn-rev value=\"$rev\" />\n" if ($rev);
    print "  </global>\n";
    print "  <file-types>\n";
  } elsif ($export eq "text") {
    print "\n$title\n" if ($title);
    print "\nCheckers Run = $num_checks\n";
    print "Files Processed = $num_files\n";
    if ($num_issues) {
      print "Total Issues = $num_issues";
    } else {
      print "No Issues Found!";
    }
    print " ...as of ";
    print asOf();
    print " (SVN revision $rev)" if ($rev);
    print "\n\n";
  }
}

# printFooter function: print the footer string, according to export type.
sub printFooter {

  if ( $export eq "xml" ) {
    print "  </file-types>\n";
    if ($list) {
      print "</tool>\n";
    } else {
      print "</tool-result>\n";
    }
  }
}

# printCheck function: print the "check" and "result" strings, according
# to export type.
sub printCheck() {
  my ($check, $result) = @_;
  return if ($check eq "");
  if ( $export eq "text" ) {
    print "$check $result\n";
  } elsif ( $export eq "xml" ) {
    # Would be nice maybe to have the checker name in here.
    # something like:
    # <check name="foo" desc="checks for foo">
    print "      <check desc=\"", &xmlify( $check ), "\">\n";
  }
}

# textDescription function: return the description.txt found in $1
sub textDescription {
  my ($pth) = @_;
  return "" if ( !$pth );
  my ($f) = "$pth" . "/description.txt";
  open( F, "$f" ) || return "";
  my ($line) = "";
  while ( $line = <F> ) {
    last;
  }
  close(F);
  chomp($line);
  return ($line);
}

# printFType function: print the File Type line, according to export type.
sub printFType {
  my ( $pth, $ftype ) = @_;

  return if ( $export eq "textlist" || $export eq "textedit" );

  my ($desc) = &textDescription($pth);
  $desc = "For File Type $ftype" if ( $desc eq "" );
  if ( $export eq "xml" ) {
    print "    <file-type value=\"$ftype\">\n";
  } else {
    print "== $ftype: $desc ==\n";
  }
}

# printOOPS function: print the OOPS lines, according to export type.
sub printOOPS {
  my ( $component, $module, $subdir ) = split( "/", $cms );
  $component = "" if ( !defined($component) );
  $module    = "" if ( !defined($module) );
  $subdir    = "" if ( !defined($subdir) );

  my ($o);

  for $o ( split( "\n", $_[0] ) ) {
    chomp($o);

    if ( $export eq "textlist" ) {
      my ($t) = split( ":", $o );
      $t =~ s+^\s++;
      print "$t\n";
    } elsif ( $export eq "textedit" ) {

      #file:line:issue
      my ($f);
      my ( $ls1, $ls2, $ls3 ) = ( "", "", "" );
      ( $f, $ls1, $ls2, $ls3 ) = split( ":", $o );
      $f =~ s+^[[:space:]]*\./++;
      my (@ls) = ();
      push( @ls, &arrayLineify($ls1) ) if ( defined($ls1) );
      push( @ls, &arrayLineify($ls2) ) if ( defined($ls2) );
      push( @ls, &arrayLineify($ls3) ) if ( defined($ls3) );

      my ($subissue) = $o;
      $subissue =~ s+^\s*\./$f:\s*++;
      $subissue =~ s+\s*line#.*$++;
      $subissue =~ s+$f:++;
      $subissue =~ s/:\s*$//g;
      $subissue =~ s/\(\d+\)//g;
      $subissue =~ s/^\s*$//;
      $subissue =~ s/\s*$//;
      $subissue .= ", " if ( $subissue ne "" );
      $subissue =~ s+^[[:space:]]*++;
      my ($issue) = $_[1];
      $issue =~ s+^[Cc]hecks[[:space:]]*++;
      $issue =~ s+^[Cc]heck[[:space:]]*++;
      $issue =~ s+^for[[:space:]]*++;
      $issue =~ s+\s*\[.*\]++;
      $issue =~ s+\.*[[:space:]]*$++g;
      $f     =~ s+^[[:space:]]*++;

      my ($l,$hint);
      for $l (@ls) {
        $l =~ s/[[:alpha:]]*$//g;
        $l =~ s/^[[:alpha:]]*//g;
        $hint = $l;
        $hint =~ s/\d+\s*://;
        $l =~ s/:\[.*\]//g;
        $l =~ s/\s*$//;
        $hint = "" if ($hint eq $l);
        if ($hint) {
          #prepend 1 leading space
          $hint =~ s/^\s*//;
          $hint = " " . $hint;
        }
        print "$f:$l:$subissue$issue$hint\n";
      }
    } elsif ( $export eq "xml" ) {
      # We need to think about this. The output of the checkers should be made
      # consitent.
      my ( $ls1, $ls2, $ls3 ) = ( "", "", "" );
      my ( $file );
      ( $file, $ls1, $ls2, $ls3 ) = split( ":", $o );

      # Remove leading whitespace and ./
      $file =~ s/^\s*(\.\/)?//;
      $ls1 =~ s/^\s*//;

      # Ugly due to inconsitent output of checkers and my total lack of perl and
      # re skills.
      my( $lines );
      my( $message );
      if ( $ls1 =~ m/line#/ ) {
        $lines = $ls1;
        $message = $ls1;
      } elsif ( defined($ls2) && $ls2 =~ m/line#/ ) {
        $lines = $ls2;
        $message = $ls1;
      } elsif ( defined($ls3) && $ls3 =~ m/line#/ ) {
        $lines = $ls3;
        $message = $ls1;
      } else {
        $lines = "";
        $message = $ls1; # No line# part, so only set the message.
      }

      $message =~ s/\s*line\#.*$//;    # remove the lines#123,345 part
      $message =~ s/^[[:space:]]*//;   # remove leading spaces.
      $message =~ s/\(\d+\)//;         # remove the number of issues if it's there.

      $lines =~ s/^.*line#//;          # only keep the numbers
      $lines =~ s/\(\d+\)//;           # remove the number of issues if it's there.

      # Sometimes a check returns per line the issue like:
      # utils/input.cpp: line#155[Couldn't],184[Couldn't],200[Couldn't],258[Couldn't] (4)
      #
      # Another format is: value[expected value]
      # tokenizer.cpp: line#54 int8_t[qint8],#56 int16_t[qint16],#58 int32_t[qint32] (3)
      if ($lines =~ m/(\d+)\s*(\w*\[\w*\]),?#?/) {
        $lines =~ s/(\d+)\s*(\w*\[\w*\]),?#?/            <line issue="$2">$1<\/line>\n/g;
      } elsif ($lines =~ m/(\d+)\s*(\[[\s\w]+\]),*/) {
          $lines =~ s/(\d+)\s*\[([\s\w]+)\],*/            <line issue="$2">$1<\/line>\n/g;
      } elsif ($lines =~ /(\d+),?/) {
          $lines =~ s/(\d+),?/            <line>$1<\/line>\n/g;
      }

      print "        <file name=\"$file\">\n";
      if ($message) {
        print "          <message>", encode_entities( $message ), "</message>\n";
      }
      print "          <issues>\n";
      if ( $lines ) {
        print $lines;
      } else { # There is only a message, probably something that is not bound to a specific line.
        print "            <line>-1</line>\n";
      }
      print "          </issues>\n";
      print "        </file>\n";
    } else {
      print "$o\n";
    }
  }
}

# printExplain function: print the explanation lines, according to export type.
sub printExplain {
  my ( $explanation, $isextra ) = @_;

  if ( $export eq "xml" ) {
    $explanation =~ s/^\s*//;
    print "  " if ( $isextra );
    print "         <explanation>" . xmlify( $explanation ) . "\n";
    print "  " if ( $isextra );
    print "         </explanation>\n";
  } else {
    # text export
    print wrap( "        ", "        ", $explanation ) . "\n";
  }
}

# xmlify function: turn plain text into html
sub xmlify {
  my ($s) = @_;
  $s = encode_entities($s);
  $s =~ s+&lt;http:(.*?)&gt;+&lt;a href="http:$1"&gt;http:$1&lt;/a&gt;+gs;
  $s =~ s=\*(\S+)\*=&lt;strong&gt;$1&lt;/strong&gt;=g;    # *word* becomes boldified
  $s =~ s=\b\_(\S+)\_\b=&lt;em&gt;$1&lt;/em&gt;=g;        # _word_ becomes italicized
  return $s;
}

# turn a comma-separated line list into an array
sub arrayLineify {
  my ($s) = @_;

  return () if ( $s !~ m/line#/ );

  $s =~ s/^.*line#//;
  $s =~ s/\s*\(\d*\)\s*//g;
#  $s =~ s/\s*\[\w+\]\s*//g;
  $s =~ s/\[/:\[/g;
  return split( ",", $s );
}

# xmlify the line number string for lxr
sub lxrLineify {
  my ( $s, $url ) = @_;

  $s = &xmlify($s);
  return $s if ( $s !~ m/line#/ );

  $s =~ s+line#(\d*)+line#<a href=\"$url#$1\">$1</a>+g;
  $s =~ s+,(\d*)+,<a href=\"$url#$1\">$1</a>+g;
  return $s;
}

__END__

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

=head1 NAME

krazy2 - Sanity checks KDE source code.

=head1 SYNOPSIS

krazy2 [OPTIONS] [FILES]

=head1 DESCRIPTION

krazy2 scans KDE source code looking for issues that should be fixed
for reasons of policy, good coding practice, optimization, or any other
good reason.  In typical use, krazy2 simply counts up the issues
and provides the line numbers where those issues occurred in each
file processed.  With the verbose option, the offending content will
be printed as well.

krazy2 uses "checker programs" which are small plugin programs to do the
real work of the scanning.  It is easy to write your own plugins
(see B<PLUGINS>) and tell krazy2 how to use them (see B<ENVIRONMENT>).

A list of FILES to process is either specified on the command line
or read from standard input if the first file name is "-", in which
case 1 file per line is expected.  Blank lines and lines starting
with a '#' are ignored when reading the file list from standard input.

=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 all the supported file types and exit.

=item B<--list-sets>

Print a list of all the checker sets 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<--explain>

For each checker program, if any issues are found, print an explanation
of the problem along with solving instructions.  May be used in conjunction
with the B<--list> option to provide a more detailed description of the
checker programs.

=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).

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

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

Add the specified "extra" program(s) to the list of checkers to run.

Use the B<--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
                 the format is:  file:line-number: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 export.  Must be a slash-delimited triple containing the
component, module, and subdir which is being scanned.

=item B<--rev>

Subversion revision number to be printed on the output report, if provided.

=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 EXAMPLES

=over 4

=item Print a list of all available checker programs along with a short description:

 % krazy2 --list

 Available KDE source code sanitizer checks:
 For c++ file types ==
    captruefalse: Check for FALSE macro
          captrue: Check for TRUE macro
        copyright: Check for an acceptable copyright
 doublequote_char: Check for adding single char string to a QString
          license: Check for an acceptable license
    nullstrassign: Check for assignments to QString::null
   nullstrcompare: Check for compares to QString::null
             qmax: Check for QMAX macros
             qmin: Check for QMIN macros

For desktop file types ==
      contractions: Check for contractions in strings
   endswithnewline: Check that file ends with a newline

For designer file types ==
   endswithnewline: Check that file ends with a newline
      i18ncheckarg: Check validity of i18n calls
          spelling: Check for spelling errors

=item Run all checker programs on a file:

 % krazy2 fred.cc

 =>c++/captruefalse test in-progress.done
 =>c++/captrue test in-progress.done
 =>c++/copyright test in-progress.done
 =>c++/doublequote_chars test in-progress.done
 =>c++/license test in-progress.done
 =>c++/nullstrassign test in-progress.done
 =>c++/nullstrcompare test in-progress.done
 =>c++/qmax test in-progress.done
 =>c++/qmin test in-progress.done

 No Issues Found!

 1. Check for FALSE macro... okay!

 2. Check for TRUE macro... okay!

 3. Check for an acceptable copyright... okay!

 4. Check for adding single char string to a QString... okay!

 5. Check for an acceptable license... okay!

 6. Check for assignments to QString::null... okay!

 7. Check for compares to QString::null... okay!

 8. Check for QMAX macros... okay!

 9. Check for QMIN macros... okay!

=item Run all checker programs B<except> F<license> and F<copyright> the .cpp files in the current working directory:

 % krazy2 --exclude license,copyright *.cpp

=item Run the C<captruefalse> checker programs on the *.cpp, *.cc, and *.h found in the current working directory tree, printing explanations if any issues are encountered:

 % find . -name "*.cpp" -o -name "*.cc" -o -name "*.h" | \
 xargs krazy2 --check captruefalse --explain

 =>c++/captruefalse test in-progress........done

 Total Issues = 10

 1. Check for FALSE macro... OOPS! 232 issues found!
        ./fred.h: line#176 (1)
        ./fredmsg.h: line#41,54 (2)
        ./fred.cpp.cpp: line#436,530,702,724,1030,1506,1525 (7)

        The FALSE macro is obsolete and should be replaced by the
        false (all lower case) macro.

=item Run all the checker programs on desktop files only:

 % krazy2 --types=desktop *

=back

=head1 DIRECTIVES

The Krazy plugins support the following list of in-code directives:

=over 4

=item //krazy:skip

 no krazy2 tests will run on this file.

=item //krazy:excludeall=<name1[,name2,...,nameN]>

 the krazy2 tests name1, etc will not be run on this file.

=item //krazy:exclude=<name1[,name2,...,nameN]>

 the krazy2 tests name1, etc. will not be run on the line where
 this directive is found.

=back

Note that these directives must be C++ style comments that can be put anywhere in the file desired (except embedded within C-style comments).


=head1 PLUGINS

Write your own plugin:

=over 4

=item
Copy TEMPLATE.pl to your new file.

=item
Make the new file executable C<chmod +x file>.

=item
Move the new file into the installed plugins directory, or create
your own krazy-plugins directory and add it to the $KRAZY_PLUGIN_PATH
environment (see B<ENVIRONMENT>).

=back

You may write the plugin in the language of your choice,
but it must follow these rules:

=over 4

=item 1.
must accept the following optional command line args:

 --help:     print one-line help message and exit
 --version:  print one-line version information and exit
 --priority: report issues of the specified priority only
 --strict:   report issues with the specified strictness level only
 --explain:  print an explanation with solving instructions
 --installed file is to be installed
 --quiet:    suppress all output messages
 --verbose:  print the offending content

=item 2.
must require one command line argument which is the file to test.

=back

=over

=item 3.
must exit with status 0, and print "okay" if the file passes the test.

=item 4.
must exit with non-zero status if issues are encountered.

=item 5.
if issues are encountered, the final line printed should be "ISSUES=N",
where N is the total number of issues found.  Krazy2 assumes this is the
last line of output, even if the plugin prints more information after
the "ISSUES=N" line.

=item 6.
must print a string to standard output showing line number(s) that fail the test.

=item 7.
the plugin should be a quick test of a source code file.

=item 8.
the --explain option must print an explanation of why the offending code is a problem, along with instructions on how to fix the code.

=item 9.
I<finally, and importantly, the plugin must eliminate false positives as much as possible.>

=back

=head1 ENVIRONMENT

B<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>.

B<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>.

B<KRAZY_SET_PATH> - this is a colon-separated list of paths which is
searched when locating checker 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>)

B<KRAZY_STYLE_CPPSTYLE> - tells the "style" checker to follow the guidelines
of the specified C++-style.  I<Note>: Krazy uses a heuristic to decide
what type of C++-style to check against.  That heuristic isn't very smart;
so use this setting to enforce the C++-style.

C++-style must be one of the following:

=over 2

"kde" (default), see http://techbase.kde.org/Policies/Kdelibs_Coding_Style

"pim" kdepim style, see http://pim.kde.org/development/coding-korganizer.php

=back

B<KRAZY_STYLE_OFFSET> - tells the "style" checker to check for a basic
indentation level, usually a small integer like 2 or 4. This value overrides
the offset provided as part of the C++-style (see B<KRAZY_STYLE_CPPSTYLE>).

B<KRAZY_STYLE_LINEMAX> - tells the "style" checker to check for lines longer
than this number of characters.  The default is 100 characters.

=head1 EXIT STATUS

In normal operation, krazy2 exits with a status equal to the total number
of issues encountered during processing.

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

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

=head1 COPYRIGHT

Copyright (c) 2005-2010 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))

krazy2 first parses a F<.krazy> file found in the user's home directory,
if such a file exists.

Next, krazy2 searches up the current working directory tree for a
project specific F<.krazy> file. If one is found, its settings are
merged with (or override) those already found from F<$HOME/.krazy>.

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

If krazy2 determines that the current working directory is not located within
the KDE directory structure, then it simply reads the settings from the
F<.krazy> file found in the current working directory, if such a file exists.

If krazy2 determines that the current working directory is located
within the KDE directory structure but above a project subdir
(eg. F<trunk/KDE/kdepim>), then it assumes no project specific F</.krazy> file.

=head1 SEE ALSO

krazyrc(3), krazy2all(1), krazy2xml(1)

Ben Meyer's kdetestscripts - Automated scripts are to catch problems in KDE,
L<http://websvn.kde.org/trunk/playground/base/kdetestscripts>.

flawfinder - Examines source code looking for security weaknesses,
L<http://www.dwheeler.com/flawfinder>.

=head1 AUTHORS

Allen Winter, <winter@kde.org>

=cut
