home *** CD-ROM | disk | FTP | other *** search
- //------------------------------------------------------------------------
- // ^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>
- # 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 ;
- 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
- // 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:
- // Trivial.
- //-^^----
- CmdArg *
- CmdLine::pos_match(void) 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 ((cmdarg->syntax() & CmdArg::isPOS) &&
- (! (cmdarg->flags() & CmdArg::GIVEN)))
- {
- return cmdarg ;
- }
- } //for iter
- } //for list_iter
- return NULL ;
- }
-