This is private.c in view mode; [Download] [Up]
//------------------------------------------------------------------------
// ^FILE: private.c - private/protected functions used by the CmdLine library
//
// ^DESCRIPTION:
// This file implements functions that are for the exclusive use of
// the CmdLine library. The following functions are implemented:
//
// ck_need_val() -- see if we left an argument without a value
// handle_arg() -- compile the string value of an argument
// syntax() -- find out the desired syntax for usage messages
// missing_args() -- check for missing required arguments
// opt_match() -- match an option
// kwd_match() -- match a keyword
// pos_match() -- match a positional parameter
//
// ^HISTORY:
// 01/09/92 Brad Appleton <brad@ssd.csd.harris.com> Created
//
// 03/03/93 Brad Appleton <brad@ssd.csd.harris.com>
// - Added exit_handler() and quit() member-functions to CmdLine
//-^^---------------------------------------------------------------------
#include <iostream.h>
#include <strstream.h>
#include <fstream.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
extern "C" {
int isatty(int fd);
#ifdef GNU_READLINE
# include <readline.h>
#endif
}
#ifndef GNU_READLINE
# ifdef unix
//# include <malloc.h> // Commented out by Ivan K.
# else
extern "C" void * malloc(size_t);
extern "C" void free(void *);
# endif
#endif
#include "cmdline.h"
#include "states.h"
#include "arglist.h"
// Need a portable version of tolower
//
// NOTE:: I would make this inline except that cfront refuses
// to inline it because it is used twice in expressions
//
#define TO_LOWER(c) ((isupper(c)) ? tolower(c) : c)
#ifdef vms
# define getenv getsym
extern const char * getsym(const char *);
#endif
//-------
// ^FUNCTION: CmdLine::handle_arg - compile the string value of an argument
//
// ^SYNOPSIS:
// extern int CmdLine::handle_arg(cmdarg, arg);
//
// ^PARAMETERS:
// CmdArg * cmdarg;
// -- the matched argument whose value is to be "handled"
//
// const char * & arg;
// -- the string value for the argument (from the command-line).
// upon exit, this will be NULL (if all of "arg" was used) or will
// point to the first character or "arg" that was not used by the
// argument's "compile" function.
//
// ^DESCRIPTION:
// After we have matched an argument on the command-line to an argument
// in the "cmd" object, we need to "handle" the value supplied for that
// argument. This entails updating the state of the argument and calling
// its "compile" function as well as updating the state of the command.
//
// ^REQUIREMENTS:
// None that weren't covered in the PARAMETERS section.
//
// ^SIDE-EFFECTS:
// - modifies the value pointed to by "arg"
// - prints a message on stderr if "arg" is invalid and QUIET is NOT set.
// - modifies the state of "cmd".
// - modifies the "value" and "flags" of "cmdarg".
//
// ^RETURN-VALUE:
// The value returned by calling the "compile" function associated
// with the argument "cmdarg".
//
// ^ALGORITHM:
// - if this is a cmdargUsage argument then print usage and call quit()
// - call the operator() of "cmdarg" and save the result.
// - if the above call returned SUCCESS then set the GIVEN and VALGIVEN
// flags of the argument.
// - update the parse_state of "cmd" if we were waiting for this value.
// - if "cmdarg" corresponds to a LIST then set things up so that succeeding
// arguments will be values for this "cmdarg"'s list.
//-^^----
int
CmdLine::handle_arg(CmdArg * cmdarg, const char * & arg)
{
++cmd_nargs_parsed; // update the number of parsed args
// call the argument compiler
const char * save_arg = arg ; // just in case someone forgets to set it
int bad_val = (*cmdarg)(arg, *this);
if (! bad_val) {
cmdarg->set(CmdArg::GIVEN) ;
cmdarg->sequence(cmd_nargs_parsed) ;
if (arg != save_arg) {
cmdarg->set(CmdArg::VALGIVEN) ;
}
}
// if we were waiting for a value - we just got it
if (arg != save_arg) {
if (cmdarg == cmd_matched_arg) cmd_parse_state = cmd_START_STATE ;
}
// if this is a list - optional values may follow the one given
if ((cmdarg->syntax() & CmdArg::isLIST) && (arg != save_arg)) {
cmd_matched_arg = cmdarg ;
cmd_parse_state = cmd_WANT_VAL ;
}
return bad_val ;
}
//-------
// ^FUNCTION: CmdLine::ck_need_val - See if an argument needs a value
//
// ^SYNOPSIS:
// extern void CmdLine::ck_needval(void)
//
// ^PARAMETERS:
// NONE.
//
// ^DESCRIPTION:
// We parse command-lines using something akin to a deterministic
// finite state machine. Each argv[] element on the command-line is
// considered a single input to the machine and we keep track of an
// associated machine-state that tells us what to do next for a given
// input.
//
// In this function, we are merely trying to query the "state" of the
// machine by asking it if it is expecting to see a value for an
// argument that was matched in a previous argv[] element.
//
// It is assumed that this function is called only after it has already
// been determined that the current argv[] element is NOT an argument
// value.
//
// ^REQUIREMENTS:
//
// ^SIDE-EFFECTS:
// - updates the "state" of the command.
// - updates the "status" of the command.
// - modifies the last matched argument if it takes an optional value.
// - prints a message on stderr if cmd_QUIET is NOT set and we were
// expecting a required value.
//
// ^RETURN-VALUE:
// None.
//
// ^ALGORITHM:
// If we were expecting an optional value then
// - set the GIVEN flag of the last arg we matched (DO NOT set VALGIVEN).
// - call the compiler-function of the last-matched arg using NULL
// as the argument (unless the arg is a LIST and VALGIVEN is set).
// - reset the command-state
// Else if we were expecting a required value then
// - print a an error message if cmd_QUIET is not set
// - set the command-status to VAL_MISSING
// - reset the command-state
// Endif
//-^^----
void
CmdLine::ck_need_val(void)
{
const char * null_str = NULL;
if (cmd_parse_state == cmd_WANT_VAL) {
// argument was given but optional value was not
cmd_matched_arg->set(CmdArg::GIVEN) ;
if ((! (cmd_matched_arg->syntax() & CmdArg::isLIST)) ||
(! (cmd_matched_arg->flags() & CmdArg::VALGIVEN))) {
(void) handle_arg(cmd_matched_arg, null_str) ;
}
cmd_parse_state = cmd_START_STATE ;
} else if (cmd_parse_state == cmd_NEED_VAL) {
// argument was given but required value was not
if (! (cmd_flags & QUIET)) {
arg_error("value required for", cmd_matched_arg) << "." << endl ;
}
cmd_status |= VAL_MISSING ;
cmd_parse_state = cmd_START_STATE ;
}
}
#ifndef GNU_READLINE
//
// readline() -- indigent person's version of the GNU readline() function
//
#define PROMPT_BUFSIZE 256
static char *
readline(const char * prompt)
{
char * buf = (char *) ::malloc(PROMPT_BUFSIZE);
if (buf == NULL) return NULL ;
*buf = '\0';
// prompt the user and collect input
cerr << prompt << flush ;
cin.getline(buf, PROMPT_BUFSIZE);
return buf ;
}
#endif // ! GNU_READLINE
//-------
// ^FUNCTION: CmdLine::prompt_user - prompt the user for a missing argument
//
// ^SYNOPSIS:
// unsigned CmdLine::prompt_user(cmdarg);
//
// ^PARAMETERS:
// CmdArg * cmdarg;
// -- the argument that we need to prompt for
//
// ^DESCRIPTION:
// If cin is connected to a terminal, then we will prompt the user
// for an argument corresponding to "cmdarg" and attempt to "compile" it
// into the desired internal format. The user only has one chance
// to get the "argument" right; we do not continue prompting if the
// value that was entered is invalid.
//
// ^REQUIREMENTS:
// "cmdarg" should be a REQUIRED argument that has already been determined
// to be missing from the command-line.
//
// ^SIDE-EFFECTS:
// - modifies the status of the command.
// - modifies "cmdarg".
// - prints a prompt on cerr and reads from cin
//
// ^RETURN-VALUE:
// 0 if the argument was succesfully entered by the user,
// ARG_MISSING otherwise.
//
// ^ALGORITHM:
// - if cin is not a terminal return ARG_MISSING.
// - if "cmdarg" is a LIST, make sure we prompt the use once for each
// possible value in the list.
// - prompt the user for an argument and read the result.
// - if the user just typed <RETURN> return ARG_MISSING.
// - "handle" the value that was entered.
// - continue prompting if we are a LIST and a valid, non-empty,
// value was given
// - if an invalid value was given return ARG_MISSING
// - else return 0
//-^^----
unsigned
CmdLine::prompt_user(CmdArg * cmdarg)
{
// dont prompt if cin or cerr is not interactive
int fd = ((filebuf *)(cin.rdbuf()))->fd();
if (! ::isatty(fd)) return ARG_MISSING ;
fd = ((filebuf *)(cerr.rdbuf()))->fd();
if (! ::isatty(fd)) return ARG_MISSING ;
// if we have a list, need to prompt repeatedly
if (cmdarg->syntax() & CmdArg::isLIST) {
cerr << "Enter one " << cmdarg->value_name() << " per line "
<< "(enter a blank-line to stop)." << endl ;
}
char prompt[256], * buf = NULL;
ostrstream oss(prompt, sizeof(prompt));
oss << "\rEnter " << cmdarg->value_name() << ": " << ends ;
int errs = 0, first = 1;
do { // need repeated prompting for a LIST
if (buf) ::free(buf);
buf = ::readline(prompt) ;
if (buf == NULL) return ARG_MISSING ;
// make sure we read something!
if (! *buf) {
if (first) {
error() << "error - no " << cmdarg->value_name()
<< " given!" << endl ;
++errs;
}
continue;
}
#ifdef GNU_READLINE
// add this line to the history list
::add_history(buf);
#endif
// try to handle the value we read (remember - buf is temporary)
if (! errs) {
const char * arg = buf;
unsigned save_cmd_flags = cmd_flags;
cmd_flags |= TEMP;
errs = handle_arg(cmdarg, arg);
if (errs) {
arg_error("bad value for", cmdarg) << "." << endl ;
}
cmd_flags = save_cmd_flags;
}
first = 0;
} while (!errs && (cmdarg->syntax() & CmdArg::isLIST) && *buf);
if (! errs) cmdarg->set(CmdArg::VALSEP);
if (buf) ::free(buf);
return (errs) ? ARG_MISSING : NO_ERROR ;
}
//-------
// ^FUNCTION: CmdLine::syntax - determine usage message syntax
//
// ^SYNOPSIS:
// CmdLine::CmdLineSyntax CmdLine::syntax(void);
//
// ^PARAMETERS:
//
// ^DESCRIPTION:
// One of the things we keep track of in the CmdLine object is whether
// options and/or keywords (long-options) were used on the command-line.
// If a command-line syntax error occurs and only options (keywords)
// were used then the usage message will only contain option (keyword)
// syntax. If BOTH were used or if usage was specifically requested via
// a cmdargUsage option (which we also keep track of) then we want the
// the usage message to contain the syntac for both options and keywords.
//
// If neither options nor keywords were given (meaning only positional
// parameters were used) then we only use option-syntax (for brevity).
//
// ^REQUIREMENTS:
//
// ^SIDE-EFFECTS:
// None.
//
// ^RETURN-VALUE:
// The desired usage message syntax to use.
//
// ^ALGORITHM:
// Trivial.
//-^^----
CmdLine::CmdLineSyntax
CmdLine::syntax(void) const
{
if (cmd_flags & KWDS_ONLY) {
return cmd_KWDS_ONLY;
} else if (cmd_flags & OPTS_ONLY) {
return cmd_OPTS_ONLY;
} else if ((cmd_state & cmd_OPTIONS_USED) &&
(cmd_state & cmd_KEYWORDS_USED)) {
return cmd_BOTH ;
} else if (cmd_state & cmd_KEYWORDS_USED) {
return cmd_KWDS_ONLY ;
} else {
return cmd_OPTS_ONLY ;
}
}
//-------
// ^FUNCTION: CmdLine::missing_args - check for missing required arguments
//
// ^SYNOPSIS:
// unsigned CmdLine::missing_args(void);
//
// ^PARAMETERS:
//
// ^DESCRIPTION:
// This function checks to see if there is a required argument in the
// CmdLine object that was NOT specified on the command. If this is
// the case and PROMPT_USER is set (or $PROMPT_USER exists and is
// non-empty) then we attempt to prompt the user for the missing argument.
//
// ^REQUIREMENTS:
//
// ^SIDE-EFFECTS:
// - modifies the status of "cmd".
// - terminates execution by calling quit() if cmd_NOABORT is NOT
// set and a required argument (that was not properly supplied by
// the user) is not given.
// - prints on stderr if an argument is missing and cmd_QUIET is NOT set.
// - also has the side-effects of prompt_user() if we need to prompt
// the user for input.
//
// ^RETURN-VALUE:
// The current value of the (possibly modified) command status. This is a
// combination of bitmasks of type cmdline_flags_t defined in <cmdline.h>
//
// ^ALGORITHM:
// Foreach argument in cmd
// if argument is required and was not given
// if required, prompt for the missing argument
// if prompting was unsuccesful add ARG_MISSING to cmd-status
// endif
// else add ARG_MISSING to cmd-status
// endif
// endif
// endfor
// return the current cmd-status
//-^^----
unsigned
CmdLine::missing_args(void)
{
char buf[256];
CmdArgListListIter list_iter(cmd_args);
for (CmdArgList * alist = list_iter() ; alist ; alist = list_iter()) {
CmdArgListIter iter(alist);
for (CmdArg * cmdarg = iter() ; cmdarg ; cmdarg = iter()) {
if (cmdarg->is_dummy()) continue;
if ((cmdarg->syntax() & CmdArg::isREQ) &&
(! (cmdarg->flags() & CmdArg::GIVEN)))
{
if (! (cmd_flags & QUIET)) {
fmt_arg(cmdarg, buf, sizeof(buf), syntax(), TERSE_USAGE);
error() << buf << " required." << endl ;
}
if (cmd_status & ARG_MISSING) {
// user didnt supply the missing argument
return cmd_status ;
} else if ((! (cmd_flags & NO_ABORT)) && cmd_status) {
// other problems
return cmd_status ;
} else if (cmd_flags & PROMPT_USER) {
cmd_status |= prompt_user(cmdarg);
} else {
char * env = ::getenv("PROMPT_USER");
if (env && *env) {
cmd_status |= prompt_user(cmdarg);
} else {
cmd_status |= ARG_MISSING ;
}
}
} //if
} //for iter
} //for list_iter
return cmd_status ;
}
//-------
// ^FUNCTION: CmdLine::opt_match - attempt to match on option
//
// ^SYNOPSIS:
// CmdArg * CmdLine::opt_match(optchar);
//
// ^PARAMETERS:
// char optchar;
// -- a possible option for "cmd"
//
// ^DESCRIPTION:
// If "cmd" has an argument that has "optchar" as a single-character
// option then this function will find and return that argument.
//
// ^REQUIREMENTS:
//
// ^SIDE-EFFECTS:
// None.
//
// ^RETURN-VALUE:
// If we find a match, then we return a pointer to its argdesc,
// otherwise we return NULL.
//
// ^ALGORITHM:
// Trivial.
//-^^----
CmdArg *
CmdLine::opt_match(char optchar) const
{
CmdArgListListIter list_iter(cmd_args);
for (CmdArgList * alist = list_iter() ; alist ; alist = list_iter()) {
CmdArgListIter iter(alist);
for (CmdArg * cmdarg = iter() ; cmdarg ; cmdarg = iter()) {
if (cmdarg->is_dummy()) continue;
if (optchar == cmdarg->char_name()) {
// exact match
return cmdarg ;
} else if (cmd_flags & ANY_CASE_OPTS) {
// case-insensitive match
if (TO_LOWER(optchar) == TO_LOWER(cmdarg->char_name())) {
return cmdarg ;
}
}
} //for iter
} //for list_iter
return NULL ;
}
//-------
// ^FUNCTION: CmdLine::kwd_match - purpose
//
// ^SYNOPSIS:
// extern CmdArg * CmdLine::kwd_match(kwd, len, is_ambiguous, match_value);
//
// ^PARAMETERS:
// const char * kwd;
// -- a possible kewyord of "cmd"
//
// int len;
// -- the number of character of "kwd" to consider (< 0 if all characters
// of "kwd" should be used).
//
// int & is_ambiguous;
// -- upon return, the value pointed to is set to 1 if the keyword
// matches more than 1 keyword in "cmd"; Otherwise it is set to 0.
//
// int match_value;
// -- if this is non-zero, then if a keyword_name is NULL use the
// value_name instead.
//
// ^DESCRIPTION:
// If "cmd" has an argument that matches "kwd" as a kewyord
// then this function will find and return that argument.
//
// ^REQUIREMENTS:
//
// ^SIDE-EFFECTS:
// None.
//
// ^RETURN-VALUE:
// If we find a match, then we return a pointer to its argdesc,
// otherwise we return NULL.
//
// ^ALGORITHM:
// Set is_ambigous to 0.
// For each argument in cmd
// if argument's keyword-name matches kwd then
// if this was an exact match then return this argument
// else if we had a previous partial match of this argument then
// if argument is a default argument return the previous match
// else set is_ambiguous to 1 and return NULL
// else remember we had a partial match here and keep trying
// endif
// endif
// end for
// if we has a partial match and we get to here then it is NOT ambiguous do
// go ahead and return the argument we matched.
//-^^----
CmdArg *
CmdLine::kwd_match(const char * kwd,
int len,
int & is_ambiguous,
int match_value) const
{
CmdArg * matched = NULL;
is_ambiguous = 0 ;
CmdArgListListIter list_iter(cmd_args);
for (CmdArgList * alist = list_iter() ; alist ; alist = list_iter()) {
CmdArgListIter iter(alist);
for (CmdArg * cmdarg = iter() ; cmdarg ; cmdarg = iter()) {
if (cmdarg->is_dummy()) continue;
// attempt to match this keyword
strmatch_t result = str_NONE ;
const char * source = cmdarg->keyword_name();
if (source && *source) {
result = strmatch(source, kwd, len) ;
} else if (match_value) {
result = strmatch(cmdarg->value_name(), kwd, len) ;
}
if (result == str_EXACT) {
return cmdarg ;
} else if (result == str_PARTIAL) {
if (matched) {
is_ambiguous = 1 ;
return NULL ; // ambiguous keyword
}
matched = cmdarg ; // we matched this one
}
} //for iter
if (matched) break;
} //for list_iter
return matched ;
}
//-------
// ^FUNCTION: CmdLine::pos_match - match a positional argument
//
// ^SYNOPSIS:
// CmdArg * CmdLine::pos_match(void)
//
// ^PARAMETERS:
//
// ^DESCRIPTION:
// If "cmd" has an positional argument that has not yet been given,
// or that corresponds to a list, then this function will find and
// return the first such argument.
//
// ^REQUIREMENTS:
//
// ^SIDE-EFFECTS:
// None.
//
// ^RETURN-VALUE:
// If we find a match, then we return a pointer to its argument,
// otherwise we return NULL.
//
// ^ALGORITHM:
// First look for the first unmatched positional argument!!
// If there aren't any, then look for the LAST positional list!
//-^^----
CmdArg *
CmdLine::pos_match(void) const
{
CmdArg * last_pos_list = NULL;
CmdArgListListIter list_iter(cmd_args);
for (CmdArgList * alist = list_iter() ; alist ; alist = list_iter()) {
CmdArgListIter iter(alist);
for (CmdArg * cmdarg = iter() ; cmdarg ; cmdarg = iter()) {
if (cmdarg->is_dummy()) continue;
if (cmdarg->syntax() & CmdArg::isPOS) {
if (! (cmdarg->flags() & CmdArg::GIVEN)) {
return cmdarg ;
} else if (cmdarg->flags() & CmdArg::isLIST) {
last_pos_list = cmdarg;
}
}
} //for iter
} //for list_iter
return last_pos_list ;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.