home *** CD-ROM | disk | FTP | other *** search
- //------------------------------------------------------------------------
- // ^FILE: cmdparse.c - implementation of the CmdParseCommand
- //
- // ^DESCRIPTION:
- // This file implements the member functions of the CmdParseCommand
- // class.
- //
- // ^HISTORY:
- // 04/26/92 Brad Appleton <brad@ssd.csd.harris.com> Created
- //
- // 03/01/93 Brad Appleton <brad@ssd.csd.harris.com>
- // - Added ALLOW_PLUS to list of CmdLine configuration flags
- //-^^---------------------------------------------------------------------
-
- #include <stdlib.h>
- #include <iostream.h>
- #include <fstream.h>
- #include <strstream.h>
- #include <string.h>
- #include <ctype.h>
-
- #include "argtypes.h"
- #include "cmdparse.h"
- #include "syntax.h"
- #include "quoted.h"
-
- enum { SUCCESS = 0, FAILURE = -1 } ;
-
- enum { MAX_IDENT_LEN = 64, MAX_DESCRIPTION_LEN = 1024 } ;
-
-
- extern "C" {
- int isatty(int fd);
- }
-
- //------------------------------------------------------------------------ copy
-
- //-------------------
- // ^FUNCTION: copy - copy a string
- //
- // ^SYNOPSIS:
- // copy(dest, src)
- //
- // ^PARAMETERS:
- // char * & dest;
- // -- where to put the copy we make
- //
- // const char * src;
- // -- the string to copy
- //
- // ^DESCRIPTION:
- // This function duplicates its second parameter to its first parameter.
- //
- // ^REQUIREMENTS:
- // None.
- //
- // ^SIDE-EFFECTS:
- // "dest" is modified to point to the newly allocated and copied result.
- //
- // ^RETURN-VALUE:
- // None.
- //
- // ^ALGORITHM:
- // Trivial.
- //-^^----------------
- static void
- copy(char * & dest, const char * src)
- {
- if (src == NULL) return ;
- dest = new char[::strlen(src) + 1] ;
- if (dest) ::strcpy(dest, src);
- }
-
- //------------------------------------------------------------------ CmdArgVers
-
- CmdArgVers::CmdArgVers(char opt, const char * kwd, const char * description)
- : CmdArg(opt, kwd, description, CmdArg::isOPT)
- {
- }
-
- CmdArgVers::~CmdArgVers(void)
- {
- }
-
- int
- CmdArgVers::operator()(const char * & , CmdLine & cmd)
- {
- cerr << cmd.name() << "\trelease " << cmd.release()
- << " at patchlevel " << cmd.patchlevel() << endl ;
- cmd.quit(e_VERSION);
- return 0; // shutup the compiler about not returning a value
- }
-
- //------------------------------------------------------------- CmdParseCommand
-
- //-------------------
- // ^FUNCTION: CmdParseCommand::parse_declarations - parse user arguments
- //
- // ^SYNOPSIS:
- // CmdParseCommand::parse_declarations()
- //
- // ^PARAMETERS:
- // None.
- //
- // ^DESCRIPTION:
- // This routine will go through all the input sources that were supplied
- // on the command-line. Each of these "input sources" is something that
- // contains user argument declarations that need to be parsed. If no
- // input sources were given and cin is not connected to a terminal, then
- // we try to read the declarations from cin.
- //
- // If input sources were given, they are parsed in the following order:
- // 1) from a string
- // 2) from an environment variable
- // 3) from a file
- //
- // If more than one input source is specified then they are processed in
- // the above order and each argument is appended to the user's CmdLine
- // object in the order that it was seen.
- //
- // ^REQUIREMENTS:
- // This routine should be called by CmdParseCommand::operator() after
- // the command-line has been parsed.
- //
- // ^SIDE-EFFECTS:
- // If input comes from a file, then the file is read (until eof or an
- // error condition occurs).
- //
- // Any arguments found are "compiled" and appended to "usr_cmd", the user's
- // CmdLine object.
- //
- // ^RETURN-VALUE:
- // 0 upon success, non-zero upon failure.
- //
- // ^ALGORITHM:
- // Follow along - its pretty straightforward.
- //-^^----------------
- int
- CmdParseCommand::parse_declarations(void)
- {
- const char * str = input_str ;
- const char * varname = input_var ;
- const char * filename = input_file ;
-
- // If no "input sources" were specified, try cin.
- if ((str == NULL) && (varname == NULL) && (filename == NULL)) {
- // Make sure cin is NOT interactive!
- int fd = ((filebuf *)(cin.rdbuf()))->fd();
- if (::isatty(fd)) {
- error() << "Can't read argument declarations from a terminal."
- << endl ;
- return -1;
- }
- return parse_declarations(cin);
- }
-
- int rc = 0;
-
- // First - parse from the string
- if (str) {
- rc += parse_declarations(str);
- }
-
- // Second - parse from the environment variable
- if (varname) {
- const char * contents = ::getenv(varname);
- if (contents) {
- rc += parse_declarations(contents);
- } else {
- error() << varname << " is empty or is undefined." << endl ;
- return -1;
- }
- }
-
- // Third - parse from the file. If the filename is "-" then it
- // means that standard input should be used.
- //
- if (filename) {
- if (::strcmp(filename, "-") == 0) {
- rc += parse_declarations(cin);
- } else {
- ifstream ifs(filename);
- if (ifs) {
- rc += parse_declarations(ifs);
- } else {
- error() << "Unable to read from " << filename << '.' << endl ;
- return -1;
- }
- }
- }
-
- return rc;
- }
-
-
- //-------------------
- // ^FUNCTION: CmdParseCommand::usr_append - add a user argument
- //
- // ^SYNOPSIS:
- // CmdParseCommand::usr_append(type, varname, arg, description)
- //
- // ^PARAMETERS:
- // const char * type;
- // -- the name of the desired argument type (which should correspond
- // to either CmdArgUsage, CmdArgDummy, or one of the types defined
- // in "argtypes.h").
- //
- // const char * varname;
- // -- the name of the shell variable that will be receiving the value
- // that was supplied to this argument on the command-line.
- //
- // ArgSyntax & arg;
- // -- the argument syntax corresponding to the "syntax-string" supplied
- // by the user.
- //
- // const char * description;
- // -- the user's description of this argument.
- //
- // ^DESCRIPTION:
- // This member function will create a corresponding instance of a derived
- // class of CmdArg named by "type" and will append it to the user's CmdLine
- // object (named usr_cmd).
- //
- // "type" may be one of the following (the leading "CmdArg" prefix may be
- // omitted):
- //
- // CmdArgInt, CmdArgFloat, CmdArgChar, CmdArgStr, CmdArgBool,
- // CmdArgSet, CmdArgClear, CmdArgToggle, CmdArgUsage, CMdArgDummy
- //
- // It is NOT necessary to use the name of a "List" CmdArg type in order
- // to indicate a list, that will have been inferred from the syntax string
- // and the appropriate ShellCmdArg<Type> object that we create will know
- // how to handle it.
- //
- // ^REQUIREMENTS:
- // This function should be called after an argument declaration has been
- // completely parsed.
- //
- // ^SIDE-EFFECTS:
- // If "type" is invalid - an error is printed on cerr, otherwise
- // we create an appopriate CmdArg<Type> object and append it to usr_cmd.
- //
- // ^RETURN-VALUE:
- // 0 for success; non-zero for failure.
- //
- // ^ALGORITHM:
- // Trivial - there's just a lot of type-names to check for.
- //-^^----------------
- int
- CmdParseCommand::usr_append(const char * type,
- const char * varname,
- ArgSyntax & arg,
- const char * description)
- {
- char * name = NULL ;
- char * kwd = NULL ;
- char * val = NULL ;
- char * desc = NULL ;
- unsigned flags = arg.syntax() ;
- char opt = arg.optchar() ;
-
- // Need to make copies of some things because we cant assume they
- // will be sticking around. We assume that ShellCmdArg::~ShellCmdArg
- // will deallocate this storage.
- //
- ::copy(name, varname);
- ::copy(kwd, arg.keyword());
- ::copy(val, arg.value());
- ::copy(desc, description);
-
- // Skip any leading "Cmd", "Arg", or "CmdArg" prefix in the type-name.
- if (CmdLine::strmatch("Cmd", type, 3) == CmdLine::str_EXACT) type += 3;
- if (CmdLine::strmatch("Arg", type, 3) == CmdLine::str_EXACT) type += 3;
-
- // Create an argument for the appropriate type and append it
- // to usr_cmd.
- //
- if (CmdLine::strmatch("Usage", type) == CmdLine::str_EXACT) {
- delete [] name ;
- usr_cmd.append(new CmdArgUsage(opt, kwd, desc)) ;
- }
- else if (CmdLine::strmatch("Dummy", type) == CmdLine::str_EXACT) {
- delete [] name ;
- usr_cmd.append(new CmdArgDummy(opt, kwd, val, desc, flags));
- }
- else if (CmdLine::strmatch("Set", type) == CmdLine::str_EXACT) {
- usr_cmd.append(new ShellCmdArgBool(name, opt, kwd, desc, flags));
- }
- else if (CmdLine::strmatch("Clear", type) == CmdLine::str_EXACT) {
- usr_cmd.append(new ShellCmdArgClear(name, opt, kwd, desc, flags));
- }
- else if (CmdLine::strmatch("Toggle", type) == CmdLine::str_EXACT) {
- usr_cmd.append(new ShellCmdArgToggle(name, opt, kwd, desc, flags));
- }
- else if (CmdLine::strmatch("Boolean", type) != CmdLine::str_NONE) {
- usr_cmd.append(new ShellCmdArgBool(name, opt, kwd, desc, flags));
- }
- else if (CmdLine::strmatch("Integer", type) != CmdLine::str_NONE) {
- usr_cmd.append(new ShellCmdArgInt(name, opt, kwd, val, desc, flags));
- }
- else if (CmdLine::strmatch("Float", type) != CmdLine::str_NONE) {
- usr_cmd.append(new ShellCmdArgFloat(name, opt, kwd, val, desc, flags));
- }
- else if (CmdLine::strmatch("Character", type) != CmdLine::str_NONE) {
- usr_cmd.append(new ShellCmdArgChar(name, opt, kwd, val, desc, flags));
- }
- else if (CmdLine::strmatch("String", type) != CmdLine::str_NONE) {
- usr_cmd.append(new ShellCmdArgStr(name, opt, kwd, val, desc, flags));
- }
- else {
- cerr << "Unknown argument type \"" << type << "\"." << endl ;
- delete [] kwd ;
- delete [] val ;
- delete [] desc ;
- return FAILURE ;
- }
-
- return SUCCESS ;
- }
-
-
- //-------------------
- // ^FUNCTION: CmdParseCommand::parse_declarations - parse from a string
- //
- // ^SYNOPSIS:
- // CmdParseCommand::parse_declarations(str);
- //
- // ^PARAMETERS:
- // const char * str;
- // -- the string containing the argument declarations.
- //
- // ^DESCRIPTION:
- // Parse the user's argument declarations from an input string.
- //
- // ^REQUIREMENTS:
- // This member function should only be called by parse_declarations(void).
- //
- // ^SIDE-EFFECTS:
- // - modifies usr_cmd by appending to it any valid arguments that we parse.
- //
- // ^RETURN-VALUE:
- // 0 for success; non-zero for failure.
- //
- // ^ALGORITHM:
- // Just turn the string into an instream and parse the instream.
- //-^^----------------
- int
- CmdParseCommand::parse_declarations(const char * str)
- {
- int rc = 0;
- char * strbuf = new char[::strlen(str) + 1] ;
- (void) ::strcpy(strbuf, str);
- istrstream iss(strbuf);
- rc = parse_declarations(iss);
- delete strbuf ;
- return rc ;
- }
-
-
-
- //-------------------
- // ^FUNCTION: CmdParseCommand::parse_declarations - parse from an instream
- //
- // ^SYNOPSIS:
- // CmdParseCommand::parse_declarations(is);
- //
- // ^PARAMETERS:
- // istream & is;
- // -- the instream containing the argument declarations.
- //
- // ^DESCRIPTION:
- // Parse the user's argument declarations from an input steam.
- //
- // ^REQUIREMENTS:
- // This member function should only be called by parse_declarations(void).
- //
- // ^SIDE-EFFECTS:
- // - modifies usr_cmd by appending to it any valid arguments that we parse.
- //
- // ^RETURN-VALUE:
- // 0 for success; non-zero for failure.
- //
- // ^ALGORITHM:
- // while not eof do
- // - read the type
- // - read the name
- // - read the syntax
- // - read the description
- // - convert (type, name, syntax, description) into something we can
- // append to usr_cmd.
- // done
- //-^^----------------
- int
- CmdParseCommand::parse_declarations(istream & is)
- {
- // Keep track of the number of declarations that we parse.
- unsigned nargs = 0;
-
- if (is.eof()) return SUCCESS;
-
- char arg_type[MAX_IDENT_LEN];
- char arg_name[MAX_IDENT_LEN];
- QuotedString arg_description(MAX_DESCRIPTION_LEN);
-
- while (is) {
- ++nargs;
-
- // Skip all non-alpha-numerics
- int c = is.peek() ;
- while ((c != EOF) && (c != '_') && (! isalnum(c))) {
- (void) is.get();
- c = is.peek();
- }
-
- // First parse the argument type
- is.width(sizeof(arg_type) - 1);
- is >> arg_type ;
- if (! is) {
- if (is.eof()) {
- return SUCCESS; // end of args
- } else {
- error() << "Unable to extract type for argument #" << nargs
- << '.' << endl ;
- return FAILURE;
- }
- }
-
- // Now parse the argument name
- is.width(sizeof(arg_name) - 1);
- is >> arg_name ;
- if (! is) {
- if (is.eof()) {
- error() << "Premature end of input.\n"
- << "\texpecting a name for argument #" << nargs
- << '.' << endl ;
- } else {
- error() << "Unable to extract name of argument #" << nargs
- << '.' << endl ;
- }
- return FAILURE;
- }
-
- // Now parse the argument syntax
- ArgSyntax arg;
- is >> arg;
- if (! is) {
- error() << "Unable to get syntax for \"" << arg_name << "\" argument."
- << endl ;
- return FAILURE ;
- }
-
- // Now parse the argument description
- is >> arg_description ;
- if (! is) {
- error() << "Unable to get description for \"" << arg_name
- << "\" argument." << endl ;
- return FAILURE ;
- }
-
- if (usr_append(arg_type, arg_name, arg, arg_description)) {
- error() << "Unable to append \"" << arg_name << "\" argument "
- << "to the list." << endl ;
- return FAILURE;
- }
- }
- return SUCCESS;
- }
-
-
- //-------------------
- // ^FUNCTION: CmdParseCommand::set_args - set the user's arguments.
- //
- // ^SYNOPSIS:
- // CmdParseCommand::set_args(shell)
- //
- // ^PARAMETERS:
- // UnixShell * shell;
- // -- the command-interpreter (shell) that we need to output
- // variable settings for.
- //
- // ^DESCRIPTION:
- // For each argument that was given on the user's command-line, we need to
- // output a variable setting using the sepcified shell's syntax to indicate
- // the value that was specified.
- //
- // ^REQUIREMENTS:
- // This member function should only be called by CmdParseCommand::operator()
- //
- // ^SIDE-EFFECTS:
- // All the variable settings or sent to cout (standard output), the invoking
- // user is responsible for evaluating what this member function outputs.
- //
- // ^RETURN-VALUE:
- // None.
- //
- // ^ALGORITHM:
- // For each of the user's command-argument objects
- // - if the argument is a dummy, then skip it
- // - if the argument corresponds to the positional parameters for
- // this shell but was not given a value on the command-line then
- // unset this shell's positional parameters.
- // - if the argument was given then
- // - if the argument took a value and no value was given, then
- // set the variable <argname>_FLAG to be TRUE.
- // - else set the corresponding shell variable.
- // endif
- // endif
- // endfor
- //-^^----------------
- void
- CmdParseCommand::set_args(UnixShell * shell)
- {
- unsigned flags, syntax;
- CmdLineCmdArgIter iter(usr_cmd);
-
- for (CmdArg * cmdarg = iter() ; cmdarg ; cmdarg = iter()) {
- flags = cmdarg->flags();
- syntax = cmdarg->syntax();
-
- if (cmdarg->is_dummy()) continue;
-
- ShellCmdArg * sh_cmdarg = (ShellCmdArg *)cmdarg;
-
- if ((syntax & CmdArg::isPOS) && (! (flags & CmdArg::VALGIVEN))) {
- // if these are the positional-parameters then unset them!
- if (shell->is_positionals(sh_cmdarg->name())) {
- shell->unset_args(sh_cmdarg->name());
- }
- }
-
- if (! (flags & CmdArg::GIVEN)) continue;
-
- if ((syntax & CmdArg::isVALTAKEN) && (! (flags & CmdArg::VALGIVEN))) {
- // flag was given without its value - we need to record that
- char var_name[256];
- (void) ::strcpy(var_name, sh_cmdarg->name());
- (void) ::strcat(var_name, suffix_str);
- ShellVariable sh_var(var_name);
- sh_var.set(ShellCmdArgBool::True());
- shell->set(sh_var);
- } else {
- // output the value
- if (sh_cmdarg->is_array()) {
- shell->set(sh_cmdarg->array(), array_variant);
- } else {
- shell->set(sh_cmdarg->variable());
- }
- }
- } //for
- }
-
-
- //-------------------------------------------- CmdParseCommand::CmdParseCommand
-
- CmdParseCommand::CmdParseCommand(const char * name)
- : CmdLine(name),
- anywhere('a', "anywhere",
- "Allow options (and keywords) to follow positional parameters."
- ),
- anycase('i', "ignore-case",
- "Ignore character case on options."
- ),
- no_abort('n', "noabort",
- "Dont exit if bad syntax; try to continue parsing."
- ),
- no_guessing('g', "noguessing",
- "Dont \"guess\" for unmatched options/keywords."
- ),
- prompt('p', "prompt",
- "Prompt the user interactively for any missing required arguments."
- ),
- plus('+', "plus",
- "Allow the string \"+\" to be used as a long-option prefix."
- ),
- opts_only('o', "options-only",
- "Dont match keywords (long-options)."
- ),
- kwds_only('k', "keywords-only",
- "Dont match options."
- ),
- quiet('q', "quiet",
- "Dont print command-line syntax error messages."
- ),
- array_variant('A', "arrays",
- "Use alternative syntax for arrays."
- ),
- usage('u', "usage",
- "Print command-line usage and exit."
- ),
- version('v', "version",
- "Print version information and exit."
- ),
- true_str('T', "true", "string",
- "The string to use for boolean arguments that are turned ON \
- (default=\"TRUE\")."
- ),
- false_str('F', "false", "string",
- "The string to use for boolean arguments that are turned OFF \
- (default=\"\")."
- ),
- suffix_str('S', "suffix", "string",
- "The suffix to use for missing optional values. (default=\"_FLAG\")."
- ),
- usr_shell('s', "shell", "shellname",
-
- "Set program arguments using the syntax of the given shell \
- (default=\"sh\")."
- ),
- input_file('f', "file", "filename",
- "The file from which program argument declarations are read."
- ),
- input_var('e', "env", "varname",
- "The environment variable containing the program argument declarations."
- ),
- input_str('d', "decls", "string",
- "The string that contains the program argument declarations."
- ),
- dummy_arg("--",
- "Indicates the end of options/keywords."
- ),
- usr_prog('N', "name", "program-name",
- "The name of the program whose arguments are to be parsed.",
- (CmdArg::isPOS | CmdArg::isREQ | CmdArg::isVALREQ)
- ),
- usr_args("[arguments ...]",
- "The program-arguments to be parsed"
- )
- {
- // Append options.
- (*this) << anywhere << anycase << no_abort << no_guessing << prompt << plus
- << opts_only << kwds_only << quiet << array_variant << usage
- << version << true_str << false_str << suffix_str << usr_shell
- << input_file << input_var << input_str << dummy_arg ;
-
- // Append positional parameters.
- (*this) << usr_prog << usr_args ;
-
- set(CmdLine::KWDS_ONLY);
-
- // Set up defaults
- usr_shell = "sh" ;
- true_str = "TRUE" ;
- false_str = "" ;
- suffix_str = "_FLAG" ;
- }
-
- //------------------------------------------- CmdParseCommand::~CmdParseCommand
-
- CmdParseCommand::~CmdParseCommand(void)
- {
- CmdLineCmdArgIter iter(usr_cmd);
-
- for (CmdArg * cmdarg = iter() ; cmdarg ; cmdarg = iter()) {
- delete cmdarg ;
- }
- }
-
-
- //-------------------
- // ^FUNCTION: CmdParseCommand::operator()
- //
- // ^SYNOPSIS:
- // CmdParseCommand::operator(iter)
- //
- // ^PARAMETERS:
- // CmdLineArgIter & iter;
- // -- an object to iterate over the arguments on the command-line.
- //
- // ^DESCRIPTION:
- // This member function is the "guts" of a CmdParseCommand object.
- // We perform all the actions necessary to read the user's argument
- // declaratins, read the user's command-line, and set the corresponding
- // shell variables.
- //
- // ^REQUIREMENTS:
- // None.
- //
- // ^SIDE-EFFECTS:
- // - Modifies all parts of the corresponding CmdParseCommand object.
- // - prints variable settings on cout
- // - prints usage/error messages on cerr
- //
- // ^RETURN-VALUE:
- // e_SUCCESS -- no errors
- // e_USAGE -- no errors - usage printed
- // e_VERSION -- no errors - version printed
- // e_CMDSYNTAX -- command-line syntax error
- // e_BADSHELL -- invalid shell specified
- // e_BADDECLS -- invalid declaration(s) given
- //
- // ^ALGORITHM:
- // It gets complicated so follow along.
- //-^^----------------
- int
- CmdParseCommand::operator()(CmdLineArgIter & iter)
- {
- // Parse arguments
- parse(iter);
-
- // Use the specified shell
- UnixShell * shell = new UnixShell(usr_shell);
- if (! shell->is_valid()) {
- error() << "\"" << usr_shell
- << "\" is not a known command interpreter." << endl ;
- return e_BADSHELL ;
- }
-
- // Handle "-true" and "-false" options
- if (true_str.flags() & CmdArg::GIVEN) ShellCmdArgBool::True(true_str);
- if (false_str.flags() & CmdArg::GIVEN) ShellCmdArgBool::False(false_str);
-
- // Intitialize user's command-line
- usr_cmd.name(usr_prog);
- if (parse_declarations()) return e_BADDECLS ;
-
- // Set user parsing preferences
- if (anywhere) usr_cmd.clear(CmdLine::OPTS_FIRST);
- if (anycase) usr_cmd.set(CmdLine::ANY_CASE_OPTS);
- if (no_abort) usr_cmd.set(CmdLine::NO_ABORT);
- if (no_guessing) usr_cmd.set(CmdLine::NO_GUESSING);
- if (prompt) usr_cmd.set(CmdLine::PROMPT_USER);
- if (plus) usr_cmd.set(CmdLine::ALLOW_PLUS);
- if (opts_only) usr_cmd.set(CmdLine::OPTS_ONLY);
- if (kwds_only) usr_cmd.set(CmdLine::KWDS_ONLY);
- if (quiet) usr_cmd.set(CmdLine::QUIET);
-
- // Just print usage if thats all that is desired
- if (usage) {
- usr_cmd.usage(cout);
- return e_USAGE ;
- }
-
- // Parse user's command-line
- usr_cmd.prologue();
- for (int i = 0 ; i < usr_args.count() ; i++) {
- usr_cmd.parse_arg(usr_args[i]);
- }
- usr_cmd.epilogue();
-
- // Set user's variables
- set_args(shell);
-
- delete shell ;
- return 0;
- }
-
-