/* $Id$ */ // Copyright (C) 2007, International Business Machines // Corporation and others. All Rights Reserved. // This code is licensed under the terms of the Eclipse Public License (EPL). #include #include #include #include "CoinUtilsConfig.h" #include "CoinParam.hpp" #include #include #include #ifdef COIN_HAS_READLINE #include #include #endif /* Unnamed local namespace */ namespace { /* cmdField: The index of the current command line field. Forced to -1 when accepting commands from stdin (interactive) or a command file. readSrc: Current input source. pendingVal: When the form param=value is encountered, both keyword and value form one command line field. We need to return `param' as the field and somehow keep the value around for the upcoming call that'll request it. That's the purpose of pendingVal. */ int cmdField = 1 ; FILE *readSrc = stdin ; std::string pendingVal = "" ; /* Get next command or field in command. When in interactive mode, prompt the user and read the resulting line of input. */ std::string nextField (const char *prompt) { static char line[1000] ; static char *where = NULL ; std::string field ; const char *dflt_prompt = "Eh? " ; if (prompt == 0) { prompt = dflt_prompt ; } /* Do we have a line at the moment? If not, acquire one. When we're done, line holds the input line and where points to the start of the line. If we're using the readline library, add non-empty lines to the history list. */ if (!where) { #ifdef COIN_HAS_READLINE if (readSrc == stdin) { where = readline(prompt) ; if (where) { if (*where) add_history (where) ; strcpy(line,where) ; free(where) ; where = line ; } } else { where = fgets(line,1000,readSrc) ; } #else if (readSrc == stdin) { fprintf(stdout,"%s",prompt) ; fflush(stdout) ; } where = fgets(line,1000,readSrc) ; #endif /* If where is NULL, we have EOF. Return a null string. */ if (!where) return field ; /* Clean the image. Trailing junk first. The line will be cut off at the last non-whitespace character, but we need to scan until we find the end of the string or some other non-printing character to make sure we don't miss a printing character after whitespace. */ char *lastNonBlank = line-1 ; for (where = line ; *where != '\0' ; where++) { if (*where != '\t' && *where < ' ') { break ; } if (*where != '\t' && *where != ' ') { lastNonBlank = where ; } } *(lastNonBlank+1) = '\0' ; where = line ; } /* Munch through leading white space. */ while (*where == ' ' || *where == '\t') where++ ; /* See if we can separate a field; if so, copy it over into field for return. If we're out of line, return the string "EOL". */ char *saveWhere = where ; while (*where != ' ' && *where != '\t' && *where!='\0') where++ ; if (where != saveWhere) { char save = *where ; *where = '\0' ; field = saveWhere ; *where = save ; } else { where = NULL ; field = "EOL" ; } return (field) ; } } /* Visible functions */ namespace CoinParamUtils { /* As mentioned above, cmdField set to -1 is the indication that we're reading from stdin or a file. */ void setInputSrc (FILE *src) { if (src != 0) { cmdField = -1 ; readSrc = src ; } } /* A utility to allow clients to determine if we're processing parameters from the comand line or otherwise. */ bool isCommandLine () { assert(cmdField != 0) ; if (cmdField > 0) { return (true) ; } else { return (false) ; } } /* A utility to allow clients to determine if we're accepting parameters interactively. */ bool isInteractive () { assert(cmdField != 0) ; if (cmdField < 0 && readSrc == stdin) { return (true) ; } else { return (false) ; } } /* Utility functions for acquiring input. */ /* Return the next field (word) from the current command line. Generally, this is expected to be of the form `-param' or `--param', with special cases as set out below. If we're in interactive mode (cmdField == -1), nextField does all the work to prompt the user and return the next field from the resulting input. It is assumed that the user knows not to use `-' or `--' prefixes in interactive mode. If we're in command line mode (cmdField > 0), cmdField indicates the current command line word. The order of processing goes like this: * A stand-alone `-' is converted to `stdin' * A stand-alone '--' is returned as a word; interpretation is up to the client. * A prefix of '-' or '--' is stripped from the field. If the result is `stdin', it's assumed we're switching to interactive mode and the user is prompted for another command. Whatever results from the above sequence is returned to the client as the next field. An empty string indicates end of input. Prompt will be used by nextField if it's necessary to prompt the user for a command (only when reading from stdin). If provided, pfx is set to the prefix ("-", "--", or "") stripped from the field. Lack of prefix is not necessarily an error because of the following scenario: To read a file, the verbose command might be "foo -import myfile". But we might want to allow a short form, "foo myfile". And we'd like "foo import" to be interpreted as "foo -import import" (i.e., import the file named `import'). */ std::string getCommand (int argc, const char *argv[], const std::string prompt, std::string *pfx) { std::string field = "EOL" ; pendingVal = "" ; int pfxlen ; if (pfx != 0) { (*pfx) = "" ; } /* Acquire the next field, and convert as outlined above if we're processing command line parameters. */ while (field == "EOL") { pfxlen = 0 ; if (cmdField > 0) { if (cmdField < argc) { field = argv[cmdField++] ; if (field == "-") { field = "stdin" ; } else if (field == "--") { /* Prevent `--' from being eaten by next case. */ } else { if (field[0] == '-') { pfxlen = 1 ; if (field[1] == '-') pfxlen = 2 ; if (pfx != 0) (*pfx) = field.substr(0,pfxlen) ; field = field.substr(pfxlen) ; } } } else { field = "" ; } } else { field = nextField(prompt.c_str()) ; } if (field == "stdin") { std::cout << "Switching to line mode" << std::endl ; cmdField = -1 ; field = nextField(prompt.c_str()) ; } } /* Are we left with something of the form param=value? If so, separate the pieces, returning `param' and saving `value' for later use as per comments at the head of the file. */ std::string::size_type found = field.find('='); if (found != std::string::npos) { pendingVal = field.substr(found+1) ; field = field.substr(0,found) ; } return (field) ; } /* Function to look up a parameter keyword (name) in the parameter vector and deal with the result. The keyword may end in one or more `?' characters; this is a query for information about matching parameters. If we have a single match satisfying the minimal match requirements, and there's no query, we simply return the index of the matching parameter in the parameter vector. If there are no matches, and no query, the return value will be -3. No matches on a query returns -1. A single short match, or a single match of any length with a query, will result in a short help message If present, these values are set as follows: * matchCntp is set to the number of parameters that matched. * shortCntp is set to the number of matches that failed to meet the minimum match requirement. * queryCntp is set to the number of trailing `?' characters at the end of name. Return values: >0: index of the single unique match for the name -1: query present -2: no query, one or more short matches -3: no query, no match -4: multiple full matches (indicates configuration error) The final three parameters (matchCnt, shortCnt, queryCnt) are optional and default to null. Use them if you want more detail on the match. */ int lookupParam (std::string name, CoinParamVec ¶mVec, int *matchCntp, int *shortCntp, int *queryCntp) { int retval = -3 ; if (matchCntp != 0) { *matchCntp = 0 ; } if (shortCntp != 0) { *shortCntp = 0 ; } if (queryCntp != 0) { *queryCntp = 0 ; } /* Is there anything here at all? */ if (name.length() == 0) { return (retval) ; } /* Scan the parameter name to see if it ends in one or more `?' characters. If so, take it as a request to return a list of parameters that match name up to the first `?'. The strings '?' and '???' are considered to be valid parameter names (short and long help, respectively) and are handled as special cases: If the whole string is `?'s, one and three are commands as is, while 2 and 4 or more are queries about `?' or `???'. */ int numQuery = 0 ; { int length = static_cast(name.length()) ; int i ; for (i = length-1 ; i >= 0 && name[i] == '?' ; i--) { numQuery++ ; } if (numQuery == length) { switch (length) { case 1: case 3: { numQuery = 0 ; break ; } case 2: { numQuery -= 1 ; break ; } default: { numQuery -= 3 ; break ; } } } name = name.substr(0,length-numQuery) ; if (queryCntp != 0) { *queryCntp = numQuery ; } } /* See if we can match the parameter name. On return, matchNdx is set to the last match satisfying the minimal match criteria, or -1 if there's no match. matchCnt is the number of matches satisfying the minimum match length, and shortCnt is possible matches that were short of the minimum match length, */ int matchNdx = -1 ; int shortCnt = 0 ; int matchCnt = CoinParamUtils::matchParam(paramVec,name,matchNdx,shortCnt) ; /* Set up return values before we get into further processing. */ if (matchCntp != 0) { *matchCntp = matchCnt ; } if (shortCntp != 0) { *shortCntp = shortCnt ; } if (numQuery > 0) { retval = -1 ; } else { if (matchCnt+shortCnt == 0) { retval = -3 ; } else if (matchCnt > 1) { retval = -4 ; } else { retval = -2 ; } } /* No matches? Nothing more to be done here. */ if (matchCnt+shortCnt == 0) { return (retval) ; } /* A unique match and no `?' in the name says we have our parameter. Return the result. */ if (matchCnt == 1 && shortCnt == 0 && numQuery == 0) { assert (matchNdx >= 0 && matchNdx < static_cast(paramVec.size())) ; return (matchNdx) ; } /* A single match? There are two possibilities: * The string specified is shorter than the match length requested by the parameter. (Useful for avoiding inadvertent execution of commands that the client might regret.) * The string specified contained a `?', in which case we print the help. The match may or may not be short. */ if (matchCnt+shortCnt == 1) { CoinParamUtils::shortOrHelpOne(paramVec,matchNdx,name,numQuery) ; return (retval) ; } /* The final case: multiple matches. Most commonly this will be multiple short matches. If we have multiple matches satisfying the minimal length criteria, we have a configuration problem. The other question is whether the user wanted help information. Two question marks gets short help. */ if (matchCnt > 1) { std::cout << "Configuration error! `" << name <<"' was fully matched " << matchCnt << " times!" << std::endl ; } std::cout << "Multiple matches for `" << name << "'; possible completions:" << std::endl ; CoinParamUtils::shortOrHelpMany(paramVec,name,numQuery) ; return (retval) ; } /* Utility functions to acquire parameter values from the command line. For all of these, a pendingVal is consumed if it exists. */ /* Read a string and return a pointer to the string. Set valid to indicate the result of parsing: 0: okay, 1: , 2: not present. */ std::string getStringField (int argc, const char *argv[], int *valid) { std::string field ; if (pendingVal != "") { field = pendingVal ; pendingVal = "" ; } else { field = "EOL" ; if (cmdField > 0) { if (cmdField < argc) { field = argv[cmdField++] ; } } else { field = nextField(0) ; } } if (valid != 0) { if (field != "EOL") { *valid = 0 ; } else { *valid = 2 ; } } return (field) ; } /* Read an int and return the value. Set valid to indicate the result of parsing: 0: okay, 1: parse error, 2: not present. */ int getIntField (int argc, const char *argv[], int *valid) { std::string field ; if (pendingVal != "") { field = pendingVal ; pendingVal = "" ; } else { field = "EOL" ; if (cmdField > 0) { if (cmdField < argc) { field = argv[cmdField++] ; } } else { field = nextField(0) ; } } /* The only way to check for parse error here is to set the system variable errno to 0 and then see if it's nonzero after we try to convert the string to integer. */ int value = 0 ; errno = 0 ; if (field != "EOL") { value = atoi(field.c_str()) ; } if (valid != 0) { if (field != "EOL") { if (errno == 0) { *valid = 0 ; } else { *valid = 1 ; } } else { *valid = 2 ; } } return (value) ; } /* Read a double and return the value. Set valid to indicate the result of parsing: 0: okay, 1: bad parse, 2: not present. But we'll never return valid == 1 because atof gives us no way to tell.) */ double getDoubleField (int argc, const char *argv[], int *valid) { std::string field ; if (pendingVal != "") { field = pendingVal ; pendingVal = "" ; } else { field = "EOL" ; if (cmdField > 0) { if (cmdField < argc) { field = argv[cmdField++] ; } } else { field = nextField(0) ; } } /* The only way to check for parse error here is to set the system variable errno to 0 and then see if it's nonzero after we try to convert the string to integer. */ double value = 0.0 ; errno = 0 ; if (field != "EOL") { value = atof(field.c_str()) ; } if (valid != 0) { if (field != "EOL") { if (errno == 0) { *valid = 0 ; } else { *valid = 1 ; } } else { *valid = 2 ; } } return (value) ; } /* Utility function to scan a parameter vector for matches. Sets matchNdx to the index of the last parameter that meets the minimal match criteria (but note there should be at most one such parameter if the parameter vector is properly configured). Sets shortCnt to the number of short matches (should be zero in a properly configured vector if a minimal match is found). Returns the number of matches satisfying the minimal match requirement (should be 0 or 1 in a properly configured vector). The routine allows for the possibility of null entries in the parameter vector. In order to handle `?' and `???', there's nothing to it but to force a unique match if we match `?' exactly. (This is another quirk of clp/cbc parameter parsing, which we need to match for historical reasons.) */ int matchParam (const CoinParamVec ¶mVec, std::string name, int &matchNdx, int &shortCnt) { int vecLen = static_cast(paramVec.size()) ; int matchCnt = 0 ; matchNdx = -1 ; shortCnt = 0 ; for (int i = 0 ; i < vecLen ; i++) { CoinParam *param = paramVec[i] ; if (param == 0) continue ; int match = paramVec[i]->matches(name) ; if (match == 1) { matchNdx = i ; matchCnt++ ; if (name == "?") { matchCnt = 1 ; break ; } } else { shortCnt += match>>1 ; } } return (matchCnt) ; } /* Now a bunch of routines that are useful in the context of generating help messages. */ /* Simple formatting routine for long messages. Used to print long help for parameters. Lines are broken at the first white space after 65 characters, or when an explicit return (`\n') character is scanned. Leading spaces are suppressed. */ void printIt (const char *msg) { int length = static_cast(strlen(msg)) ; char temp[101] ; int i ; int n = 0 ; for (i = 0 ; i < length ; i++) { if (msg[i] == '\n' || (n >= 65 && (msg[i] == ' ' || msg[i] == '\t'))) { temp[n] = '\0' ; std::cout << temp << std::endl ; n = 0 ; } else if (n || msg[i] != ' ') { temp[n++] = msg[i] ; } } if (n > 0) { temp[n] = '\0' ; std::cout << temp << std::endl ; } return ; } /* Utility function for the case where a name matches a single parameter, but either it's short, or the user wanted help, or both. The routine allows for the possibility that there are null entries in the parameter vector, but matchNdx should point to a valid entry if it's >= 0. */ void shortOrHelpOne (CoinParamVec ¶mVec, int matchNdx, std::string name, int numQuery) { int i ; int numParams = static_cast(paramVec.size()) ; int lclNdx = -1 ; /* For a short match, we need to look up the parameter again. This should find a short match, given the conditions where this routine is called. But be prepared to find a full match. If matchNdx >= 0, just use the index we're handed. */ if (matchNdx < 0) { int match = 0 ; for (i = 0 ; i < numParams ; i++) { CoinParam *param = paramVec[i] ; if (param == 0) continue ; int match = param->matches(name) ; if (match != 0) { lclNdx = i ; break ; } } assert (lclNdx >= 0) ; if (match == 1) { std::cout << "Match for '" << name << "': " << paramVec[matchNdx]->matchName() << "." ; } else { std::cout << "Short match for '" << name << "'; possible completion: " << paramVec[lclNdx]->matchName() << "." ; } } else { assert(matchNdx >= 0 && matchNdx < static_cast(paramVec.size())) ; std::cout << "Match for `" << name << "': " << paramVec[matchNdx]->matchName() ; lclNdx = matchNdx ; } /* Print some help, if there was a `?' in the name. `??' gets the long help. */ if (numQuery > 0) { std::cout << std::endl ; if (numQuery == 1) { std::cout << paramVec[lclNdx]->shortHelp() ; } else { paramVec[lclNdx]->printLongHelp() ; } } std::cout << std::endl ; return ; } /* Utility function for the case where a name matches multiple parameters. Zero or one `?' gets just the matching names, while `??' gets short help with each match. The routine allows for the possibility that there are null entries in the parameter vector. */ void shortOrHelpMany (CoinParamVec ¶mVec, std::string name, int numQuery) { int numParams = static_cast(paramVec.size()) ; /* Scan the parameter list. For each match, print just the name, or the name and short help. */ int lineLen = 0 ; bool printed = false ; for (int i = 0 ; i < numParams ; i++) { CoinParam *param = paramVec[i] ; if (param == 0) continue ; int match = param->matches(name) ; if (match > 0) { std::string nme = param->matchName() ; int len = static_cast(nme.length()) ; if (numQuery >= 2) { std::cout << nme << " : " << param->shortHelp() ; std::cout << std::endl ; } else { lineLen += 2+len ; if (lineLen > 80) { std::cout << std::endl ; lineLen = 2+len ; } std::cout << " " << nme ; printed = true ; } } } if (printed) { std::cout << std::endl ; } return ; } /* A generic help message that explains the basic operation of parameter parsing. */ void printGenericHelp () { std::cout << std::endl ; std::cout << "For command line arguments, keywords have a leading `-' or '--'; " << std::endl ; std::cout << "-stdin or just - switches to stdin with a prompt." << std::endl ; std::cout << "When prompted, one command per line, without the leading `-'." << std::endl ; std::cout << "abcd value sets abcd to value." << std::endl ; std::cout << "abcd without a value (where one is expected) gives the current value." << std::endl ; std::cout << "abcd? gives a list of possible matches; if there's only one, a short" << std::endl ; std::cout << "help message is printed." << std::endl ; std::cout << "abcd?? prints the short help for all matches; if there's only one" << std::endl ; std::cout << "match, a longer help message and current value are printed." << std::endl ; return ; } /* Utility function for various levels of `help' command. The entries between paramVec[firstParam] and paramVec[lastParam], inclusive, will be printed. If shortHelp is true, the short help message will be printed for each parameter. If longHelp is true, the long help message will be printed for each parameter. If hidden is true, even parameters with display = false will be printed. Each line is prefaced with the specified prefix. The routine allows for the possibility that there are null entries in the parameter vector. */ void printHelp (CoinParamVec ¶mVec, int firstParam, int lastParam, std::string prefix, bool shortHelp, bool longHelp, bool hidden) { bool noHelp = !(shortHelp || longHelp) ; int i ; int pfxLen = static_cast(prefix.length()) ; bool printed = false ; if (noHelp) { int lineLen = 0 ; for (i = firstParam ; i <= lastParam ; i++) { CoinParam *param = paramVec[i] ; if (param == 0) continue ; if (param->display() || hidden) { std::string nme = param->matchName() ; int len = static_cast(nme.length()) ; if (!printed) { std::cout << std::endl << prefix ; lineLen += pfxLen ; printed = true ; } lineLen += 2+len ; if (lineLen > 80) { std::cout << std::endl << prefix ; lineLen = pfxLen+2+len ; } std::cout << " " << nme ; } } if (printed) { std::cout << std::endl ; } } else if (shortHelp) { for (i = firstParam ; i <= lastParam ; i++) { CoinParam *param = paramVec[i] ; if (param == 0) continue ; if (param->display() || hidden) { std::cout << std::endl << prefix ; std::cout << param->matchName() ; std::cout << ": " ; std::cout << param->shortHelp() ; } } std::cout << std::endl ; } else if (longHelp) { for (i = firstParam ; i <= lastParam ; i++) { CoinParam *param = paramVec[i] ; if (param == 0) continue ; if (param->display() || hidden) { std::cout << std::endl << prefix ; std::cout << "Command: " << param->matchName() ; std::cout << std::endl << prefix ; std::cout << "---- description" << std::endl ; printIt(param->longHelp().c_str()) ; std::cout << prefix << "----" << std::endl ; } } } std::cout << std::endl ; return ; } } // end namespace CoinParamUtils