Cloned SEACAS for EXODUS library with extra build files for internal package management.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

411 lines
12 KiB

2 years ago
// Copyright(C) 1999-2021 National Technology & Engineering Solutions
// of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with
// NTESS, the U.S. Government retains certain rights in this software.
//
// See packages/seacas/LICENSE for details
/* S Manoharan. Advanced Computer Research Institute. Lyon. France */
#include <GetLongOpt.h>
#include <cstring>
#include <fmt/color.h>
#include <fmt/ostream.h>
#include <sstream>
/** \brief Create an empty options database.
*
* \param optmark The command line symbol designating options.
*/
GetLongOption::GetLongOption(const char optmark) : optmarker(optmark)
{
ustring = "[valid options and arguments]";
}
/** \brief Frees dynamically allocated memory.
*
* Frees memory for the private struct variables representing the options.
*/
GetLongOption::~GetLongOption()
{
Cell *t = table;
while (t != nullptr) {
Cell *tmp = t;
t = t->next;
delete tmp;
}
}
/** \brief Extract the base file name from a full path.
*
* Finds the last instance of the '/' character and
* extracts the part of the string that follows.
*
* \param[in] pathname The full path.
* \return The base file name.
*/
char *GetLongOption::basename(char *const pathname)
{
char *s = strrchr(pathname, '/');
if (s == nullptr) {
s = pathname;
}
else {
++s;
}
return s;
}
/** \brief Enroll a command line option into the database.
*
* Dynamically allocates memory for the option, sets its name
* type, description, value, and default value, and links it
* to the preceding option.
*
* \param[in] opt The long option name.
* \param[in] t The option type.
* \param[in] desc A short description of the option.
* \param[in] val The option value.
* \param[in] optval The default value.
* \returns 1 if successful, 0 if unsuccessful.
*/
bool GetLongOption::enroll(const char *const opt, const OptType t, const char *const desc,
const char *const val, const char *const optval, bool extra_line)
{
if (options_parsed) {
return false;
}
auto *c = new Cell;
c->option = opt;
c->type = t;
c->description = desc != nullptr ? desc : "no description available";
c->value = val;
c->opt_value = optval;
c->next = nullptr;
c->extra_line = extra_line;
if (last == nullptr) {
table = last = c;
}
else {
last->next = c;
last = c;
}
return true;
}
const char *GetLongOption::program_name() const { return pname == nullptr ? "[UNSET]" : pname; }
/** \brief Get a command line option object.
*
* \param[in] opt The option name.
* \returns The option object.
*/
const char *GetLongOption::retrieve(const char *const opt) const
{
Cell *t;
for (t = table; t != nullptr; t = t->next) {
if (strcmp(opt, t->option) == 0) {
return t->value;
}
}
fmt::print(stderr, "GetLongOption::retrieve - unenrolled option {}{}\n", optmarker, opt);
return nullptr;
}
/** \brief parse command line arguments
*
* Set the values of options in the option table based on
* the given command line arguments.
*
* \param[in] argc Number of command line arguments passed in from main(int argc, char *argv[]).
* \param[in] argv Command line arguments passed in from main(int argc, char *argv[]).
* \returns Number of options processed, or -1 on failure.
*
*/
int GetLongOption::parse(int argc, char *const *argv)
{
int my_optind = 1;
std::ostringstream multiple_match;
pname = basename(*argv);
options_parsed = true;
if (argc-- <= 1) {
return my_optind;
}
while (argc >= 1) {
char *token = *++argv;
--argc;
// '--' signifies end of options if followed by space
if (token[0] != optmarker || (token[1] == optmarker && strlen(token) == 2)) {
break; /* end of options */
}
++my_optind;
char *tmptoken = ++token;
if (token[0] == optmarker) { // Handle a double '--'
tmptoken = ++token;
}
while (*tmptoken != '\0' && *tmptoken != '=') {
++tmptoken;
}
/* (tmptoken - token) is now equal to the command line option
length. */
Cell *t;
enum { NoMatch, ExactMatch, PartialMatch, MultipleMatch } matchStatus = NoMatch;
Cell *pc = nullptr; // pointer to the partially-matched cell
for (t = table; t != nullptr; t = t->next) {
if (strncmp(t->option, token, (tmptoken - token)) == 0) {
if (static_cast<int>(strlen(t->option)) == (tmptoken - token)) {
/* an exact match found */
int stat = setcell(t, tmptoken, *(argv + 1), pname);
if (stat == -1) {
return -1;
}
if (stat == 1) {
++argv;
--argc;
++my_optind;
}
matchStatus = ExactMatch;
break;
}
else {
/* partial match found */
if (pc == nullptr) {
matchStatus = PartialMatch;
pc = t;
}
else {
// Multiple partial matches...Print warning
if (matchStatus == PartialMatch) {
// First time, print the message header and the first
// matched duplicate...
fmt::print(multiple_match, "ERROR: {}: Multiple matches found for option '{}{}'.\n",
pname, optmarker, strtok(token, "= "));
fmt::print(multiple_match, "\t{}{}: {}\n", optmarker, pc->option, pc->description);
}
fmt::print(multiple_match, "\t{}{}:{}\n", optmarker, t->option, t->description);
matchStatus = MultipleMatch;
}
}
} /* end if */
} /* end for */
if (matchStatus == PartialMatch) {
int stat = setcell(pc, tmptoken, *(argv + 1), pname);
if (stat == -1) {
return -1;
}
if (stat == 1) {
++argv;
--argc;
++my_optind;
}
}
else if (matchStatus == NoMatch) {
fmt::print(stderr, "{}: unrecognized option {}{}\n", pname, optmarker, strtok(token, "= "));
return -1; /* no match */
}
else if (matchStatus == MultipleMatch) {
std::cerr << multiple_match.str();
return -1; /* no match */
}
} /* end while */
return my_optind;
}
/** \brief parse an argument string.
*
* Set the values of options in the option table based on
* the given option string.
*
* \param[in] str The option string.
* \param[in] p A string to be used in error reporting
* \returns 1 if successful, or -1 otherwise.
*
*/
int GetLongOption::parse(char *const str, char *const p)
{
options_parsed = true;
char *token = strtok(str, " \t");
const char *name = p != nullptr ? p : "GetLongOption";
while (token != nullptr) {
if (token[0] != optmarker || (token[1] == optmarker && strlen(token) == 2)) {
fmt::print(stderr, "{}: nonoptions not allowed\n", name);
return -1; /* end of options */
}
char *ladtoken = nullptr; /* lookahead token */
char *tmptoken = ++token;
while (*tmptoken != '\0' && *tmptoken != '=') {
++tmptoken;
}
/* (tmptoken - token) is now equal to the command line option
length. */
Cell *t;
enum { NoMatch, ExactMatch, PartialMatch } matchStatus = NoMatch;
Cell *pc = nullptr; // pointer to the partially-matched cell
for (t = table; t != nullptr; t = t->next) {
if (strncmp(t->option, token, (tmptoken - token)) == 0) {
if (static_cast<int>(strlen(t->option)) == (tmptoken - token)) {
/* an exact match found */
ladtoken = strtok(nullptr, " \t");
int stat = setcell(t, tmptoken, ladtoken, name);
if (stat == -1) {
return -1;
}
if (stat == 1) {
ladtoken = nullptr;
}
matchStatus = ExactMatch;
break;
}
else {
/* partial match found */
matchStatus = PartialMatch;
pc = t;
}
} /* end if */
} /* end for */
if (matchStatus == PartialMatch) {
ladtoken = strtok(nullptr, " \t");
int stat = setcell(pc, tmptoken, ladtoken, name);
if (stat == -1) {
return -1;
}
if (stat == 1) {
ladtoken = nullptr;
}
}
else if (matchStatus == NoMatch) {
fmt::print(stderr, "{}: unrecognized option {}{}\n", name, optmarker, strtok(token, "= "));
return -1; /* no match */
}
token = ladtoken != nullptr ? ladtoken : strtok(nullptr, " \t");
} /* end while */
return 1;
}
/* ----------------------------------------------------------------
GetLongOption::setcell returns
-1 if there was an error
0 if the nexttoken was not consumed
1 if the nexttoken was consumed
------------------------------------------------------------------- */
int GetLongOption::setcell(Cell *c, char *valtoken, char *nexttoken, const char *name)
{
if (c == nullptr) {
return -1;
}
switch (c->type) {
case GetLongOption::NoValue:
if (*valtoken == '=') {
fmt::print(stderr, "{}: unsolicited value for flag {}{}\n", name, optmarker, c->option);
return -1; /* unsolicited value specification */
}
// Set to a non-zero value. Used to be "(char*) ~0", but that
// gives out-of-range warnings on some systems...
c->value = (char *)1;
return 0;
case GetLongOption::OptionalValue:
if (*valtoken == '=') {
c->value = ++valtoken;
return 0;
}
else {
if (nexttoken != nullptr && nexttoken[0] != optmarker) {
c->value = nexttoken;
return 1;
}
c->value = c->opt_value;
return 0;
}
case GetLongOption::MandatoryValue:
if (*valtoken == '=') {
c->value = ++valtoken;
return 0;
}
else {
if (nexttoken != nullptr) {
c->value = nexttoken;
return 1;
}
fmt::print(stderr, "{}: mandatory value for {}{} not specified\n", name, optmarker,
c->option);
return -1; /* mandatory value not specified */
}
default: break;
}
return -1;
}
/** \brief Print the program usage string.
*
* \param[in] outfile The output stream to which the usage string is printed.
*/
void GetLongOption::usage(std::ostream &outfile) const
{
// The API of `usage` specifies an `ostream` for the output location. However,
// the fmt::print color options do not work with an ostream and instead
// want a FILE*. To give formatting in the usage message, we convert the
// ostream to a FILE* if it is std::cout or std::cerr; otherwise use the old
// non-formatted version...
FILE *out = nullptr;
if (&outfile == &std::cout) {
out = stdout;
}
else if (&outfile == &std::cerr) {
out = stderr;
}
if (out != nullptr) {
fmt::print(out, fmt::emphasis::bold, "\nusage: {} {}\n", pname, ustring);
for (Cell *t = table; t != nullptr; t = t->next) {
fmt::print(out, fmt::emphasis::bold, "\t{}{}", optmarker, t->option);
if (t->type == GetLongOption::MandatoryValue) {
fmt::print(out, fmt::emphasis::italic | fmt::emphasis::bold, " <$val>");
}
else if (t->type == GetLongOption::OptionalValue) {
fmt::print(out, fmt::emphasis::italic | fmt::emphasis::bold, " [$val]");
}
fmt::print(out, " ({})\n", t->description);
if (t->extra_line) {
fmt::print(out, "\n");
}
}
}
else {
fmt::print(outfile, "\nusage: {} {}\n", pname, ustring);
for (Cell *t = table; t != nullptr; t = t->next) {
fmt::print(outfile, "\t{}{}", optmarker, t->option);
if (t->type == GetLongOption::MandatoryValue) {
fmt::print(outfile, " <$val>");
}
else if (t->type == GetLongOption::OptionalValue) {
fmt::print(outfile, " [$val]");
}
fmt::print(outfile, " ({})\n", t->description);
if (t->extra_line) {
fmt::print(outfile, "\n");
}
}
outfile.flush();
}
}