// Copyright 2022, FOSS-VG Developers and Contributers // // Author(s): // BodgeMaster, Shwoomple // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, version 3. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // version 3 along with this program. // If not, see https://www.gnu.org/licenses/agpl-3.0.en.html #include <string> #include <vector> #include <map> #include "cli.hpp" #include "error.hpp" namespace CLI { Flag::Flag() { this->present = false; } Flag::Flag(char shortName, std::string longName, std::string description) { this->shortName = shortName; this->longName = longName; this->description = description; this->present = false; } Option::Option() { this->present = false; } Option::Option(char shortName, std::string longName, std::string placeholder, std::string description) { this->shortName = shortName; this->longName = longName; this->description = description; this->placeholder = placeholder; this->present = false; } Argument::Argument() { this->present = false; } Argument::Argument(std::string placeholder, std::string description) { this->description = description; this->placeholder = placeholder; this->present = false; } // using int here bc that's how main() is defined ArgumentsParser::ArgumentsParser(int argc, const char* const argv[], std::vector<Flag> flags, std::vector<Option> options, std::vector<Argument> arguments) { this->wrongUsage = false; this->wrongUsageMessages = std::vector<std::string>(); this->programName = std::string(argv[0]); this->arguments = arguments; // create lookup tables for all flags and options by their names this->flagsByShortName = std::map<char, Flag*>(); this->flagsByLongName = std::map<std::string, Flag*>(); for (Flag flag: flags) { Flag* flagPointer = new Flag(); *flagPointer = flag; this->flagsByShortName[flag.shortName] = flagPointer; this->flagsByLongName[flag.longName] = flagPointer; } this->optionsByShortName = std::map<char, Option*>(); this->optionsByLongName = std::map<std::string, Option*>(); for (Option option: options) { Option* optionPointer = new Option(); *optionPointer = option; this->optionsByShortName[option.shortName] = optionPointer; this->optionsByLongName[option.longName] = optionPointer; } Option* optionWaitingForValue = nullptr; std::vector<CLI::Argument>::size_type argumentCounter = 0; for (int i=1; i<argc; i++) { std::string argument(argv[i]); if (argument[0]=='-') { // do we have unfinished business? if (optionWaitingForValue!=nullptr) { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Argument expects value but has none: ")+optionWaitingForValue->longName); optionWaitingForValue = nullptr; } // long name or short name? if (argument[1]=='-') { // long name //FIXME: instead of auto, this should be what string // length is defined as // (std::__cxx11::basic_string<char>::size_type ?) // argument with =value specified? auto position = argument.find("="); if (position==std::string::npos) { // no =value //is option or flag? std::string argumentName = argument.substr(2,argument.length()-2); if (flagsByLongName.contains(argumentName)) { // flag flagsByLongName[argumentName]->present = true; } else if (optionsByLongName.contains(argumentName)) { // option optionsByLongName[argumentName]->present = true; optionWaitingForValue = optionsByLongName[argumentName]; if (i+1 == argc) { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Argument expects value but has none: ")+argumentName); } } else { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Unknown argument or flag: ")+argument); } } else { // has =value std::string value = argument.substr(position+1, argument.length()-position-1); std::string argumentName = argument.substr(2, position-2); if (optionsByLongName.contains(argumentName)) { optionsByLongName[argumentName]->present = true; optionsByLongName[argumentName]->value = value; } else { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Unknown argument (or it's a flag that doesn't take a value): ")+argument); } } } else { // short name //FIXME: instead of int, this should use what string // length is defined as // (std::__cxx11::basic_string<char>::size_type ?) // starting at 1 because 0 is '-' for (int j=1; j<(int) argument.length(); j++) { // is option or flag? if (flagsByShortName.contains(argument[j])) { // flag flagsByShortName[argument[j]]->present = true; } else if (optionsByShortName.contains(argument[j])) { // option optionsByShortName[argument[j]]->present = true; //FIXME: see above if (j+1==(int) argument.length()) { optionWaitingForValue = optionsByShortName[argument[j]]; if (i+1 == argc) { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Argument expects value but has none: ")+this->optionsByShortName[argument[j]]->longName); } } else { //assume the rest of the argv is a concatenated argument value optionsByShortName[argument[j]]->value = argument.substr(j+1, argument.length()-j-1); break; } } else { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Unknown argument or flag(s): ")+argument.substr(j, argument.length()-j)); // err on the side of caution to ensure that // no unwanted options get activated on programs // that deal gracefully with unrecognized command // line parameters break; } } } } else { // argument or value for option? if (optionWaitingForValue==nullptr) { // argument if (argumentCounter < this->arguments.size()) { this->arguments.at(argumentCounter).present = true; this->arguments.at(argumentCounter).value = argument; } else { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Too many arguments! Unexpected encounter of: ")+argument); } argumentCounter++; } else { // value for option optionWaitingForValue->value = argument; optionWaitingForValue = nullptr; } } } for (Argument const& argument: this->arguments) { if (!argument.present) { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Too few arguments! Missing: ")+argument.placeholder); } } } ArgumentsParser::ArgumentsParser(int argc, const char* const argv[], std::vector<Flag> flags, std::vector<Option> options, std::vector<Argument> arguments, std::string description): ArgumentsParser::ArgumentsParser(argc, argv, flags, options, arguments) { this->description = description; } ArgumentsParser::ArgumentsParser(int argc, const char* const argv[], std::vector<Flag> flags, std::vector<Option> options, std::vector<Argument> arguments, std::string description, std::string additionalInfo): ArgumentsParser::ArgumentsParser(argc, argv, flags, options, arguments) { this->description = description; this->additionalInfo = additionalInfo; } ArgumentsParser::~ArgumentsParser() { //TODO: check that this actually runs for (auto const& [shortName, flag]: this->flagsByShortName) { delete flag; } for (auto const& [shortName, option]: this->optionsByShortName) { delete option; } } ErrorOr<bool> ArgumentsParser::getFlag(char shortName) { if (!this->flagsByShortName.contains(shortName)) return ErrorOr<bool>(true, ErrorCodes::UNKNOWN_KEY, false); if (this->wrongUsage) { if (this->flagsByShortName[shortName]->present) return ErrorOr<bool>(true, ErrorCodes::WRONG_USAGE, true); else return ErrorOr<bool>(true, ErrorCodes::NOT_PRESENT, false); } if (this->flagsByShortName[shortName]->present) return ErrorOr<bool>(true); else return ErrorOr<bool>(false, ErrorCodes::NOT_PRESENT, false); } ErrorOr<bool> ArgumentsParser::getFlag(std::string longName) { if (!this->flagsByLongName.contains(longName)) return ErrorOr<bool>(true, ErrorCodes::UNKNOWN_KEY, false); if (this->wrongUsage) { if (this->flagsByLongName[longName]->present) return ErrorOr<bool>(true, ErrorCodes::WRONG_USAGE, true); else return ErrorOr<bool>(true, ErrorCodes::NOT_PRESENT, false); } if (this->flagsByLongName[longName]->present) return ErrorOr<bool>(true); else return ErrorOr<bool> (false, ErrorCodes::NOT_PRESENT, false); } ErrorOr<std::string> ArgumentsParser::getArgument(std::vector<CLI::Argument>::size_type position){ if (position >= this->arguments.size()) return ErrorOr<std::string>(true, ErrorCodes::OUT_OF_RANGE, std::string("")); if (this->wrongUsage) { if (this->arguments.at(position).present) return ErrorOr<std::string>(true, ErrorCodes::WRONG_USAGE, this->arguments.at(position).value); else return ErrorOr<std::string>(true, ErrorCodes::NOT_PRESENT, std::string("")); } return ErrorOr<std::string>(this->arguments.at(position).value); } ErrorOr<std::string> ArgumentsParser::getOption(char shortName) { if (!this->optionsByShortName.contains(shortName)) return ErrorOr<std::string>(true, ErrorCodes::UNKNOWN_KEY, std::string("")); if (this->wrongUsage) { if (this->optionsByShortName[shortName]->present) return ErrorOr<std::string>(true, ErrorCodes::WRONG_USAGE, this->optionsByShortName[shortName]->value); else return ErrorOr<std::string>(true, ErrorCodes::NOT_PRESENT, std::string("")); } if (this->optionsByShortName[shortName]->present) return ErrorOr<std::string>(this->optionsByShortName[shortName]->value); // argument is not present, but this is not an error -> false, NOT_PRESENT, "" else return ErrorOr<std::string>(false, ErrorCodes::NOT_PRESENT, std::string("")); } ErrorOr<std::string> ArgumentsParser::getOption(std::string longName) { if (!this->optionsByLongName.contains(longName)) return ErrorOr<std::string>(true, ErrorCodes::UNKNOWN_KEY, std::string("")); if (this->wrongUsage) { if (this->optionsByLongName[longName]->present) return ErrorOr<std::string>(true, ErrorCodes::WRONG_USAGE, this->optionsByLongName[longName]->value); else return ErrorOr<std::string>(true, ErrorCodes::NOT_PRESENT, std::string("")); } if (this->optionsByLongName[longName]->present) return ErrorOr<std::string>(this->optionsByLongName[longName]->value); // argument is not present, but this is not an error -> false, NOT_PRESENT, "" else return ErrorOr<std::string>(false, ErrorCodes::NOT_PRESENT, std::string("")); } std::string ArgumentsParser::getUsage(){ std::string usageString = ""; if (this->description != "") { usageString += "Help: " + this->programName + "\n\n\t" + this->description + "\n\n"; } usageString += "Usage: " + this->programName + " "; if(!this->flagsByShortName.empty()){ usageString += "[-"; } for(const auto& [key, value]: this->flagsByShortName){ usageString.push_back(key); } if(!this->flagsByShortName.empty()){ usageString += "] "; } for(const auto& [key, value]: this->optionsByShortName){ usageString += "[-"; usageString.push_back(key); usageString += " " + value->placeholder + "] "; } for(const auto& argument: this->arguments){ usageString += argument.placeholder + " "; } usageString.push_back('\n'); if(!this->flagsByShortName.empty()){ usageString += "\nFlags:\n"; for(const auto& [key, value]: this->flagsByShortName){ usageString += "\t-"; usageString.push_back(key); usageString += ", --" + value->longName + "\n\t\t" + value->description + "\n"; } } if(!this->optionsByShortName.empty()){ usageString += "\nOptions:\n"; for(const auto& [key, value]: this->optionsByShortName){ usageString += "\t-"; usageString.push_back(key); usageString += " " + value->placeholder + ", --" + value->longName + "=" + value->placeholder + "\n\t\t" + value->description + "\n"; } } if(!this->arguments.empty()){ usageString += "\nArguments:\n"; for(const auto& argument: this->arguments){ usageString += "\t" + argument.placeholder + "\n\t\t" + argument.description + "\n"; } } if (this->additionalInfo != "") { usageString += "\nAdditional Info:\n\n\t" + this->additionalInfo + "\n"; } return usageString; } }