#include // find_if #include // size_t #include // printf #include // numeric_limits #include // stod, stoi, stoul #include #include #include "util/argparser.h" namespace Util { ArgParser::ArgParser() { } ArgParser::~ArgParser() { } void ArgParser::printOptionError(char name, Error error) { char tmp[] { name, '\0' }; printOptionError(tmp, error, false); } void ArgParser::printOptionError(const char* name, Error error, bool longName) { if (!m_errorReporting) { return; } if (error == Error::InvalidOption) { printf("%s: invalid option -- '%s'\n", m_name, name); } else if (error == Error::UnrecognizedOption) { printf("%s: unrecognized option -- '%s'\n", m_name, name); } else if (error == Error::DoesntAllowArgument) { printf("%s: option '--%s' doesn't allow an argument\n", m_name, name); } else if (error == Error::RequiresArgument) { if (longName) { printf("%s: option '--%s' requires an argument", m_name, name); } else { printf("%s: option requires an argument -- '%s'\n", m_name, name); } } // TODO: Print command usage, if it's enabled. } // Required: directly after || separated by space // Optional: directly after bool ArgParser::parseShortOption(std::string_view option, std::string_view next) { bool result = true; printf("Parsing short option: '%s'\n", option.data()); char c; std::string_view value; for (size_t i = 0; i < option.size(); ++i) { c = option.at(i); printf("short '%c'\n", c); auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&c](Option& it) -> bool { return it.shortName == c; }); // Option does not exist if (foundOption == m_options.cend()) { printOptionError(c, Error::InvalidOption); result = false; if (m_exitOnFirstError) { return result; } } else if (foundOption->requiresArgument == Required::No) { // FIXME: Figure out why providing a nullptr breaks the lambda here. result = foundOption->acceptValue(""); } else if (foundOption->requiresArgument == Required::Yes) { value = option.substr(i + 1); if (value.empty() && next.empty()) { foundOption->error = Error::RequiresArgument; printOptionError(c, Error::RequiresArgument); result = false; if (m_exitOnFirstError) { return result; } } else if (!value.empty()) { result = foundOption->acceptValue(value.data()); } else if (next[0] == '-') { foundOption->error = Error::RequiresArgument; printOptionError(c, Error::RequiresArgument); result = false; if (m_exitOnFirstError) { return result; } } else { result = foundOption->acceptValue(next.data()); m_optionIndex++; } break; } else if (foundOption->requiresArgument == Required::Optional) { value = option.substr(i + 1); if (!value.empty()) { result = foundOption->acceptValue(value.data()); break; } } } return result; } // Required: directly after, separated by '=' || separated by space // Optional: directly after, separated by '=' bool ArgParser::parseLongOption(std::string_view option, std::string_view next) { bool result = true; std::string_view name = option.substr(0, option.find_first_of('=')); std::string_view value = option.substr(option.find_first_of('=') + 1); bool argumentProvided = true; if (name.compare(value) == 0 && option.find('=') == std::string_view::npos) { argumentProvided = false; } printf("Parsing long option: '%s' with value '%s'\n", name.data(), argumentProvided ? value.data() : ""); auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&name](Option& it) -> bool { return it.longName == name; }); if (foundOption == m_options.cend()) { printOptionError(name.data(), Error::UnrecognizedOption); result = false; } else if (argumentProvided) { if (foundOption->requiresArgument == Required::No) { foundOption->error = Error::DoesntAllowArgument; printOptionError(name.data(), Error::DoesntAllowArgument); result = false; } else if (foundOption->requiresArgument == Required::Yes) { result = foundOption->acceptValue(value.data()); } else if (foundOption->requiresArgument == Required::Optional) { result = foundOption->acceptValue(value.data()); } } else if (!next.empty() && foundOption->requiresArgument == Required::Yes) { if (next[0] == '-') { foundOption->error = Error::RequiresArgument; printOptionError(name.data(), Error::RequiresArgument); result = false; } else { result = foundOption->acceptValue(next.data()); m_optionIndex++; } } else if (foundOption->requiresArgument == Required::Yes) { foundOption->error = Error::RequiresArgument; printOptionError(name.data(), Error::RequiresArgument); result = false; } else { // FIXME: Figure out why providing a nullptr breaks the lambda here. result = foundOption->acceptValue(""); } return result; } bool ArgParser::parseArgument(std::string_view argument) { // No handler for argument if (m_argumentIndex >= m_arguments.size()) { return false; } Argument& currentArgument = m_arguments.at(m_argumentIndex); bool result = currentArgument.acceptValue(argument.data()); if (result) { currentArgument.addedValues++; } if (currentArgument.addedValues >= currentArgument.maxValues) { m_argumentIndex++; } return result; } bool ArgParser::parse(int argc, const char* argv[]) { bool result = true; // Set looping indices m_optionIndex = 1; m_argumentIndex = 0; // By default parse all '-' prefixed parameters as options m_nonOptionMode = false; // Get program name m_name = argv[0] + std::string_view(argv[0]).find_last_of('/') + 1; std::string_view argument; std::string_view next; for (; m_optionIndex < (size_t)argc; ++m_optionIndex) { printf("argv[%zu]: %s\n", m_optionIndex, argv[m_optionIndex]); // Get the current and next parameter argument = argv[m_optionIndex]; if (m_optionIndex + 1 < (size_t)argc && argv[m_optionIndex + 1][0] != '-') { next = argv[m_optionIndex + 1]; } else { next = {}; } // Stop parsing '-' prefixed parameters as options afer '--' if (argument.compare("--") == 0) { m_nonOptionMode = true; continue; } // Long Option if (!m_nonOptionMode && argument[0] == '-' && argument[1] == '-') { argument = argument.substr(argument.find_first_not_of('-')); if (!parseLongOption(argument, next)) { result = false; } } // Short Option else if (!m_nonOptionMode && argument[0] == '-') { argument = argument.substr(argument.find_first_not_of('-')); if (!parseShortOption(argument, next)) { result = false; } } // Argument else { if (m_stopParsingOnFirstNonOption) { m_nonOptionMode = true; } if (!parseArgument(argument)) { result = false; } } if (m_exitOnFirstError && !result) { break; } } // Check any leftover arguments for required for (; m_argumentIndex < m_arguments.size(); ++m_argumentIndex) { Argument& currentArgument = m_arguments.at(m_argumentIndex); if (currentArgument.minValues > currentArgument.addedValues) { result = false; // TODO: decide if and what to print here } } return result; } // ----------------------------------------- void ArgParser::addOption(Option&& option) { m_options.push_back(option); } void ArgParser::addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString) { addOption({ shortName, longName, nullptr, usageString, manString, Required::No, getAcceptFunction(value) }); } void ArgParser::addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) { addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); } void ArgParser::addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) { addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); } void ArgParser::addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) { addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); } void ArgParser::addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) { addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); } void ArgParser::addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) { addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); } void ArgParser::addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) { addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); } void ArgParser::addOption(std::vector& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) { addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(values) }); } // ----------------------------------------- void ArgParser::addArgument(Argument&& argument) { m_arguments.push_back(argument); } void ArgParser::addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); } void ArgParser::addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); } void ArgParser::addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); } void ArgParser::addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); } void ArgParser::addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); } void ArgParser::addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); } void ArgParser::addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); } void ArgParser::addArgument(std::vector& values, const char* name, const char* usageString, const char* manString, Required required) { size_t minValues = required == Required::Yes ? 1 : 0; addArgument({ name, usageString, manString, minValues, values.max_size(), 0, getAcceptFunction(values) }); } // ----------------------------------------- AcceptFunction ArgParser::getAcceptFunction(bool& value) { return [&value](const char*) -> bool { value = true; return true; }; } AcceptFunction ArgParser::getAcceptFunction(const char*& value) { return [&value](const char* v) -> bool { value = v; return true; }; } AcceptFunction ArgParser::getAcceptFunction(std::string& value) { return [&value](const char* v) -> bool { value = v; return true; }; } AcceptFunction ArgParser::getAcceptFunction(std::string_view& value) { return [&value](const char* v) -> bool { value = v; return true; }; } AcceptFunction ArgParser::getAcceptFunction(int& value) { return [&value](const char* v) -> bool { try { value = std::stoi(v); return true; } catch (...) { return false; } }; } AcceptFunction ArgParser::getAcceptFunction(unsigned int& value) { return [&value](const char* v) -> bool { unsigned long convert = 0; try { convert = std::stoul(v); } catch (...) { return false; } if (convert <= std::numeric_limits::max()) { value = static_cast(convert); return true; } return false; }; } AcceptFunction ArgParser::getAcceptFunction(double& value) { return [&value](const char* v) -> bool { try { value = std::stod(v); return true; } catch (...) { return false; } }; } AcceptFunction ArgParser::getAcceptFunction(std::vector& value) { return [&value](const char* v) -> bool { value.push_back(v); return true; }; } } // namespace Util