#!/usr/bin/perl -w

eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
    if 0; # not running under some shell

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

# FOR KDAB:
# if in a non-KDE project (i.e. no /kdefoo/ in pwd), then use the pwd/.krazy

# TODO:
#  --config=foo, read config file foo (and no other .krazy files)
#  --types=type1,type2,..typeN  <- only process these file types
#  --exclude-types=type1,type2,..typeN <- exclude these file types
#  support TYPES and EXCLUDE-TYPES in .krazy files
#  support more command line options in .krazy files (like <D>brief, <D>quiet, verbose,...)
#  fix textedit export (for kate usage)
#  "sets/classes" of checks? eg, KDE; KDEPIM; Qt; Gnome???

# 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
#        --brief:     print only checks with at least 1 issue
#        --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. must print a string to standard output showing line number(s) that
#      fail the test.
#   6. the plugin should be a quick test of a source code file
#   7. 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:        list all the checker programs
#   --list-types:  list all the supported file types
#   --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
#   --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
#   --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|ebn|ebnlxr|html|htmllxr|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
#                    ebn -> English Breakfast Network style
#                    ebnlxr -> English Breakfast Network style + lxr.kde.org
#                    html -> plain old html
#                    htmllxr -> plain old html + lxr.kde.org
#                    xml -> XML formatted
#   --title:       give the output a project title.
#   --cms:         component/module/subdir triple for html and ebn exports
#   --rev:         subversion revision number
#   --ignorerc:    ignore .krazy files
#   --dry-run:     don't execute the checks; only show what would be run
#   --brief:       print only checks with at least 1 issue
#   --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);
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.8';    #split line so MakeMaker can find the version here

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

exit 1
  if ( !GetOptions(
		   'help'      => \$help,
		   'version'   => \$version,
		   'explain'   => \$explain,
		   'list'      => \$list,
		   'list-types'=> \$listtypes,
		   'dry-run'   => \$dryrun,
		   'ignorerc'  => \$ignorerc,
		   'verbose'   => \$verbose,
		   'brief'     => \$brief,
		   'quiet'     => \$quiet,
		   'check=s'   => \$only,
		   'exclude=s' => \$exclude,
		   '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 && $#ARGV < 0 ) { &Help(); exit 0; }
&Version() if ($version);

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/+;

# 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();

############################################################
# This section builds the list of checker programs to run. #
############################################################
$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 );

my (@sanity_paths);

# Generate an array of paths to search for plugins
my ($sp);
for $sp ( split( /:/, $KRAZY_PLUGIN_PATH ) ) {
  push( @sanity_paths, $sp ) if ( -d $sp );
}
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 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, $xp ) if ( -d $xp );
}
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;
  }
}

# Print the list of available checker programs and exit.
&List() if $list;

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

### Follow the commands in a .krazy file is one exists in this directory.
my (%ds);
if ( !$ignorerc ) {
  my ($rcfile) = topSubdir($CWD);
  if ($rcfile) {
    $rcfile .= "/.krazy";
    %ds = ParseKrazyRC($rcfile);
  }
}

# override .krazy file settings with command line settings
$extra   = $ds{'EXTRA'}   if ( !$extra && defined( $ds{'EXTRA'} ) );
$only    = $ds{'CHECK'}   if ( !$only && defined( $ds{'CHECK'} ) );
$exclude = $ds{'EXCLUDE'} if ( !$exclude && defined( $ds{'EXCLUDE'} ) );
$skip = $ds{'SKIPREGEX'}  if ( defined( $ds{'SKIPREGEX'} ) && $ds{'SKIPREGEX'} );
$priority = $ds{'PRIORITY'} if ( !$priority && defined( $ds{'PRIORITY'} ) );
$strict = $ds{'STRICT'}     if ( !$strict && defined( $ds{'STRICT'} ) );
$output = $ds{'OUTPUT'} if ( defined( $ds{'OUTPUT'} ) );
$quiet = 1 if ( !$quiet && $output eq "quiet" );
$brief = 1 if ( !$brief && $output eq "brief" );

# if ($verbose) {
#   print "\nDirectives:\n";
#   print "CHECK $only\n"        if ($only);
#   print "EXCLUDE $exclude\n"   if ($exclude);
#   print "EXTRA $extra\n"       if ($extra);
#   print "SKIP $skip\n"         if ($skip);
#   print "PRIORITY $priority\n" if ($priority);
#   print "STRICT $strict\n"     if ($strict);
# }

my ($p);
## process extras
if ($extra) {
  my (@extra_progs) = split( ",", $extra );
  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... exiting\n";
      exit 1;
    }
  }
}

# If only, then run the specified check program only
if ($only) {
  my (@only_progs) = split( ",", $only );
  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 ) {
      print "No such checker \"$o\"... exiting\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 );
  deDupe(@exclude_progs);
  my ( $i, $e );
  for $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 ) {
      print "No such checker to exclude \"$e\"... exiting\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 ".";
        }
      }
      $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";
	  while (<SANE>) {
	    chomp($_);
	    $result{$p} .= "\t" . $f . ": " . $_ . "\n"
	      unless ( $_ =~ m+[Oo][Kk][Aa][Yy]$+ || $_ =~ m+[Nn]/[Aa]+ );
	  }
	  close(SANE);
	  $status{$p} += $? >> 8;  #TODO:invent another way to get #issues
	} else {
	  print "$p $opts $insopt $f\n";
	}
	print STDERR "." unless ( $nf % 10 || $quiet );
      } # 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);
    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( wrap( "        ", "        ", $use ) );
        }
      }
      print "\n" if ($cline ne "" && $export ne "textlist" && $export ne "textedit");
      print "      </check>\n" if ( $export eq "xml" );
    }

    if ( $export eq "xml" ) {
      print "    </file-type>\n";
    } elsif ( $export !~ "text" ) {
      print "</ol>\n<br>\n"
    }
  }
  &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    list all the checker programs\n";
  print "  --list-types list all the supported file types\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 "  --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 "  --export <text|textlist|textedit|ebn|ebnlxr|html|htmllxr|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 "              ebn -> English Breakfast Network style\n";
  print "              ebnlxr -> English Breakfast Network style,\n";
  print "                        with links to lxr.kde.org\n";
  print "              html -> plain old html\n";
  print "              htmllxr -> plain old html,\n";
  print "                         with links to lxr.kde.org\n";
  print "              xml -> XML formatted\n";
  print "  --title   give the output a project title\n";
  print "  --cms     component/module/subdir triple for html and ebn exports\n";
  print "  --rev:    subversion revision number\n";
  print "  --ignorerc: ignore .krazy files\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 "  --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'      => [],
  );
  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} } = ();
  }
}

# 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);
    $use .= `$prog --help 2>/dev/null`;
    chomp($use);
    $use = "(no description available)" if ( length($use) < 4 );
    if ( $export !~ "text" ) {
      print "<li><b>" . &basename($prog) . ":</b> $use\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( wrap( "        ", "        ", $use ) );
    }
  }
}

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

  $title = "Available KDE source code sanitizer checks";
  if ( $export =~ "ebn" ) {
    &printEbnHeader();
  } elsif ( $export =~ "xml" ) {
    &printXmlHeader();
  } elsif ( $export =~ "html" ) {
    &printHtmlHeader();
  }

  if ( $export !~ "text" ) {
    print "<ul>\n";
    print "<h2>$title</h2>\n";
  } else {
    print "$title:\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 ) {
      &printList( 1, @{ $xCheckers{$type} } );
    }
    if ( $export !~ "text" ) {
      print "</ol>\n<br>\n";
    } else {
      print "\n";
    }
  }

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

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

  $title = "Supported file types";
  if ( $export =~ "ebn" ) {
    &printEbnHeader();
  } elsif ( $export =~ "xml" ) {
    &printXmlHeader();
  } elsif ( $export =~ "html" ) {
    &printHtmlHeader();
  }

  if ( $export !~ "text" ) {
    print "<ul>\n";
    print "<h2>$title</h2>\n";
  } else {
    print "$title:\n";
  }

  foreach $type ( keys %Checkers ) {
    my($desc) = &fileTypeDesc($type);
    printf( "%12.12s: %s", $type, $desc );
    if ( $export !~ "text" ) {
      print "</ol>\n<br>\n";
    } else {
      print "\n";
    }
  }

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

# printEbnHeader function: print the header string for the EBN export type.
sub printEbnHeader {
  print
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"DTD/xhtml1-transitional.dtd\">\n";
  print
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n";
  print "<head>\n";
  print "<title>$title</title>\n";
  print
"<link rel=\"stylesheet\" type=\"text/css\" title=\"Normal\" href=\"/style.css\" />\n";
  print "</head>\n";
  print "<body>\n";
  print "<div id=\"title\">\n";
  print "<div class=\"logo\">&nbsp;</div>\n";
  print "<div class=\"header\">\n";
  print "<h1><a href=\"/\">English Breakfast Network</a></h1>\n";
  print
    "<p><a href=\"/\">Almost, but not quite, entirely unlike tea.</a></p>\n";
  print "</div>\n";
  print "</div>\n";
  print "<div id=\"content\">\n";
  print "<div class=\"inside\">\n";
}

# printXmlHeader function: print the header string for the XML export type.
sub printXmlHeader {
  print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
	print "<krazy>\n";
}

# printEbnBreadcrumbs function: print breadcrumbs for EBN navigation
sub printEbnBreadcrumbs {
  my ( $component, $module, $subdir ) = @_;
  my ($upcomp) = uc($component);
  $upcomp =~ s/-/ /;

  print "<p style=\"font-size: x-small;font-style: sans-serif;\">\n";
  print "<a href=\"/index.php\">Home</a>&nbsp;&gt;&nbsp;\n";
  print "<a href=\"/$Prog/index.php\">Krazy Code Checker</a>&nbsp;&gt;&nbsp;\n";
  print
"<a href=\"/$Prog/index.php?component=$component\">$upcomp</a>&nbsp;&gt;&nbsp;\n"
    if ($component);
  print
"<a href=\"/$Prog/index.php?component=$component&module=$module\">$module</a>&nbsp;&gt;&nbsp;\n"
    if ( $component && $module );
  print "$subdir\n" if ($subdir);
  print "</p>\n";
}

# printEbnQualityLinks function: print links to other EBN quality tools
sub printEbnQualityLinks {
  my ( $component, $module, $subdir ) = @_;

  print "<p style=\"font-size: x-small;font-style: sans-serif;\">\n";
  print "Other $module/$subdir reports:\n";
  print
"[<a href=\"/apidocs/apidox-$component/$module-$subdir.html\">APIDOX</a>]\n";
  print
"[<a href=\"/sanitizer/reports/$component/$module/$subdir/index.html\">Docs</a>]\n";
  print "</p>\n";
}

# printHtmlHeader function: print the header string for the HTML export type.
sub printHtmlHeader {
  print
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"DTD/xhtml1-transitional.dtd\">\n";
  print
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n";
  print "<head>\n";
  print "<title>$title</title>\n";
  print "</head>\n";
  print "<body>\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 =~ "ebn" ) {
    &printEbnHeader();

    # Breadcrumbs
    &printEbnBreadcrumbs( $component, $module, $subdir );

    # Links to other available reports
    if ( $component && $module && $subdir ) {
      &printEbnQualityLinks( $component, $module, $subdir );
    }

    print "<h1>$title</h1>\n";
    print "<p>Checkers Run = $num_checks<br>\n";
    print "Files Processed = $num_files<br>\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 "</p>\n";
    print "<ul>\n";
  } elsif ($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";
  } else {
    if ( $export eq "html" ) {
      &printHtmlHeader();
      print "<h1>$title</h1>\n";
      print "<p>Checkers Run = $num_checks<br>\n";
      print "Files Processed = $num_files<br>\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 "</p>\n";
      print "<ul>\n";
    } else {
      if ( $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";
      }
    }
  }
}

# printEbnFooter function: print the footer string for the EBN export type.
sub printEbnFooter {
  print "\n";
  print "</ul>\n";
  print "</div>\n";
  print "</div>\n";
  print "<div id=\"footer\">\n";
  print "<p>Site content Copyright 2005-2008 by Adriaan de Groot,<br/>\n";
  print "except images as indicated.</p>\n";
  print "</div>\n";
  print "</body>\n";
  print "</html>\n";
}

# printHtmlFooter function: print the footer string for the HTML export type.
sub printHtmlFooter {
  print "</ul>\n";
  print "</body>\n";
  print "</html>\n";
}

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

  if ( $export eq "xml" ) {
    print "  </file-types>\n";
    print "</krazy>\n";
  } elsif ( $export =~ "ebn" ) {
    &printEbnFooter();
  } else {
    if ( $export eq "html" ) {
      &printHtmlFooter();
    } else {
      if ( $export eq "text" ) {
        print "";
      }
    }
  }
}

# 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=\"", &htmlify( $check ), "\">\n";
  } elsif ( $export =~ "ebn" || $export =~ "html" ) {
    # export is "ebn" or "html"
    print "<li>";
    print "<span class=\"toolmsg\">" if ( $export =~ "ebn" );
    print "$check\n<b>$result</b>";
    print "</span>" if ( $export =~ "ebn" );
  }
}

# ftypeDescription function: return the File Type description
sub ftypeDescription {
  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) = &ftypeDescription($pth);
  $desc = "For File Type $ftype" if ( $desc eq "" );
  if ( $export eq "xml" ) {
    print "    <file-type value=\"$ftype\">\n";
  } elsif ( $export !~ "text" ) {
    print "<li><b><u>$desc\n</u></b>\n<ol>\n";
  } else {
    print "==>$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);
  print "\n<ul>\n" if ( $export !~ "text" && $export !~ "xml" );
  for $o ( split( "\n", $_[0] ) ) {
    chomp($o);
    print "<li>" if ( $export !~ "text" && $export ne "xml" );

    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 .= ", " if ( $subissue ne "" );
      my ($issue) = $_[1];
      $issue =~ s+^[Cc]hecks[[:space:]]*++;
      $issue =~ s+^[Cc]heck[[:space:]]*++;
      $issue =~ s+^for[[:space:]]*++;
      $issue =~ s+\.*[[:space:]]*$++g;
      $f     =~ s+^[[:space:]]*++;
      my ($l);

      for $l (@ls) {
        print "$f:$l:$subissue$issue\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+^[[:space:]]*\./++;
        $ls1 =~ s+^[[:space:]]*++;

        # 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 ( $ls2 =~ m/line#/ ) {
          $lines = $ls2;
          $message = $ls2;
        } elsif ( $ls3 =~ m/line#/ ) {
          $lines = $ls3;
          $message = $ls3;
        } else {
          $lines = "";
          $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)
        # $lines = "            <line>$lines<\/line>\n";
        if ($lines =~ /(\d+)\[[\w']*\],?/) {
          $lines =~ s/(\d+)\[([\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";
        }
        if ( $lines ) {
          print "          <issues>\n";
          print $lines;
          print "          </issues>\n";
        }
        print "        </file>\n";
    } else {
      if ( $export =~ "lxr" ) {
        my ($url);
        if ( $component eq "kde-4.x" ) {
          $module =~ s+kdebase-apps+kdebase/apps+;
          $module =~ s+kdebase-runtime+kdebase/runtime+;
          $module =~ s+kdebase-workspace+kdebase/workspace+;
          $url = "http://lxr.kde.org/source/KDE/";
          if ($module) {
            $url .= "$module/";
            $url .= "$subdir/" if ($subdir);
          }
        } elsif ( $component eq "bundled-apps" && $module eq "koffice" ) {
          $url = "http://lxr.kde.org/source/koffice/$subdir/";
        } elsif ( $component eq "bundled-apps" &&
          $module eq "enterprise-kdepim" ) {
          print &htmlify($o);
          print "\n";
          goto here;
        } elsif ( $component eq "kdereview" ) {
          $url = "http://lxr.kde.org/source/kdereview/$subdir/";
        } elsif ( $component eq "kdesupport" ) {
          $url = "http://lxr.kde.org/source/kdesupport/$subdir/";
        } else {
          $url = "http://lxr.kde.org/source/$component/$module/$subdir/";
        }

        my ($f);
        my ( $ls1, $ls2, $ls3 ) = ( "", "", "" );
        ( $f, $ls1, $ls2, $ls3 ) = split( ":", $o );
        $f =~ s+^[[:space:]]*\./++;
        $url .= "$f";
        $ls1 = &lxrLineify( $ls1, $url ) if ( defined($ls1) );
        $ls2 = &lxrLineify( $ls2, $url ) if ( defined($ls2) );
        $ls3 = &lxrLineify( $ls3, $url ) if ( defined($ls3) );
        print "<a href=\"$url\">$f</a>: $ls1";
        print ": $ls2" if ($ls2);
        print ": $ls3" if ($ls3);
        print "\n";
      } elsif ( $export =~ "ebn" || $export =~ "html" ) {
        print &htmlify($o);
        print "\n";
      } else {
        print "$o\n";
      }
    }
  here:
    print "</li>\n" if ( $export !~ "text" && $export ne "xml" );
  }
  print "</ul>\n" if ( $export !~ "text" && $export ne "xml" );
}

# printExplain function: print the explanation lines, according to export type.
sub printExplain {
  if ( $export =~ "ebn" ) {
    print "<p class=\"explanation\">";
    print &htmlify( $_[0] );
    print "</p>\n</li>\n";
  } elsif ( $export eq "html" ) {
      print "<p>";
      print "<p><p><b>Why should I care?</b>";
      print &htmlify( $_[0] );
      print "</p>\n</li>\n";
  } elsif ( $export eq "xml" ) {
    print "        <explanation>", &htmlify( $_[0] ), "</explanation>";
  } else {
    # text export
    print "$_[0]\n";
  }
}

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

  #  $s =~ s=\b\_(\S+)\_\b=<em>$1</em>=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+[[:space:]]*\(\d*\)[[:space:]]*$++;
  $s =~ s+\[+:\[+;
  return split( ",", $s );
}

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

  $s = &htmlify($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 the support file types 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<--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.

=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<--export> <text|textlist|textedit|ebn|ebnlxr|html|htmllxr|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
     ebn -> English Breakfast Network style
     ebnlxr -> English Breakfast Network style, with links to lxr.kde.org
     html -> plain old html
     htmllxr -> plain old html, with links to lxr.kde.org
     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 ebn and html 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<--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 ==
        capfalse: 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++/capfalse 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<capfalse> 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 capfalse --explain

 =>c++/capfalse 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.

=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 (=total issues) if issues are encountered.

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

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

=item 7.
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 8.
I<finally, and importantly, the plugin must eliminate false positives as much as possible.>

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

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

=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>
or B<--list-types> options it will exit with status=0.

=head1 COPYRIGHT

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

krazy2 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 krazy2 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(5), krazy2all(1), krazy2ebn(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
