/*
*  ChkTeX, finds typographic errors in (La)TeX files.
*  Copyright (C) 1995-96 Jens T. Berger Thielemann
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*  Contact the author at:
*              Jens Berger
*              Spektrumvn. 4
*              N-0666 Oslo
*              Norway
*              E-mail: <[email protected]>
*
*
*/


#include "ChkTeX.h"
#ifdef KPATHSEA
#include <kpathsea/getopt.h>
#else
#include <getopt.h>
#endif
#include "OpSys.h"
#include "Utility.h"
#include "FindErrs.h"
#include "Resource.h"
#include <string.h>

#undef MSG
#define MSG(num, type, inuse, ctxt, text) {(enum ErrNum)num, type, inuse, ctxt, text},

struct ErrMsg PrgMsgs[pmMaxFault + 1] = {
   PRGMSGS {(enum ErrNum)pmMaxFault, etErr, TRUE, 0, INTERNFAULT}
};

struct Stack CharStack = {0L};
struct Stack InputStack = {0L};
struct Stack EnvStack = {0L};
struct Stack ConTeXtStack = {0L};
struct Stack FileSuppStack = {0L};
struct Stack UserFileSuppStack = {0L};
struct Stack MathModeStack = {0L};

/************************************************************************/

const char BrOrder[NUMBRACKETS + 1] = "()[]{}";

unsigned long Brackets[NUMBRACKETS];

/************************************************************************/


/*
* Have to do things this way, to ease some checking throughout the
* program.
*/


NEWBUF(TmpBuffer, BUFFER_SIZE);
NEWBUF(ReadBuffer, BUFFER_SIZE);

static const char *Banner =
   "ChkTeX v" PACKAGE_VERSION " - Copyright 1995-96 Jens T. Berger Thielemann.\n"
#ifdef __OS2__
   "OS/2 port generated with emx compiler, by Wolfgang Fritsch, <[email protected]>\n"
#elif defined(__MSDOS__)
   "MS-DOS port by Bj\\o rn Ove Thue, <[email protected]>\n"
#endif
#if HAVE_PCRE
   "Compiled with PCRE regex support."
#else
#if HAVE_POSIX_ERE
   "Compiled with POSIX extended regex support."
#else
   "Compiled with no regex support."
#endif
#endif
   "\n";

static const char *BigBanner =
   "ChkTeX comes with ABSOLUTELY NO WARRANTY; details on this and\n"
   "distribution conditions in the GNU General Public License file.\n"
   "Type \"ChkTeX -h\" for help, \"ChkTeX -i\" for distribution info.\n"
   "Author: Jens Berger.\n"
   "Bug reports: https://savannah.nongnu.org/bugs/?group=chktex\n"
   "             or [email protected]\n"
   "Press " STDIN_BREAK " to terminate stdin input.\n";

static const char *Distrib =
   "\n"
   "This program is free software; you can redistribute it and/or modify\n"
   "it under the terms of the GNU General Public License as published by\n"
   "the Free Software Foundation; either version 2 of the License, or\n"
   "(at your option) any later version.\n"
   "\n"
   "This program is distributed in the hope that it will be useful,\n"
   "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
   "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
   "GNU General Public License for more details.\n"
   "\n"
   "You should have received a copy of the GNU General Public License\n"
   "along with this program; if not, write to the Free Software\n"
   "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n";

static const char *OnText = "On";

static const char *OffText = "Off";

static const char *HowHelp =
   "-h or --help gives usage information. See also ChkTeX.{ps,dvi}.\n";

static const char *HelpText =
   "\n"
   "\n"
   "                         Usage of ChkTeX v" PACKAGE_VERSION "\n"
   "                         ~~~~~~~~~~~~~~~~~~~~~~\n"
   "\n"
   "                               Template\n"
   "                               ~~~~~~~~\n"
   "chktex [-hiqrW] [-v[0-...]] [-l <rcfile>] [-[wemn] <[1-42]|all>]\n"
   "       [-d[0-...]] [-p <name>] [-o <outfile>] [-[btxgI][0|1]]\n"
   "       file1 file2 ...\n"
   "\n"
   "----------------------------------------------------------------------\n"
   "                       Description of options:\n"
   "                       ~~~~~~~~~~~~~~~~~~~~~~~\n"
   "Misc. options\n"
   "~~~~~~~~~~~~~\n"
   "    -h  --help      : This text.\n"
   "    -i  --license   : Show distribution information\n"
   "    -l  --localrc   : Read local .chktexrc formatted file.\n"
   "    -d  --debug     : Debug information. A bit field with 5 bits.\n"
   "                      Each bit shows a different type of information.\n"
   "    -r  --reset     : Reset settings to default.\n"
   "    -S  --set       : Read it's argument as if from chktexrc.\n"
   "                      e.g., -S TabSize=8 will override the TabSize.\n"
   "\n"
   "Muting warning messages:\n"
   "~~~~~~~~~~~~~~~~~~~~~~~~\n"
   "    -w  --warnon    : Makes msg # given a warning and turns it on.\n"
   "    -e  --erroron   : Makes msg # given an error and turns it on.\n"
   "    -m  --msgon     : Makes msg # given a message and turns it on.\n"
   "    -n  --nowarn    : Mutes msg # given.\n"
   "    -L  --nolinesupp: Disables per-line and per-file suppressions.\n"
   "\n"
   "Output control flags:\n"
   "~~~~~~~~~~~~~~~~~~~~~\n"
   "    -v  --verbosity : How errors are displayed.\n"
   "                      Default 1, 0=Less, 2=Fancy, 3=lacheck.\n"
   "    -V  --pipeverb  : How errors are displayed when stdout != tty.\n"
   "                      Defaults to the same as -v.\n"
   "    -s  --splitchar : String used to split fields when doing -v0\n"
   "    -o  --output    : Redirect error report to a file.\n"
   "    -q  --quiet     : Shuts up about version information.\n"
   "    -p  --pseudoname: Input file-name when reporting.\n"
   "    -f  --format    : Format to use for output\n"
   "\n"
   "Boolean switches (1 -> enables / 0 -> disables):\n"
   "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
   "    -b  --backup    : Backup output file.\n"
   "    -x  --wipeverb  : Ignore contents of `\\verb' commands.\n"
   "    -g  --globalrc  : Read global .chktexrc file.\n"
   "    -I  --inputfiles: Execute \\input statements.\n"
   "    -H  --headererr : Show errors found before \\begin{document}\n"
   "\n"
   "Miscellaneous switches:\n"
   "~~~~~~~~~~~~~~~~~~~~~~~\n"
   "    -W  --version   : Version information\n"
   "\n"
   "----------------------------------------------------------------------\n"
   "If no LaTeX files are specified on the command line, we will read from\n"
   "stdin.   For explanation of warning/error messages, please consult the\n"
   "main documentation ChkTeX.dvi, ChkTeX.ps or ChkTeX.pdf:\n"
   "  http://www.nongnu.org/chktex/ChkTeX.pdf\n"
   "\n"
   "Any of the above arguments can be made permanent by setting them in the\n"
   "chktexrc file "
#if defined(__unix__)
   "(~/.chktexrc).\n"
#else
   "(see documentation for location).\n"
#endif
   ;




/*
* Options we will set.
*
*/

enum Quote Quote;

enum CmdSpace CmdSpace;

char VerbNormal[] = "%k %n in %f line %l: %m\n" "%r%s%t\n" "%u\n";

#define DEF(type, name, value)  type name = value
OPTION_DEFAULTS;
STATE_VARS;
#undef DEF
FILE *OutputFile = NULL;

char *PrgName;

int StdInTTY, StdOutTTY;

/*
* End of config params.
*/

static int ParseArgs(int argc, char **argv);
static void ShowIntStatus(void);
static int OpenOut(void);
static int ShiftArg(char **Argument);


/*
* Duplicates all arguments, and appends an asterix to each of them.
*/

static void AddStars(struct WordList *wl)
{
   unsigned long Count, CmdLen;
   char *Data;

   FORWL(Count, *wl)
   {
       Data = wl->Stack.Data[Count];
       CmdLen = strlen(Data);
       if (Data[CmdLen - 1] != '*')
       {
           strcpy(TmpBuffer, Data);
           strcat(TmpBuffer, "*");
           InsertWord(TmpBuffer, wl);
       }
   }
}

/*
* Sets up all the lists.
*
*/

static void SetupLists(void)
{
   unsigned long i;

   AddStars(&VerbEnvir);
   AddStars(&MathEnvir);

   MakeLower(&UserWarnCase);

   ListRep(&WipeArg, ':', 0);
   ListRep(&NoCharNext, ':', 0);

#define ThisItem ((char *) AbbrevCase.Stack.Data[i])

   FORWL(i, AbbrevCase)
   {
       if (isalpha((unsigned char)ThisItem[0]))
       {
           ThisItem[0] = toupper((unsigned char)ThisItem[0]);
           InsertWord(ThisItem, &Abbrev);
           ThisItem[0] = tolower((unsigned char)ThisItem[0]);
       }
       InsertWord(ThisItem, &Abbrev);
   }
}

#define NOCOMMON(a,b) NoCommon(&a,#a,&b,#b)

/*
* Checks that two lists don't have any common element.
*/

static void
NoCommon(struct WordList *a, const char *aName,
        struct WordList *b, const char *bName)
{
   unsigned long i;

   FORWL(i, *a)
   {
       if (HasWord((char *) a->Stack.Data[i], b))
           PrintPrgErr(pmNoCommon, a->Stack.Data[i], aName, bName);
   }
}

/*
* Expands the tabs in a string to regular intervals sized
* TSize.
*/

static void ExpandTabs(char *From, char *To, long TSize, long MaxDiff)
{
   char *Next;
   char *Orig;
   unsigned long Diff;
   static short HasExpandedTooLong = 0;

   Next = From;
   Orig = To;

   while ((Next = strchr(From, '\t')))
   {
       if ((Diff = Next - From))
       {
           strncpy(To, From, Diff);
           To += Diff;
           Diff = TSize - ((To - Orig) % TSize);
       }
       else
           Diff = TSize;

       /* Make sure we don't expand this buffer out of the memory we
        * have allocated for it. */
       if ( Diff > MaxDiff+1 )
       {
           Diff = MaxDiff+1;
           if ( !HasExpandedTooLong )
           {
               PrintPrgErr(pmTabExpands, BUFFER_SIZE);
           }
           HasExpandedTooLong = 1;
       }
       MaxDiff -= (Diff-1);

       memset(To, ' ', Diff);
       To += Diff;

       From = ++Next;
   }
   strcpy(To, From);
}

void ReadRcFiles(void)
{
   unsigned long i;
   while (SetupVars())
   {
       InsertWord(ConfigFile, &ConfigFiles);
   }
   FORWL(i, ConfigFiles)
   {
       ReadRC(ConfigFiles.Stack.Data[i]);
   }
}


int main(int argc, char **argv)
{
   int retval = EXIT_FAILURE, ret, CurArg;
   unsigned long Count;
   int StdInUse = FALSE;
   long Tab = 8;

#ifdef __LOCALIZED

   InitStrings();
#endif

   OutputFile = stdout;
#ifdef KPATHSEA
   kpse_set_program_name(argv[0], "chktex");
   PrgName = kpse_program_name;
#ifdef WIN32
   setmode(fileno(stdout), _O_BINARY);
#endif
#else
   PrgName = argv[0];
#endif

#undef KEY
#undef LCASE
#undef LIST
#undef LNEMPTY
#define KEY(a, def)
#define LCASE(a)
#define LIST(a)
#define LNEMPTY(a) InsertWord("", &a);

   RESOURCE_INFO

   ReadRcFiles();

   if (CmdLine.Stack.Used)
   {
       ParseArgs(CmdLine.Stack.Used, (char **) CmdLine.Stack.Data);
       CmdLine.Stack.Used = 1L;
   }

   if ((CurArg = ParseArgs((unsigned long) argc, argv)))
   {
       retval = EXIT_SUCCESS;
       if (CmdLine.Stack.Used)
       {
           ParseArgs(CmdLine.Stack.Used, (char **) CmdLine.Stack.Data);
           CmdLine.Stack.Used = 1L;
       }

       if (!Quiet || LicenseOnly)
           fprintf(stderr, "%s", Banner);

       if (CurArg == argc)
           UsingStdIn = TRUE;

#if defined(HAVE_FILENO) && defined(HAVE_ISATTY)

       StdInTTY = isatty(fileno(stdin));
       StdOutTTY = isatty(fileno(stdout));
#else

       StdInTTY = StdOutTTY = TRUE;
#endif

       SetupTerm();

       if ((UsingStdIn && StdInTTY && !Quiet) || LicenseOnly)
       {
           fprintf(stderr, "%s", BigBanner);
       }

       if (!StdOutTTY && PipeOutputFormat)
           OutputFormat = PipeOutputFormat;

       if (LicenseOnly)
       {
           fprintf(stderr, "%s", Distrib);
       }
       else
       {
           SetupLists();
           if (QuoteStyle)
           {
               if (!strcasecmp(QuoteStyle, "LOGICAL"))
                   Quote = quLogic;
               else if (!strcasecmp(QuoteStyle, "TRADITIONAL"))
                   Quote = quTrad;
               else
               {
                   PrintPrgErr(pmQuoteStyle, QuoteStyle);
                   Quote = quTrad;
               }
           }
           if (CmdSpaceStyle)
           {
               if (!strcasecmp(CmdSpaceStyle, "IGNORE"))
                   CmdSpace = csIgnore;
               else if (!strcasecmp(CmdSpaceStyle, "INTERWORD"))
                   CmdSpace = csInterWord;
               else if (!strcasecmp(CmdSpaceStyle, "INTERSENTENCE"))
                   CmdSpace = csInterSentence;
               else if (!strcasecmp(CmdSpaceStyle, "BOTH"))
                   CmdSpace = csBoth;
               else
               {
                   PrintPrgErr(pmCmdSpaceStyle, CmdSpaceStyle);
                   CmdSpace = csIgnore;
               }
           }

           if (DebugLevel)
               ShowIntStatus();

           NOCOMMON(Italic, NonItalic);
           NOCOMMON(Italic, ItalCmd);
           NOCOMMON(LowDots, CenterDots);

           if (TabSize && isdigit((unsigned char)*TabSize))
               Tab = strtol(TabSize, NULL, 10);

           if (OpenOut())
           {
               for (;;)
               {
                   for (Count = 0; Count < NUMBRACKETS; Count++)
                       Brackets[Count] = 0L;

#define DEF(type, name, value) name = value
                   STATE_VARS;
#undef DEF
                   if (UsingStdIn)
                   {
                       if (StdInUse)
                           break;
                       else
                       {
                           StdInUse = TRUE;
                           PushFile("stdin", stdin, &InputStack);
                       }
                   }
                   else
                   {
                       if (CurArg <= argc)
                       {
                           const char *filename = NULL;
                           if (CurArg < argc)
                               filename = argv[CurArg++];

                           AddDirectoryFromRelativeFile(filename,&TeXInputs);
                           if (!PushFileName(filename, &InputStack))
                               break;
                       }
                   }

                   if (StkTop(&InputStack) && OutputFile)
                   {
                       while (!ferror(OutputFile)
                              && StkTop(&InputStack)
                              && !ferror(CurStkFile(&InputStack))
                              && FGetsStk(ReadBuffer, BUFFER_SIZE - 1,
                                          &InputStack))
                       {

                           /* Make all spaces ordinary spaces */

                           strrep(ReadBuffer, '\n', ' ');
                           strrep(ReadBuffer, '\r', ' ');
                           ExpandTabs(ReadBuffer, TmpBuffer, Tab,
                                      BUFFER_SIZE - 1 - strlen(ReadBuffer));
                           strcpy(ReadBuffer, TmpBuffer);

                           strcat(ReadBuffer, " ");
                           ret = FindErr(ReadBuffer, CurStkLine(&InputStack));
                           if ( ret != EXIT_SUCCESS ) {
                               retval = ret;
                           }
                       }

                       PrintStatus(CurStkLine(&InputStack));
                   }
               }
           }
       }
   }
   return retval;
}

/*
* Opens the output file handle & possibly renames
*/

static int OpenOut(void)
{
   int Success = TRUE;

   if (*OutputName)
   {
       if (BackupOut && fexists(OutputName))
       {
           strcpy(TmpBuffer, OutputName);
           AddAppendix(TmpBuffer, BAKAPPENDIX);

           if (fexists(TmpBuffer))
               remove(TmpBuffer);

           if (!rename(OutputName, TmpBuffer))
               PrintPrgErr(pmRename, OutputName, TmpBuffer);
           else
           {
               PrintPrgErr(pmRenameErr, OutputName, TmpBuffer);
               Success = FALSE;
           }

       }

       if (Success)
       {
#ifdef KPATHSEA
           if (!(OutputFile = fopen(OutputName, "wb")))
#else
           if (!(OutputFile = fopen(OutputName, "w")))
#endif
           {
               PrintPrgErr(pmOutOpen);
               Success = FALSE;
           }
       }
   }
   else
       OutputFile = stdout;

   return (Success);
}

#ifndef STRIP_DEBUG
static void ShowWL(const char *Name, const struct WordList *wl)
{
   unsigned long i, j, percent;

   fprintf(stderr, "Name: %12s", Name);

   if (DebugLevel & FLG_DbgListInfo)
   {
       fprintf(stderr, ", MaxLen: %3ld, Entries: %3ld, ",
               wl->MaxLen, wl->Stack.Used);

       if (wl->Hash.Index && wl->Stack.Used)
       {
           for (i = j = 0; i < HASH_SIZE; i++)
           {
               if (wl->Hash.Index[i])
                   j++;
           }
           percent = (j * 10000) / wl->Stack.Used;


           fprintf(stderr, "Hash usage: %3ld.%02ld%%",
                   percent / 100, percent % 100);
       }
       else
           fprintf(stderr, "No hash table.");
   }

   fputc('\n', stderr);

   if (DebugLevel & FLG_DbgListCont)
   {
       FORWL(i, *wl) fprintf(stderr, "\t%s\n", (char *) wl->Stack.Data[i]);
   }
}
#endif

#define BOOLDISP(var)           ((var)? OnText : OffText)
#define SHOWSTAT(text, arg)     fprintf(stderr, "\t" text ": %s\n", arg)
#define BOOLSTAT(name, var)     SHOWSTAT(name, BOOLDISP(var))
#define SHOWSTR(text, arg)      fprintf(stderr, "%s:\n\t%s\n", text, arg);


/*
* Prints some of the internal flags; mainly for debugging purposes
*/

static void ShowIntStatus(void)
{
#ifndef STRIP_DEBUG
   unsigned long Cnt;
   const char *String;
   const char *iuStr = "";

   if (DebugLevel & FLG_DbgMsgs)
   {
       fprintf(stderr, "There are %d warnings/error messages available:\n",
               emMaxFault - emMinFault - 1);

       for (Cnt = emMinFault + 1; Cnt < emMaxFault; Cnt++)
       {
           switch (LaTeXMsgs[Cnt].Type)
           {
           case etWarn:
               String = "Warning";
               break;
           case etErr:
               String = "Error";
               break;
           case etMsg:
               String = "Message";
               break;
           default:
               String = "";
               break;
           }

           switch (LaTeXMsgs[Cnt].InUse)
           {
           case iuOK:
               iuStr = "In use";
               break;
           case iuNotUser:
               iuStr = "User muted";
               break;
           case iuNotSys:
               iuStr = "System muted";
               break;
           }

           fprintf(stderr, "Number: %2ld, Type: %s, Status: %s\n"
                   "\tText: %s\n\n",
                   Cnt, String, iuStr, LaTeXMsgs[Cnt].Message);
       }
   }

#undef KEY
#undef LCASE
#undef LNEMPTY
#undef LIST

#define LNEMPTY    LIST
#define LIST(a)    ShowWL(#a, &a);
#define LCASE(a)   LIST(a); LIST(a ## Case);
#define KEY(a,def) SHOWSTR(#a, a);


   if (DebugLevel & (FLG_DbgListInfo | FLG_DbgListCont))
   {
       ShowWL("ConfigFilesRead", &ConfigFiles);
       RESOURCE_INFO
   }

   if (DebugLevel & FLG_DbgOtherInfo)
   {
       SHOWSTR("Outputformat", OutputFormat);

       fprintf(stderr, "Current flags include:\n");

       BOOLSTAT("Read global resource", GlobalRC);
       BOOLSTAT("Wipe verbose commands", WipeVerb);
       BOOLSTAT("Backup outfile", BackupOut);
       BOOLSTAT("Quiet mode", Quiet);
       BOOLSTAT("Show license", LicenseOnly);
       BOOLSTAT("Use stdin", UsingStdIn);
       BOOLSTAT("\\input files", InputFiles);
       BOOLSTAT("Output header errors", HeadErrOut);
       BOOLSTAT("No line suppressions", NoLineSupp);
   }
#endif
}

/*
* Resets all stacks.
*
*/

#undef KEY
#undef LCASE
#undef LNEMPTY
#undef LIST

#define LNEMPTY     LIST
#define LIST(a)     ClearWord(&a);
#define LCASE(a)    LIST(a); LIST(a ## Case);
#define KEY(a,def)  a = def;

static void ResetStacks(void)
{
   RESOURCE_INFO
}

/*
* Resets all flags (not wordlists) to their default values. Sets
* Outputfile to stdout.
*
*/

static void ResetSettings(void)
{

#define DEF(type, name, value)  name = value
   OPTION_DEFAULTS;
#undef DEF
   if (OutputFile != stdout)
   {
       fclose(OutputFile);
       OutputFile = stdout;
   }
}

/*
* Reads a numerical argument from the argument. Supports concatenation
* of arguments (main purpose)
*/

static int ParseNumArg(long *Dest,      /* Where to put the value */
                      long Default,    /* Value to put in if no in argue */
                      char **Argument) /* optarg or similar */
{
   if (Argument && *Argument && isdigit((unsigned char)**Argument))
       *Dest = strtol(*Argument, Argument, 10);
   else
       *Dest = Default;

   return (ShiftArg(Argument));
}

/*
* Same as above; however, will toggle the boolean if user doesn't
* supply value
*/

static int ParseBoolArg(int *Dest,      /* Boolean value */
                       char **Argument)        /* optarg or similar */
{
   long D = *Dest ? 1L : 0L;
   int Retval;

   Retval = ParseNumArg(&D, *Dest ? 0L : 1L, Argument);

   *Dest = D ? TRUE : FALSE;

   return (Retval);
}

/*
* Returns the first character in the string passed, updates the
* string pointer, if the string is non-empty.
*
* 0 if the string is empty.
*/

static int ShiftArg(char **Argument)    /* optarg or similar */
{
   if (Argument && *Argument && **Argument)
       return (*((char *) (*Argument)++));
   else
       return 0;
}

/*
* Parses an argv similar array.
*/

static int ParseArgs(int argc, char **argv)
{
   /* Needed for option parsing. */

   static const struct option long_options[] = {
       {"help", no_argument, 0L, 'h'},
       {"localrc", required_argument, 0L, 'l'},
       {"output", required_argument, 0L, 'o'},
       {"warnon", required_argument, 0L, 'w'},
       {"erroron", required_argument, 0L, 'e'},
       {"msgon", required_argument, 0L, 'm'},
       {"nowarn", required_argument, 0L, 'n'},
       {"nolinesupp", no_argument, 0L, 'L'},
       {"verbosity", optional_argument, 0L, 'v'},
       {"pipeverb", optional_argument, 0L, 'V'},
       {"debug", required_argument, 0L, 'd'},
       {"reset", no_argument, 0L, 'r'},
       {"set", required_argument, 0L, 'S'},
       {"quiet", no_argument, 0L, 'q'},
       {"license", no_argument, 0L, 'i'},
       {"splitchar", required_argument, 0L, 's'},
       {"format", required_argument, 0L, 'f'},
       {"pseudoname", required_argument, 0L, 'p'},

       {"inputfiles", optional_argument, 0L, 'I'},
       {"backup", optional_argument, 0L, 'b'},
       {"globalrc", optional_argument, 0L, 'g'},
       {"wipeverb", optional_argument, 0L, 'x'},
       {"tictoc", optional_argument, 0L, 't'},
       {"headererr", optional_argument, 0L, 'H'},
       {"version", no_argument, 0L, 'W'},

       {0L, 0L, 0L, 0L}
   };

   int option_index = 0L, c, i, nextc, ErrType;

   int Retval = FALSE, InUse;
   int Success, Foo;
   long Err, Verb = 1, PipeVerb = 1;

   enum
   {
       aeNoErr = 0,
       aeArg,                  /* missing/bad required argument */
       aeOpt,                  /* unknown option returned */
       aeHelp,                 /* just a call for help */
       aeMem                   /* no memory */
   } ArgErr = aeNoErr;

   optind = 0;

   while (!ArgErr &&
          ((c = getopt_long((int) argc, argv,
                            "b::d:e:f:g::hH::I::il:m:n:Lo:p:qrs:S:t::v::V::w:Wx::",
                            long_options, &option_index)) != EOF))
   {
       while (c)
       {
           nextc = 0;
           switch (c)
           {
           case 'S':
               ReadRCFromCmdLine(optarg);
               break;

           case 's':
               if (!(Delimit = strdup(optarg)))
               {
                   PrintPrgErr(pmStrDupErr);
                   ArgErr = aeMem;
               }

               break;
           case 'p':
               if (!(PseudoInName = strdup(optarg)))
               {
                   PrintPrgErr(pmStrDupErr);
                   ArgErr = aeMem;
               }

               break;

           case 'd':
#ifdef STRIP_DEBUG

               PrintPrgErr(pmNoDebugFlag);
#else

               nextc = ParseNumArg(&DebugLevel, 0xffff, &optarg);
#endif

               break;
           case 'i':
               LicenseOnly = TRUE;

               nextc = ShiftArg(&optarg);
               break;
           case 'L':
               NoLineSupp = TRUE;

               nextc = ShiftArg(&optarg);
               break;
           case 'q':
               Quiet = TRUE;

               nextc = ShiftArg(&optarg);
               break;

           LOOP(warntype,
           case 'w': ErrType = etWarn; InUse = iuOK; LAST(warntype);
           case 'e': ErrType = etErr; InUse = iuOK; LAST(warntype);
           case 'm': ErrType = etMsg; InUse = iuOK; LAST(warntype);
           case 'n': ErrType = etMsg; InUse = iuNotUser; LAST(warntype);
               )
               if (isdigit((unsigned char)*optarg))
               {
                   nextc = ParseNumArg(&Err, -1, &optarg);
                   if (betw(emMinFault, Err, emMaxFault))
                   {
                       LaTeXMsgs[Err].Type = ErrType;
                       LaTeXMsgs[Err].InUse = InUse;
                   }
                   else
                   {
                       ArgErr = aeOpt;
                       PrintPrgErr(pmWarnNumErr);
                   }
               }
               else if (!strcasecmp(optarg, "all"))
               {
                   for (i = emMinFault + 1; i < emMaxFault; i++)
                   {
                       LaTeXMsgs[i].Type = ErrType;
                       LaTeXMsgs[i].InUse = InUse;
                   }
               }
               else
               {
                   ArgErr = aeOpt;
                   PrintPrgErr(pmWarnNumErr);
               }

               break;

           case 'g':
               nextc = ParseBoolArg(&GlobalRC, &optarg);
               if (!GlobalRC)
               {
                   ResetStacks();
               }
               break;

           case 'r':
               ResetSettings();
               nextc = ShiftArg(&optarg);
               break;

           case 'l':
               if (optarg)
                   ReadRC(optarg);
               break;

           case 'f':
               if (!(OutputFormat = strdup(optarg)))
               {
                   PrintPrgErr(pmStrDupErr);
                   ArgErr = aeMem;
               }
               break;

           case 'v':
               nextc = ParseNumArg(&Verb, 2, &optarg);

               if (Verb < (long) OutFormat.Stack.Used)
                   OutputFormat = strdup(OutFormat.Stack.Data[Verb]);
               else
               {
                   PrintPrgErr(pmVerbLevErr);
                   ArgErr = aeArg;
               }
               break;
           case 'V':
               nextc = ParseNumArg(&PipeVerb, 1, &optarg);

               if (PipeVerb < (long) OutFormat.Stack.Used)
                   PipeOutputFormat = strdup(OutFormat.Stack.Data[PipeVerb]);
               else
               {
                   PrintPrgErr(pmVerbLevErr);
                   ArgErr = aeArg;
               }
               break;

           case 'o':
               if (optarg)
               {
                   if (*OutputName)
                   {
                       PrintPrgErr(pmOutTwice);
                       ArgErr = aeOpt;
                   }
                   else
                   {
                       if (!(OutputName = strdup(optarg)))
                       {
                           PrintPrgErr(pmStrDupErr);
                           ArgErr = aeMem;
                       }
                   }
               }

               break;

           case 't':
               nextc = ParseBoolArg(&Foo, &optarg);
               break;
           case 'x':
               nextc = ParseBoolArg(&WipeVerb, &optarg);
               break;
           case 'b':
               nextc = ParseBoolArg(&BackupOut, &optarg);
               break;
           case 'I':
               nextc = ParseBoolArg(&InputFiles, &optarg);
               break;
           case 'H':
               nextc = ParseBoolArg(&HeadErrOut, &optarg);
               break;
           case 'W':
               printf("%s", Banner);
               exit(EXIT_SUCCESS);
           case '?':
           default:
               fputs(Banner, stderr);
               fputs(HowHelp, stderr);
               exit(EXIT_FAILURE);
               break;
           case 'h':
               ArgErr = aeHelp;
               break;
           }
           c = nextc;
       }
   }

   if ((argc > optind) && !strcmp(argv[optind], "?"))
       ArgErr = aeHelp;

   if (ArgErr)
   {
       fputs(Banner, stderr);
       fputs(BigBanner, stderr);
       fputs(HelpText, stderr);
       Success = FALSE;
   }
   else
       Success = TRUE;

   if (Success)
       Retval = optind;

   return (Retval);
}

/*
* Outputs a program error.
*/


void PrintPrgErr(enum PrgErrNum Error, ...)
{
   const char *Type;
   va_list MsgArgs;

   if (betw(pmMinFault, Error, pmMaxFault))
   {
       switch (PrgMsgs[Error].Type)
       {
       case etWarn:
           Type = "WARNING";
           break;
       case etMsg:
           Type = "NOTE";
           break;
       default:
       case etErr:
           Type = "ERROR";
           break;
       }
       fprintf(stderr, "%s: %s -- ", PrgName, Type);

       va_start(MsgArgs, Error);
       vfprintf(stderr, PrgMsgs[Error].Message, MsgArgs);
       va_end(MsgArgs);
       fputc('\n', stderr);

       if (PrgMsgs[Error].Type == etErr)
           exit(EXIT_FAILURE);
   }
}

void ErrPrintf(const char *fmt, ...)
{
   va_list MsgArgs;

   va_start(MsgArgs, fmt);
   vfprintf(stderr, fmt, MsgArgs);
   va_end(MsgArgs);
}