// 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 #include #include #include #include /** \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(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(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(); } }