lib/cli: Add a library for command line arguments parsing
This has the parser itself already implemented, all other functionality is still missing. So are tests. Yes, I’m making my own getopt. Let me.BodgeMaster-unfinished
parent
7e049fcfd1
commit
91f3dfaa5e
|
@ -0,0 +1,192 @@
|
||||||
|
// 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 <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#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 description, std::string placeholder) {
|
||||||
|
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<Flag> flags, std::vector<UnpositionalArgument> unpositionalArguments, std::vector<PositionalArgument> positionalArguments) {
|
||||||
|
this->wrongUsage = false;
|
||||||
|
this->wrongUsageMessages = std::vector<std::string>();
|
||||||
|
this->positionalArguments = positionalArguments;
|
||||||
|
// create lookup tables for all flags and unpositional arguments
|
||||||
|
// 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->argumentsByShortName = std::map<char, UnpositionalArgument*>();
|
||||||
|
this->argumentsByLongName = std::map<std::string, UnpositionalArgument*>();
|
||||||
|
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<CLI::PositionalArgument>::size_type positionalArgumentCounter = 0;
|
||||||
|
for (int i=0; i<argc; i++) {
|
||||||
|
std::string argument(argv[i]);
|
||||||
|
if (argument[0]=='-') {
|
||||||
|
// do we have unfinished business?
|
||||||
|
if (argumentWaitingForValue!=nullptr) {
|
||||||
|
this->wrongUsage = 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<char>::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<char>::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "error.h++"
|
||||||
|
|
||||||
|
namespace CLI {
|
||||||
|
|
||||||
|
struct Flag {
|
||||||
|
char shortName;
|
||||||
|
std::string longName;
|
||||||
|
// used for automatic usage generation
|
||||||
|
std::string description;
|
||||||
|
|
||||||
|
bool present;
|
||||||
|
|
||||||
|
Flag();
|
||||||
|
Flag(char shortName, std::string longName, std::string description);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnpositionalArgument {
|
||||||
|
char shortName;
|
||||||
|
std::string longName;
|
||||||
|
// used for automatic usage generation
|
||||||
|
std::string description;
|
||||||
|
std::string placeholder; // the "COUNT" in "ping [-c <COUNT>] <HOST>"
|
||||||
|
|
||||||
|
bool present;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
UnpositionalArgument();
|
||||||
|
UnpositionalArgument(char shortName, std::string longName, std::string placeholder, std::string description);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PositionalArgument {
|
||||||
|
// used for automatic usage generation
|
||||||
|
std::string description;
|
||||||
|
std::string placeholder; // the "HOST" in "ping [-c <COUNT>] <HOST>"
|
||||||
|
|
||||||
|
bool present;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
PositionalArgument();
|
||||||
|
PositionalArgument(std::string description, std::string placeholder);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArgumentsParser {
|
||||||
|
bool wrongUsage;
|
||||||
|
std::vector<std::string> wrongUsageMessages;
|
||||||
|
|
||||||
|
std::string programName;
|
||||||
|
std::map<char, Flag*> flagsByShortName;
|
||||||
|
std::map<std::string, Flag*> flagsByLongName;
|
||||||
|
std::map<char, UnpositionalArgument*> argumentsByShortName;
|
||||||
|
std::map<std::string, UnpositionalArgument*> argumentsByLongName;
|
||||||
|
std::vector<PositionalArgument> positionalArguments;
|
||||||
|
|
||||||
|
// using int here bc that's how main() is defined
|
||||||
|
ArgumentsParser(int argc, char* argv[], std::vector<Flag> flags, std::vector<UnpositionalArgument> unpositionalArguments, std::vector<PositionalArgument> positionalArguments);
|
||||||
|
~ArgumentsParser();
|
||||||
|
|
||||||
|
ErrorOr<std::string> getProgramName();
|
||||||
|
ErrorOr<bool> getFlag(int argc, char* argv, char shortName);
|
||||||
|
ErrorOr<bool> getFlag(int argc, char* argv, std::string longName);
|
||||||
|
ErrorOr<std::string> getPositionalArgument(int argc, char* argv, int position);
|
||||||
|
ErrorOr<std::string> getUnpositionalArgument(int argc, char* argv, char shortName);
|
||||||
|
ErrorOr<std::string> getUnpositionalArgument(int argc, char* argv, std::string longName);
|
||||||
|
|
||||||
|
std::string getUsage();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue