     [file front.txi not included]

Introduction to the CMD function library
****************************************

This document describes a library of data structures and functions developed over the years for programs in the Occasional Publications in Academic Computing series published by SIL International.  (For SIL International, "academic" refers to linguistics, literacy, anthropology, translation, and related fields.)  It is hoped that this documentation will make future maintenance of these programs easier.

This particular function library is used by the PC-Kimmo and PC-PATR programs.  It is quite possible that no other programs will be developed using this style of user interface, since the whole world has been convinced that hand-eye coordination is the most important talent for using a computer, that is, pointing and clicking with a mouse is universally considered better than typing commands.

     [file naming.txi not included]

CMD function library data structures
************************************

This chapter describes the data structure defined for the CMD function library.  The CMD function library also makes heavy use of the NumberedMessage data structure from the OPAC function library. See NumberedMessage (OPAC Function Library Reference Manual).

CmdKeyword
==========

Definition
----------

     #include "cmd.h"

     typedef struct {
         char *      pszKeyword;
         int         iValue;
         int         iFlags;
         } CmdKeyword;

Description
-----------

CmdKeyword is a data structure for building command keyword tables (arrays) for programs that use an interactive user interface based on typing commands.

The fields of the CmdKeyword data structure are as follows:

pszKeyword
     points to a NUL-terminated keyword string.

iValue
     is the value associated with the keyword (a positive integer).

iFlags
     contains special bit flags associated with the keyword:

     iFlags & CMD_INVISIBLE
          the keyword is not shown in help messages

     iFlags & CMD_DISABLED
          the keyword is currently disabled

Source File
-----------

cmd.h

The CMD function library global variables
*****************************************

This chapter gives the proper usage information about each of the global variables found in the CMD function library.  For each global variable that the library provides, this information includes which CMD library functions use it.

bCmdShowWarnings_g
==================

Syntax
------

     #include "cmd.h"

     extern int bCmdShowWarnings_g;

Description
-----------

The CMD function library uses displayNumberedMessage (see displayNumberedMessage (OPAC Function Library Reference Manual)) to display error and warning messages.  bCmdShowWarnings_g determines what to do with messages of type WARNING_MSG.  If bCmdShowWarnings_g is FALSE (zero), then messages are totally ignored unless they are of type ERROR_MSG.  This includes both screen display and writing the message to a log file.

The default value of bCmdShowWarnings_g is TRUE (1).

Example
-------

See Sample Program.

Source File
-----------

cmdmessg.c

Used By
-------

doCmdChdir
doCmdClose
doCmdEdit
doCmdLog
doCmdTake

bCmdSilentMessages_g
====================

Syntax
------

     #include "cmd.h"

     extern int bCmdSilentMessages_g;

Description
-----------

The CMD function library uses displayNumberedMessage (see displayNumberedMessage (OPAC Function Library Reference Manual)) to display error and warning messages.  If bCmdSilentMessages_g is TRUE (nonzero), then messages are not displayed on the screen; they may, however, still be written to a log file.  If bCmdSilentMessages_g is FALSE (zero), then messages are displayed on the screen, and may also be written to a log file.

The default value of bCmdSilentMessages_g is FALSE (0).

Example
-------

See Sample Program.

Source File
-----------

cmdmessg.c

Used By
-------

doCmdChdir
doCmdClose
doCmdEdit
doCmdLog
doCmdTake

pCmdLogFP_g
===========

Syntax
------

     #include "cmd.h"

     extern FILE * pCmdLogFP_g;

Description
-----------

The CMD function library uses displayNumberedMessage (see displayNumberedMessage (OPAC Function Library Reference Manual)) to display error and warning messages.  pCmdLogFP_g points to an optional output log file used to record the messages displayed by displayNumberedMessage.  The application program can use it to record other information as well.

The default value of pCmdLogFP_g is NULL (no log file).

Example
-------

See Sample Program, Example, and Example.

Source File
-----------

cmddata.c

Used By
-------

doCmdTake
doCmdChdir
doCmdEdit
doCmdLog
doCmdClose

pCmdOutputFP_g
==============

Syntax
------

     #include "cmd.h"

     extern FILE * pCmdOutputFP_g;

Description
-----------

pCmdOutputFP_g is used by handleCmdSigint, as a means to safely close an output file when processing is interrupted by the user. It may also be used by the application program to refer to the output file.

The default value of pCmdOutputFP_g is NULL (no output file to close).

Example
-------

See Sample Program.

Source File
-----------

cmddata.c

Used By
-------

handleCmdSigint

pszCmdLogFile_g
===============

Syntax
------

     #include "cmd.h"

     extern char * pszCmdLogFile_g;

Description
-----------

pszCmdLogFile_g records the name of the output log file opened for pCmdLogFP_g.

The default value for pszCmdLogFile_g is NULL (no log file).

Example
-------

See Sample Program, Example, and Example.

Source File
-----------

cmddata.c

Used By
-------

doCmdClose
doCmdLog

pszCmdProgramName_g
===================

Syntax
------

     #include "cmd.h"

     extern char * pszCmdProgramName_g;

Description
-----------

pszCmdProgramName_g points to a form of the application program's name that is suitable for serving as the basis of a filename.

The default value of pszCmdProgramName_g is NULL (no program name).

Example
-------

See Sample Program, and Example.

Source File
-----------

cmddata.c

Used By
-------

doCmdLog
doCmdSystem
doCmdTake

sCmdAmbiguousKeyword_g
======================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdAmbiguousKeyword_g;

Description
-----------

sCmdAmbiguousKeyword_g is defined as follows:

     NumberedMessage sCmdAmbiguousKeyword_g = {
         ERROR_MSG,
         105,
         "Ambiguous keyword in %s command: %s"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdAmbiguous_g
===============

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdAmbiguous_g;

Description
-----------

sCmdAmbiguous_g is defined as follows:

     NumberedMessage sCmdAmbiguous_g = {
         ERROR_MSG,
         101,
         "Ambiguous command: %s"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdBadArgument_g
=================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdBadArgument_g;

Description
-----------

sCmdBadArgument_g is defined as follows:

     NumberedMessage sCmdBadArgument_g = {
         ERROR_MSG,
         107,
         "Invalid argument in %s command: %s"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdBadInputFile_g
==================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdBadInputFile_g;

Description
-----------

sCmdBadInputFile_g is defined as follows:

     NumberedMessage sCmdBadInputFile_g = {
         ERROR_MSG,
         109,
         "Cannot open input file %s in %s command"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

doCmdTake

sCmdBadKeyword_g
================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdBadKeyword_g;

Description
-----------

sCmdBadKeyword_g is defined as follows:

     NumberedMessage sCmdBadKeyword_g = {
         ERROR_MSG,
         106,
         "Invalid keyword in %s command: %s"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdBadOutputFile_g
===================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdBadOutputFile_g;

Description
-----------

sCmdBadOutputFile_g is defined as follows:

     NumberedMessage sCmdBadOutputFile_g = {
         ERROR_MSG,
         110,
         "Cannot open output file %s in %s command"
         };

Example
-------

See Sample Program, and Example.

Source File
-----------

messages.c

Used By
-------

doCmdLog

sCmdClosingLog_g
================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdClosingLog_g;

Description
-----------

sCmdClosingLog_g is defined as follows:

     NumberedMessage sCmdClosingLog_g = {
         WARNING_MSG,
         114,
         "Closing the existing log file %s"
         };

This example is the actual library function source code (at least when this documentation was written).

Example
-------

     #include <stdio.h>
     #include "allocmem.h"
     #include "cmd.h"
     ...
     void doCmdLog(pszFilename_in)
     const char *    pszFilename_in;
     {
     char buffer[80];
     char *  pszFilename;

     sprintf(buffer, "LOG [<file>] (default is %s.log)",
             pszCmdProgramName_g ? pszCmdProgramName_g : "log");
     if (wantCmdHelp(pszFilename_in, buffer))
         return;
     if (pCmdLogFP_g != NULL)
         fclose(pCmdLogFP_g);
     if (pszCmdLogFile_g != NULL)
         {
         displayNumberedMessage(&sCmdClosingLog_g, NULL, NULL,
                           pszCmdLogFile_g);
         freeMemory(pszCmdLogFile_g);
         pszCmdLogFile_g = NULL;
         }
     sprintf(buffer, "%s.log",
             pszCmdProgramName_g ? pszCmdProgramName_g : "log");

     pszFilename = setCmdFilename(pszFilename_in, buffer, ".log");
     pCmdLogFP_g = fopen(pszFilename,"w");
     if (pCmdLogFP_g == NULL)
         {
         displayNumberedMessage(&sCmdBadOutputFile_g, NULL, NULL,
                           pszFilename, "LOG");
         freeMemory(pszFilename);
         }
     else
         pszCmdLogFile_g = pszFilename;
     }

Source File
-----------

messages.c

Used By
-------

doCmdLog

sCmdErrorInTake_g
=================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdErrorInTake_g;

Description
-----------

sCmdErrorInTake_g is defined as follows:

     NumberedMessage sCmdErrorInTake_g = {
         ERROR_MSG,
         112,
         "TAKE file aborted due to invalid command: %s"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdInvalidDirectory_g
======================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdInvalidDirectory_g;

Description
-----------

sCmdInvalidDirectory_g is defined as follows:

     NumberedMessage sCmdInvalidDirectory_g = {
         ERROR_MSG,
         117,
         "Bad pathname for CD command"
         };

Example
-------

This example is simpler than the actual library function source code.

     #include <stdlib.h>
     #include "cmd.h"
     ...
     void doCmdChdir(pszPathname_in)
     const char *    pszPathname_in;
     {
     char            szPathname[1026];
     char *          pszHomeDir;
     const char *    pszDirectory;

     if ((pszPathname_in == NULL) || (*pszPathname_in == NUL))
         {
         /* default to home directory */
         pszHomeDir = getenv("HOME");
         if (pszHomeDir != NULL)
             pszDirectory = pszHomeDir;
         else
             {
             displayNumberedMessage(&sCmdMissingDirectory_g, NULL, NULL);
             return;
             }
         }
     else
         pszDirectory = pszPathname_in;

     if (wantCmdHelp(pszPathname_in, "CD <pathname>   (no default)\n"))
         return;
     if (getcwd(szPathname, 1026) == NULL)
         {
         perror("CD");
         return;
         }
     if (chdir(pszDirectory) != 0)
         {
         chdir(szPathname);
         displayNumberedMessage(&sCmdInvalidDirectory_g, NULL, pszPathname_in);
         return;
         }
     if (getcwd(szPathname, 1026) != NULL)
         fprintf(stderr, "%s\n", szPathname);
     else
         perror("CD");
     }

Source File
-----------

messages.c

Used By
-------

doCmdChdir

sCmdInvalid_g
=============

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdInvalid_g;

Description
-----------

sCmdInvalid_g is defined as follows:

     NumberedMessage sCmdInvalid_g = {
         ERROR_MSG,
         102,
         "Invalid command: %s"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdJmpBuf_g
============

Syntax
------

     #include <setjmp.h>
     #include "cmd.h"

     extern jmp_buf sCmdJmpBuf_g;

Description
-----------

sCmdJmpBuf_g is used by handleCmdSigint to jump out of an undesired situation back to the beginning of the command loop.  It must be filled by setjmp before calling signal with handleCmdSigint.

Example
-------

See Sample Program.

Source File
-----------

cmdsig.c

Used By
-------

handleCmdSigint

sCmdMissingArgument_g
=====================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdMissingArgument_g;

Description
-----------

sCmdMissingArgument_g is defined as follows:

     NumberedMessage sCmdMissingArgument_g = {
         ERROR_MSG,
         104,
         "Missing argument in %s command"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdMissingDirectory_g
======================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdMissingDirectory_g;

Description
-----------

sCmdMissingDirectory_g is defined as follows:

     NumberedMessage sCmdMissingDirectory_g = {
         ERROR_MSG,
         116,
         "Missing pathname for CD command"
         };

Example
-------

See Example.

Source File
-----------

messages.c

Used By
-------

doCmdChdir

sCmdMissingEditFile_g
=====================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdMissingEditFile_g;

Description
-----------

sCmdMissingEditFile_g is defined as follows:

     NumberedMessage sCmdMissingEditFile_g = {
         ERROR_MSG,
         115,
         "Missing filename for EDIT command"
         };

Example
-------

This example is simpler than the actual library function source code.

     #include <stdlib.h>
     #include "cmd.h"
     ...
     void doCmdEdit(pszFilename_in)
     const char *    pszFilename_in;
     {
     char    szEditCommand[200];
     char *  pszEditor;

     if (pszFilename_in == (char *)NULL)
         {
         displayNumberedMessage(&sCmdMissingEditFile_g, NULL, NULL);
         return;
         }
     if (wantCmdHelp(pszFilename_in,
                     "EDIT <file> (no default filename or type)"))
         return;

     pszEditor = getenv("EDITOR");
     if (pszEditor == NULL)
         pszEditor = "emacs";
     sprintf(szEditCommand, "%s %s", pszEditor, pszFilename_in);
     doCmdSystem(szEditCommand);
     }

Source File
-----------

messages.c

Used By
-------

doCmdEdit

sCmdMissingInputFile_g
======================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdMissingInputFile_g;

Description
-----------

sCmdMissingInputFile_g is defined as follows:

     NumberedMessage sCmdMissingInputFile_g = {
         ERROR_MSG,
         108,
         "Missing input file argument in %s command"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdMissingKeyword_g
====================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdMissingKeyword_g;

Description
-----------

sCmdMissingKeyword_g is defined as follows:

     NumberedMessage sCmdMissingKeyword_g = {
         ERROR_MSG,
         103,
         "Missing keyword in %s command"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdMissingOutputFile_g
=======================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdMissingOutputFile_g;

Description
-----------

sCmdMissingOutputFile_g is defined as follows:

     NumberedMessage sCmdMissingOutputFile_g = {
         ERROR_MSG,
         118,
         "Missing output file argument in %s command"
         };

Example
-------

See Sample Program.

Source File
-----------

messages.c

Used By
-------

sCmdNoLogFile_g
===============

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdNoLogFile_g;

Description
-----------

sCmdNoLogFile_g is defined as follows:

     NumberedMessage sCmdNoLogFile_g = {
         ERROR_MSG,
         113,
         "No log file was open"
         };

Example
-------

This example is the actual library function source code (at least when this documentation was written).

     void doCmdClose()
     {
     if ((pCmdLogFP_g == NULL) && (pszCmdLogFile_g == NULL))
         {
         displayNumberedMessage(&sCmdNoLogFile_g, NULL, NULL);
         return;
         }
     if (pCmdLogFP_g != (FILE *)NULL)
         {
         fclose(pCmdLogFP_g);
         pCmdLogFP_g = (FILE *)NULL;
         }
     if (pszCmdLogFile_g != (char *)NULL)
         {
         freeMemory(pszCmdLogFile_g);
         pszCmdLogFile_g = (char *)NULL;
         }
     }

Source File
-----------

messages.c

Used By
-------

doCmdClose

sCmdTooDeepTake_g
=================

Syntax
------

     #include "cmd.h"

     extern NumberedMessage sCmdTooDeepTake_g;

Description
-----------

sCmdTooDeepTake_g is defined as follows:

     NumberedMessage sCmdTooDeepTake_g = {
         ERROR_MSG,
         111,
         "TAKE files nested too deeply"
         };

Example
-------

This example is the actual library function source code (at least when this documentation was written).

     #include <stdio.h>
     #include "opaclib.h"
     #include "cmd.h"
     ...
     typedef struct {
         FILE *      pInputFP;
         char *      pszFilename;
         unsigned    uiLineNumber;
         } CmdTakeFile;
     #define CMD_TAKE_LEVELS 3

     static CmdTakeFile asTake_m[CMD_TAKE_LEVELS];
     static int iTakeLevel_m = -1;
     static char szTakeUsage_m[] =
         "TAKE [<file>] (default name is %s.tak, default type is .tak)";
     ...
     void doCmdTake(char * pszFilename_in)
     {
     char *  pszFile;
     char *  p;
     char    szBuffer[128];

     if (iTakeLevel_m >= CMD_TAKE_LEVELS-1)
         {
         displayNumberedMessage(&sCmdTooDeepTake_g, NULL, NULL);
         return;
         }
     if ((pszCmdProgramName_g != NULL) && (*pszCmdProgramName_g != NUL))
         sprintf(szBuffer, szTakeUsage_m, pszCmdProgramName_g);
     else
         sprintf(szBuffer, szTakeUsage_m, "cmd");
     if (wantCmdHelp(pszFilename_in, szBuffer))
         return;
     if ((pszCmdProgramName_g != NULL) && (*pszCmdProgramName_g != NUL))
         sprintf(szBuffer, "%s.tak", pszCmdProgramName_g);
     else
         strcpy(szBuffer, "cmd.tak");
     pszFile = setCmdFilename(pszFilename_in, szBuffer, ".tak");
     if (iTakeLevel_m > -1)
         {
         p = buildAdjustedFilename(pszFile,
                                   asTake_m[iTakeLevel_m].pszFilename,
                                   NULL);
         freeMemory( pszFile );
         pszFile = p;
         }
     ++iTakeLevel_m;
     asTake_m[iTakeLevel_m].pInputFP = fopen(pszFile, "r");
     if (asTake_m[iTakeLevel_m].pInputFP == NULL)
         {
         displayNumberedMessage(&sCmdBadInputFile_g, NULL, NULL,
                           pszFile, "TAKE");
         --iTakeLevel_m;
         freeMemory(pszFile);
         }
     else
         {
         asTake_m[iTakeLevel_m].uiLineNumber = 1;
         asTake_m[iTakeLevel_m].pszFilename  = pszFile;
         }
     }

Source File
-----------

messages.c

Used By
-------

doCmdTake

The CMD functions
*****************

This chapter gives the proper usage information about each of the functions found in the CMD function library.  The prototypes and type definitions relevent to the use of these functions are all found in the cmd.h header file.

closeCmdTake
============

Syntax
------

     #include "cmd.h"

     int closeCmdTake(void);

Description
-----------

closeCmdTake closes the current ``take'' file, if any are open. If there were two or more ``take'' files open, future calls to getCmdString will read from the parent ``take'' file. Otherwise, this causes getCmdString to read from the keyboard.

closeCmdTake does not have any arguments.

Return Value
------------

the number of (nested) ``take'' files open when closeCmdTake finishes

Example
-------

See Sample Program.

Source File
-----------

cmd.c

doCmdChdir
==========

Syntax
------

     #include "cmd.h"

     void doCmdChdir(const char * pszDirectory_in);

Description
-----------

doCmdChdir executes a ``change directory'' command.  If read from a ``take'' file, it is relative to the location of the take file unless an absolute path is given.

doCmdChdir has one argument:

pszDirectory_in
     points to a pathname for a directory that becomes the current directory.

Return Value
------------

none

Example
-------

See Sample Program, and Example.

Source File
-----------

cmdcd.c

doCmdClose
==========

Syntax
------

     #include "cmd.h"

     void doCmdClose(void);

Description
-----------

doCmdClose closes the currently open log file, clearing the global variables pCmdLogFP_g and pszCmdLogFile_g.  It is designed to perform the operation of an interactive close command.

doCmdClose does not have any arguments.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmdlog.c

doCmdDirectory
==============

Syntax
------

     #include "cmd.h"

     void doCmdDirectory(const char * pszArgument_in);

Description
-----------

doCmdDirectory lists the contents of a directory on the screen. It is designed to to perform an interactive directory <options> command.

doCmdDirectory works by running the operating system's standard directory listing program as a subprocess.  It does not work on the Macintosh.

doCmdDirectory has one argument:

pszArgument_in
     points to an argument string suitable for the standard directory listing program, or is NULL.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmddir.c

doCmdEdit
=========

Syntax
------

     #include "cmd.h"

     void doCmdEdit(const char * pszFilename_in);

Description
-----------

doCmdEdit edits a file.  It is designed to to perform an interactive edit <filename> command.

doCmdEdit works by running a text editor program as a subprocess.  Either the program specified by the EDITOR environment variable or a standard editor is used.  For MS-DOS, this is edit; for Unix, this is emacs; for VMS, this is edt.  doCmdEdit does not work on the Macintosh.

doCmdEdit has one argument:

pszFilename_in
     points to the the pathname of the file to edit.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmdedit.c

doCmdLog
========

Syntax
------

     #include "cmd.h"

     void doCmdLog(const char * pszFilename_in);

Description
-----------

doCmdLog opens a log file, setting the global variables pCmdLogFP_g and pszCmdLogFile_g after automatically closing any previously open log file.  It is designed to perform the operation of an interactive log <filename> command.

doCmdLog has one argument:

pszFilename_in
     points to the pathname of the log file to open.

Return Value
------------

none

Example
-------

See Sample Program, and Example.

Source File
-----------

cmdlog.c

doCmdSystem
===========

Syntax
------

     #include "cmd.h"

     void doCmdSystem(const char * pszCommand_in);

Description
-----------

doCmdSystem executes a system command in a subshell.  It is designed to perform the operation of an interactive system <command line> command.

doCmdSystem works by executing the command as a subprocess using the standard shell.  For MS-DOS, the standard shell is given by either the SHELL or the COMSPEC environment variable (or defaults to command.com).  For Unix, the standard shell is given by the SHELL environment variable (or defaults to /bin/sh).  For VMS, the standard shell is DCL.

doCmdSystem does not work on the Macintosh.

doCmdSystem has one argument:

pszCommand_in
     points to the command string to execute in a subshell, or is NULL. If it is NULL or an empty string, a subshell is spawned.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmdsys.c

doCmdTake
=========

Syntax
------

     #include "cmd.h"

     void doCmdTake(const char * pszFilename_in);

Description
-----------

doCmdTake opens a ``take'' file, unless 3 levels of ``take'' files are already open.  If successful, this causes future calls to getCmdString to read from the ``take'' file.

doCmdTake has one argument:

pszFilename_in
     points to the pathname of the ``take'' file.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmd.c

getCmdString
============

Syntax
------

     #include "cmd.h"

     char * getCmdString(const char *  pszCommand_in,
                         const char *  pszPrompt_in,
                         int           cComment_in);

Description
-----------

getCmdString gets a command from the appropriate place, in this order of priority:

 1. the command argument

 2. a ``take'' established by a previous call to doCmdTake

 3. the keyboard (standard input)

``Take'' files are closed, and the current ``take'' level decremented, if the end of the current ``take'' file is detected.

The arguments to getCmdString are as follows:

pszCommand_in
     points to a command string, or is NULL (the usual case).

pszPrompt_in
     points to a prompt string.

cComment_in
     is the character that starts a comment in a command line, or is NUL ('\0').

Return Value
------------

a pointer to the command string (which may be overwritten by next call to getCmdString)

Example
-------

See Sample Program.

Source File
-----------

cmd.c

getCmdTakeFile
==============

Syntax
------

     #include "cmd.h"

     char * getCmdTakeFile(void);

Description
-----------

getCmdTakeFile gets the name of the current ``take'' file, if any are open.

getCmdTakeFile does not have any arguments.

Return Value
------------

the pathname of the current ``take'' file, or NULL if none are open

Example
-------

See Sample Program.

Source File
-----------

cmd.c

getCmdTakeLevel
===============

Syntax
------

     #include "cmd.h"

     int getCmdTakeLevel(void);

Description
-----------

getCmdTakeLevel gets the number of open ``take'' files.

getCmdTakeLevel does not have any arguments.

Return Value
------------

the number of (nested) ``take'' files open

Example
-------

See Sample Program.

Source File
-----------

cmd.c

handleCmdSigint
===============

Syntax
------

     #include "cmd.h"

     RETSIGTYPE handleCmdSigint(int iSignal_in);

Description
-----------

handleCmdSigint handles SIGINT signals by closing the current output file and jumping to a predefined spot.  It is invoked indirectly after being established by

     signal( SIGINT, handleCmdSigint );

handleCmdSigint uses the global variables pCmdOutputFP_g and sCmdJmpBuf_g, setting the former to NULL after closing the file.

handleCmdSigint has one argument:

iSignal_in
     is the type of signal (should always be SIGINT).

Return Value
------------

none (it does not return)

Example
-------

See Sample Program.

Source File
-----------

cmdsig.c

lookupCmdKeyword
================

Syntax
------

     #include "cmd.h"

     int lookupCmdKeyword(
         char *             pszCommand_in,
         const CmdKeyword * pKeywordTable_in,
         int                iTableSize_in,
         char *             pszHelp_in);

Description
-----------

lookupCmdKeyword searches the given table of command keywords for the given command.  The keyword table must be arranged in ascending alphabetical order, and all letters must be lowercase.

A match is successful if the target matches a keyword exactly, or if the target is a prefix of exactly one keyword.  It is ambiguous if the target matches two or more keywords from the table unless it matches one exactly.

The arguments to lookupCmdKeyword are as follows:

pszCommand_in
     points to a command keyword (that may be modified by strlwr).

pKeywordTable_in
     points to an array of CmdKeyword data structures.

iTableSize_in
     is the size of the array.

pszHelp_in
     points to a help message string for this set of commands (if NULL, no help at all is displayed), displayed following the list of keywords.

Return Value
------------

the keyword's associated value (iValue) if found, or

CMD_NULL
     if the input command is empty or NULL

CMD_AMBIGUOUS
     if the input command is ambiguous with respect to the input keyword table

CMD_INVALID
     if the input command is not found in the input keyword table

CMD_HELP
     if ? is found in the input command (the keyword list is displayed automatically)

Example
-------

See Sample Program.

Source File
-----------

cmd.c

setCmdFilename
==============

Syntax
------

     #include "cmd.h"

     char * setCmdFilename(const char * pszFilename_in,
                           const char * pszDefaultName_in,
                           const char * pszExtension_in);

Description
-----------

setCmdFilename sets a filename using one provided by the user, appending the default extension if the provided filename lacks an extension, or using a default filename.

The arguments to setCmdFilename are as follows:

pszFilename_in
     points to the filename given by the user, or is NULL.

pszDefaultName_in
     points to the default full filename, or is NULL.

pszExtension_in
     points to the default filename extension, including the leading period (.), or is NULL.

Return Value
------------

a dynamically allocated filename string, or NULL if pszFilename_in and pszDefaultName_in are both NULL

Example
-------

See Sample Program.

Source File
-----------

setcmdfi.c

showCmdTiming
=============

Syntax
------

     #include "cmd.h"

     void showCmdTiming(void );

Description
-----------

showCmdTiming displays the elapsed time for an operation, using the internal values set by startCmdTiming and stopCmdTiming.

showCmdTiming does not have any arguments.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmdtime.c

startCmdTiming
==============

Syntax
------

     #include "cmd.h"

     void startCmdTiming(void);

Description
-----------

startCmdTiming sets an internal variable to the starting time of an operation.  It is useful only in conjunction with stopCmdTiming and showCmdTiming.

startCmdTiming does not have any arguments.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmdtime.c

stopCmdTiming
=============

Syntax
------

     #include "cmd.h"

     void stopCmdTiming(void );

Description
-----------

stopCmdTiming sets an internal variable to the ending time of an operation.  It is useful only in conjunction with startCmdTiming and showCmdTiming.

stopCmdTiming does not have any arguments.

Return Value
------------

none

Example
-------

See Sample Program.

Source File
-----------

cmdtime.c

wantCmdHelp
===========

Syntax
------

     #include "cmd.h"

     int wantCmdHelp(const char * pszArgument_in,
                     const char * pszHelpMessage_in);

Description
-----------

wantCmdHelp displays the help message if the first argument is equal to a single question mark ("?").

The arguments to wantCmdHelp are as follows:

pszArgument_in
     points to a command argument which may or may not be equal to "?", or is NULL.

pszHelpMessage_in
     points to a message string that provides helpful information about a command.

Return Value
------------

TRUE (nonzero) if help provided, otherwise FALSE (zero)

Example
-------

See Sample Program.

Source File
-----------

wanthelp.c

Sample Program
**************

Rather than try to create small code fragments to illustrate each of the functions and global variables in the CMD function library, a complete (but rather useless) sample program is provided in this chapter.  It has been compiled correctly on Unix systems (actually Linux and SunOS).

     /* SAMPLE.C - sample program for the CMD function library
      *******************************************************************
      * Copyright 1997 by SIL International.
      */
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     #include <ctype.h>
     #include <time.h>
     #include "cmd.h"
     #include "opaclib.h"

     #define KW_CHDIR         1
     #define KW_CLOSE         2
     #define KW_DIRECTORY     3
     #define KW_EDIT          4
     #define KW_HELP          5
     #define KW_LOAD          6
     #define KW_LOG           7
     #define KW_PROCESS       8
     #define KW_QUIT          9
     #define KW_SAVE         10
     #define KW_SET          11
     #define KW_SHOW         12
     #define KW_SYSTEM       13
     #define KW_TAKE         14

     static CmdKeyword asCommandTable_m[] = {
         { "cd",             KW_CHDIR,       CMD_INVISIBLE },
         { "chdir",          KW_CHDIR,       0 },
         { "close",          KW_CLOSE,       0 },
         { "directory",      KW_DIRECTORY,   0 },
         { "edit",           KW_EDIT,        0 },
         { "exit",           KW_QUIT,        CMD_INVISIBLE },
         { "help",           KW_HELP,        0 },
         { "load",           KW_LOAD,        0 },
         { "log",            KW_LOG,         0 },
         { "ls",             KW_DIRECTORY,   CMD_INVISIBLE },
         { "process",        KW_PROCESS,     0 },
         { "quit",           KW_QUIT,        0 },
         { "save",           KW_SAVE,        0 },
         { "set",            KW_SET,         0 },
         { "show",           KW_SHOW,        0 },
         { "system",         KW_SYSTEM,      0 },
         { "take",           KW_TAKE,        0 }
         };
     static int iCommandTableSize_m =
                     (sizeof(asCommandTable_m) / sizeof(CmdKeyword));

     static char szProcessUsage_m[] =
         "PROCESS <infile> <outfile> (no default filenames or types)";

     #define KW_CODE         11
     #define KW_COMMENT      12
     #define KW_SILENT       13
     #define KW_TIMING       14
     #define KW_WARNING      15

     static CmdKeyword asSetTable_m[] = {
         { "code",           KW_CODE,        0 },
         { "comment",        KW_COMMENT,     0 },
         { "silent",         KW_SILENT,      0 },
         { "timing",         KW_TIMING,      0 },
         { "warnings",       KW_WARNING,     0 }
         };
     static int iSetTableSize_m =
                     (sizeof(asSetTable_m) / sizeof(CmdKeyword));

     #define KW_OFF  16
     #define KW_ON   17

     static CmdKeyword asOnOffTable_m[] = {
         { "false",          KW_OFF, CMD_INVISIBLE },
         { "off",            KW_OFF, 0 },
         { "on",             KW_ON,  0 },
         { "true",           KW_ON,  CMD_INVISIBLE }
         };
     static int iOnOffTableSize_m =
                     (sizeof(asOnOffTable_m) / sizeof(CmdKeyword));

     static int              bTiming_m        = FALSE;
     static int              cCommentMarker_m = ';';
     static unsigned char    uiCode_m         = 0xFF;

     static char szWhitespace_m[7] = " \t\r\n\f\v";
     static char szPrompt_m[] = "Command>";

     /*******************************************************************
      * NAME
      *    do_help_set
      * DESCRIPTION
      *    execute a HELP SET command
      * RETURN VALUE
      *    none
      */
     static void do_help_set(char * pszKeyword_in)
     {
     static char     szGenericSet_s[] =
         "Type HELP SET <parameter> for more help\n";

     switch (lookupCmdKeyword(pszKeyword_in,
                              asSetTable_m, iSetTableSize_m, ""))
         {
         case CMD_NULL:
             fputs("\n\
     set code      sets the byte code value for the PROCESS command\n\
     set comment   sets the comment characters for TAKE files\n\
     set silent    enables or disables error/warning messages\n\
     set timing    enables or disables timing the PROCESS command\n\
     set warnings  enables or disables warning messages\n\
     \n", stderr);
             fputs(szGenericSet_s, stderr);
             break;
         case CMD_AMBIGUOUS:
             displayNumberedMessage(&sCmdAmbiguousKeyword_g, NULL, NULL,
                               "HELP SET", pszKeyword_in);
             break;
         case CMD_INVALID:
             displayNumberedMessage(&sCmdBadKeyword_g, NULL, NULL,
                               "HELP SET", pszKeyword_in);
             break;
         case CMD_HELP:
             fputs(szGenericSet_s, stderr);
             break;
         case KW_CODE:
             fputs("\
     SET CODE <number> sets the code byte for the PROCESS command to\n\
     the indicated value.  The default code byte is 0xFF (255).\n",
                   stderr);
             break;
         case KW_COMMENT:
             fputs("\
     SET COMMENT <char> sets the comment character to the indicated\n\
     value.  If <char> is missing (or equal to the current comment\n\
     character), then comment handling is disabled.  The default\n\
     comment character is ';' (the semicolon).\n", stderr);
             break;
         case KW_SILENT:
         fputs("\
     SET SILENT ON disables all error and warning messages.\n\
     SET SILENT OFF enables all error and warning messages.\n\
     The default is OFF.\n", stderr);
             break;
         case KW_TIMING:
             fputs("\
     SET TIMING ON turns on timing mode, and SET TIMING OFF turns it\n\
     off.  If timing mode is ON, then the elapsed time required to\n\
     process a command is displayed when the command finishes.  If\n\
     timing mode is OFF, then the elapsed time is not shown.  The\n\
     default is OFF.\n", stderr);
             break;
         case KW_WARNING:
         fputs("\
     SET WARNING ON turns on warning mode.  SET WARNING OFF turns off\n\
     warning mode. If warning mode is ON, then warning messages are\n\
     displayed on the output. If warning mode is OFF, then no warning\n\
     messages are displayed.  The default is ON.\n", stderr);
             break;
         }
     }

     /*******************************************************************
      * NAME
      *    do_help_show
      * DESCRIPTION
      *    execute a HELP SHOW command
      * RETURN VALUE
      *    none
      */
     static void do_help_show(char * pszKeyword_in)
     {
     static char     szGenericShow_s[] =
         "Type HELP SHOW <parameter> for more help\n";

     switch (lookupCmdKeyword(pszKeyword_in,
                              asSetTable_m, iSetTableSize_m, ""))
         {
         case CMD_NULL:
             fputs("\n\
     show code      shows the byte code value for the PROCESS command\n\
     show comment   shows the comment characters for TAKE files\n\
     show silent    shows whether error/warning messages are disabled\n\
     show timing    shows whether timing of PROCESS command is enabled\n\
     show warnings  shows whether warning messages are enabled\n\
     \n\
     show           shows all of the above values\n\
     \n", stderr);
             fputs(szGenericShow_s, stderr);
             break;
         case CMD_AMBIGUOUS:
             displayNumberedMessage(&sCmdAmbiguousKeyword_g, NULL, NULL,
                               "HELP SHOW", pszKeyword_in);
             break;
         case CMD_INVALID:
             displayNumberedMessage(&sCmdBadKeyword_g, NULL, NULL,
                               "HELP SHOW", pszKeyword_in);
             break;
         case CMD_HELP:
             fputs(szGenericShow_s, stderr);
             break;
         case KW_CODE:
             fputs("\
     SHOW CODE displays the code byte for the PROCESS command.\n\
     ", stderr);
             break;
         case KW_COMMENT:
             fputs("\
     SHOW COMMENT displays the comment character.\n", stderr);
             break;
         case KW_SILENT:
         fputs("\
     SHOW SILENT tells whether error and warning messages are disabled\n\
     (not displayed on the screen).\n", stderr);
             break;
         case KW_TIMING:
             fputs("\
     SHOW TIMING tells whether  the elapsed time required to process a\n\
     command is displayed when the command finishes.\n", stderr);
             break;
         case KW_WARNING:
         fputs("\
     SHOW WARNING tells whether warning messages are displayed on the\n\
     screen.\n", stderr);
             break;
         }
     }

     /*******************************************************************
      * NAME
      *    do_help
      * DESCRIPTION
      *    execute the HELP command
      * RETURN VALUE
      *    none
      */
     static void do_help(char * pszKeyword_in)
     {
     static char     szGeneric_s[] =
         "Type HELP <command> for more help on a particular command\n";
     char *  pszProg;
     char *  pszFile;

     if ((pszCmdProgramName_g == NULL) || (*pszCmdProgramName_g == NUL))
         {
         pszProg = "the program";
         pszFile = "cmd";
         }
     else
         {
         pszProg = pszCmdProgramName_g;
         pszFile = pszCmdProgramName_g;
         }
     switch (lookupCmdKeyword(pszKeyword_in,
                              asCommandTable_m, iCommandTableSize_m,
                              ""))
         {
         case CMD_NULL:
             fputs("\n\
     chdir (or cd)      changes the current directory\n\
     close              closes the log file\n\
     directory (or ls)  lists the files in the current directory\n\
     edit               edits a file with your favorite editor\n\
     help               provides nice messages like this one\n\
     load               ...\n\
     log                opens a log file\n\
     process            processes one file to create another\n\
     quit (or exit)     exits the program\n\
     save               ...\n\
     set                sets a program parameter\n\
     show               shows the settings of one or more parameters\n\
     system (or !)      executes a command in a subshell\n\
     take (or @)        reads (\"takes\") commands from a file\n\
     \n", stderr);
             fputs(szGeneric_s, stderr);
             break;
         case CMD_AMBIGUOUS:
             displayNumberedMessage(&sCmdAmbiguousKeyword_g, NULL, NULL,
                               "HELP", pszKeyword_in);
             break;
         case CMD_INVALID:
             if (strcmp(pszKeyword_in, "!") == 0)
                 goto help_system;
             if (strcmp(pszKeyword_in, "@") == 0)
                 goto help_take;
             displayNumberedMessage(&sCmdBadKeyword_g, NULL, NULL,
                               "HELP", pszKeyword_in);
             break;
         case CMD_HELP:              /* ? - list of commands displayed */
             fputs(szGeneric_s, stderr);
             break;
         case KW_CHDIR:
             fputs("\
     CHDIR <pathname> changes the current directory.  You can give a\n\
     full path starting with a / (for example,\n\
     \"/u/evan/pckimmo/englex/new\"); a path starting with ../ which\n\
     indicates the directory above the current one; and so on.\n\
     Directories are separated by the / character.\n", stderr);
             break;
         case KW_CLOSE:
             fputs("\
     CLOSE closes the log file opened by a LOG command.\n", stderr);
             break;
         case KW_EDIT:
             fputs("\
     EDIT <file> attempts to edit a file using the program indicated\n\
     by the environment variable EDITOR.  If this environment variable\n\
     is not defined, then emacs is used to edit the file.\n", stderr);
             break;
         case KW_DIRECTORY:
             fputs("\
     DIRECTORY lists the contents of the current directory.\n", stderr);
             break;
         case KW_HELP:
             fprintf(stderr, "\
     HELP [<command-name>] displays a description of a command.  If\n\
     HELP is typed by itself, %s displays a list of commands with\n\
     short descriptions of each command.\n", pszProg);
             break;
         case KW_LOAD:
             fputs("\
     LOAD <filename> ... (has not been written)\n", stderr);
             break;
         case KW_LOG:
             fprintf(stderr, "\
     LOG [<file>] opens a log file.  If a filename is given on the\n\
     same line as the LOG command, then that file is used for the log\n\
     file.  Any previously existing file with the same name will be\n\
     overwritten.  If no filename is provided, then the file %s.log\n\
     in the current directory is used for the log file.\n\
     \n\
     Use CLOSE to stop recording in a log file.  If a LOG command is\n\
     given when a log file is already open, then the earlier log file\n\
     is closed before the new log file is opened.\n", pszFile);
             break;
         case KW_PROCESS:
             fputs("\
     PROCESS <infile> <outfile> processes the input file to create the\n\
     output file.\n", stderr);
             break;
         case KW_QUIT:
             fputs("\
     Either EXIT or QUIT terminates the program.\n", stderr);
             break;
         case KW_SAVE:
             fputs("\
     SAVE <filename> ... (has not been written)\n", stderr);
             break;
         case KW_SET:
             do_help_set( strtok(NULL, szWhitespace_m) );
             break;
         case KW_SHOW:
             do_help_show( strtok(NULL, szWhitespace_m) );
             break;
         case KW_SYSTEM:
     help_system:
         fprintf(stderr, "\
     SYSTEM [<command>] allows the user to execute an operating system\n\
     command (such as listing the files in the current directory) from\n\
     within %s.  If no system-level command is given on the\n\
     line with the SYSTEM command, then %s is pushed into the\n\
     background and a new system command processor (shell) is started.\n\
     Control is usually returned to %s in this case by typing\n\
     EXIT as the operating system command.\n\
     \n\
     ! (exclamation point) is a synonym for SYSTEM.\n",
                 pszProg, pszProg, pszProg);
             break;
         case KW_TAKE:
     help_take:
             fprintf(stderr, "\
     TAKE [<file>] redirects command input to the specified file.\n\
     \n\
     The default filetype extension for TAKE is \".tak\", and the\n\
     default filename is \"%s.tak\" (without the quotation marks,\n\
     of course).\n\
     \n\
     TAKE files can be nested three deep.  That is, the user types\n\
     TAKE file1, file1 contains the command TAKE file2, and file2 has\n\
     the command TAKE file3.\n\
     It would be an error for file3 to contain a TAKE command.  This\n\
     should not prove to be a serious limitation.\n", pszFile);
             break;
         }
     }

     /*******************************************************************
      * NAME
      *    do_set
      * DESCRIPTION
      *    execute the SET command
      * RETURN VALUE
      *    none
      */
     static void do_set(char * pszKeyword_in)
     {
     char *          pszArg;
     char *          p;
     unsigned        uiVal;

     switch (lookupCmdKeyword(pszKeyword_in,
                              asSetTable_m, iSetTableSize_m, ""))
         {
         case CMD_NULL:
             displayNumberedMessage(&sCmdMissingKeyword_g, NULL, NULL,
                               "SET");
             break;
         case CMD_AMBIGUOUS:
             displayNumberedMessage(&sCmdAmbiguousKeyword_g, NULL, NULL,
                               "SET", pszKeyword_in);
             break;
         case CMD_INVALID:
             displayNumberedMessage(&sCmdBadKeyword_g, NULL, NULL,
                               "SET", pszKeyword_in);
             break;
         case CMD_HELP:                      /* ? */
             fprintf(stderr, "Type HELP SET for more help\n");
             break;
         case KW_CODE:
             pszArg = strtok(NULL, szWhitespace_m);
             if ((pszArg == NULL) || (*pszArg == NUL))
                 displayNumberedMessage(&sCmdMissingArgument_g,
                                   NULL, NULL, "SET CODE");
             else
                 {
                 uiVal = strtoul(pszArg, &p, 0);
                 if ((*p != NUL) || (uiVal > 255))
                     {
                     displayNumberedMessage(&sCmdBadArgument_g,
                                   NULL, NULL, "SET CODE", pszArg);
                     }
                 else
                     uiCode_m = uiVal;
                 }
             break;
         case KW_COMMENT:
             pszArg = strtok(NULL, szWhitespace_m);
             if ((pszArg == NULL) || (*pszArg == NUL))
                 displayNumberedMessage(&sCmdMissingArgument_g,
                                   NULL, NULL, "SET COMMENT");
             else
                 cCommentMarker_m = *pszArg;
             break;
         case KW_SILENT:
             pszArg = strtok(NULL, szWhitespace_m);
             switch (lookupCmdKeyword(pszArg,
                                      asOnOffTable_m, iOnOffTableSize_m,
                                      ""))
                 {
                 case CMD_NULL:
                     displayNumberedMessage(&sCmdMissingKeyword_g,
                                       NULL, NULL, "SET SILENT");
                     break;
                 case CMD_AMBIGUOUS:
                     displayNumberedMessage(&sCmdAmbiguousKeyword_g,
                                       NULL, NULL, "SET SILENT", pszArg);
                     break;
                 case CMD_INVALID:
                     displayNumberedMessage(&sCmdBadKeyword_g,
                                       NULL, NULL, "SET SILENT", pszArg);
                     break;
                 case CMD_HELP:
                     break;
                 case KW_ON:
                     bCmdSilentMessages_g = TRUE;
                     break;
                 case KW_OFF:
                     bCmdSilentMessages_g = FALSE;
                     break;
                 }
             break;
         case KW_TIMING:
             pszArg = strtok(NULL, szWhitespace_m);
             switch (lookupCmdKeyword(pszArg,
                                      asOnOffTable_m, iOnOffTableSize_m,
                                      ""))
                 {
                 case CMD_NULL:
                     displayNumberedMessage(&sCmdMissingKeyword_g,
                                       NULL, NULL, "SET TIMING");
                     break;
                 case CMD_AMBIGUOUS:
                     displayNumberedMessage(&sCmdAmbiguousKeyword_g,
                                       NULL, NULL, "SET TIMING", pszArg);
                     break;
                 case CMD_INVALID:
                     displayNumberedMessage(&sCmdBadKeyword_g,
                                       NULL, NULL, "SET TIMING", pszArg);
                     break;
                 case CMD_HELP:
                     break;
                 case KW_ON:
                     bTiming_m = TRUE;
                     break;
                 case KW_OFF:
                     bTiming_m = FALSE;
                     break;
                 }
             break;
         case KW_WARNING:
             pszArg = strtok(NULL, szWhitespace_m);
             switch (lookupCmdKeyword(pszArg,
                                      asOnOffTable_m, iOnOffTableSize_m,
                                      ""))
                 {
                 case CMD_NULL:
                     displayNumberedMessage(&sCmdMissingKeyword_g,
                                       NULL, NULL, "SET SILENT");
                     break;
                 case CMD_AMBIGUOUS:
                     displayNumberedMessage(&sCmdAmbiguousKeyword_g,
                                       NULL, NULL, "SET SILENT", pszArg);
                     break;
                 case CMD_INVALID:
                     displayNumberedMessage(&sCmdBadKeyword_g,
                                       NULL, NULL, "SET SILENT", pszArg);
                     break;
                 case CMD_HELP:
                     break;
                 case KW_ON:
                     bCmdShowWarnings_g = TRUE;
                     break;
                 case KW_OFF:
                     bCmdShowWarnings_g = FALSE;
                     break;
                 }
             break;
         }
     }

     /*******************************************************************
      * NAME
      *    do_show
      * DESCRIPTION
      *    execute the SHOW command
      * RETURN VALUE
      *    none
      */
     static void do_show(char * pszKeyword_in)
     {
     switch (lookupCmdKeyword(pszKeyword_in,
                              asSetTable_m, iSetTableSize_m, ""))
         {
         case CMD_NULL:
             fprintf(stderr, "CODE    is %x\n", uiCode_m);
             if (cCommentMarker_m)
                 fprintf(stderr, "COMMENT is %c\n", cCommentMarker_m);
             else
                 fprintf(stderr, "COMMENT is not set\n");
             fprintf(stderr, "SILENT  is %s\n",
                     bCmdSilentMessages_g ? "ON" : "OFF");
             fprintf(stderr, "TIMING  is %s\n",
                     bCmdSilentMessages_g ? "ON" : "OFF");
             fprintf(stderr, "WARNING is %s\n",
                     bCmdShowWarnings_g ? "ON" : "OFF");
             break;
         case CMD_AMBIGUOUS:
             displayNumberedMessage(&sCmdAmbiguousKeyword_g, NULL, NULL,
                               "SHOW", pszKeyword_in);
             break;
         case CMD_INVALID:
             displayNumberedMessage(&sCmdBadKeyword_g, NULL, NULL,
                               "SHOW", pszKeyword_in);
             break;
         case CMD_HELP:
             fprintf(stderr, "Type HELP SHOW for more help\n");
             break;
         case KW_CODE:
             fprintf(stderr, "CODE is %x\n", uiCode_m);
             break;
         case KW_COMMENT:
             if (cCommentMarker_m)
                 fprintf(stderr, "COMMENT is %c\n", cCommentMarker_m);
             else
                 fprintf(stderr, "COMMENT is not set\n");
             break;
         case KW_SILENT:
             fprintf(stderr, "SILENT is %s\n",
                     bCmdSilentMessages_g ? "ON" : "OFF");
             break;
         case KW_TIMING:
             fprintf(stderr, "TIMING is %s\n",
                     bCmdSilentMessages_g ? "ON" : "OFF");
             break;
         case KW_WARNING:
             fprintf(stderr, "WARNING is %s\n",
                     bCmdShowWarnings_g ? "ON" : "OFF");
             break;
         }
     }

     /*******************************************************************
      * NAME
      *    do_process
      * DESCRIPTION
      *    process the input file to create the output file
      * RETURN VALUE
      *    none
      */
     static void do_process(char * pszInput_in, char * pszOutput_in)
     {
     time_t  iTime = time(NULL);
     int     c;
     FILE *  pInputFP;
     char *  pszInputFile;
     char *  pszOutputFile;

     if (pszInput_in == NULL)
         {
         displayNumberedMessage(&sCmdMissingInputFile_g, NULL, NULL,
                           "PROCESS");
         return;
         }
     if (pszOutput_in == NULL)
         {
         displayNumberedMessage(&sCmdMissingOutputFile_g, NULL, NULL,
                           "PROCESS");
         return;
         }
     if (    wantCmdHelp(pszInput_in,  szProcessUsage_m) ||
             wantCmdHelp(pszOutput_in, szProcessUsage_m) )
         return;
     if (getCmdTakeLevel() != 0)
         {
         pszInputFile  = buildAdjustedFilename(pszInput_in,
                                               getCmdTakeFile(), NULL);
         pszOutputFile = buildAdjustedFilename(pszOutput_in,
                                               getCmdTakeFile(), NULL);
         }
     else
         {
         pszInputFile  = duplicateString(pszInput_in);
         pszOutputFile = duplicateString(pszOutput_in);
         }
     pInputFP = fopen( pszInputFile, "rb");
     if (pInputFP == NULL)
         {
         displayNumberedMessage(&sCmdBadInputFile_g, NULL, NULL,
                           pszInputFile, "PROCESS");
         }
     else
         {
         pCmdOutputFP_g = fopen( pszOutputFile, "wb" );
         if (pCmdOutputFP_g == NULL)
             {
             displayNumberedMessage(&sCmdBadOutputFile_g, NULL, NULL,
                               pszOutputFile, "PROCESS");
             }
         else
             {
             if (bTiming_m)
                 startCmdTiming();
             if (pCmdLogFP_g != NULL)
                 {
                 fprintf(pCmdLogFP_g,
                         "Processing %s to create %s (log = %s) at %s",
                         pszInput_in, pszOutput_in,
                         pszCmdLogFile_g ? pszCmdLogFile_g : "?",
                         ctime(&iTime));
                 }
             while ((c = fgetc(pInputFP)) != EOF)
                 {
                 c ^= uiCode_m;
                 fputc(c, pCmdOutputFP_g);
                 }
             fclose(pCmdOutputFP_g);
             pCmdOutputFP_g = NULL;
             if (bTiming_m)
                 {
                 stopCmdTiming();
                 showCmdTiming();
                 }
             }
         fclose(pInputFP);
         }
     freeMemory( pszInputFile );
     freeMemory( pszOutputFile );
     }

     /*******************************************************************
      * NAME
      *    do_load
      * DESCRIPTION
      *    execute a LOAD command
      * RETURN VALUE
      *    none
      */
     static void do_load(char * pszFilename_in)
     {
     if ((pszFilename_in == NULL) || (*pszFilename_in == NUL))
         {
         displayNumberedMessage(&sCmdMissingInputFile_g, NULL, NULL,
                           "LOAD");
         return;
         }
     /* left as an exercise for the reader :-) */
     fprintf(stderr, "load %s is not implemented\n", pszFilename_in);
     }

     /*******************************************************************
      * NAME
      *    do_save
      * DESCRIPTION
      *    execute a SAVE command
      * RETURN VALUE
      *    none
      */
     static void do_save(char * pszFilename_in)
     {
     if ((pszFilename_in == NULL) || (*pszFilename_in == NUL))
         {
         displayNumberedMessage(&sCmdMissingOutputFile_g, NULL, NULL,
                           "SAVE");
         return;
         }
     /* left as an exercise for the reader :-) */
     fprintf(stderr, "save %s is not implemented\n", pszFilename_in);
     }

     /*******************************************************************
      * NAME
      *    do_command
      * DESCRIPTION
      *    read and execute a command
      * RETURN VALUE
      *    TRUE (1) to continue, FALSE (0) to stop the program
      */
     static int do_command()
     {
     char *  pszCommand;
     char *  pszSaved;
     char *  pszArg;
     char *  p;

     pszCommand = getCmdString(NULL, szPrompt_m, cCommentMarker_m);
     if ((pszCommand == NULL) || (*pszCommand == NUL))
         return 1;                   /* ignore empty commands */
     /*
      *  save a copy of the command line
      */
     pszSaved = duplicateString(pszCommand + strspn(pszCommand,
                                                    szWhitespace_m));
     /*
      *  parse the first keyword of the command
      */
     pszArg = strtok(pszCommand, szWhitespace_m);
     switch (lookupCmdKeyword(pszArg,
                              asCommandTable_m, iCommandTableSize_m, ""))
         {
         case CMD_NULL:
             break;                  /* ignore empty commands */

         case CMD_AMBIGUOUS:
             displayNumberedMessage(&sCmdAmbiguous_g, NULL, NULL, pszCommand);
             break;

         case CMD_INVALID:
             if (*pszArg == '!')
                 {
                 pszArg  = pszSaved + 1;
                 pszArg += strspn(pszArg, szWhitespace_m);
                 doCmdSystem( pszArg );
                 }
             else if (*pszArg == '@')
                 {
                 if (pszArg[1] == NUL)
                     pszArg = strtok(NULL, szWhitespace_m);
                 else
                     ++pszArg;
                 doCmdTake( pszArg );
                 }
             else if (getCmdTakeLevel() != 0)
                 {
                 displayNumberedMessage(&sCmdErrorInTake_g, NULL, NULL,
                                   pszArg);
                 closeCmdTake();
                 }
             else
                 displayNumberedMessage(&sCmdInvalid_g, NULL, NULL, pszArg);
             break;

         case CMD_HELP:              /* ? - list of commands displayed */
             fprintf(stderr, "Type HELP for more help\n");
             break;

         case KW_CHDIR:
             pszArg = strtok(NULL, szWhitespace_m);
             doCmdChdir( pszArg );
             break;

         case KW_CLOSE:
             doCmdClose();
             break;

         case KW_EDIT:
             pszArg = strtok(NULL, szWhitespace_m);
             if (getCmdTakeLevel() != 0)
                 pszArg = buildAdjustedFilename(pszArg, getCmdTakeFile(),
                                                NULL);
             doCmdEdit( pszArg );
             freeMemory( pszArg );
             break;

         case KW_DIRECTORY:
             pszArg  = pszSaved;
             pszArg += strcspn(pszArg, szWhitespace_m);
             pszArg += strspn(pszArg, szWhitespace_m);
             doCmdDirectory( pszArg );
             break;

         case KW_HELP:
             do_help( strtok(NULL, szWhitespace_m) );
             break;

         case KW_LOAD:
             pszArg = strtok(NULL, szWhitespace_m);
             pszArg = setCmdFilename( pszArg, "settings.ini", ".ini");
             if (getCmdTakeLevel() != 0)
                 {
                 p = buildAdjustedFilename(pszArg, getCmdTakeFile(),
                                           NULL);
                 freeMemory( pszArg );
                 pszArg = p;
                 }
             do_load( pszArg );
             freeMemory( pszArg );
             break;

         case KW_LOG:
             doCmdLog( strtok(NULL, szWhitespace_m) );
             break;

         case KW_PROCESS:
             pszArg = strtok(NULL,szWhitespace_m);
             do_process(pszArg, strtok(NULL, szWhitespace_m));
             break;

         case KW_QUIT:                       /* QUIT or EXIT */
             while (closeCmdTake())
                 ;
             freeMemory(pszSaved);
             return 0;

         case KW_SAVE:
             pszArg = strtok(NULL, szWhitespace_m);
             pszArg = setCmdFilename( pszArg, "settings.ini", ".ini");
             if (getCmdTakeLevel() != 0)
                 {
                 p = buildAdjustedFilename(pszArg, getCmdTakeFile(),
                                           NULL);
                 freeMemory( pszArg );
                 pszArg = p;
                 }
             do_save( pszArg );
             freeMemory( pszArg );
             break;

         case KW_SET:
             do_set( strtok(NULL, szWhitespace_m) );
             break;

         case KW_SHOW:
             do_show( strtok(NULL, szWhitespace_m) );
             break;

         case KW_SYSTEM:
             pszArg  = pszSaved;
             pszArg += strcspn(pszArg, szWhitespace_m);
             pszArg += strspn(pszArg, szWhitespace_m);
             doCmdSystem( pszArg );
             break;

         case KW_TAKE:
             pszArg = strtok(NULL, szWhitespace_m);
             if (getCmdTakeLevel() != 0)
                 pszArg = buildAdjustedFilename(pszArg, getCmdTakeFile(),
                                                NULL);
             doCmdTake( pszArg );
             freeMemory( pszArg );
             break;
         }
     freeMemory(pszSaved);
     return 1;
     }

     /*******************************************************************
      * NAME
      *    main
      * DESCRIPTION
      *    the main procedure for this program
      * RETURN VALUE
      *    1 to indicate successful execution of the program
      */
     int main(int argc, char ** argv)
     {
     char *  p;
     /*
      *  establish the program basename
      */
     pszCmdProgramName_g = strrchr(argv[0], '/');
     if (pszCmdProgramName_g == NULL)
         pszCmdProgramName_g = argv[0];
     else
         ++pszCmdProgramName_g;
     if ((pszCmdProgramName_g == NULL) || (*pszCmdProgramName_g == NUL))
         pszCmdProgramName_g = "unknown";
     pszCmdProgramName_g = duplicateString(pszCmdProgramName_g);
     if ((p = strrchr(pszCmdProgramName_g, '.')) != NULL)
         *p = NUL;
     /*
      *  set up for error or SIGINT trapping
      */
     if (setjmp(sCmdJmpBuf_g) != 0)
         {
         fputs("\nRETURNING TO PC-KIMMO COMMAND PROCESSING\n\n", stderr);
         }
     else
         {
     #ifdef USE_SIGNAL
     #ifdef SIGINT
         signal( SIGINT, handleCmdSigint );
     #endif
     #endif
         }
     /*
      *  read and execute commands until QUIT or EXIT
      */
     fprintf(stderr, "%s - test program for the CMD function library\n",
             argv[0]);
     while (do_command())
         ;
     return 1;
     }