/*
 Author: Andy Lowry
 Rewritten by Howie Kaye

 Columbia University Center for Computing Activities, April 1987.
Copyright (C) 1986, 1987, Trustees of Columbia University in the City
of New York.  Permission is granted to any individual or institution
to use, copy, or redistribute this software so long as it is not sold
for profit, provided this copyright notice is retained.  */

/*
 * The purpose of this program is to demonstrate some of the features of
 * CCMD.  It is also a way of testing the various functions of the
 * interface.  Hopefully it will be instrumental in defining a style for
 * writing code to use CCMD.
 *
 * There are certain issues addressed in several of these routines which
 * are of note.
 * 1) the value returned from a parse MUST be copied before the next parse.
 *    The next parse will invalidate the parseval structure used.
 *    This does not mean that the data pointed to becomes invalid.  Only the
 *    pointer (note that for file parses, the data pointed to is reused by
 *    the next file parse)
 * 2) due to reparsing, multiple calls to malloc() may be done.  It is
 *    important to free these pointers before the next malloc().  This is
 *    similar to the problem with JFNs under COMND% in TOPS20.
 */

#include "ccmd.h"

int debug = 0;
/*
 * forward declare parse routines.
 */
int octnum(), decnum(), hexnum(), key(), qst(), dst(), b5num(), exitcmd();
int text(), word(), swit(), tok(), fil(), tim(), tad(), dat(), user();
int groupcmd(),para(),contpara(),take(),comment(), password();
int domatch(), help(), character(), shell(), nil(), history();

char *malloc();
static char *atmbuf;
/*
 * set up the top level keyword table
 * This will be used by the help command also.
 */

static keywrd cmds[] = {
  { "base-5",	0,	(keyval) b5num },
  { "character",0,	(keyval) character },
  { "comment",	0,	(keyval) comment },
  { "continue",	0,	(keyval) contpara },
  { "date",	0,	(keyval) dat },
  { "decimal",	0,	(keyval) decnum },
  { "delimited",0,	(keyval) dst },
  { "exit",	0,	(keyval) exitcmd },
  { "file",	0,	(keyval) fil },
  { "group",	0,	(keyval) groupcmd },
  { "h", 	KEY_ABR|KEY_INV, (keyval) 11 },	/* abbrev for help -- index == 11 */
  { "help",	0,	(keyval) help },
  { "hexadecimal",0,	(keyval) hexnum },
  { "history",  0, 	(keyval) history },
  { "line",	0,	(keyval) text },	
  { "match",	0,	(keyval) domatch },
  { "octal",	0,	(keyval) octnum },
  { "paragraph",0,	(keyval) para },
  { "password",	0,	(keyval) password },
  { "person",	0,	(keyval) key },
  { "q", 	KEY_ABR|KEY_INV, (keyval) 21 },	/* abbrev for quit -- index == 12 */
  { "quit",	KEY_INV,(keyval) exitcmd },
  { "quoted",	0,	(keyval) qst },
  { "switch",	0,	(keyval) swit },
  { "tad",	0,	(keyval) tad },
  { "take",	0,	(keyval) take },
  { "time",	0,	(keyval) tim },
  { "token",	0,	(keyval) tok },
  { "user",	0,	(keyval) user },
  { "word",	0,	(keyval) word },
  { "shell",	0,	(keyval) shell },
  { "nil", 	KEY_INV,(keyval) nil },
};

/*
 * the actual keyword table type has a length in it.
 */
static keytab cmdtab = { (sizeof(cmds)/sizeof(keywrd)), cmds };

pval parseval;
fdb *used;


int argc;
char *argv;
main(Argc,Argv) int Argc; char *Argv;
{
  char *cmini();
  argc = Argc;
  argv = Argv;
  atmbuf = cmini();			/* initialize ccmd */
  cmhst(100);
  cmxprintf("Test program Version 0.0\n"); /* display version info */
  cmxprintf("%s\n",cm_version());	/* and CCMD version as well. */
  toplevel();				/* top level cmd parser. */
  cmdone();				/* restore terminal, etc */
  return(0);
}

int done = FALSE;			/* exit flag */
toplevel() {
					/* FDB for top level commands */
  static fdb cmdfdb = { _CMKEY, 0, NULL, (pdat) &(cmdtab), "Command, ", 
			  NULL, NULL };
  while (!done) {
    cmseter();				/* set error trap */
					/* control will come here in the */
					/* case of a parse error. */
    if (cmcsb._cmerr == CMxEOF)		/* exit on EOF -- this is useful */
      break;				/* for take's */
    if (cmargs(argc,argv))		/* check for command line args */
      done = TRUE;
    else
      prompt("Test> ");			/* prompt */
    cmsetrp();				/* set reparse trap */
					/* control will come here in the */
					/* case of a reparse. */
    parse(&cmdfdb,&parseval,&used);	/* parse a command */
    execute(parseval._pvkey);		/* execute it. */
  }
}

/*
 * execute a command.
 * call the function, with the help flag off.
 */
execute(f) int (*f)(); {
  (*f)(FALSE);				/* call function with help flag off */
}

/*
 * call a command function with the help flag on
 */
dohelp(f) int (*f)(); {
  (*f)(TRUE);				/* call function with help flag on */
}


/*
 * top level commands.
 * all of these take the form 
 * command(helpflag) {
 *   if (helpflag) print a help string
 *   else { parse and execute }
 *
 * this style is encouraged, as the documentation for each command
 * resides inside the command itself.  This leads to self documenting
 * parses, and documentation which is consistant with the code.
 */

key(helpflg) int helpflg; {
/*
 * keyword table for the name command
 */
  static keywrd keys[] = {
    { "andy", 0, (keyval) 1 },
    { "and", KEY_NOR+KEY_INV, (keyval) 2}, /* this essentially turns off esc */
					/* completion on andy. */
    { "dee", 0, (keyval) 3 },
    { "doug", 0, (keyval) 4 },
    { "d", KEY_ABR+KEY_INV, (keyval) 3 },
    { "frank", KEY_INV, (keyval) 6 },	/* frank is invisible */
    { "howie", 0, (keyval) 7 },
    { "how", KEY_NOR+KEY_INV, (keyval) 8 },/*complete on howie after the 'i' */
    { "mark", 0, (keyval) 9 },
    { "tom.chow", 0, (keyval) 10 }	/* has a dot in it. */
  };

  static keytab ktab = { (sizeof(keys)/sizeof(keywrd)), keys };

/* 
 * custom break table -- like the standard for keywords, but allows
 * periods in other than the first position.
 */

  static brktab keybrk = {
    {					/* 1st char break array */
      					/* all but letters, digits, hyphen */
      0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
      0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
    },
    {					/* subsequent char break array */
					/* same as above, plus dots */
      0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, 0x3f, 
      0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
    }
  };
	
					/* fdb for names.   default to dee */
  static fdb keyfdb = { _CMKEY, 0, NULL, (pdat) &ktab, "Name, ", "dee",
			&keybrk };

  static int choice;

/*
 * at last, some code.
 */
  if (helpflg) {
    cmxprintf("\
The key command parses a bunch of keywords.  Actually, these are mostly\n\
people's names.  There are a few ignored keywords, which prevent escape\n\
completion on others.\n");
  }
  else {
    noise("of choice is");		/* give a guide word */
    parse(&keyfdb,&parseval,&used);	/* parse a keyword */
    choice = parseval._pvkey;		/* remember which was chosen */
    confirm();				/* confirm the request */
    cmxprintf("Selected name: %s\n",keys[choice]._kwkwd);
  }
}

/*
 * the switch command
 */

swit(helpflg)
int helpflg;
{
  static keywrd swits[] = {
    { "find:", KEY_INV, (keyval) 0 },	/* invisible, takes a value */
					/* prevent completion on hello-world */
    { "hell", KEY_NOR+KEY_INV, (keyval) 1 },
    { "hello-world", 0, (keyval) 2 },
    { "junk", 0, (keyval) 3 },
    { "old", 0, (keyval) 4 },
    { "qui", KEY_NOR+KEY_INV, (keyval) 5},
    { "quiet", 0, (keyval) 6 },
    { "s", KEY_ABR+KEY_INV, (keyval) 8 },
    { "stuff:", 0, (keyval) 8 },	/* takes a value */
    { "stupid", 0, (keyval) 9 },
    { "time", 0, (keyval) 10 },
  };

  static keytab stab = { (sizeof(swits)/sizeof(keywrd)), swits };
  static fdb swifdb = { _CMSWI, 0, NULL, (pdat) &stab, NULL, "/time", NULL };
  static int choice, value, valflg;
  static fdb valfdb = { _CMNUM, 0, NULL, (pdat) 10, NULL, NULL, NULL };

  if (helpflg) {
    cmxprintf("\
The switch command parses a bunch of switches.  Switches are keywords which\n\
begin with a switch character.  In this case, the switch character is '/'.\n\
It is possible to change the switch character to any other character by \n\
changing the 'cmswbeg' variable in ccmd.  You can do this with in this \n\
program by using the 'character' command.  Type 'help character' for more\n\
information about it.\n\
\n\
A switch may also take a value after it.  If the switch ends with a \n\
colon ':', then it accepts another (integer) value.\n");
  }
  else {
    noise("selection is");		/* some extra help */
    parse(&swifdb,&parseval,&used);	/* parse the switch */
    choice = parseval._pvkey;		/* remember which */
    if (cmcsb._cmflg & CM_SWT) {	/* if the switch takes a value */
      parse(&valfdb,&parseval,&used);	/*  parse  that too.*/
      value = parseval._pvint;
      valflg = TRUE;
    }
    else
      valflg = FALSE;
  }
  confirm();				/* get comfirmation */
  cmxprintf("Selected switch: %s\n",swits[choice]._kwkwd);
  if (valflg)
    cmxprintf("Switch value: %d\n",value);
}

/*
 * parse a hex number
 */
hexnum(helpflg)
int helpflg;
{
  static int value;
  static fdb hexfdb = { _CMNUM, NUM_US, NULL, (pdat) 16, NULL, NULL, NULL };

  if (helpflg)
    cmxprintf("\
The hexnum command accepts a Hexadecimal number, and displays it.  the\n\
number is actually returned in decimal, and printed out again in \n\
hexadecimal.\n");
  else {
    noise("number is");
    parse(&hexfdb,&parseval,&used);
    value = parseval._pvint;
    confirm();
    cmxprintf("Value: %x\n", value);
  }
}

/*
 * parse a decimal number
 */
decnum(helpflg)
int helpflg;
{
  static int value;
  static fdb decfdb = { _CMNUM, 0, NULL, (pdat) 10, NULL, NULL, NULL };

  if (helpflg)
    cmxprintf("\
The decimal command accepts a decimal number, and displays it.\n");
  else {
    noise("number is");
    parse(&decfdb,&parseval,&used);
    value = parseval._pvint;
    confirm();
    cmxprintf("Value: %d\n", value);
  }
}

/*
 * parse an octal number
 */
octnum(helpflg)
int helpflg;
{
  static char *value = NULL;
  static int octdat = 8;
  static fdb octfdb = { _CMNUM, NUM_PO, NULL, (pdat) 8, NULL, NULL, NULL };

  if (helpflg) {
    cmxprintf("The octal command accepts an octal number.\n");
  }
  else {
    noise("number is");
    parse(&octfdb,&parseval,&used);	/* parse an octal number */
    if (value) {
      free(value);
      value = NULL;
    }
    value = malloc(strlen(atmbuf)+1);	/* get the string, not the number */
    strcpy(value,atmbuf);		/* because printf doesn't support */
    confirm();				/* the printing of octal numbers */
    cmxprintf("Value: %s\n", value);
    free(value);
    value = NULL;
  }
}

/*
 * a base five number
 */
b5num(helpflg)
int helpflg;
{
  static int value;
  static int b5dat = 5;
  static fdb b5fdb = { _CMNUM, 0, NULL, (pdat) 5, NULL, NULL, NULL };

  if (helpflg)
    cmxprintf("accepts a number in base 5, and displays it in decimal.\n");
  else {
    noise("number is");
    parse(&b5fdb,&parseval,&used);
    value = parseval._pvint;		/* get the number in decimal */
    confirm();
    cmxprintf("Value (decimal): %d\n", value); /* print it */
  }
}

/*
 * the exit command
 */
exitcmd(helpflg)
int helpflg;
{
  if (helpflg) {
    cmxprintf("\
The exit command exits from this program.  The invisible quit command is\n\
a synonym for it.\n");
  }
  else {
    noise("this goofy program");
    confirm();
    done = TRUE;			/* just set the global exitflg */
  }
}

/*
 * parse a quoted string.
 * quoted strings must be quoted by '"'.  They are a specific case of
 * delimited strings.
 */

qst(helpflg)
int helpflg;
{
  static fdb qstfdb = { _CMQST, 0, NULL, NULL, NULL, NULL, NULL };
  static char *valstr= NULL;
  
  if (helpflg) {
    cmxprintf("Accepts a quoted string.\n");
  }
  else {
    noise("string is");
    parse(&qstfdb,&parseval,&used);	/* parse a string */
    if (valstr) {
      free(valstr);
      valstr = NULL;
    }
    valstr = malloc(strlen(atmbuf)+1);
    strcpy(valstr,atmbuf);
    confirm();
    cmxprintf("String: %s\n",valstr);
    free(valstr);
    valstr = NULL;
  }
}



/* 
 * break chars for delimited parse
 * only first char break chars are valid delimiters.
 */
static brktab dstbrk = {
  {
    0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
  },
  {
    0x00, 0xac, 0x25, 0x10, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
  }
};

dst(helpflg)
int helpflg;
{
  static fdb dstfdb = { _CMQST, 0, NULL, NULL, NULL, NULL, &dstbrk };
  static char *valstr= NULL;
  
  if (helpflg) {
    cmxprintf("\
Accepts a delimited string.  The final delimiter must match the\n\
first delimiter.\n");
  }
  else {
    noise("string is");
    parse(&dstfdb,&parseval,&used);
    if (valstr) {
      free(valstr);
      valstr = NULL;
    }
    valstr = malloc(strlen(atmbuf)+1);
    strcpy(valstr,atmbuf);
    confirm();
    cmxprintf("String: %s\n",valstr);
    free(valstr);
    valstr = NULL;
  }
}

/*
 * parse text.   up to a newline.
 */
text(helpflg)
int helpflg;
{
  static char *valstr;
  static fdb txtfdb = { _CMTXT, 0, NULL, NULL, NULL, NULL, NULL };
  
  if (helpflg) {
    cmxprintf("Accepts an arbitrary line of text\n");
  }
  else {
    noise("of text is");
    parse(&txtfdb,&parseval,&used);
    if (valstr) {
      free(valstr);
      valstr = NULL;
    }
    valstr = malloc(strlen(atmbuf)+1);
    strcpy(valstr,atmbuf);
    confirm();
    cmxprintf("Line: %s\n",valstr);
    free(valstr);
    valstr = NULL;
  }
}

/* 
 * parse a word.
 * actually, a field.
 */
word(helpflg)
int helpflg;
{
  static char *valstr= NULL;
  static fdb wrdfdb = { _CMFLD, 0, NULL, NULL, NULL, NULL, NULL };
  
  if (helpflg) {
    cmxprintf("Accepts a word.  Words are delimited by whitespace, or other\n\
characters which are not in the current break mask.\n");
  }
  else {
    noise("of the day is");
    parse(&wrdfdb,&parseval,&used);
    if (valstr) {
      free(valstr);
      valstr = NULL;
    }
    valstr = malloc(strlen(atmbuf)+1);
    strcpy(valstr,atmbuf);
    confirm();
    cmxprintf("Word: %s\n",valstr);
    free(valstr);
    valstr = NULL;
  }
}


/*
 * parse a token.
 * use the chainfdb call.   this actually modifies the fdbs, so be careful.
 */
tok(helpflg)
int helpflg;
{
  static fdb tok1fdb = { _CMTOK, TOK_WAK, NULL, (pdat) "+", NULL, NULL, NULL };
  static fdb tok2fdb = { _CMTOK, 0, NULL, (pdat) "-", NULL, NULL, NULL };
  static fdb tok3fdb = { _CMTOK, 0, NULL, (pdat) "**#|", NULL, NULL, NULL };
  static fdb tok4fdb = { _CMTOK, 0, NULL, (pdat) "hello^friend", NULL, NULL,
			   NULL };
  static char *t;
  fdb *x;
  if (helpflg) {
    cmxprintf("Accepts a token.  Type 'token ?'  to see what the tokens \
are.\n");
  }
  else {
    noise("a dollar please!");		/* for you new yorkers!!! */
    parse(fdbchn(&tok1fdb,&tok2fdb,&tok3fdb,&tok4fdb,NULL),&parseval,&used);
    t = (char *) used->_cmdat;
    confirm();
    cmxprintf("Selected token: %s\n",t);
  }
}

/*
 * parse a (possibly wild) username.
 * wild cards are the same as for files, or groups.
 * '*' matches anything.
 * '?' matches any character
 * []  match a range (may have a ^ as the first char, to negate the range).
 * {}  match any of the strings inside (separated by commas).  The strings 
 *     within the {} may contain any wild cards.
 */
user(helpflg) 
int helpflg;
{
					/* a wild user */
  static fdb usrfdb = { _CMUSR ,USR_WILD, NULL, NULL, NULL, NULL, NULL };
  static struct passwd **p;		/* returns vector of passwd entries */
  struct passwd *p1;
  int i;

  if (helpflg) {
    cmxprintf("parses a username\n");
  }
  else {
    noise("username");
    parse(&usrfdb, &parseval, &used);
    p = parseval._pvusr;
    confirm();
#ifndef MSDOS
    for(i = 0,p1 = *p; p1 != NULL; p1 = p[++i]) {
      cmxprintf("%s:%s:%d:%d:%s:%s:%s\n",p1->pw_name, p1->pw_passwd, 
	     p1->pw_uid, p1->pw_gid, p1->pw_gecos,p1->pw_dir,p1->pw_shell);
      fflush(stdout);
    }
#endif
  }
}

/*
 * parse a possibly wild group name 
 */
groupcmd(helpflg) 
int helpflg;
{
  int i,j;
  static fdb grpfdb = { _CMGRP ,GRP_WILD, NULL, NULL, NULL, NULL, NULL };
  static struct group **g,*g1;		/* returns vector of group entries */
  
  if (helpflg) {
    cmxprintf("parses a wild group name\n");
  }
  else {
    noise("group name");
    parse(&grpfdb, &parseval, &used);
    g = parseval._pvgrp;
    confirm();
#ifndef MSDOS
    for (j = 0, g1 = *g; g1 != NULL; g1 = g[++j]) {
      cmxprintf("%s:%s:%d:",g1->gr_name, g1->gr_passwd, g1->gr_gid);
      for (i = 0; g1->gr_mem[i] != NULL; i++) {
	cmxprintf("%s",g1->gr_mem[i]);
	if (g1->gr_mem[i+1])
	  putchar(',');
      }
      cmxprintf("\n"); fflush(stdout);
    }
#endif
  }
}

/*
 * parse a wild filename.
 * show the type of file on help.
 */

fil(helpflg)
int helpflg;
{
  static fdb filfdb = { _CMFIL ,FIL_NODIR, NULL, NULL, NULL, 
			    NULL, NULL };
  FILE *fp;
  char c;
  char **filelist;
  int i;

  if (helpflg) {
    cmxprintf("parses a possibly wild filename.\n");
  }
  else {
    noise("to see");
    parse(&filfdb, &parseval, &used);
    filelist = parseval._pvfil;
    confirm();
    for(i = 0; filelist[i] != NULL; i++)
      cmxprintf("%s\n",filelist[i]);
  }
}

/*
 * parse a time and/or date.
 */
xtad(helpflg,flags)
int helpflg,flags;
{
  static fdb tadfdb = { _CMTAD, 0, NULL, (pdat) 0, NULL, NULL, NULL };
  static datime dtblk, *dt;
  static char *(dows[]) = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  static char *(mths[]) = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
	  "Aug", "Sep", "Oct", "Nov", "Dec" };
  int tz;
  tzinf *tzi,*dttzinf();
  char *tzname,tzsign;

  if (helpflg) {
    cmxprintf("parse a%s%s%s\n", !(flags&DTP_NTI) ? " time" : "",
	      !(flags & (DTP_NTI|DTP_NDA)) ? " and" : "",
	      !(flags&DTP_NDA) ? " date" : "");
  }
  else {
    tadfdb._cmffl = flags;
    noise("is");
    parse(&tadfdb,&parseval,&used);
    dt = &parseval._pvtad;
    dtblk._dtmon = dt->_dtmon;
    dtblk._dtday = dt->_dtday;
    dtblk._dtyr = dt->_dtyr;
    dtblk._dtdow = dt->_dtdow;
    dtblk._dthr = dt->_dthr;
    dtblk._dtmin = dt->_dtmin;
    dtblk._dtsec = dt->_dtsec;
    dtblk._dttz = dt->_dttz;
    dtblk._dtdst = dt->_dtdst;
    confirm();
    cmxprintf("%s %d-%s-%d ",dows[dtblk._dtdow], dtblk._dtday+1,
	    mths[dtblk._dtmon], dtblk._dtyr);
    cmxprintf("%d:%d:%d-",dtblk._dthr, dtblk._dtmin, dtblk._dtsec);
    tz = dtblk._dttz;
    tzi = dttzinf(tz);
    if (tzi == NULL) {
      tzsign = tz < 0 ? '+' : '-';
      if (tz < 0) tz = -tz;
      cmxprintf("GMT%c%d:%d%s\n",tzsign,tz/60,tz%60,
	      dtblk._dtdst==0?"":" DST");
    }
    else
      cmxprintf("%s\n",dtblk._dtdst==0?tzi->_tznam:tzi->_tzdnm);
  }
}
    
/*
 * parse a time
 */
tim(helpflg)
int helpflg;
{
  xtad(helpflg,DTP_NDA);
}

/*
 * parse a date
 */
dat(helpflg)
int helpflg;
{
  xtad(helpflg,DTP_NTI);
}

/*
 * parse time and date
 */
tad(helpflg)
int helpflg;
{
  xtad(helpflg,0);
}

/*
 * parse a paragraph
 */
char *paratxt;
int paracont = FALSE;
static fdb parafdb = { _CMPARA, 0, NULL, NULL, NULL, NULL, NULL };
para_data pd = { NULL, NULL };

para(helpflg)
int helpflg;
{
  if (helpflg) {
    cmxprintf("parse a paragraph, using a TEXTI style form of input.\n");
  }
  else {
    static char *tmp=NULL;

    if (!paracont) {
      noise("of text");
      confirm();
      if (paratxt) free(paratxt);
      paratxt = NULL;
    }
    parafdb._cmdat = (char *)&pd;
    pd.buf = paratxt; 
    cmcls();				/* clear the screen */
    cmxprintf(" Message (End with CTRL/D\n\
  Use CTRL/B to insert a file, CTRL/E to enter editor, CTRL/K to redisplay\n\
  message, CTRL/L to clear screen and redisplay, CTRL/N to abort.):\n");
    parse(&parafdb,&parseval,&used);
    paratxt = parseval._pvpara;
    if (paratxt == NULL) {
      cmxprintf("Aborted!\n");
    }
    else {
      if (tmp) 
	free(tmp);
      tmp = (char *)malloc(strlen(paratxt)+1);
      strcpy(tmp,paratxt);
      paratxt = tmp;
      cmxprintf("Read %d chars.\n",strlen(paratxt)); fflush(stdout);
/*
 * don't free tmp, so that the continue command can still have a handle on 
 * the buffer.  It will be free()ed the next time this routine is executed.
 */
    }
    fflush(stdout);
  }
  paracont = FALSE;
}

/*
 * continue parsing a paragraph
 */
contpara(helpflg) {
  if (helpflg) {
    cmxprintf("continue editing the paragraph previously typed paragraph\n");
  }
  else {
    noise("paragraph");
    confirm();
    paracont = TRUE;
    para(helpflg);
  }
}

/*
 * take commands from some other FILE *
 */
take() {
  cmtake(toplevel);
}

/*
 * change the characters that define a comment
 * the defaults are ";", and "!" to "!".
 */
comment(helpflg) {
  static fdb qstfdb = { _CMQST, 0, NULL, NULL, NULL, NULL, NULL };
  static char xtoeol[20], xbeg[20], xend[20];
  static char toeol[20], beg[20], end[20];
  if (helpflg) {
    cmxprintf("set the CCMD comment characters");
  }
  else {
    noise("To eol");
    parse(&qstfdb,&parseval,&used);
    strcpy(xtoeol,atmbuf);
    noise("start");
    parse(&qstfdb,&parseval,&used);
    strcpy(xbeg,atmbuf);
    noise("end");
    parse(&qstfdb,&parseval,&used);
    strcpy(xend,atmbuf);
    confirm();
    strcpy(toeol,xtoeol);
    strcpy(beg,xbeg);
    strcpy(end,xend);
    cmcsb._cmntb = toeol;
    cmcsb._cmnts = beg;
    cmcsb._cmnte = end;
  }
}

/*
 * parse a password  -- don't echo.
 */
password(helpflg) int helpflg; {
  char *cmpasswd();
  char *passwd;
  if (helpflg) {
    cmxprintf("parse for a password.   Don't echo.\n");
  }
  else {
    confirm();
    passwd = cmpasswd("Password:");
    if (passwd == NULL) {
      cmxerr("?Invalid characters in password");
    }
    else cmxprintf("Password: %s\n", passwd);
  }
}

/*
 * match a string via an fdb chain.
 * currently used to expand "*" as a filename.
 */

domatch(helpflg) int helpflg; {
  static fdb matfdb = { _CMTAD ,0, NULL, NULL, NULL, NULL, NULL };
  static char *matstr = "17-May-86 13:52:06-EDT"; 
  char **p,*p1;
  int i;
  if (helpflg) {
    cmxprintf("\
match a filename against the string '*'.  This tests ccmd's ability to \n\
match strings in memory rather than having to rely on user input.\n");
  }
  else {
    int ret, parselen;
    static fdb tadfdb = { _CMTAD, 0, NULL, (pdat) 0, NULL, NULL, NULL };
    static datime dtblk, *dt;
    static char *(dows[]) = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    static char *(mths[]) = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
				  "Aug", "Sep", "Oct", "Nov", "Dec" };
    int tz;
    tzinf *tzi,*dttzinf();
    char *tzname,tzsign;
    noise(matstr);
    confirm();
    ret = match(matstr,strlen(matstr),&matfdb, &parseval, &used, &parselen);
    dt = &parseval._pvtad;
    dtblk._dtmon = dt->_dtmon;
    dtblk._dtday = dt->_dtday;
    dtblk._dtyr = dt->_dtyr;
    dtblk._dtdow = dt->_dtdow;
    dtblk._dthr = dt->_dthr;
    dtblk._dtmin = dt->_dtmin;
    dtblk._dtsec = dt->_dtsec;
    dtblk._dttz = dt->_dttz;
    dtblk._dtdst = dt->_dtdst;
    if (ret == CMxOK) {
	cmxprintf("%s %d-%s-%d ",dows[dtblk._dtdow], dtblk._dtday+1,
		  mths[dtblk._dtmon], dtblk._dtyr);
	cmxprintf("%d:%d:%d-",dtblk._dthr, dtblk._dtmin, dtblk._dtsec);
	tz = dtblk._dttz;
	tzi = dttzinf(tz);
	if (tzi == NULL) {
	    tzsign = tz < 0 ? '+' : '-';
	    if (tz < 0) tz = -tz;
	    cmxprintf("GMT%c%d:%d%s\n",tzsign,tz/60,tz%60,
		      dtblk._dtdst==0?"":" DST");
	}
	else
	    cmxprintf("%s\n",dtblk._dtdst==0?tzi->_tznam:tzi->_tzdnm);
    }
    else {
      cmxeprintf("domatch: ");
      cmperr(ret);
    }
  }
}

/*
 * give help.
 */
help(helpflg) {
  static fdb cmdfdb = { _CMKEY, 0, NULL, (pdat) &(cmdtab), "Command, ", 
			  NULL, NULL };
  static fdb hlpfdb = { _CMCFM, 0, &cmdfdb, NULL, NULL, NULL, NULL };
  
  if (helpflg) {
    cmxprintf("The help command gives help.\n");
  }
  else {
    noise("me with");
    parse(&hlpfdb,&parseval,&used);	/* parse a command */
    if (used == &hlpfdb) {
      cmxprintf("\
This is a program to test and display some of the capabilities of the CCMD\n\
package.  CCMD is a user interface package which expands upon the COMND% \n\
JSYS used as a user interface by DEC's TOPS-20.  CCMD is a library, written\n\
to be portable to many machines.  Currently, it runs under 4.2 BSD UNIX, and\n\
MS-DOS, and will hopefully soon run on a diverse set of machines.  It is\n\
hoped that this will allow a homogenious user interface to evolve on many\n\
different machines.  If you are unfamiliar with the COMND interface, try the\n\
'?' key.  Also try typing part of a command, and then hitting escape.\n");
    }
    else {
      int which = parseval._pvkey;
      confirm();
      dohelp(which);			/* help on a subject. */
    }
  }
}

/*
 * change the character to use as a beginning switch delimiter.
 */
character(helpflg) {
  static fdb swifdb = { _CMCHAR, 0, NULL, NULL, NULL, NULL, NULL };

  if (helpflg) {
    cmxprintf("set the character that switches switch on\n");
  }
  else {
    char c;
    extern char cmswbeg;

    noise("to switch on");
    parse(&swifdb,&parseval,&used);
    c = parseval._pvchr;
    confirm();
    cmswbeg = c;
  }
}

shell(helpflg) int helpflg; {
  static fdb txtfdb = { _CMTXT, 0, NULL, NULL, NULL, NULL, NULL };
  char buf[100];
  if (!helpflg) {
    noise("escape");
    parse(&txtfdb, &parseval, &used);
    strcpy(buf, "$SHELL");
    if (strlen(atmbuf) > 0) {
      strcat(buf," -c \'");
      strcat(buf, atmbuf);
      strcat(buf, "\'");
    }
    confirm();
    cmsystem(buf);
  }
  else {
    cmxprintf("fork up a shell\n");
  }
}

nil(helpflg) {
    static fdb usrfdb = { _CMUSR ,USR_WILD|USR_UPDONLY|USR_NOUPD };
    int len = 0;
    if (helpflg) {
	cmxprintf("do nothing\n");
    }
    else {
        noise("empty");
	match("", 0, &usrfdb, &parseval, &used, &len);
	confirm();
	cmxprintf("t\n");
    }
}

history(helpflg) {
    static fdb numfdb = { _CMNUM, 0, NULL, (pdat) 10, NULL, NULL, NULL };    
    int n;

    parse(&numfdb, &parseval, &used);
    n = parseval._pvint;
    confirm();
    cmhst(n);
    printf("setting history to %d\n",n);
}
