// (c) 1993-2007 Electronic Arts Inc.

const char *SPIT_history[] =
{
"------------------------------------------------------------------",
"                                                                  ",
"               GSR - Global Search and Replace v1.90              ",
"                                                                  ",
"                    by Frank G. Barchard, EAC                     ",
"                                                                  ",
"                   Main program - Oct 3, 2005                     ",
"                                                                  ",
"------------------------------------------------------------------",
"                                                                  ",
" Version Date   SE History:                                       ",
" ------- ------ -- --------                                       ",
"    1.00 880208 FB Initial version by Brad Gour                   ",
"    1.10 920810 FB Rebuilt with C7/added $ eoln to search         ",
"    1.11 921027 FB -i replaces -f option                          ",
"    1.12 921215 FB -l remove matching lines entirely/-f firstonly ",
"    1.13 921216 FB default to sr full file                        ",
"    1.14 930214 FB use library wildcard function complex names    ",
"    1.15 930215 FB 2 pass - 1st read only, 2nd replace            ",
"    1.16 930305 FB -n for maximum line instead of just n          ",
"    1.17 930708 FB added all required header files/built as tiny  ",
"    1.18 930810 FB bump max files to 2000/fixed protos            ",
"    1.19 931019 FB SGI version (no wildcard expansion)            ",
"    1.20 931211 FB Rewritten based on txt (SPIT)                  ",
"    1.25 940712 FB -c Case match, -e entire word match, $ removed ",
"    1.26 940714 FB GS variation on GSR/Mac gsr -c/-e working      ",
"    1.27 940719 FB output formatted like grep, flush long lines   ",
"    1.28 940721 FB Dont show files that didnt match for GS        ",
"    1.29 950529 FB -a append following line                       ",
"    1.30 951012 FB gimex fileio                                   ",
"    1.31 951027 FB case match by default                          ",
"    1.32 951103 FB case match use last char is longer than orig   ",
"    1.33 951104 FB -v1 just make list of matching files           ",
"    1.34 951108 FB gs prlong name of function properly             ",
"    1.35 951112 FB -f first only working                          ",
"    1.36 951206 FB -l full line implimented                       ",
"    1.37 961114 FB srline \0 fix,take dest,v4,no doit, quote sr   ",
"    1.38 970218 FB -h0 to not show filenames                      ",
"    1.39 970401 FB dont rename to lower for win95/nt              ",
"    1.40 970709 FB -tolower on entire line found                  ",
"    1.41 970826 FB -h0 also disable search string                 ",
"    1.42 980227 SK response files, and multiple args supported    ",
"    1.43 980305 SK wildcard suppport, changed -r to -x            ",
"    1.44 980309 SK multiple response files, multiple -r -x        ",
"    1.45 980310 SK @ works                                        ",
"    1.46 980311 SK changed output, fixed -l option                ",
"    1.47 980320 SK supports new SPIT_opt, re-entranct SPIT_parse  ",
"    1.48 980402 KO Allows formated time string like strftime()    ",
"    1.49 980407 SK Supports leading and trailing wildcards        ",
"    1.50 980407 SK Fixed problem with NL (changed to 10, 13)      ",
"    1.51 980414 SK Re-implemented -f<n> for first n matches       ",
"    1.52 980414 SK Oops. Didn't know -f was for each file         ",
"    1.53 980417 SK Updated Abstract (tons).  Fixed reparse func   ",
"    1.54 980420 SK Fixed delimiters in search strings. Added -o   ",
"    1.55 980429 SK Fixed problem with -e.  Now it works           ",
"    1.56 980513 SK Fixed a problem with -e (grabbing extra char)  ",
"    1.57 980708 SK Fixed prob where trailing * ate the NL.        ",
"    1.58 980731 SK Added comments option.  Can skip/use comments. ",
"    1.59 980825 SK Fixed bug with nested comments (-m option)     ",
"    1.60 981019 FB -v1 show next to nothing                       ",
"    1.61 981117 FB use stdgimex to do renaming/temp files         ",
"    1.62 990112 FB /s for *                                       ",
"    1.63 990119 FB case sensative and no case match by default    ",
"    1.64 990502 FB -e rewritten                                   ",
"    1.65 990621 FB -r overload, -wild0 to disable wildcards       ",
"    1.66 990702 LC returned -e options; comment out -wild option  ",
"    1.67 990713 FB -wild option back in and documented            ",
"    1.68 990826 FB -wildupper                                     ",
"    1.69 991013 FB 16K maximum line length                        ",
"    1.70 000218 FB -c set -i1 by default                          ",
"    1.71 000313 FB optwrite is 1 to write now                     ",
"    1.72 000317 FB -@ fixed mask and buffer len bugs              ",
"    1.73 000531 FB .xml                                           ",
"    1.74 001003 FB -p for expanding paths                         ",
"    1.75 001112 FB write to temp file for new stdgimex            ",
"    1.76 010510 FB -time                                          ",
"    1.77 010919 FB detect read only file                          ",
"    1.78 010928 FB -opt?                                          ",
"    1.79 010930 FB -wild# to disable just # wildcard              ",
"    1.80 011002 FB -e dont do word test                           ",
"    1.81 011103 FB -wild0,1,2 apply to gsr not spit               ",
"    1.82 020528 FB binary                                         ",
"    1.83 020717 FB use SPIT_tempname                              ",
"    1.84 021113 FB -return to return number of occurances         ",
"    1.85 030401 FB avoid reading off end of file (warning fix)    ",
"    1.86 040925 FB verbosity change. 1=error, 2=list, 3=more      ",
"    1.87 041014 FB -v2 dont output blank line                     ",
"    1.88 050505 FB -namelist                                      ",
"    1.89 050705 FB -return filesfound, lines                      ",
"    1.90 051003 FB increased max line to 64k                      ",
"    1.91 051115 FB -bin to disable cr/lf translation              ",
"                                                                  ",
"------------------------------------------------------------------",
"\0"
};

/****************************************************************/
/*  Spit Setup                                                  */
/****************************************************************/

#if defined(_MSC_VER)
#pragma warning(push,1)
#include <windows.h>
#pragma warning(pop)
#endif

#include "spit.h"
#include "stdgimex.h"
#include "wildmatch.h"
#include "expr.h"
#include "range.h"
#include "ranger.h"

const char TextVersion[]="1.91 (Samba) 2007.12.4";
#define TOOL_VERSION 191
#ifdef GS
#define TOOL_NAME "gs"
#else
#define TOOL_NAME "gsr"
#endif

#define SEARCHMAX 2048
#define SEARCHMAXLINE 8192
#define GSRMAXLINE 65536

int OPTalwaysyes;
int OPTaskme;
int OPTentirewords;
int OPTcasematch;
int OPTfirstn;
int OPTtype; // crlf type. 0=pc,1=mac,2=unix,3=binary
int OPTstats;
int OPTnamelower;
int OPTignorecase;
int OPTfullline;
int OPTfollow;
int OPTheader;
int OPTtolower;
int OPTwild;
int OPTwildupper;
int OPTpath;
int OPTspecialchars;
int OPTcomments;
char OPTonlyrange[256];
int OPTnumsearches = 0;
int donechanges=0;
int in_c_comment=0;
int in_string=0;
int in_cpp_comment=0;
char OPTreturn[SPITMAXLINE];
int OPTnamelist;

int  numberoflines=0;
int  numberseen=0;
int  numberofchanges=0;
int (*StrCmpNC)(char *dest, const char *src, const char *wildin, char *wildout, int *index);

char OPTsearch[SEARCHMAX][SEARCHMAXLINE];
char OPTreplace[SEARCHMAX][SEARCHMAXLINE];

// check if a file exists by attempting to open the file and checking
// success.
int GSR_fileexist(char *nextname)
{
    int ok=0;
    GSTREAM *g;
    g = gopen(nextname);
    if (g)
    {
        ok = 1;
        gclose(g);
    }
    return(ok);
}


#if defined(_MSC_VER)
static void expandtofullpath(char *fullname,const char *pathname)
{
    char name[512];
    char *newname;

    GetFullPathName(pathname,512,name,&newname);
    strcpy(fullname,name);
//    printf("name %s (%s) <- %s\n",name,newname,pathname);

    return;
}
#else
static void expandtofullpath(char *fullname,const char *pathname)
{
    strcpy(fullname,pathname);
    return;
}
#endif

static void replaceSpecialChars(char *str)
{
    char tmp[GSRMAXLINE];
    char *ptr1 = str;
    char *ptr2 = tmp;
    time_t now;

    while (*ptr1 != 0)
    {
        if (*ptr1 == '\\')
        {
            ++ptr1;
            switch (*(ptr1))
            {
                case '\\': *ptr2 = '\\'; break;
                case 'n':
                    switch (OPTtype)
                    {
                        case 0: *ptr2++ = 13; *ptr2 = 10; break;
                        case 1: *ptr2 = 13; break;
                        case 2: *ptr2 = 10; break;
                        case 3: *ptr2 = 10; break;
                    }
                case 'a': *ptr2 = 7; break;
                case 'r': *ptr2 = 13; break;
                case 't': *ptr2 = 9; break;
                case 's': *ptr2 = '*'; break;
                default: --ptr1; *ptr2 = *ptr1;
            }
        }
        else
            *ptr2 = *ptr1;
        ++ptr1;
        ++ptr2;
    }
    *ptr2 = 0;

    /* Parse all time info from the tmp into str */
    now = time(NULL);
    strftime(str, GSRMAXLINE, tmp, localtime(&now));

    return;
}

// test if first or last character is a * wildcard, in which case gsr will parse off words to apply wildcards to

static bool GSR_enablewordwildcards(const char *search)
{
    bool iswild=false;
    if (strlen(search))
    {
        if (search[0]=='*')
            iswild=true;
        else
        {
            search = search+strlen(search)-1;
            if (search[0]=='*')
                iswild=true;
        }
    }
    return(iswild);
}

int srline(char *dest, const char *s, const char *mask, const char *search, char *replace, const char *fname, int linechanged, int showresults)
{
    char tempbuf[GSRMAXLINE];
    char templine[GSRMAXLINE];
    static int linestoshow = 0;
    int strpos;
    int i = 0;
    int j = 0;
    int m;
    bool wordwild=false;

    wordwild = GSR_enablewordwildcards(search);


    *dest = 0;

    /* Loop through and replace where necessary */
    while(s[i] != 0)
    {
        if (search[0] != '*' && search[0] != '?' && search[0] != '#')
        {
            while (((search[0]!=s[i] && search[0]!=(s[i]^' ')) || !mask[i]) && s[i])
                dest[j++] = s[i++];
        }
        if (s[i] == 0)
        {
            dest[j] = 0;
        }
        else
        {
            /* Because of leading or trailing wildcards, only give a string of one word to match for OPTentirewords */
            if (wordwild && OPTentirewords == 1)
            {
                m = i;
                while (isalnum(s[m]) || s[m] == '_')
                    ++m;
                strncpy(templine, &s[i], m-i);
                templine[m-i] = 0;
            }
            else if (wordwild && OPTentirewords == 2)
            {
                m = i;
                while (s[m] != ' ' && s[m] != '\t' && s[m] != 0 && s[m] != 10 && s[m] != 13)
                    ++m;
                strncpy(templine, &s[i], m-i);
                templine[m-i] = 0;
            }
            else
                strcpy(templine, s+i);

            if (StrCmpNC(tempbuf, templine, search, replace, &strpos) && ((numberofchanges < OPTfirstn) || !OPTfirstn))
            {
                int len=0;
                int changes = 0;
                int inmask=1;
                int k;

                /* Check the mask */
                for (k=i; k<i+strpos; ++k)
                    if (!mask[k]) inmask = 0;

                if (inmask)
                {
                    if (OPTspecialchars)
                        replaceSpecialChars(tempbuf);
                    len = strlen(tempbuf);
                    if (OPTentirewords == 1)
                    {
                        if (!i && !(isalnum(s[i+strpos])) && (s[i+strpos] != '_'))
                            ++changes;
                        else if(!(isalnum(s[i-1])) && (s[i-1] != '_') && !(isalnum(s[i+strpos])) && (s[i+strpos] != '_'))
                            ++changes;
                    }
                    else if (OPTentirewords == 2)
                    {
                        if (!i && (s[i+strpos] == ' ' || s[i+strpos] == 0 || s[i+strpos] == '\t' || s[i+strpos] == 10 || s[i+strpos] == 13))
                            ++changes;
                        else if((s[i-1] == ' ' || s[i-1] == '\t') && (s[i+strpos] == ' ' || s[i+strpos] == 0 || s[i+strpos] == '\t' || s[i+strpos] == 10 || s[i+strpos] == 13))
                            ++changes;
                    }
                    else
                        ++changes;
                }
                if (changes)
                {
                    ++numberseen;
                    if (OPTaskme && !OPTalwaysyes)
                    {
                        char c;
                        int len = strlen(s)-1;
                        if (OPTverbose>=2)
                        {
                            printf("Found '%s' in:\n%s\n", search, s);
                            if (s[len] == 10 || s[len] == 13)
                                printf("Replace with '%s'? (y/n/a)", replace);
                            else
                                printf("\nReplace? (y/n/a) ");
                        }
                        c = (char) getchar();
                        while(c != (char)'n' && c != (char)'N' && c != (char)'y' && c != (char)'Y' && c != (char)'a' && c != (char)'A')
                            c = (char)getchar();
                        if (c == (char)'n' || c == (char)'N')
                            changes = 0;
                        else if (c == (char)'y' || c == (char)'Y')
                            changes = 1;
                        else if (c == (char)'a' || c == (char)'A')
                            changes = OPTalwaysyes = 1;
                        puts("");
                    }
                    else if (*OPTonlyrange)
                        changes = RANGE_match(OPTonlyrange, "", numberseen);
                    if (changes)
                    {
                        if (OPTfullline)
                            strcpy(&dest[0], tempbuf);
                        else
                            strcpy(&dest[j], tempbuf);
                        ++linechanged;
                        ++numberofchanges;
                        j += len;
                        i += strpos;
                    }
                    else
                       dest[j++] = s[i++];
                }
                else
                    dest[j++] = s[i++];
            }
            else
                dest[j++] = s[i++];
        }
    }
    dest[j] = 0;

    /* Change to lower case if option set */
    if (linechanged && OPTtolower)
    {
        strlwr(dest);
    }

    /* Print appropriate stats */
    if ((linechanged || linestoshow>0) && showresults)
    {
        if (linechanged)
            linestoshow = OPTfollow;
        if (linestoshow)
            --linestoshow;

        if ((numberofchanges==1) && OPTheader && OPTverbose>=3)
            printf("\n");

        if ((OPTverbose>=3 && showresults) || OPTverbose>=4)
        {
#ifndef GS
            if (OPTverbose>=5)
            {
                printf("\nChange %d %s line %d from/to:\n%s\n%s\n",numberofchanges,fname,numberoflines,s,dest);
            }
            else
#endif
            if (OPTverbose>=4)
            {
#ifdef GS
                printf("\nOccurance %d %s line %d:\n%s\n",numberofchanges,fname,numberoflines,dest);
#else
                printf("\nChange %d %s line %d:\n%s\n",numberofchanges,fname,numberoflines,dest);
#endif
            }
            else
                if (OPTverbose>=3)
                    printf("%s\n", dest);
            if (OPTaskme && !OPTalwaysyes)
                puts("");
        }
    }

    return linechanged;
}

/* OPTcomments = 0  ->  use all */
/* OPTcomments = 1  ->  use no comments */
/* OPTcomments = 2  ->  use only comments */
void GetCommentMask(char *mask, const char *buf)
{
    char lastchar=0;
    int  bufpos=0;

    if (!OPTcomments)
    {
        while(buf[bufpos])
            mask[bufpos++] = 1;
    }
    else
    {
        while (buf[bufpos])
        {
            if (in_c_comment || in_cpp_comment)
                mask[bufpos] = (char)((OPTcomments==1) ? 0 : 1);
            else
                mask[bufpos] = (char)((OPTcomments==1) ? 1 : 0);

            if (buf[bufpos]=='*' && lastchar=='/' && !in_cpp_comment && !in_string)
            {
                ++in_c_comment;
                mask[bufpos-1] = (char)((OPTcomments==1) ? 0 : 1);
                mask[bufpos] =   (char)((OPTcomments==1) ? 0 : 1);
            }
            else if (buf[bufpos]=='/' && lastchar=='/' && !in_c_comment && !in_string)
            {
                in_cpp_comment = 1;
                mask[bufpos-1] = (char)((OPTcomments==1) ? 0 : 1);
                mask[bufpos] =   (char)((OPTcomments==1) ? 0 : 1);
            }
            else if (buf[bufpos]=='/' && lastchar=='*' && !in_cpp_comment && !in_string)
            {
                if (in_c_comment)
                    --in_c_comment;
            }
            else if (buf[bufpos]=='"' && lastchar!='\\' && !in_cpp_comment && !in_c_comment)
            {
                in_string = (in_string) ? 0 : 1;
            }

            lastchar = buf[bufpos++];
        }
    }
    mask[bufpos] = 0;
    in_cpp_comment = 0;

    return;
}


static int bufpos=0;
static char bufc[GSRMAXLINE];
static const char *bufname;

void SPIT_flushc(GSTREAM *out, int endline)
{
    char bufout[GSRMAXLINE];
    int bufoutsize;
    int i;
    int changes = 0;

    bufc[bufpos] = 0;
    memcpy(bufout, bufc,bufpos+1);

    for (i = 0; i < OPTnumsearches; ++i)
    {
        char buftemp[GSRMAXLINE];
        char commentmask[GSRMAXLINE];
        int showchanges;

        /* Generate comment mask here */
        GetCommentMask(commentmask, bufout);

        memcpy(buftemp, bufout, bufpos+1);
        showchanges = (i==(OPTnumsearches-1));
        changes = srline(bufout, buftemp, commentmask, OPTsearch[i], OPTreplace[i],bufname,changes,showchanges);
    }

    bufoutsize = strlen(bufout);

    if (endline)
    {
        switch (OPTtype)
        {
            case 0: /* PC style cr/lf */
                bufout[bufoutsize++] = 13;
                bufout[bufoutsize++] = 10;
                break;
            case 1: /* Mac style cr */
                bufout[bufoutsize++] = 13;
                break;
            case 2:    /* unix,amiga,3do style lf */
            default:   /* binary */
                bufout[bufoutsize++] = 10;
                break;
        }
    }

    if (OPTwrite)
        gwrite(out,bufout,bufoutsize);

    SPIT_flush();
    bufpos = 0;
}

void SPIT_putc(GSTREAM *out,int c)
{
    bufc[bufpos++] = (char) c;
    if (bufpos>(GSRMAXLINE-32))
        SPIT_flushc(out, 0);
}

int gsr(const char *fname,GSTREAM *in,GSTREAM *out)
{
    int lastch=0;
    int ch=0;
    GPOS fpos=0;

    numberoflines=0; // global so srline can show it

    GPOS flen = glen(in);
    bufname = fname;  // pass file name to srline via global
    while (fpos<flen)
    {
        unsigned char buf[4];
        if (!gread(in,buf,1))
            break;

        lastch = ch;
        ch = buf[0];
        ++fpos;

        if (OPTtype==3) // binary
        {
            SPIT_putc(out,ch);
        }
        else
        {
            switch(ch)
            {
                case 13:
                    if (lastch==10)
                    {
                        ch = 0;
                    }
                    else
                    {
                        ++numberoflines;
                        SPIT_flushc(out, 1);
                    }
                    break;

                case 10:
                    if (lastch==13)
                    {
                        ch = 0;
                    }
                    else
                    {
                        ++numberoflines;
                        SPIT_flushc(out, 1);
                    }
                    break;

                default:
                    SPIT_putc(out,ch);
            }
        }
    }
    SPIT_flushc(out, 0);

    return numberoflines;
}


void SPIT_openstr(char *statbuf,char *fname)
{
    statbuf[0] = 0;
    if (OPTverbose)
    {
        if (OPTverbose>=3)
        {
            strcpy(statbuf,"File ");
            statbuf += strlen("File ");
        }

        if (OPTverbose>=4)
            sprintf(statbuf,"%s: ",fname);
        else
        {
            if (OPTverbose>=3)
                sprintf(statbuf,"%s: ", fname);
        }
    }
}

/* Check source argument 0 for -s, -r pairs, or -& response file */
void reparse(char *str)
{
    char *ptr = str;
    int smatch = 0;   /* 1 == seen search, 0 == not seen search */
    int rmatch = 0;   /* 1 == seen replace, 0 == not seen replace */

    while (*ptr == (char)' ')
        ++ptr;
    for (;;)
    {
        /* Search string */
        if (*ptr == (char)'-' && *(ptr+1) == (char)'s')
        {
            if (smatch == 1 && OPTverbose>=3)
                printf(TOOL_NAME " - ERROR: Unmatched search/replace argument: #%i\n", OPTnumsearches);
            ptr = SPIT_getstropt( OPTsearch[OPTnumsearches],ptr+2, "search string", "", SPITYES, SEARCHMAXLINE);
            smatch = 1;
        }
        /* Replace string */
        else if (*ptr == (char)'-' && *(ptr+1) == (char)'r')
        {
            if (rmatch == 1 && OPTverbose>=3)
                printf(TOOL_NAME " - ERROR: Unmatched search/replace argument: #%i\n", OPTnumsearches);
            ptr = SPIT_getstropt( OPTreplace[OPTnumsearches],ptr+2, "replace string", "", SPITYES, SEARCHMAXLINE);
            rmatch = 1;
        }
        /* Response file */
        else if (*ptr == (char)'-' && (*(ptr+1) == (char)'@' || *(ptr+1) == (char)'&'))
        {
            char lim[3];
            char delim[3];
            char fname[GSRMAXLINE];
            char temp[256];
            char *buf1;
            char *buf2;
            char *bufptr;
#ifndef GS
            char last = (char)'r';
#endif
            int len;
            int i = 0;
            GSTREAM *g;

            ptr = SPIT_getstropt( fname,ptr+2, "search/replace file", "", SPITYES, GSRMAXLINE);
            g = gopen(fname);
            if (g)
            {
                len = (int)glen(g);

                /* Setup buffer 2 */
                buf2 = (char *) galloc(len*2 + 1);
                buf2[0] = 0;

                /* Setup buffer 1 - replace tabs and newlines and nulls with spaces */
                buf1 = (char *) galloc(len+1);
                bufptr = buf1;
                gread(g, buf1, len);
                buf1[len] = 0;
                for (i = 0; i < len; ++i)
                    if (buf1[i] == (char)'\t' || buf1[i] == 10 || buf1[i] == 13 || buf1[i] == 0)
                        buf1[i] = (char)' ';

                /* Rip down file */
                while (*bufptr)
                {
                    delim[0] = 0;
                    lim[0] = *bufptr;
                    lim[1] = 0;
                    if (*lim == (char)'"' || *lim == (char)'(' || *lim == (char)'\'' || *lim == (char)'`' || *lim == (char)'[' || *lim == (char)'{')
                    {
                        strcpy(delim, lim);
                        if (*lim == (char)'(') *delim = (char)')';
                        if (*lim == (char)'[') *delim = (char)']';
                        if (*lim == (char)'{') *delim = (char)'}';
                    }
                    else
                        *lim = 0;
                    bufptr = SPIT_getstropt( temp,bufptr, "temp string", "", SPITYES, 256);
                    if (!*delim && temp[0] == (char)'-' && (temp[1] == (char)'s' || temp[1] == (char)'r' || temp[1] == (char)'&' || temp[1] == (char)'@'))
                    {
#ifdef GS
                        if (temp[1] != (char)'r')
                        {
                            strcat(buf2, temp);
                            strcat(buf2, " ");
                        }
#else
                        if (temp[1] != (char)'@' && temp[1] != (char)'&')
                            last = temp[1];
                        strcat(buf2, temp);
                        strcat(buf2, " ");
#endif
                    }
                    else
                    {
#ifdef GS
                        if (delim)
                        {
                            strcat(buf2, "-s");
                            strcat(buf2, lim);
                        }
                        else
                            strcat(buf2, "-s");
#else
                        if (last == (char)'r')
                        {
                            if (delim)
                            {
                                strcat(buf2, "-s");
                                strcat(buf2, lim);
                            }
                            else
                                strcat(buf2, "-s");
                            last = (char)'s';
                        }
                        else
                        {
                            if (delim)
                            {
                                strcat(buf2, "-r");
                                strcat(buf2, lim);
                            }
                            else
                                strcat(buf2, "-r");
                            last = (char)'r';
                        }
#endif
                        strcat(buf2, temp);
                        if (delim)
                        {
                            strcat(buf2, delim);
                            strcat(buf2, " ");
                        }
                        else
                            strcat(buf2, " ");
                    }
                    while(*bufptr == (char)' ')
                        ++bufptr;
                }
                gclose(g);
                gfree(buf1);
                reparse(buf2);
                gfree(buf2);
            }
        }
        else if (*ptr == 0)
        {
#ifndef GS
            if (smatch != 0 || rmatch != 0)
                if (OPTverbose>=2)
                    printf(TOOL_NAME " - ERROR: Unmatched search/replace argument: #%i\n", OPTnumsearches);
#endif
            return;
        }
        else
        {
            if (OPTnumsearches > 0 && OPTverbose>=3)
                printf(TOOL_NAME " - ERROR: Ill formmed search/replace argument.\n");
            return;
        }
#ifdef GS
        if (smatch == 1)
        {
            strcpy(OPTreplace[OPTnumsearches], OPTsearch[OPTnumsearches]);
#else
        if (smatch == 1 && rmatch == 1)
        {
#endif
            smatch = 0;
            rmatch = 0;
            ++OPTnumsearches;
        }
        while (*ptr == (char)' ')
            ++ptr;
    }
}


/*
;
; ABSTRACT
;
; gsr - global search and replace
;
; SUMMARY
;
; gsr -options from to [files]
;
; <html b>Note:<html /b> default options are in bold
;     -s<str>     search string (wildcards permitted)
;     -r<str>     replace string (wildcards permitted)
;     -b0         disable banner
;     -bin        search/replace binary file (aka -t3)
;     -c0         disable case match in replacement (see <html a href="#casematching">Case Matching<html /a> below)
; <html b>   -c1         replacement case will match source case<html /b>
; <html b>   -e0         don't search for entire words<html /b>
;     -e1         search for entire words with C style delimiting (see <html a href="#wordmatching">Word Matching<html /a> below)
;     -e2         search for entire words with white space delimiting
;     -f<n>       find first n matches per file <html b>(n=all)<html /b>
;     -header0    disable header (dont show filename and changes summary)
; <html b>   -header1    enable header (show filename and changes summary)<html /b>
;     -i0         case sensitive search (see <html a href="#casematching">Case Matching<html /a> below)
; <html b>   -i1         case insensitive search<html /b>
; <html b>   -l0         don't replace full line with match<html /b>
;     -l1         replace full line with match
; <html b>   -m0         use comments and source in match<html /b>
;     -m1         don't match any comments
;     -m2         only match in comments
; <html b>   -n0         don't adjust case of input and output filenames<html /b>
;     -n1         make input and output filenames lowercase
;     -o<RANGE>   only make this range of changes per file (eg. -o1,2,3,4..100+=3)
;     -oaskme     ask me what changes to make, only make those I approve
;     -p<n>       print n lines per match <html b>(n=1)<html /b>
;     -r          recurse subdirectories to find files
;     -r%ENV%     traverse environment variable ENV to find files
;     -t0         output uses PC style carriage returns (cr lf) <html b>(defaults to host computer type)<html /b>
;     -t1         output uses Mac style carriage returns (cr)
;     -t2         output uses Unix style carriage returns (lf)
;     -t3         no translation (aka -bin)
;     -tolower    make entire matching line lowercase
; <html b>   -u0         don't replace special characters (see <html a href="#specialcharacters">Special Characters<html /a> below)<html /b>
;     -u1         replace special characters
; <html b>   -z0         don't print statistics<html /b>
;     -v0         no console output
;     -v1         print source file names and errors
; <html b>   -v2         print filenames, errors, and matches<html /b>
;     -v3         additional info
;     -namelist   output filename list of files with search matches and nothing else
;     -wild0      disable wildcard replacements
;     -wild1      * ? wildcards replacements (default)
;     -wild2      full wildcards replacements
;     -wildupper  convert wildcard replacements to uppercase
;     -path       show full pathname for files matching
;     -w0         don't output file
;     -w1         output to file (default)
;     -z1         print statistics
;     -return<expr> return expression (errors)
;              variables available: errors, found
;     -@file      search/replace file (see <html a href="#searchreplacefiles">Search/Replace Files<html /a> below)
;     @file       response file, containing a list of options and files
;     =file       specify a output file ie gsr source=dest
;
; DESCRIPTION
;
; GSR is used to make changes to text files.  It searches for given words
; and replaces them with given replacements, obeying the vast search
; criteria described above.  GSR also converts textfile formats between
; PC, Mac and Unix, as specified with the -t option given above.  Note
; that GSR supports any wildcard strings in its search and replace
; arguments that are supported by the WildMatch module.
;
; GS is the same tool as GSR, but only searches for the specified
; string.  All the same options apply.  No output file is written.
; <html h4><html a name="casematching">CASE MATCHING<html /a><html /h4>    GSR can do smart case matching in its replacement arguments.
; When the -c option is set, GSR capitalizes those letters in the
; replacement string that appear capitalized in the <html i>matched<html /i> search
; string.  If the replacement string is longer than the search
; string, then the remainder of the string is case matched to the
; last matched character.  Note that this option can only be used
; if case insensitivety (-i) is set.  This is because if the
; search case is known, then the destination case is also known.
; If the case sensitivety option is set, then GSR only matches
; those search strings that have matching case in the search
; files.  A valid list of -i and -c combinations is given below:
;
; <html b>Case Sensitivety Verses Case Matching<html /b>
; <html u>Case InSensitivety<html /u>   <html u>Case Matching<html /u>   <html u>Result                        <html /u>
; 0                    0               Case Sensitive   - No Matching
; 0                    1               Case Sensitive   - No Matching
; 1                    0               Case Insensitive - No Matching
; 1                    1               Case Insensitive - Matching
;
; <html b>Example Case Matchings<html /b>
;     GSR -sPeter -rLouise Peter.txt=Louise.txt
;     <html u>-i  <html /u>   <html u>-c  <html /u>   <html u>Line In Peter.txt<html /u>   <html u>Result in Louise.txt<html /u>
;     0      0(1)   '... peter ...'     No Match
;     0      0(1)   '... Peter ...'     '... Louise ...'
;     1      0      '... peTEr ...'     '... Louise ...'
;     1      1      '... peTEr ...'     '... loUIse ...'
;     1      1      '... PeteR ...'     '... LouiSE ...'
; <html h4><html a name="wordmatching">WORD MATCHING<html /a><html /h4>    GSR searches for strings differently based on the value of the
; -e option.  If the -e option is 0 then GSR looks for any match.
; If the -e option is 1 then GSR looks only for matches that are
; entire words delimited by C characters.  If the -e option is
; 2 then GSR looks only for matches that are entire words
; delimited by white space.  The C style delimiting is especially
; useful when applying GSR to source code.
;
; <html b>Delimiting Characters<html /b>
; <html u>C Style Delimiters          <html /u>   <html u>White Space Delimiters      <html /u>
; All characters except          Spaces
; '0' to '9', 'a' to 'z',        Tabs
; 'A' to 'Z', and '_'            Newlines (returns)
;
; <html b>Example Word Matchings<html /b>
;     1) If -e0 is set then -sPeter matches in:
;        "Betchacan'tfindPeterinthissentence."
;     2) If -e1 is set then -sPeter matches in:
;        "TotalAge += (Peter.Age);"
;     3) If -e2 is set then -sPeter matches in:
;        "My friend Peter is tall."
; <html h4><html a name="specialcharacters">SPECIAL CHARACTERS<html /a><html /h4>    The destination string may contain ANSI C character and time codes
; used by strftime().  If the -u option is set then special characters
; are replaced, otherwise all characters are used as is.
;
; <html b>ANSI C Characters<html /b>
; <html u>Converts From<html /u>     <html u>Converts To             <html /u>    <html u>What it is        <html /u>
; \n                (PC=crlf,Mac=cr,Unix=lf)    Newline character
; \\                \                           Backslash
; \a                Beep character              Makes speaker beep
; \t                Tab character               Tab character
; \s                Star (*)                    Star (*)
;
; <html b>STRFTIME Characters<html /b>
; <html u>Conversion Spec<html /u>   <html u>What it is               <html /u>   <html u>Example               <html /u>
; %a                Abbreviated weekday name    "Wed"
; %A                Weekday name                "Wednesday"
; %b                Abbreviated month name      "Mar"
; %B                Month name                  "March"
; %c                Date and time               "Apr 02 05:46:23 1998"
; %d                Day of the month            "01"
; %H                Hour of the 24 hour day     "15"
; %h                Hour of the 12 hour day     "03"
; %j                Day of the year             "204"
; %m                Month of the year           "9"
; %M                Minute after the hour       "23"
; %p                AM or PM                    "AM"
; %s                Second after the hour       "43"
; %U                Week of the year(Sat-Sun)   "33"
; %w                Day of the week(0-6)        "5"
; %x                Date                        "Sep 01 1996"
; %X                Time                        "02:23:41"
; %y                Year of the century         "98"
; %Y                Year                        "1998"
; %Z                Time Zone                   "PDT"
; %%                Percent Character           "%"
;
; Since MSDOS uses % to reference Environment variables you must enter %%
; on the command line to avoid this (This is not required if using a response file).
;
; <html b>Example Special Character String<html /b>
;     -r'Date and %%%%time is \t %%c'  Would produce:
;     Date and %time is      Apr 02 05:46:23 1998
; <html h4><html a name="searchreplacefiles">SEARCH / REPLACE FILES<html /a><html /h4>    GSR accepts a special type of file known as a Search/Replace file.
; To use a Search/Replace file just use the -@ or -& command option.
; These files are just listings of search and replace pairs.  There are
; two different ways that the pairs can be organized.  The first way
; is to have a file organized with alternating search/replace piars:
;     <search> <replace>
;     <search> <replace>
;     <search> <replace>
;     ...
; The second way is to label each search and replace with the propper
; options tags:
;     -s<search> -r<replace>
;     -s<search> -r<replace>
;     -s<search> -r<replace>
;     ...
; You may also call another Search/Replace file from within a Search/
; Replace file by using the -& or -@ option in the script.  Note that
; the same file can be used for both GSR and GS.  GS will just ignore
; the replace strings.
;
; <html b>Example Search/Replace File<html /b>
;     -ssearch1 -rreplace1
;     search2 replace2
;     -&SRFile.txt
;     -ssearch3 -rreplace3
;
;
; RETURNS
;
; error count (0 for success) or value of -return expression if specified
;
; SEE ALSO
;
; grep, gs, xd -t, tabs, type, Spit, WildMatch
;
; SAMPLE OUTPUT
; <html b>Command Line<html /b>
; gsr -@srfile.txt test.txt=out.txt
;
; <html b>Console Output<html /b>
; Replacing 'quick' with 'swift' using case mix and matching
; Replacing 'brown' with 'maroon' using case mix and matching
; Replacing 'dog' with 'platapus' using case mix and matching
; Replacing 'jumps' with 'leaps high' using case mix and matching
; Replacing 'over' with 'above' using case mix and matching
; Replacing 'the' with 'de' using case mix and matching
;
; File test.txt:
; De swift maroon fox leaps high above de lazy platapus.
; De SWIft maROon FOx leapS HIGH above De lazy platapus.
; DE SWIFT MAROON FOX LEAPS HIGH ABOVE DE LAZY PLATAPUS.
;
; <html b>Search/Replace File: srfile.txt<html /b>
; quick swift
; -sbrown -rmaroon
; -rplatapus -sdog
; -&srfile2.txt
; -sthe -rde
;
; <html b>Search/Replace File: srfile2.txt<html /b>
; -sjumps -r"leaps high"
; -rabove -sover
;
; <html b>Input File: test.txt<html /b>
; The quick brown fox jumps over the lazy dog.
; The QUIck brOWn FOx jumpS over ThE lazy dog.
; THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.
;
; <html b>Output File: out.txt<html /b>
; De swift maroon fox leaps high above de lazy platapus.
; De SWIft maROon FOx leapS HIGH above De lazy platapus.
; DE SWIFT MAROON FOX LEAPS HIGH ABOVE DE LAZY PLATAPUS.
;
; END ABSTRACT
;
*/

char *optionhelp[]=
{
    "    -s<str>     search string (wildcards permitted)\n",
    "    -r<str>     replace string (wildcards permitted)\n",
    "    -@file      use a search/replace file\n",
    "    @file       response file, containing a list of options and files\n",
    "    =file       specify a output file (eg. gsr source=dest)\n",
    "    -b0         disable banner\n",
    "    -c0         disable case match in replacement\n"
    "    -c1         replacement case will match source case\n",
    "    -e0         don't search for entire words\n"
    "    -e1         search for entire words with C style delimiting\n"
    "    -e2         search for entire words with white space delimiting\n"
    "    -f<n>       find first n matches per file (default n=all)\n",
    "    -header0    disable header (dont show filename and changes summary)\n",
    "    -header1    enable header (show filename and changes summary)\n",
    "    -i0         case sensitive search (default)\n"
    "    -i1         case insensitive search (aka -i)\n",
    "    -l0         don't replace full line with match\n"
    "    -l1         replace full line with match\n",
    "    -m0         use comments and source in match (default)\n"
    "    -m1         don't match any comments\n"
    "    -m2         only match in comments\n",
    "    -n0         don't adjust case of input and output filenames\n"
    "    -n1         make input and output filenames lowercase\n",
    "    -o<RANGE>   only make this range of changes per file (eg. -o1,2,3..100+=7)\n",
    "    -oaskme     ask me what changes to make, only make those I approve\n",
    "    -p<n>       print n lines per match (default n=1)\n",
    "    -r          recurse subdirectories to find files\n"
    "    -d          same as -r for grep compatibility\n"
    "    -r%ENV%     traverse environment variable ENV to find files\n",
    "    -t0         output uses PC style carriage returns (cr lf)\n"
    "    -t1         output uses Mac style carriage returns (cr)\n"
    "    -t2         output uses Unix style carriage returns (lf)\n",
    "    -tolower    make entire matching line lowercase\n",
    "    -u0         don't replace special characters\n"
    "    -u1         replace special characters\n",
    "    -v0         no console output\n"
    "    -v1         print source file names and errors\n"
    "    -v2         print filenames, errors, and matches\n"
    "    -v3         additional info\n",
    "    -namelist   output filename list of files with search matches and nothing else\n",
    "    -wild0      disable wildcard replacements\n"
    "    -wild1      * ? wildcards replacements (default)\n"
    "    -wild2      full wildcards replacements\n"
    "    -wildupper  # of wildcard replacements to make uppercase\n",
    "    -path0      show simple filename (default)\n",
    "    -path1      show full pathname\n"
    "    -w0         don't output file\n"
    "    -w1         output to file (default)\n",
    "    -z0         don't print statistics\n"
    "    -z1         print statistics\n",
    "    -return<expr> return expression (default errors)\n"
    "                variables available: errors, found\n",
    "    -time       show execution time\n",
    "\n",
    "RANGE SYNTAX:\n"
    "    [[!][<start>][..][<end>][+=<step>][,]]*\n"
    "    ie -f             all frames\n"
    "       -f5            frame 5\n"
    "       -f5..9         frames 5..9 inclusive\n"
    "       -f!10          all frames except\n"
    "       -f5,7,9        frames 5,7 and 9\n"
    "       -f10..98+=2    even frames in range between 10 and 98\n",
    "\n",
    0,
};

/****************************************************************/
/*  SPIT Routines                                               */
/****************************************************************/

void SPIT_usage(const char *prgname, const char *envname, const char *urlname, int utype)
{
    char **hlp;


    if (!OPTnamelist && ((OPTverbose>=2 && utype==2) || OPTverbose>=4))
    {
        printf("%s v%s by FrANK G. Barchard, EAC\n",TOOL_NAME,TextVersion);

        if (utype)
            printf("USAGE: %s -options from to [files]\n", prgname);
    }

    switch (utype)
    {
        case 2:
#ifdef GS
            puts("    -s search                    -return(found) return expression");
#else
            puts("    -s search                    -r replace");
#endif
            puts("    -b show banner               -c case matching in replacement");
            puts("    -e match only entire words   -f find only first n matches");
            puts("    -header show header          -i case insensitive searching");
            puts("    -l replace full lines        -m don't match comments");
            puts("    -n make filenames lowercase  -o find matches in range");
            puts("    -p print n lines per match   -r recurse subdirectories");
            puts("    -t crtype (0 PC 1 Mac 2 Unix)-tolower make match line lc");
            puts("    -u replace special chars     -v verbosity");
            puts("    -w write file                -z print statistics");
            puts("    -wild wildcard disable       -wildupper make * upper");
            puts("    -path show full pathname     -time show execution time");
            puts("    -& use a search/replace file -bin no crlf translation");
            puts("     = output file                @ response file");
          printf("type '%s -h' for more help\n", prgname);
          printf("type '%s -help' for web page (%s)\n", prgname, urlname);
            break;

        case 3:
          printf("VERSION: %s v%s, Spit v%.2f\n",SPIT_MACHINE,TextVersion,(float)SPIT_VERSION/100.0f);
#ifdef GS
            puts("SUMMARY: Search for a string in text files");
#else
            puts("SUMMARY: Search for a string and replace with another in text files");
#endif
          printf("         SPIT environment variable is %s\n",envname);
            puts("");

            hlp = optionhelp;
            while (*hlp)
            {
                printf("%s",*hlp);
                ++hlp;
            }
            break;

         case 4:
            puts("");
            {
                hlp = optionhelp;
                while (*hlp)
                {
                    if (SPIT_strstr(*hlp,envname))
                        printf("%s",*hlp);
                    ++hlp;
                }
                puts("");
            }
            break;

        default:
            break;
    }
}

static int smatch = 0;
static int rmatch = 0;

char *SPIT_opt(const char *optptr)
{
    char str[GSRMAXLINE];
    char *ptr;
    char *d=NULL;

    switch (tolower(*optptr))
    {
        case 'b':
            if (!strnicmp(optptr, "bin", 3))
            {
                OPTtype = 3; // crlf type 3 is binary asis
                d = (char *)optptr+3;
            }
            break;

        case 's':
            smatch = 1;
#ifdef GS
            ptr = SPIT_getstropt( OPTsearch[OPTnumsearches],optptr+1, "search string", "", SPITYES, SEARCHMAXLINE);
            strcpy(OPTreplace[OPTnumsearches], OPTsearch[OPTnumsearches]);
            ++OPTnumsearches;
            return ptr;
#else
            if (smatch == 1 && rmatch == 1)
            {
                smatch = rmatch = 0;
                ++OPTnumsearches;
                return(SPIT_getstropt( OPTsearch[OPTnumsearches-1],optptr+1, "search string", "", SPITYES, SEARCHMAXLINE));
            }
            else
                return(SPIT_getstropt( OPTsearch[OPTnumsearches],optptr+1, "search string", "", SPITYES, SEARCHMAXLINE));
#endif

        /* NOTE -r is for recursion */
        case 'r':
            if (!strnicmp(optptr,"return",6))
            {
                return SPIT_getfield( OPTreturn,optptr+6,1,0);
            }
            else if ((unsigned char)*(optptr+1)<=(unsigned char)' ' || *(optptr+1)=='%')
                return(0);
            // .. fall into 'x' replacement parser

        case 'x':
            rmatch = 1;
#if !defined(GS)
            if (smatch == 1 && rmatch == 1)
            {
                smatch = rmatch = 0;
                ++OPTnumsearches;
                return(SPIT_getstropt( OPTreplace[OPTnumsearches-1],optptr+1, "replace string", "", SPITYES, SEARCHMAXLINE));
            }
            else
                return(SPIT_getstropt( OPTreplace[OPTnumsearches],optptr+1, "replace string", "", SPITYES, SEARCHMAXLINE));
#endif
        case 'c':
        {
            ptr = SPIT_getopt( &OPTcasematch,optptr+1, "case match", 0, 1);
            if (OPTcasematch && !OPTignorecase)
                OPTignorecase = 1;
            return(ptr);
        }
        case 'd': return(SPIT_getopt( &OPTrecurse,optptr+1, "recurse directories", 0, 1));
        case 'e': return(SPIT_getopt( &OPTentirewords,optptr+1, "entire words", 0, 2));
        case 'f': return(SPIT_getopt( &OPTfirstn,optptr+1, "first n matches", 1, 1000));
        case 'i': return(SPIT_getopt( &OPTignorecase,optptr+1, "ignore case", 0, 1));
        case 'z': return(SPIT_getopt( &OPTstats,optptr+1, "stats", 0, 1));
        case 'n':
            if (!strnicmp(optptr, "namelist", 8))
            {
                d = SPIT_getopt( &OPTnamelist,optptr+8, "print name list", 0, 1);
            }
            else
                d = SPIT_getopt( &OPTnamelower,optptr+1, "name to lower case", 0, 1);
            break;

        case 'l': return(SPIT_getopt( &OPTfullline,optptr+1, "replace full line", 0, 1));
        case 'm': return(SPIT_getopt( &OPTcomments,optptr+1, "comments", 0, 2));
        case 'h':
            if (!strnicmp(optptr, "header", 6))
                return(SPIT_getopt( &OPTheader,optptr+6, "header show filename", 0, 1));
            else
                return (char*)0;
        case 'u': return(SPIT_getopt( &OPTspecialchars,optptr+1, "special chars", 0, 1));
        case 't':
            if (!strnicmp(optptr,"time",4))
            {
                return(SPIT_getopt( &OPTtime,optptr+4, "enable timing diagnostics", 0, 1));
            }
            else if (!strnicmp(optptr,"tolower",7))
                return(SPIT_getopt( &OPTtolower,optptr+7, "to lower", 0, 1));
            return(SPIT_getopt( &OPTtype,optptr+1, "cr type", 0, 3));
        case '&':
        case '@':
            optptr = SPIT_getstropt( str,optptr-1, "response file", "", SPITYES, GSRMAXLINE);
            reparse(str);
            return((char *) optptr);
        case 'o':
            ptr = SPIT_getstropt( OPTonlyrange,optptr+1, "only this range", "", SPITYES, GSRMAXLINE);
            if (stricmp(OPTonlyrange, "askme"))
            {
                if (RANGE_check(OPTonlyrange, 0, 10000))
                    return ptr;
                printf("gsr  - improper range syntax <%s>\n", OPTonlyrange);
                return NULL;
            }
            OPTonlyrange[0] = 0;
            OPTaskme = 1;
            return ptr;

        case 'p':
            if (!strnicmp(optptr,"path",4))
            {
                ptr = SPIT_getopt( &OPTpath,optptr+4, "full pathname", 0, 1);
                return(ptr);
            }
            return(SPIT_getopt( &OPTfollow,optptr+1, "print n lines on a match", 0, 1000));

        case 'w':
            if (!strnicmp(optptr,"wildupper",9))
            {
                ptr = SPIT_getopt( &OPTwildupper,optptr+9, "wildcards uppercase", 0, 10);
                return(ptr);
            }
            else if (!strnicmp(optptr,"wild",4))
            {
                ptr = SPIT_getopt( &OPTwild,optptr+4, "wildcards enable", 0, 2);
                return(ptr);
            }
            break;

        default: ;
    }
    return d;
}

void SPIT_reset(void)
{
    int i;

    OPTalwaysyes = 0;
    OPTaskme = 0;
    OPTstats = 0;
#if defined(__WATCOMC__) || defined(_MSC_VER)
    OPTtype = 0;   /* crlf */
#else
#ifdef applec
    OPTtype = 1;   /* cr */
#else
    OPTtype = 2;   /* lf */
#endif
#endif
    OPTcasematch = 0;
    OPTtolower = 0;
    OPTentirewords = 0;
    OPTfirstn = 0;
    OPTnamelower = 0;
    OPTignorecase = 0;
    OPTfullline = 0;
    OPTfollow = 0;
    OPTbanner = 0;
    OPTminargs = 1;
    OPTheader = 1;
    OPTspecialchars = 1;
    OPTcomments = 0;
    OPTwild = 1;
    OPTwildupper = 0;
    OPTpath = 0;
#ifdef GS
    OPTwrite = 0;
#endif
    strcpy(OPTreturn,"");
    OPTnamelist = 0;

    smatch = 0;
    rmatch = 0;
    OPTnumsearches = 0;
    for (i = 0; i < SEARCHMAX; ++i)
    {
        OPTsearch[i][0] = 0;
        OPTreplace[i][0] = 0;
    }
    OPTonlyrange[0] = 0;
    OPTtime = 0;
    return;
}


int main(int argc, char **argv)
{
    int i;
    int j;
    int arg = 0;
    int ok;
    int totalnumberoflines;
    int totalnumberofchanges=0;
    int totalnumberoffileschanged=0;
    int errors=0;
    GSTREAM *infile;
    GSTREAM *outfile;
    char    statbuf[GSRMAXLINE];
    char    *whats;
    int     more;

    double totaltime = SPIT_gettime();
    SPIT_init(SPIT_reset, SPIT_usage, SPIT_opt, SPIT_history);
    do
    {
        WILDcharstar =         '*';
        WILDcharquestion =     '?';
        WILDcharnumber =       '#';
        WILDcharexclamation =  '!';
        WILDcharleftbracket =  '[';
        WILDcharrightbracket = ']';

        more = SPIT_parse(argc, argv, TOOL_NAME, TOOL_VERSION);
        if (!SPIThelpgiven)
        {
            switch (OPTwild)
            {
                case 0:
                    WILDcharstar =         -1;
                    WILDcharquestion =     -1;
                    WILDcharnumber =       -1;
                    WILDcharexclamation =  -1;
                    WILDcharleftbracket =  -1;
                    WILDcharrightbracket = -1;
                    OPTspecialchars = 0;
                    break;

                default:
                case 1:
                    WILDcharstar =         '*';
                    WILDcharquestion =     '?';
                    WILDcharnumber =       -1;
                    WILDcharexclamation =  -1;
                    WILDcharleftbracket =  -1;
                    WILDcharrightbracket = -1;
                    OPTspecialchars = 0;
                    break;

                case 2:
                    WILDcharstar =         '*';
                    WILDcharquestion =     '?';
                    WILDcharnumber =       '#';
                    WILDcharexclamation =  '!';
                    WILDcharleftbracket =  '[';
                    WILDcharrightbracket = ']';
                    break;
            }

            // kludge to avoid spit issue of parsing options twice
            if (OPTnumsearches==2)
            {
                if (!strcmp(OPTsearch[0],OPTsearch[1])
                 && !strcmp(OPTreplace[0],OPTreplace[1]))
                    OPTnumsearches = 1;
            }

            WILDtoupper = OPTwildupper;

            if (OPTignorecase && OPTcasematch)
                StrCmpNC = WILD_nicmatch;
            else if (OPTignorecase && !OPTcasematch)
                StrCmpNC = WILD_nimatch;
            else
                StrCmpNC = WILD_nmatch;

            if (OPTstats && OPTverbose<4)
                OPTverbose = 3;

            whats="matches";
            if (OPTwrite)
                whats="changes";

            if (OPTnumsearches == 0)
            {
                if (OPTsearch[0][0]==0 && arg<SPITargnum)
                {
                    strcpy(OPTsearch[0], SPITsargv[arg]);
                    ++arg;
                }
#ifdef GS
                strcpy(OPTreplace[0], OPTsearch[0]);
#else
                if (OPTreplace[0][0]==0 && arg<SPITargnum)
                {
                    strcpy(OPTreplace[0], SPITsargv[arg]);
                    ++arg;
                }
#endif
                ++OPTnumsearches;
            }

            if (arg<SPITargnum)
            {
                totalnumberoflines = 0;
                totalnumberofchanges = 0;
                totalnumberoffileschanged = 0;

                if (!OPTnamelist && OPTheader)
                {
                    if (OPTverbose>=4)
                    {
                        char Half1[41];
                        char Half2[41];
                        for (i = 0; i < OPTnumsearches; ++i)
                        {
                            if (OPTverbose>=3)
                            {
                                sprintf(Half1, "Searching for: '%s'", OPTsearch[i]);
#ifndef GS
                                sprintf(Half2, "Replacing with: '%s'", OPTreplace[i]);
#endif
                                printf("%-38s %-39s\n", Half1, Half2);
                            }
                        }
                        if (OPTverbose>=3)
                        {
                            if (OPTcasematch)
                                printf("Case mix and match\n\n");
                            else if (OPTignorecase)
                                printf("Case insensitive\n\n");
                            else
                                printf("Case sensitive\n\n");
                            SPIT_flush();
                        }
                    }
                    else if (OPTverbose>=3)
                    {
                        for (i = 0; i < OPTnumsearches; ++i)
                        {
                            if (OPTverbose>=3)
                            {
#ifdef GS
                                printf("Searching for ");
#else
                                printf("Replacing '%s' with ",OPTsearch[i]);
#endif
                                printf("'%s' using case ",OPTreplace[i]);
                                if (OPTcasematch)
                                    printf("mix and matching\n");
                                else if (OPTignorecase)
                                    printf("insensitive\n");
                                else
                                    printf("sensitive\n");
                                SPIT_flush();
                            }
                        }
                        if (OPTverbose>=3)
                            puts("");
                    }
                }

#ifdef GS
                OPTcasematch |= OPTignorecase;
#endif

                if (arg>=SPITargnum)
                {
                    if (OPTverbose>=1)
                        printf(TOOL_NAME "- ERROR.  TOO FEW ARGUMENTS\n");
                    ++errors;
                }

                for (i=arg; i<SPITargnum; ++i)
                {
                    char sourcename[512];
                    numberoflines = 0;
                    numberofchanges = 0;
                    numberseen = 0;
                    donechanges = 0;
                    in_c_comment = 0;
                    in_cpp_comment = 0;
                    in_string = 0;

                    strcpy(sourcename,SPITsargv[i]);
                    if (OPTpath)
                        expandtofullpath(sourcename,SPITsargv[i]);


                    if (OPTnamelower)
                        strlwr(sourcename);

                    SPIT_openstr(statbuf,sourcename);

                    if (OPTverbose>=3 && OPTheader)
                    {
                        int k;
                        printf("%s",statbuf);
                        for (k = strlen(statbuf); k < 80; ++k)
                            printf(" ");
                    }
                    SPIT_flush();

                    infile = gopen(sourcename);
                    if (!infile)
                    {
                        if (OPTverbose>=1)
                            printf(TOOL_NAME " - COULD NOT OPEN FILE FOR READ '%s'\n",sourcename);
                        ++errors;
                    }
                    else
                    {
                        char outname[SPITMAXLINE];

                        ok = 1;
                        if (OPTwrite)
                        {

                            if (OPTnamelower)
                                strlwr(SPITdargv[i]);

                            SPIT_tempname(outname,SPITdargv[i]);
                            remove(outname);

                            outfile = gwopen(outname);
                            if (!outfile)
                            {
                                if (OPTverbose>=1)
                                    printf(TOOL_NAME " - COULD NOT OPEN FILE FOR WRITE '%s'\n",outname);
                                gclose(infile);
                                ++errors;
                                ok = 0;
                            }
                        }
                        else
                            outfile = 0;

                        if (ok)
                        {
                            gsr(sourcename,infile,outfile);
                            totalnumberoflines += numberoflines;
                            totalnumberofchanges += numberofchanges;
                            if (numberofchanges)
                                ++totalnumberoffileschanged;

                            gclose(infile);
                            if (OPTwrite)
                            {
                                gclose(outfile);
#ifdef applec
                                fsetfileinfo(outname,'MPS ','TEXT');
#endif

                                remove(SPITdargv[i]);
                                if (GSR_fileexist(SPITdargv[i]))
                                {
                                    if (OPTverbose>=1)
                                        printf("gsr - COULD NOT REMOVE OLD BACK UP BEFORE OVERWRITING. READ ONLY? '%s'\n",SPITdargv[i]);
                                    ++errors;
                                }

                                rename(outname,SPITdargv[i]);
                            }
#ifdef applec
                            if (OPTverbose>=2)
                                printf("no %s.\n",whats);
#else
                            if (OPTverbose==3 && numberofchanges==0)
                            {
                                int k;

                                k = strlen(statbuf);
                                statbuf[0] = 0;
                                for (j=0; j<k; ++j)
                                    strcat(statbuf,"\r \r");
                                printf("%s",statbuf);
                                SPIT_flush();
                            }
#endif
                            if (OPTnamelist && OPTverbose && numberofchanges)
                            {
                                printf("%s\n",sourcename);
                                SPIT_flush();
                            }
#if defined(GS)
                            else if (OPTverbose==2 && numberofchanges)
                            {
                                printf("%s\n",sourcename);
                                SPIT_flush();
                            }
#endif
                            else if (!OPTnamelist && (OPTverbose>=4) || ((OPTverbose>=4 && totalnumberofchanges)))
                            {   printf("%d lines, %d %s, done.\n",numberoflines,numberofchanges,whats);
                                SPIT_flush();
                            }
                            if (!OPTnamelist && numberofchanges && OPTverbose>=3)
                                puts("");
                        }
                    }
                }
                if (!OPTnamelist && ((OPTverbose>=4 && OPTstats) || ((OPTverbose>=4 && OPTstats && totalnumberofchanges))))
                {   printf("total %d files, %d lines, %d %s\n",SPITargnum,totalnumberoflines,totalnumberofchanges,whats);
                    SPIT_flush();
                }
            }
        }
    } while(more > 0);

#if defined(GS)
    if (!OPTnamelist && OPTverbose>=3 && totalnumberofchanges)
#else
    if (!OPTnamelist && OPTverbose>=2 && totalnumberofchanges)
#endif
    {
           printf(TOOL_NAME " - %d changes in %d files\n",totalnumberofchanges,totalnumberoffileschanged);
           SPIT_flush();
    }

    int returncode = !totalnumberofchanges;
    if (OPTreturn[0])
    {
        EXPR *expr = EXPR_open();
        RANGER *r = RANGER_open(expr,OPTreturn,0.0,1000000.0);  // min/max mostly ignored

        EXPR_set(expr,"lines",totalnumberoflines);
        EXPR_set(expr,"filesfound",totalnumberoffileschanged);
        EXPR_set(expr,"found",totalnumberofchanges);
        EXPR_set(expr,"count",totalnumberofchanges);
        EXPR_set(expr,"errors",errors);

        returncode = (int) RANGER_closest(r, (double) returncode, RANGER_ROUND_CLOSEST);

        EXPR_close(expr);
        RANGER_close(r);
    }

    SPIT_restore();

    totaltime = SPIT_gettime()-totaltime;

    if (OPTtime)
    {
        if (totaltime>=60000.0)
            printf("TIME " TOOL_NAME " totaltime %.2f ms (%.2f seconds) (%.2f minutes)\n",totaltime,totaltime/1000.0,totaltime/60000.0);
        else if (totaltime>=1000.0)
            printf("TIME " TOOL_NAME " totaltime %.2f ms (%.2f seconds)\n",totaltime,totaltime/1000.0);
        else
            printf("TIME " TOOL_NAME " totaltime %.2f ms\n",totaltime);
    }

    return(returncode);
}
