#ifndef NO_IDENT
static char *Id = "$Id: dirarg.c,v 1.8 1995/10/21 18:17:55 tom Exp $";
#endif

/*
 * Title:	dirarg.c
 * Author:	T.E.Dickey
 * Created:	23 Jun 1984
 * Last update:
 *		19 Feb 1995, prototypes
 *		11 Oct 1988, corrected place where "/o" alone breaks code.
 *		21 Sep 1985, translate SET/SHOW to '/', '?' prefix
 *		18 Sep 1985, process non-DCL commands here also
 *		09 Sep 1985, must trim trailing '.' from FNAME-field
 *		05 Jul 1985, corrected release of dcl-list if error.
 *		03 Jul 1985, cleanup of 'filelist' definition
 *		14 May 1985, added log-file entry for implicit arguments
 *		15 Dec 1984, added 'cpy_dft' argument to 'dclarg'.
 *		17 Oct 1984, omit extra space after 0th token
 *		25 Aug 1984, cleanup of buffer limits
 *		03 Aug 1984, added call to 'sysgetsym'
 *		25 Jul 1984, added 'cmd_arg' parameter to 'dclarg()'
 *		15 Jul 1984, use PATHOF()
 *		13 Jul 1984, protect 'warn' by "%s" format
 *		04 Jul 1984
 *
 * Function:	This module performs syntax checking on the command text
 *		which is received by 'dircmd' for FLIST.  The command-name
 *		may be translated if it is a symbol (to permit FLIST to
 *		associate it with one of its "known" commands).
 *
 *		The argument portion of the string is checked.  The prefix
 *		"/o" and the command name are already parsed off, and leading
 *		blanks removed.  This module is called before executing the
 *		interpretation routines.
 *
 * Parameters:	dcl__ =>  pointer to load with linked-list address of parsed
 *			  DCL tokens.  The 0th entry of this list will be the
 *			  resulting command name.  Subsequent entries are the
 *			  options and filespecs, parsed by 'dclarg'.  All
 *			  '.dcl_text' items are uppercased by the parsed.
 *			  The '?' or '/' prefix is stripped from the command
 *			  verb (the first entry).
 *		curfile	= index to nominal current file index in 'filelist[]'.
 *			  It is used directly when the "/" substitution is
 *			  performed.
 *		argstr	= buffer containing the argument text.  It may be a
 *			  null pointer, in which case the string "" or "/"
 *			  is assumed, according to whether 'minarg' is 0 or 1.
 *		command	= string defining full name of CLI command to execute.
 *			  Since it may be a logical symbol, this procedure
 *			  must first test that, then perform a substitution
 *			  and parse.  Because 'dclarg' only knows about
 *			  defaulting items in argument lists, the command
 *			  entry must be re-adjusted.
 *		omit	= TRUE if user gave "/o" token.
 *
 * Returns:	A pointer to a character string representing the simple
 *		("/-token and logical symbol) substitution of the command
 *		and argument string.  If a process must be spawned by the
 *		command interpreter, this string is used as the actual
 *		command string, rather than the concatenated DCLARG list
 *		(due to the length, and to the level of substitution performed).
 *
 *		The command-verb is fully-substituted in this string.  The
 *		string is in lowercase.  The SET/SHOW ('/', '?') prefix is
 *		retained for use in error-checking.
 *
 *		The return value is NULL if an error condition was detected
 *		(an appropriate message is emitted via 'warn').  This procedure
 *		uses 'dclarg/dclchk' to check for syntax errors.  Further
 *		tests must be made for proper number of arguments, etc.
 *
 *		If no error is detected, garbage collection should be done
 *		on the returned string, and on the DCLARG list '*dcl__',
 *		after use.  (If an error is detected here, they are already
 *		released.)
 */

#include	<stdlib.h>
#include	<stdio.h>
#include	<ctype.h>
#include	<string.h>

#include	<rms.h>

#include	"flist.h"
#include	"dclarg.h"
#include	"dircmd.h"
#include	"freelist.h"

#include	"strutils.h"
#include	"sysutils.h"

import(filelist);

char	*dirarg (
	DCLARG	**dcl__,
	int	curfile,
	char	*argstr,
	char	*command,
	int	omit)
{
FILENT	*z	= FK_(curfile);
DCLARG	*dcl_;
VCMD2	*vcmd_	= 0;
int	cmdnum,			/* Index into 'vcmd2[]' table	*/
	cmdlen,			/* length of command-verb	*/
	cpy_dft	= FALSE,	/* Assume command is not COPY	*/
	non_DCL	= FALSE,	/* Assume command uses DCL-syntax */
	len,
	alias	= FALSE,	/* TRUE iff 'command' is symbol	*/
	subs	= FALSE,	/* TRUE iff '/' token found	*/
	havelen,		/* Current length of 'cmd[]'	*/
	twoparm	= FALSE,	/* TRUE if COPY, APPEND, etc.	*/
	used	= FALSE;	/* Used "/" current-file token	*/
char	filespec[MAX_PATH],
	symbol1	[MAX_PATH],	/* expansion via 'sysgetsym()'	*/
	symbol2	[MAX_PATH],	/* command-part of expansion	*/
	symbol3	[MAX_PATH],	/* SET/SHOW translation		*/
	*dft_	= 0,
	*cmd,
	*i_,	*o_;

	/*
	 * Numbers are a special case: they are used solely for positioning.
	 * The '/' command is also used only for positioning.  These are
	 * parsed elsewhere.
	 */
	if (isdigit(*command) || !strcmp(command, "/"))	return (nullC);

	dirent_glue (filespec, z);

	cmd	= calloc (1, havelen = MAX_PATH);

	/*
	 * If the given keyword is a FLIST-recognized command, or if the user
	 * has assigned a symbol which equates to it, set 'cmdnum' to the
	 * corresponding VCMD2-table index.  Otherwise, let the command fall-
	 * through to be treated as a non-FLIST command
	 */
#define	MODE(m)	(vcmd_->v_mode & (m))
#define	CMDNUM(t)  (cmdnum = dircmd_vcmd2 (t, strlen(t)))

	if (CMDNUM(command) < 0)
	{
		if (sysgetsym (symbol1, command, sizeof(symbol1)))
		{
			strlcpy (symbol1, nullC); /* lowercase for parse */
			len = dclarg_spec (symbol1, "@") - symbol1;
			strncpy (symbol2, symbol1, len);
			symbol2[len] = EOS;
			if (CMDNUM(symbol2) >= 0)
			{
				alias	= TRUE;
				o_	= calloc (1, strlen(argstr)
						   + strlen(symbol1));
				sprintf (o_, "%s%s", &symbol1[len], argstr);
				argstr	= o_;
				command	= symbol2;
			}
		}
	}

	/*
	 * FLIST's '/' and '?' prefixes are analogous to VMS 'SET' and 'SHOW'
	 * commands.  Translate 'SET' and 'SHOW' verbs into FLIST-format.
	 */
	if (cmdnum == 0 || cmdnum == 1)
	{
		strcpy (symbol3, (cmdnum ? "/" : "?"));
		strcpy (&symbol3[1], strskps(argstr));
		len = dclarg_spec (symbol3+1, "@") - symbol3;
		if (!alias)	argstr = calloc(1, strlen(symbol3));
		strcpy (argstr, &symbol3[len]);
		alias = TRUE;
		symbol3[len] = EOS;
		command = symbol3;
		CMDNUM(command);
	}

	/*
	 * If the command-verb is recognizable, save its state:
	 */
	if (cmdnum >= 0)
	{
		vcmd_	= dircmd_full (cmdnum);
		command	= vcmd_->v_full;
		omit	= omit || MODE(v_OMIT) || MODE(v_nonDCL);
		twoparm	= !omit && MODE(v_1_OUT + v_M_OUT);
		dft_	= vcmd_->v_dfts;
	}
	else
		omit	= TRUE;

	strcpy (cmd, command);		/* Start with 0th entry */
	i_	= argstr;
	o_	= strnull (cmd);
	cmdlen	= strlen (cmd);

	/*
	 * Substitute path,name,type,version tokens as required.
	 */
	while (*i_)
	{
#define	C	i_[1]
		subs	= FALSE;
		if ((i_[0] == '/')
		&&  isalnum(i_[1]) && !isalnum(i_[2]))
		{
			subs	= TRUE;
			switch (C)
			{
			case 'p':	strcat (o_, zPATHOF(z));	break;
			case 'n':	dirent_cat_n (o_, z);		break;
			case 't':	strcat (o_, z->ftype);		break;
			case 'v':	sprintf (o_, "%d", z->fvers);	break;
			default:	subs	= FALSE;
					strcat (o_, "/");
			}
			i_++;
			if (subs)	i_++;
		}
		/*
		 * Permit current-file substitution anywhere a '/' is found
		 * next to a character which would make it an error (or at
		 * least unlikely) for VMS syntax:
		 */
		else if (*i_ == '/'
		&& (isspace(C) || C == ',' || C == ')' || C == '/' || !C))
		{
			subs	= TRUE;
			i_++;
			if (!used)
			{
				strcpy (o_, filespec);
				used	= TRUE;
			}
		}
		else
		{
			*o_++	= *i_++;
			*o_	= EOS;
		}

		/*
		 * Re-check the allocated length of the substituted string.
		 * It may be much longer than the input string.  Reallocate
		 * if necessary, so that the next substitution will have
		 * enough space for a full filename.
		 */
		o_	= strnull (o_);
		if (subs)
		{
			if ((strlen (i_) + strlen (cmd) + MAX_PATH) > havelen)
			{
				cmd	= realloc (cmd, havelen += MAX_PATH);
				o_	= strnull (cmd);
			}
		}
	}
	*o_	= EOS;

	/*
	 * If the current entry hasn't been used, and the command mode implies
	 * its inclusion, put it on the end of the command string.  If this is
	 * a two-parameter command (e.g., COPY), at the same time trim the
	 * version number from the text so that the user will be able to run
	 * COPY in a natural manner (i.e., create a new version).
	 */
	if (!omit)
	{
		if (! used)
		{
			if (twoparm)
			{
				FILENT	ztmp	= *z;
				ztmp.fvers	= 0;
				dirent_glue (filespec, &ztmp);
			}
			sprintf (o_, " %s", filespec);
		}
		flist_log ("! %.80s", filespec);
	}
	else if (subs)
		flist_log ("! %.80s", &cmd[cmdlen]);

	if (alias)	cfree (argstr);	/* release locally-acquired bfr	*/

	/*
	 * All "normal" commands fall through this point, including things
	 * like COPY and APPEND (which do not propagate the pathname to the
	 * output specification).  Do a quick check to see if we have one
	 * of these special cases.
	 */
	o_ = cmd;
	if (*o_ == '/' || *o_ == '?')
		o_++;

	if ((cmdnum = dircmd_vcmd2(cmd, dclarg_spec(o_, "@") - cmd)) >= 0)
	{
		vcmd_	= dircmd_full(cmdnum);
		cpy_dft	= MODE(v_COPY);
		non_DCL	= MODE(v_nonDCL);
	}

	/*
	 * Parse the resulting command:
	 */
	dcl_	= dclarg (o_, dft_, non_DCL ? strlen(o_) : 1, cpy_dft);
	if (dclchk (dcl_, cmd))		/* 'cmd' is altered iff an error */
	{
		warn ("%s", cmd);
		cfree (cmd);
		freelist (dcl_);
		return (nullC);		/* error was found	*/
	}

	/*
	 * Restore '?' (normally illegal-DCL), and set command-name in
	 * uppercase to ease later comparisons.
	 */
	if (dcl_ && dcl_->dcl_text)
		strucpy (dcl_->dcl_text, nullC);
	else {
		cfree (cmd);
		return (nullC);
	}
	/*
	 * Return pointers to DCLARG-list, and to substituted commands:
	 */
	*dcl__	= dcl_;
	return (cmd);			/* No error found	*/
}
