// Copyright 2022, FOSS-VG Developers and Contributers // // 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 "cli.h++" #include #include #include #include "error.h++" 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; } UnpositionalArgument::UnpositionalArgument() { this->present = false; } UnpositionalArgument::UnpositionalArgument(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; } PositionalArgument::PositionalArgument() { this->present = false; } PositionalArgument::PositionalArgument(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, char* argv[], std::vector flags, std::vector unpositionalArguments, std::vector positionalArguments) { this->wrongUsage = false; this->wrongUsageMessages = std::vector(); this->programName = std::string(argv[0]); this->positionalArguments = positionalArguments; // create lookup tables for all flags and unpositional arguments // by their names this->flagsByShortName = std::map(); this->flagsByLongName = std::map(); for (Flag flag: flags) { Flag* flagPointer = new Flag(); *flagPointer = flag; this->flagsByShortName[flag.shortName] = flagPointer; this->flagsByLongName[flag.longName] = flagPointer; } this->argumentsByShortName = std::map(); this->argumentsByLongName = std::map(); for (UnpositionalArgument unpositionalArgument: unpositionalArguments) { UnpositionalArgument* argumentPointer = new UnpositionalArgument(); *argumentPointer = unpositionalArgument; this->argumentsByShortName[unpositionalArgument.shortName] = argumentPointer; this->argumentsByLongName[unpositionalArgument.longName] = argumentPointer; } UnpositionalArgument* argumentWaitingForValue = nullptr; std::vector::size_type positionalArgumentCounter = 0; for (int i=1; iwrongUsage = true; this->wrongUsageMessages.push_back(std::string("Argument expects value but has none: ")+argumentWaitingForValue->longName); argumentWaitingForValue = 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::size_type ?) // argument with =value specified? auto position = argument.find("="); if (position==std::string::npos) { // no =value //is argument or flag? std::string argumentName = argument.substr(2,argument.length()); if (flagsByLongName.contains(argumentName)) { // flag flagsByLongName[argumentName]->present = true; } else if (argumentsByLongName.contains(argumentName)) { // unpositional argument argumentsByLongName[argumentName]->present = true; argumentWaitingForValue = argumentsByLongName[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, argument.length()); std::string argumentName = argument.substr(2, position); if (argumentsByLongName.contains(argumentName)) { argumentsByLongName[argumentName]->present = true; argumentsByLongName[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::size_type ?) // starting at 1 because 0 is '-' for (int i=1; i<(int) argument.length(); i++) { //is argument or flag? if (flagsByShortName.contains(argument[i])) { flagsByShortName[argument[i]]->present = true; } else if (argumentsByShortName.contains(argument[i])) { argumentsByShortName[argument[i]]->present = true; //FIXME: see above if (i+1==(int) argument.length()) { argumentWaitingForValue = argumentsByShortName[argument[i]]; } else { //assume the rest of the argv is a concatenated argument value argumentsByShortName[argument[i]]->value = argument.substr(i+1, argument.length()); break; } } else { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Unknown argument or flag(s): ")+argument.substr(i, argument.length())); // 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 { // positional argument or value for unpositional arg? if (argumentWaitingForValue==nullptr) { // positional argument if (positionalArgumentCounter < this->positionalArguments.size()) { this->positionalArguments.at(positionalArgumentCounter).present = true; this->positionalArguments.at(positionalArgumentCounter).value = argument; } else { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Too many positional arguments. Unexpected encounter of: ")+argument); } positionalArgumentCounter++; } else { // value for unpositional argument argumentWaitingForValue->value = argument; } } } for (PositionalArgument const& positionalArgument: this->positionalArguments) { if (!positionalArgument.present) { this->wrongUsage = true; this->wrongUsageMessages.push_back(std::string("Too few positional arguments! Missing: ")+positionalArgument.placeholder); } } } ArgumentsParser::~ArgumentsParser() { //TODO: check that this actually runs for (auto const& [shortName, flag]: this->flagsByShortName) { delete flag; } for (auto const& [shortName, unpositionalArgument]: this->argumentsByShortName) { delete unpositionalArgument; } } ErrorOr ArgumentsParser::getProgramName() { if (this->wrongUsage) { return ErrorOr(true, ErrorCodes::WRONG_USAGE, this->programName); } return ErrorOr(this->programName); } ErrorOr ArgumentsParser::getFlag(int argc, char* argv[], char shortName) { if (!this->flagsByShortName.contains(shortName)) return ErrorOr(true, ErrorCodes::UNKNOWN_KEY, false); if (this->wrongUsage) { return ErrorOr(true, ErrorCodes::WRONG_USAGE, this->flagsByShortName[shortName]->present); } return ErrorOr(this->flagsByShortName[shortName]->present); } ErrorOr ArgumentsParser::getFlag(int argc, char* argv[], std::string longName) { if (!this->flagsByLongName.contains(longName)) return ErrorOr(true, ErrorCodes::UNKNOWN_KEY, false); if (this->wrongUsage) { return ErrorOr(true, ErrorCodes::WRONG_USAGE, this->flagsByLongName[longName]->present); } return ErrorOr(this->flagsByLongName[longName]->present); } ErrorOr ArgumentsParser::getPositionalArgument(int argc, char* argv[], std::vector::size_type position){ if (position >= this->positionalArguments.size()) return ErrorOr(true, ErrorCodes::OUT_OF_RANGE, std::string("")); if (this->wrongUsage) { if (this->positionalArguments.at(position).present) return ErrorOr(true, ErrorCodes::WRONG_USAGE, this->positionalArguments.at(position).value); else return ErrorOr(true, ErrorCodes::NOT_PRESENT, std::string("")); } return ErrorOr(this->positionalArguments.at(position).value); } ErrorOr ArgumentsParser::getUnpositionalArgument(int argc, char* argv[], char shortName) { if (!this->argumentsByShortName.contains(shortName)) return ErrorOr(true, ErrorCodes::UNKNOWN_KEY, std::string("")); if (this-wrongUsage) { if (this->argumentsByShortName[shortName]->present) return ErrorOr(true, ErrorCodes::WRONG_USAGE, this->argumentsByShortName[shortName]->value); else return ErrorOr(true, ErrorCodes::NOT_PRESENT, std::string("")); } if (this->argumentsByShortName[shortName]->present) return ErrorOr(this->argumentsByShortName[shortName]->value); // argument is not present, but this is not an error -> false, NOT_PRESENT, "" else return ErrorOr(false, ErrorCodes::NOT_PRESENT, std::string("")); } ErrorOr ArgumentsParser::getUnpositionalArgument(int argc, char* argv[], std::string longName) { if (!this->argumentsByLongName.contains(longName)) return ErrorOr(true, ErrorCodes::UNKNOWN_KEY, std::string("")); if (this-wrongUsage) { if (this->argumentsByLongName[longName]->present) return ErrorOr(true, ErrorCodes::WRONG_USAGE, this->argumentsByLongName[longName]->value); else return ErrorOr(true, ErrorCodes::NOT_PRESENT, std::string("")); } if (this->argumentsByLongName[longName]->present) return ErrorOr(this->argumentsByLongName[longName]->value); // argument is not present, but this is not an error -> false, NOT_PRESENT, "" else return ErrorOr(false, ErrorCodes::NOT_PRESENT, std::string("")); } //std::string ArgumentsParser::getUsage(); }