                 CMD Function Library Reference Manual
              functions for terminal command interaction
                             October 1997

                          by Stephen McConnel

                 Copyright (C) 2000 SIL International

Published by:
Language Software Development
SIL International
7500 W. Camp Wisdom Road
Dallas, TX 75236
U.S.A.
Permission is granted to make and distribute verbatim copies of this
file provided the copyright notice and this permission notice are
preserved in all copies.

The author may be reached at the address above or via email as
`steve@acadcomp.sil.org'.

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.

Variable and function naming conventions
****************************************

The basic goal behind choosing names in the CMD function library is for
the name to convey information about what it represents.  This is
achieved in two ways: striving for a descriptive name rather than a
short cryptic abbreviated name, and following a different pattern of
capitalization for each type of name.

Preprocessor macro names
========================

Preprocessor macro names are written entirely in capital letters.  If
the name requires more than one word for an adequate description, the
words are joined together with intervening underscore (`_') characters.

Data structure names
====================

Data structure names consist of one or more capitalized words.  If the
name requires more than one word for an adequate description, the words
are joined together without underscores, depending on the
capitalization pattern to make them readable as separate words.

Variable names
==============

Variable names in the CMD function library follow a modified form of
the Hungarian naming convention described by Steve McConnell in his
book `Code Complete' on pages 202-206.

Variable names have three parts:  a lowercase type prefix, a
descriptive name, and a scope suffix.

Type prefix
-----------

The type prefix has the following basic possibilities:

`b'
     a Boolean, usually encoded as a `char', `short', or `int'

`c'
     a character, usually a `char' but sometimes a `short' or `int'

`d'
     a double precision floating point number, that is, a `double'

`e'
     an enumeration, encoded as an `enum' or as a `char', `short', or
     `int'

`i'
     an integer, that is, an `int', `short', `long', or (rarely) `char'

`s'
     a data structure defined by a `struct' statement

`sz'
     a NUL (that is, zero) terminated character string

`pf'
     a pointer to a function

In addition, the basic types may be prefixed by these qualifiers:

`u'
     indicates that an integer or a character is unsigned

`a'
     indicates an array of the basic type

`p'
     indicates a pointer to the type, possibly a pointer to an array or
     to a pointer

Descriptive name
----------------

The descriptive name portion of a variable name consists of one or more
capitalized words concatenated together.  There are no underscores
(`_') separating these words from each other, or from the type prefix.
For the CMD function library, the descriptive name for global variables
begins with a reference to the relevant CMD data structure.

Scope suffix
------------

The scope suffix has these possibilities:

`_g'
     indicates a global variable accessible throughout the program

`_m'
     indicates a module (semiglobal) variable accessible throughout the
     file (declared `static')

`_in'
     indicates a function argument used for input

`_out'
     indicates a function argument used for output (must be a pointer)

`_io'
     indicates a function argument used for both input and output (must
     be a pointer)

`_s'
     indicates a function variable that retains its value between calls
     (declared `static')

The lack of a scope suffix indicates that a variable is declared within
a function and exists on the stack for the duration of the current call.

Function names
==============

Global function names in the CMD function library have two parts: a
verb that is all lowercase followed by a noun phrase containing one or
more capitalized words.  These pieces are concatanated without any
intervening underscores (`_').  For the CMD library functions, the noun
phrase section includes a reference to the relevant CMD data structure.

Examples
========

Given the discussion above, it is easy to discern at a glance what type
of item each of the following names refers to.
`SAMPLE_NAME'
     is a preprocessor macro.

`SampleName'
     is a data structure.

`pSampleName'
     is a local pointer variable.

`writeSampleName'
     is a function (that may apply to a data structure named
     `SampleName').

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 (opaclib.info).

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 (opaclib.info))  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 (opaclib.info))  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 (opaclib.info))  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;
     }