#!/usr/bin/perl
# ---------------------------------------------------------------------------
# 
# File:    qaw.pl But must be copied and run as qacw.* or qacppw.*
#
# Copyright (c) Programming Research, 2004.
#
# Written: C.Waddingham 09-Mar-03
#
# Version: 2.3.2
#
# Purpose: To act as a wrapper for QAC when used with a Compliance Module
#          such that command-line analysis is invoked by executing this
#          program (QACW.EXE for a PC compliance module, or qacw as
#          a Perl script in Unix).
#          The analysis program (QAC.EXE for PC or qac for Unix) is called
#          to perform main analysis and if no fatal errors occur, then this
#          wrapper calls the post-analysis program (QACUSER.EXE for a PC or,
#          qacuser_mcm (etc) as a Perl script in Unix).
#          ERRDSP and other programs may be invoked by explicit use of
#          the command line options of this program.
#
# Usage:   qaw [ 'qac' | 'qacpp' ]{ <option> } { <filename> } 
#        
#          qacw specific options are:
#      
#          -afe   specify analysis filename externsions.
#          -alias creates an 'environment variable' local to QAW.
#          -canal invokes a Configured Analysis program.
#          -crep  invokes a Configured Report program.
#          -cargs marks the start of compiler options.
#          -dafe  specify display of analysis filename errors.
#          -help  on command line gives help text for all options on the
#                 command line, or every option if there are no options on the
#                 command line.
#          -disp  causes annotated source to be generated to standard output. 
#          -ehtml ensures HTML files are up to date.
#          -etxt  ensures .txt files are up to date.
#          -exec  specifies a post-analysis program.
#                 Syntax is: -exec "<program>#<script>#<parameters>"
#          -fdisp causes annotated source to be generated to <filename>.
#          -glob  specifies a global analysis program.
#                 Syntax is: -glob "<program>#<parameters>#<renderer program>"
#          -logerrs sets error output channel(s).
#          -logmsgs sets message output channel(s).
#          -list  specify a list of files instead of/as well as individual
#                 files on the command line.
#                 Syntax is: -list <filename>
#          -maseq specifies a project wide analyser programs list
#                 Syntax is: -maseq "<program> <parameters>#<program> <parameters># ..."
#          -mode  if specified controls whether all files are analysed, 
#                 only the out of date files are analylsed, or no analysis
#                 takes place (only display options are executed).
#                 Syntax is: -mode ( all | dep | depend | none )
#          -pdsp  if selected invokes prjdsp.
#                 Syntax is: -pdsp <renderer program>
#          -pdspd if selected invokes prjdsp.  Output is specified by env var:
#                 PRQA_DEFAULT_BROWSER.
#          -plog  if selected analysis progress is displayed.
#          -sapf  if selected invokes listing of analysis parameters.
#          -saseq specifies a single file analyser programs list
#                 Syntax is: -saseq "<program> <parameters>#<program> <parameters># ..."
#          -sat   specifies compiler argument translation.
#          -sfba  suppress file based analysis.
#          -stoponfail causes analysis of a file to terminate on error(s).
#          -targs marks the start of translation options.
#          -view  if selected the analysed files are displayed in the 
#                 message browser.
#          -version if selected displays version of this program.
#          -trace if selected the file qacw.trace will be written in the
#                 current directory.
#
# Prerequisites: 
#          QAC/QACPP must use errdsp provided with QAC-5.0 / QAC++1.4.? 
#          onward.  Also need QAC-5.0 / QAC++??? format of message file, ie
#          messages must be rendered by errdsp in one line.
#
# Notes:   This program needs the following script to be executed:
#          For PC systems <QAC/PP Home>\bin\<qac/pp>conf.bat.
#          For Unix systems sourcing <QAC/PP Home>/.profile etc.
#
#          This program assumes the following specification of how QAC parses
#          its command line parameters.
#          Command line arguments are separated by spaces.
#          QAC reads an argument and, if it starts with - (dash), QAC tries
#          to match it against known options.  If the match succeeds, QAC reads
#          the parameters for that option.  
#          Whether the parameter is separated from the option by a space is
#          optional.  Hence an option may be passed as one or two command line
#          arguments - the option may be passed with its parameter in one
#          command line argument, or the option may be passed as one command
#          line argument and its parameter passed in the next command line
#          argument.
#          Examples:
#             qac -optionparameter
#             qac -option parameter
#          are both legal.
#          If the argument has more (space separated) words then it should be
#          passed as:
#             qac -option"arg_word_1 arg_word_2"  or
#             qac -option "arg_word_1 arg_word_2" or
#             qac -option arg_word_1" "arg_word_2 or
#             qac -option arg_""wo"rd_1 "arg"_wor"d_2
#
#          Note that embedded quotes are allowed if escaped with \
#          e.g.
#             qac "-opt\"c:\file path\file\""
#          Is read by QAW to have one parameter i.e.
#             "c:\file path\file"
#             
# History:
# 2.3.2       26-Jan-12 - CR-14397 .prj handling updated for QACPP-3.0.
#             04-Jan-12 - Reverted to GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS()
#                         in LOAD_OPTION_FILE()
# 2.3.1       19-Aug-11 - CR-14138 RUN handling of global options corrected.
#             06-May-11 - WHICH handling of ./ improved.
# 2.3         21-Mar-11 - WHICH handling of relative path argument improved.
#                       - CR-13912: Handle -v after -cargs.
# 2.2.1       25-Aug-10 - GEN_STRING - CR12797. Quote arguments containing single quotes
#                         GEN_FILE - Evaluate relative paths when comparing file paths.
#                         RUN      - Reduce use of errdsp
# 2.2         24-Apr-09 - GEN_PATH - IS_FILE_PATH_ABSOLUTE improved for Windows with Cygwin
#                                     path case.
#                         PARAM4   - CR12977 Windows cannot execute: `"<path>" "<path" -ver`.
#                         RUN      - CR12636. Improve speed of -mode depend checking.
#                         PARSE_CL - CR13174 Edits applied to prevent addition of trailing \ for 
#                                    -fi and -q.
#                         MODE     - CR12636. Improve speed of -mode depend checking.
#                         GEN_TIME - FORMAT_TIME formatting added.
# 2.1.2       22-Sep-08 - CR12970. -pdsp handling corrected.
# 2.1.1       24-Jul-08 - CR12893. Project wide processing inhibited if analysis errors.
# 2.1         22-Apr-08 - CR12691 Enable RP/EV handling.
# 2.0.7       01-Nov-07 - CR12558: Removed reading of qac[pp].cfg when
#                         calling components.
# 2.0.5.3     15-Mar-07 - Bugfixed for faulty multiple re-escaping of quotes.
# 2.0.5.2     22-Feb-07 - \" now allowed in -exec,-glob,-maseq,-saseq.
# 2.0.5.1     24-Oct-06 - Execution of programs uses system() where possible.
# 2.0.5       09-Feb-06 - Incorrect handling of parameter paths with spaces in options
#                         like -maseq, -saseq, -exec, -glob - corrected.
#                         Now can allow relative path in -file arg.
# 2.0.5       15-Now-05 - -cmaf <path> now included in QAC[PP] settings.via
#                         and settings.via used by errdsp, prjdsp, viewer, etc.
# 2.0.4       19-Oct-05 - -sfba option included to suppress file based analysis.
# 2.0.4       19-Oct-05 - -afe, -dafe added to allow any file extension.
# 2.0.4       10-Oct-05 - -cmaf files NOT deleted when -mode none.
# 2.0.4       22-Sep-05 - Ensured QA_BIN version of errdsp, prjdsp and viewer 
#         		  called.
# 2.0.4       19-Sep-05 - SEARCH_OPTION_SET_FOR_LAST_INSTANCE corrected.
# 2.0.4       19-Sep-05 - WHICH modified to handle ./prog
# 2.0.3       23-Aug-05 - Modified to terminate on licence error.
# 2.0.2       05-Jul-05 - File count improved.
# 2.0.1       15-Jun-05 - @INC derivation improved and now no longer requires
#                         configuration.  Require that all .pm files are in same
#                         directory as qaw.pl.        
# 2.0         09-May-05 - Usage condition changed.
#                       - Modified cmaf path handling.
# 2.0         29-Apr-05 - Added command args trace.
# 2.0         24-Apr-05 - -ver and usage reworked.
# 2.0         20-Apr-05 - Added SET_PURE_SETTINGS_VIA.
# 2.0         30-Mar-05 - Baseline for first release.
# 2.0-f       12-Mar-05 - Added -etxt.
# 2.0-e       19-Feb-05 - Handling of multiple -glob fixed.
# 2.0-c       15-Jan-05 - Plog diagnostic messages changed to use user 
#                         -format setting.
#                       - -Disp now runs irrespective of errors (if specified).
# 2.0-b       23-Dec-04 - -crep & -canal implementation completed for Unix.
# 2.0-a       10-Dec-04 - Version incremented as now have separate
#                         parameter files:
#                         1. <full qaw program path>.options
#                         2. <full qaw program path>.options.properties
#                         3. <full qaw program path>.options.help
#                         - File 1. is used by PARSE_CL by this and any other 
#                           program using PARSE_CL.
#                         - File 2. is used by QAW::QPARSE only.
#                         - File 3. is used by QAW::HELP only.
# 1.26        02-Nov-04 - -More restructuring.
#                         -Added -mrtnd, -ehtml, 
# 1.25        11-Aug-04 - -glog removed.  Heavily restructured.
#                         This version used in M2CM-1.0.
# 1.24        08-Jul-04 - -a and -rem added to %cumulatives.
# 1.23        23-Jun-04 - Handling of -file opt in -glob corrected.
# 1.22        14-Jun-04 - TSR 4568 - Handling of -rem and $ improved.
# 1.21        10-May-04 - LOAD_OPTION_FILE corrected for env vars.
#                       - GET_POST_ANALYSIS_DETAILS corrected for -rem"..
#                       - Array and hash parameter passing made via reference.
# 1.20        30-Apr-04 - QUOTE_ARG() and FULL_OPTION() corrected for 
#                         parameters like ...\"
#                         GET_QA_VERSION modified to handle QACPP-2.0.
# 1.19        04-Apr-04 - -mode dep now uses .met file instead of parsing
#                         source file for #include etc.
#                       - .prj spec extended to allow files inside and
#                         outside the prj to be analysed instead of the
#                         files of the project itself.
#                       - wildcard filespecs allowed on command line. 
#                       - INVOKE_PRJDSP improved.  Only recreates .html
#                         files when existing ones are out of date.
#                       - -CMAF option added.
#                       - CLEAN_FILE_NAMES added to tidy file names and
#                         allow for symbolic link expansion when 
#                         -ef+ is used.
# 1.18        01-Apr-04 - Handling for symbolic links added.
#                         NB Handling assumes that QAC writes output files
#                         (.err etc) for the _real_ file and not the link.
#                         This change may have to be reversed as QAC 
#                         behaviour appears to be wrong and does not agree
#                         with gcc etc.  ie QAC should be updated.
#                       - Fixed incorrect comparison of options in
#                         GET_QA_OPTS().
# 1.17        28-Mar-04 Option list extended.
# 1.16        25-Mar-04 - QAW version number added to .opt file.
# 1.15        20-Mar-04 - Modified to handle errdsp program not found.
#                       - .opt file now includes analyser specs TSR4241.
# 1.14        03-Feb-04 Corrected handling of case of -alias.
# 1.13        26-Feb-04 QUOTE_ARG updated to ignore trailing \"
# 1.12        20-Feb-04 - Added -via settings when regenerating .html
#                         for prjdsp.
#                       - Corrected QAW_DIE in packages other than
#                         QAW_MAIN.
# 1.11        31-Jan-04 Change option handling to output space between
#                       flag and option.  e.g -dfred becomes -d fred
# 1.10        25-Jan-04 Correct handling of -spragma option. TSR4130.
# 1.9         24-Nov-03 Enable Post-Analysis default corrected.
#                       Environment variable checking improved.
# 1.8         17-Nov-03 Handling of post-analysis when not-selected, 
#                       corrected.
#                       .html generated for prjdsp.
#                       Handling of unrecognised -rem updated.
#                       Following options added:
#                         -accd -allowconstconvdeduction,
#                         -atncrb -allowtempnonconstrefbind,
#                         -duc99dilt -dontusec99decintlittypes,
#                         -sig -stdisglobal.
# 1.7         11-Nov-03 Handling of analysis errors corrected.
# 1.6         08-Nov-03 -glob, -exec and -rem details written to opt.
# 1.5         03-Oct-03 Handling of error codes reworked.
# 1.4         14-Jul-03 Capability to handle mixed path separators added.
# 1.3_preview 27-Jun-03 Option -il added.
#                       Option -ex added to %cumulatives.
# 1.2 CW      26-Jun-03 Missing GEN:: inserted in front of QUOTE_ARG in
#                       PARSE_ARGS_RECURSIVE.
#                       1.1 edits restructured into sub QUOTE_ARG_SPECIAL.
# 1.1 CW      23-Jun-03 Restructured - Split into PARSE_CL and GEN.
#                       Fixed problem in WRITE_SETTINGS_FILE and
#                       WRITE_FILE_LIST when trailing \
#                       PDSP added to help.
# 1.0 CW      09-Mar-03 Initial version based on qaccm.pl with extensions.
# ---------------------------------------------------------------------------

#== Program starts here - This is main ======================================

   ### QAW VERSION ##########################################################
   #                                                                        #
   my $qaw_version = "2.3.2";                                               #
   #                                                                        #
   ##########################################################################

   $| = 1;

   #-- QAW_USAGE ------------------------------------------------------------
   #
   my $executable_name = $0;
 
   # Get 'STEM_NAME' of executable name
   #
   $executable_name = STEM_NAME( $executable_name );

   my $pre_amble;
   
   if ( ( $executable_name eq "qacw" ) ||
        ( $executable_name eq "qacppw" )
      )
   {
      $pre_amble = "$executable_name { <option> | <filename> }\n";
   }
   else
   {
      $pre_amble = "$executable_name ( qac | qacpp ) { <option> | <filename> }\n";
   }

   my $qaw_usage = 
        "Command Line Interface for QAC/QAC++ version " . 
        "$qaw_version\n" .
        "Copyright Programming Research Ltd 2006\n" .
        "\n" .
        "Usage:\n" .
        $pre_amble .
        "\n" .
        "For brief options, enter $executable_name -h { <option> }\n" .
        "For detailed information, consult ${executable_name}_user_guide HTML manual\n" .
        "For support, please contact support\@programmingresearch.com\n";

   #-- Check for -ver or -version option ------------------------------------
   #
   ARGS:
   foreach my $arg ( @ARGV )
   {
      last ARGS if ( $arg =~ /^-(cargs|targs)$/io );

      if ( $arg =~ /^-(v|ver|version)$/io )
      {
         print "$executable_name version $qaw_version\n";

         exit;
      }
   }

   #-- Check usage ----------------------------------------------------------
   #
   if ( scalar( @ARGV ) == 0 )
   {
      print "$qaw_usage\n";

      exit;
   }

   #-- Get packages ---------------------------------------------------------
   #

   # Use WHICH( $0 ) to find the full path of the $0 file.
   #

   use integer;
   use strict;

   my $this_qaw_program_full_path = WHICH( $0 );

   if ( $this_qaw_program_full_path eq "" )
   {
      die "Program error deriving full program name for \"$0\".\n";
   }

   @INC = ( DIR_NAME( $this_qaw_program_full_path ), @INC );

   require PARAM;
   require PARSE_CL;
   require OPC;
   require MODE;
   require GEN;
   require QPARSE;
   require RUN;
   require HELP;

   #-- Set QAW Version ------------------------------------------------------
   #
   PARAM::SET_QAW_VERSION( $qaw_version );

   #-- Set QAW Usage --------------------------------------------------------
   #
   PARAM::SET_QAW_USAGE( $qaw_usage );

   #-- Must use PARAM::WORKING_ARGV() and not @ARGV -------------------------
   #
   my @argv = PARAM::WORKING_ARGV();

   # Re-escape any quotes as we assume that $argv is from @ARGV 
   # and hence embedded \" character pairs in each element of @ARGV have
   # been replaced with ".
   #
   @argv = ESCAPE_QUOTES( @argv );

   #-- Set program constants ------------------------------------------------
   #
   # $cfg_filename is the name of the configuration file for the tool.
   #  e.g. qac.cfg or qacpp.cfg
   #
   my $cfg_filename = PARAM::CFG_FILENAME();

   #-- Set path for settings.via file (constant)
   #
   PARAM::SET_SETTINGS_VIA_STEM( PARAM::QA_TEMP() . "qaw_anl_settings.via" );
   PARAM::SET_QA_FILELIST_STEM( PARAM::QA_TEMP() . "qaw_filelist" );
   PARAM::SET_PURE_SETTINGS_VIA_STEM( PARAM::QA_TEMP() . "qaw_anl_pure_settings.via" );
   PARAM::SET_PURE_QA_FILELIST_STEM( PARAM::QA_TEMP() . "qaw_pure_filelist" );

   #-- Initialise QPARSE and MODE option settings ---------------------------
   #

   # Ensure following essential parameter files exist in QA_BIN
   #    qaw.options
   #    qaw.options.properties
   #
   if ( ! -e PARAM::OPTIONS_FILE_PATH() )
   {
      GEN::DIE( " Configuration error. \"" . PARAM::OPTIONS_FILE_PATH() . "\" not found" );
   }
   if ( ! -e PARAM::OPTIONS_PROPERTIES_FILE_PATH() )
   {
      GEN::DIE( " Configuration error. \"" . PARAM::OPTIONS_PROPERTIES_FILE_PATH() . "\" not found" );
   }

   PARAM::LOAD_OPTION_PARAMETER_DETAILS( PARAM::OPTIONS_PROPERTIES_FILE_PATH() );

   #-- Check for -help option -----------------------------------------------
   #
   if ( grep( /^-(h|help)$/io, @argv ) > 0 ) 
   {
      HELP::DISPLAY_HELP( $cfg_filename, \@argv );

      exit;
   }

   #-- Initialise trace output ----------------------------------------------
   #
   if ( grep( /^-trace$/io, @argv ) > 0 )
   {
      GEN::OPEN_TRACE( $executable_name );
   }

   #-- Write arguments to trace ---------------------------------------------
   #
   GEN::TRACE( "---------------------------------------------------------------------------\n" );
   GEN::TRACE( GEN::DATE_DD_MMM_YY() . " " . GEN::TIME() . "\n" );
   GEN::TRACE( "$executable_name command line arguments:\n" .
               join( "\n", @ARGV ) . "\n" );
   GEN::TRACE( "---------------------------------------------------------------------------\n\n" );

   #-- Now process command line ---------------------------------------------
   #

   # Initialise PARSE_CL, i.e. supply the name of the qaw.options file.
   # PARSE_CL::INITIALISE accepts a default of "" for this.  However, for
   # QAW we require to find qaw.options in the primary analyser bin
   # directory, alongside qaw.options.properties as loaded by
   # PARAM::LOAD_OPTION_PARAMETER_DETAILS().  PARAM::OPTIONS_FILE_PATH() 
   # provides this.
   #
   PARSE_CL::INITIALISE( PARAM::OPTIONS_FILE_PATH(), [] );

   # Load options in $cfg_filename
   #
   my @cfg_options = RUN::LOAD_CFG_FILE_OPTIONS( $cfg_filename );

# 1-11-07: CR12558 - QAW no longer reads qac[pp].cfg when reading options
# instead it leaves the tools to do this directly.
#   RUN::EXECUTE_ANALYSIS( @cfg_options, @argv );
   RUN::EXECUTE_ANALYSIS( (), @argv );

   #-- Execution complete ---------------------------------------------------
   #
   GEN::TRACE( "Execution complete\n" );

   # If any errors were encountered on running QAC etc, QAW will have failed
   # with an error at that point.  Hence having arrived at this point exit
   # with 0 return code for no errors.
   # However if -trace has been set, then keep the temp directory for
   # debug purposes.  i.e. only delete if no -trace option provided.
   #
   if ( GEN::GET_TRACEFILE() eq "" )
   {
      GEN::DELETE_FILE_OR_DIRECTORY( PARAM::QA_TEMP() );
   }

   GEN::CLOSE_TRACE();

   OPC::CLOSE_LOG_FILES();

   exit 0;

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

sub ESCAPE_QUOTES
{
   # Usage: @argv = ESCAPE_QUOTES( @argv );
   #
   # Re-escape any quotes as we assume that $ar_arg_list is from @ARGV 
   # and hence embedded \" character pairs in each element of @ARGV has
   # been replaced with ".
   #
   # NB Don't re-escape if already escaped with \.  Therefore change
   # all \" to \n before escaping, and then revert.
   #
   my @argv = @_;

   my @argv_res = ();

   foreach my $arg ( @argv )
   {
      my $a = $arg;
      $a =~ s/\"/\\\"/go;

      push @argv_res, $a;
   }

   return @argv_res;
}

#----------------------------------------------------------------------------

#
# Following functions provided here purely and only to provide STEM_NAME
# and WHICH capability needed to derive $executable_name and path.
#

# --------------------------------------------------------------------------

sub PLATFORM_IS_CYGWIN
{
   return $^O eq "cygwin";
}

# --------------------------------------------------------------------------

sub PLATFORM_IS_UNIX
{
   return $^O !~ /^MSWin/o;
}

# --------------------------------------------------------------------------

sub PLATFORM_IS_WINDOWS
{
   return $^O =~ /^MSWin/o;
}

# --------------------------------------------------------------------------

sub GET_PATH_SEPARATOR
{
   # Usage: $path_separator = GET_PATH_SEPARATOR();
   #
   if ( PLATFORM_IS_WINDOWS() )
   {
      # Here when on Windows
      #
      return "\\";
   }
   else
   {
      return "/";
   }
}

# --------------------------------------------------------------------------

sub BASE_NAME
{
   # Usage: $stem_and_extension = BASE_NAME( $path );
   #
   my $path = shift;

   my $separator = GET_PATH_SEPARATOR();

   if ( ( $path eq "" ) or ( $path =~ /$separator$/ ) )
   {
      return "";
   }

   # Remove any leading DOS drive name
   #
   $path =~ s/^[a-z]+://oi;

   # Get all path elements
   #
   my @path_elements = split( /[\\\/]/o, $path );

   if ( $#path_elements >= 0 )
   {
      return $path_elements[ $#path_elements ];
   }
   else
   {
      return "";
   }
}

# --------------------------------------------------------------------------

sub STEM_NAME
{
   # Usage: $stem = STEM_NAME( $path );
   #
   my $base_name = BASE_NAME( shift );

   my $dot_pos = rindex( $base_name, "." );

   if ( $dot_pos == -1 )
   {
      return $base_name;
   }
   elsif ( $dot_pos == 0 )
   {
      return "";
   }
   else
   {
      return substr( $base_name, 0, $dot_pos );
   }
}

# ---------------------------------------------------------------------------

sub DIR_NAME
{
   # Usage: $directory_of_file_path = DIR_NAME( $path );
   #
   my $path = shift;
   
   if ( $path eq "" )
   {
      return "";
   }

   my $n_pos = -1;
   my $keep_value;
   my $separator = GET_PATH_SEPARATOR();
  
   # Allow both Unix and DOS separators
   #
   $path = NORMALISE_PATH_SEPARATORS( $path );

   LOOP:
   for (;;)
   {
      $keep_value = $n_pos;
      $n_pos      = index( $path, $separator, $n_pos + 1 );
      last LOOP if ( $n_pos == -1 );
   }
    
   return substr( $path, 0, $keep_value + 1 );
}

# --------------------------------------------------------------------------

sub NORMALISE_PATH_SEPARATORS
{
   # Usage: $path_normalised = NORMALISE_PATH_SEPARATORS( $path );
   #
   # Swaps all occurences of \ or / for true path separator.
   #
   my $path = shift;
   
   my $separator = GET_PATH_SEPARATOR();

   $path =~ s/\\/$separator/g;
   $path =~ s/\//$separator/g;

   return $path;
}

#----------------------------------------------------------------------------

sub PWD
{
   # Usage: $current_directory_path = GEN::PWD();
   #

   require Cwd;

   my $pwd = Cwd::getcwd();

   if ( PLATFORM_IS_WINDOWS() )
   {
      $pwd =~ s/\//\\/go;
   }

   return $pwd;
}

#----------------------------------------------------------------------------

sub WHICH
{
   # Usage: $full_filename = WHICH( $program_name );
   #
   # Returns absolute path to $program_name or "" if not found.
   #
   # NB this WHICH() is identical to GEN::WHICH() and has the limitation that
   # for Windows, if the supplied file is not an executable file, e.g. fred.txt,
   # then WHICH() will return the first full path of fred.txt in
   # PATH, whether it is executable or not.  It is the responsibility
   # of the user of WHICH() to ensure that a truely executable
   # filename is supplied.
   #
   # For Unix simply uses the 'which' utility.
   # For Windows:
   #    Gets all paths on PATH and for each path:
   #       Looks for $program_name . ( '.exe' | '.com' | '' ).
   #
   # NB For Windows, this sub will return the first file path in PATH
   # that matches $program_name . ( '.exe' | '.com' | '' )
   # This may be a non-executable file.
 
   my $program_name = shift;

   my $separator = GET_PATH_SEPARATOR();

   # Normalise the separators of the supplied path
   #
   $program_name =~ s/[\\\/]/$separator/g;

   if ( $program_name eq "" )
   {
      return "";
   }

   # No need to lookup if $program_name is absolute.
   #
   elsif ( IS_FILE_PATH_ABSOLUTE( $program_name ) )
   {
      if ( PLATFORM_IS_WINDOWS() )
      {
         if ( -e "$program_name.exe" ||
              -e "$program_name.com" ||
              -e $program_name
            )
         {
            return EXECUTABLE_FULL_NAME( $program_name );
         }
         else
         {
            return "";
         }
      }
      else
      {
         if ( -e $program_name )
         {
           return EXECUTABLE_FULL_NAME( $program_name );
         }
         else
         {
           return "";
         }
      }
   }
   # If prog_name is a relative path, then can simply return pwd + path
   #
   elsif ( index( $program_name, $separator )  != -1 )
   {
      if ( PLATFORM_IS_WINDOWS() )
      {
         # Here when on Windows.
         #
         if ( -e "$program_name.exe" ||
              -e "$program_name.com" ||
              -e $program_name
            )
         {
             return EXECUTABLE_FULL_NAME( APPLY_FINAL_SEP( PWD() ) . $program_name );
         }
         else
         {
            # Here when prog_name does not exist hence cannot be a valid path.
            #
            return "";
         }
      }
      else
      {
         if ( -e $program_name )
         {
            return EXECUTABLE_FULL_NAME( APPLY_FINAL_SEP( PWD() ) . $program_name );
         }
         else
         {
            return "";
         }
      }
   }
   elsif ( PLATFORM_IS_UNIX() )
   {
      my $prog      = BASE_NAME( $program_name );
      my $exe_name  = $prog;

      my $path_delimiter = ":";
      my $path_list      = $ENV{ 'PATH' };

      # Don't append PWD() to try to ensure that $program_name specified
      # as ./name can be found, as want to reflect true Unix behaviour.
      #
      # $path_list .= $path_delimiter . PWD();

      my @paths = split( $path_delimiter, $path_list );
  
      foreach my $path ( @paths )
      {
         $path = APPLY_FINAL_SEP( $path );

         my $try_name = $path . $exe_name;

         if ( -f $try_name )
         {
            # Test for ./ path
            #
            if ( $try_name =~ /^\.\//o )
            {
               # Replace ./ with absolute path of current directory.
               #
               $try_name = APPLY_FINAL_SEP( PWD() ) . substr( $try_name, 2 );
            }

            if ( PLATFORM_IS_CYGWIN() )
            {
               return $try_name;
            }
            elsif ( IS_FILE_EXECUTABLE( $try_name ) )
            {
               return $try_name;
            }
         }
      }
   }

   # Here when on Windows.
   #
   else
   {
      my $prog       = BASE_NAME( $program_name );
      my @exe_names = ( "$prog.exe", "$prog.com", $prog );

      my $path_delimiter = ";";
      my $path_list      = $ENV{ 'PATH' };
      $path_list         = PWD() . $path_delimiter . $path_list;

      my @paths = split( $path_delimiter, $path_list );
  
      foreach my $path ( @paths )
      {
         $path = APPLY_FINAL_SEP( $path );

         foreach my $exe_name ( @exe_names )
         {
            my $try_name = $path . $exe_name;

            if ( -f $try_name )
            {
               # Test for .\ path
               #
               if ( $try_name =~ /^\.[\/\\]/o )
               {
                  # Replace .\ with absolute path of current directory.
                  #
                  $try_name = APPLY_FINAL_SEP( PWD() ) . substr( $try_name, 2 );
               }

               return EXECUTABLE_FULL_NAME( $try_name );
            }
         }
      }
   }

   return "";
}

# --------------------------------------------------------------------------

sub IS_FILE_PATH_ABSOLUTE
{
   # Usage: $is_absolute_bool = IS_FILE_PATH_ABSOLUTE( $path );
   #
   # Returns true if path starts with \ or / or c:\
   #
   my $path = shift;

   if ( PLATFORM_IS_WINDOWS() )
   {
      # Does $path start with '\' ?
      #
      if ( substr( $path, 0, 1 ) eq "\\" )
      {
         # Have absolute path
         #
         return 1;   # true
      }
      elsif ( substr( $path, 1, 2 ) eq ":\\" )
      {
         # Have absolute path
         #
	 return 1;   # true
      }
      else
      {
         # Here when path is relative.
         #
         return 0;  # false
      }
   }
   else
   {
      if ( PLATFORM_IS_CYGWIN() )
      {
   	 my $ch2_3 = substr( $path, 1, 2 );
   
   	 if ( ( $ch2_3 eq ":\\" ) ||
   	      ( $ch2_3 eq ":/" )
   	    )
   	 {
   	    # Have absolute path
   	    #
   	    return 1;
   	 }
      }

      # Platform is Unix
      #
      if ( index( "~/", substr( $path, 0, 1 ) ) != -1 )
      {
         # Have absolute path
         #
         return 1;   # true
      }
      else
      {
         # Here when path is relative.
         #
         return 0;  # false
      }
   }
}

# --------------------------------------------------------------------------

sub EXECUTABLE_FULL_NAME
{
   # Usage: $full_program_name = EXECUTABLE_FULL_NAME( $program_name );
   #
   my $program_name = shift;

   my $executable_full_name = "";

   if ( PLATFORM_IS_UNIX() )
   {
      $executable_full_name = $program_name;
   }
   else
   {
      if ( -e "$program_name.exe" )
      {
         $executable_full_name = "$program_name.exe";
      }
      if ( -e "$program_name.com" )
      {
         $executable_full_name = "$program_name.com";
      }
      elsif ( -e $program_name )
      {
         $executable_full_name = $program_name;
      }
   }

   if ( $executable_full_name eq "" )
   {
      return "";
   }

   ### $executable_full_name = GET_FILE_PATH_ABSOLUTE( $executable_full_name );
   ### $executable_full_name = EVALUATE_RELATIVE_PATHS( $executable_full_name );

   return $executable_full_name;
}

#----------------------------------------------------------------------------

sub APPLY_FINAL_SEP
{
   # e.g. $qabin = APPLY_FINAL_SEP( $QACBIN );
   #
   my $path = shift;

   my $path_separator = GET_PATH_SEPARATOR();

   if ( $path !~ /\Q$path_separator\E$/ ) 
   {
      return $path . $path_separator;
   }
   else
   {
      return $path;
   }
}

#----------------------------------------------------------------------------

sub IS_FILE_EXECUTABLE
{
   # Usage: $boolean_executable= IS_FILE_EXECUTABLE( $filename );
   #
   my $filename = shift;

   return ( -x $filename ) || ( -X $filename );
}

# -- eof qaw.pl -------------------------------------------------------------
