323 lines
8.5 KiB
C++
323 lines
8.5 KiB
C++
#include <iostream>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <set>
|
|
#include <cstdlib> // exit() and codes
|
|
|
|
#include "console-colours.h"
|
|
|
|
/** Expected use:
|
|
|
|
SimpleArgs args(argc, argv);
|
|
|
|
// positional argument
|
|
std::string foo = args.arg<std::string>("foo");
|
|
// optional argument
|
|
std::string bar = args.arg<std::string>("bar", "a string for Bar", "default");
|
|
// --flag=value
|
|
double = args.flag<double>("baz", "an optional flag", 5);
|
|
|
|
// Exits if "foo" not supplied
|
|
args.errorExit();
|
|
|
|
If you have multiple commands, each with their own options:
|
|
|
|
// Switches based on a command
|
|
if (args.command("bink", "Bink description")) {
|
|
// collect arguments for the command
|
|
}
|
|
// Exits with a help message (and list of commands) if no command matched
|
|
args.errorCommand();
|
|
|
|
By default, a flag of "-h" (or a command of "help", if any commands are used) prints a help message. To override:
|
|
SimpleArgs args(argc, argv);
|
|
args.helpFlag("h");
|
|
args.helpCommand("help");
|
|
|
|
**/
|
|
class SimpleArgs {
|
|
int argc;
|
|
const char* const* argv;
|
|
|
|
template<typename T>
|
|
T valueFromString(const char *arg);
|
|
|
|
std::string parsedCommand;
|
|
struct Keywords {
|
|
std::string keyword;
|
|
std::string description;
|
|
bool isHelp;
|
|
};
|
|
std::vector<Keywords> keywordOptions;
|
|
std::vector<Keywords> argDetails;
|
|
std::vector<Keywords> flagOptions;
|
|
std::set<std::string> flagSet;
|
|
void clearKeywords() {
|
|
keywordOptions.resize(0);
|
|
flagSet.clear();
|
|
flagOptions.clear();
|
|
}
|
|
|
|
bool helpMode = false;
|
|
bool checkedHelpCommand = false;
|
|
bool hasError = false;
|
|
std::string errorMessage;
|
|
void setError(std::string message) {
|
|
if (!hasError) {
|
|
hasError = true;
|
|
errorMessage = message;
|
|
}
|
|
}
|
|
|
|
std::map<std::string, std::string> flagMap;
|
|
void consumeFlags() {
|
|
while (index < argc && std::strlen(argv[index]) > 1 && argv[index][0] == '-') {
|
|
const char* arg = argv[index++];
|
|
size_t length = strlen(arg);
|
|
|
|
size_t keyStart = 1, keyEnd = keyStart + 1;
|
|
size_t valueStart = keyEnd;
|
|
// If it's "--long-arg" format
|
|
if (length > 1 && arg[1] == '-') {
|
|
keyStart++;
|
|
while (keyEnd < length && arg[keyEnd] != '=') {
|
|
keyEnd++;
|
|
}
|
|
valueStart = keyEnd;
|
|
if (keyEnd < length) valueStart++;
|
|
}
|
|
|
|
std::string key = std::string(arg + keyStart, keyEnd - keyStart);
|
|
std::string value = std::string(arg + valueStart);
|
|
|
|
if (key == "help") {
|
|
helpMode = true;
|
|
}
|
|
|
|
flagMap[key] = value;
|
|
}
|
|
}
|
|
|
|
int index = 1;
|
|
public:
|
|
SimpleArgs(int argc, const char* const argv[]) : argc(argc), argv(argv) {
|
|
std::string cmd = argv[0];
|
|
size_t slashPos = cmd.find_last_of("\\/");
|
|
if (slashPos != std::string::npos) cmd = cmd.substr(slashPos + 1);
|
|
parsedCommand = cmd;
|
|
}
|
|
|
|
void help(std::ostream& out=std::cerr) const {
|
|
std::string parsedCommand = this->parsedCommand;
|
|
if (keywordOptions.size() > 0) {
|
|
parsedCommand += std::string(" <command>");
|
|
}
|
|
out << "Usage:\n\t" << parsedCommand << "\n\n";
|
|
if (keywordOptions.size() > 0) {
|
|
out << "Commands:\n";
|
|
for (unsigned int i = 0; i < keywordOptions.size(); i++) {
|
|
out << "\t" << keywordOptions[i].keyword;
|
|
if (keywordOptions[i].isHelp) out << " [command...]";
|
|
if (keywordOptions[i].description.size()) out << " - " << keywordOptions[i].description;
|
|
out << "\n";
|
|
}
|
|
out << "\n";
|
|
}
|
|
if (argDetails.size() > 0) {
|
|
out << "Arguments:\n";
|
|
for (Keywords const &arg : argDetails) {
|
|
out << "\t" << arg.keyword;
|
|
if (arg.description.size()) out << " - " << arg.description;
|
|
out << "\n";
|
|
}
|
|
out << "\n";
|
|
}
|
|
if (flagOptions.size() > 0) {
|
|
out << "Options: " << Console::Dim << "(--arg=value)" << Console::Reset << "\n";
|
|
for (Keywords const &pair : flagOptions) {
|
|
out << "\t" << (pair.keyword.length() > 1 ? "--" : "-") << pair.keyword;
|
|
if (pair.description.size()) out << " - " << pair.description;
|
|
out << "\n";
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
|
|
bool isHelp() const {
|
|
return helpMode;
|
|
}
|
|
bool finished() const {
|
|
return index >= argc;
|
|
}
|
|
std::string peek() const {
|
|
return (index >= argc) ? "" : argv[index];
|
|
}
|
|
|
|
int errorExit(std::ostream& out=std::cerr) const {
|
|
if (hasError || helpMode) {
|
|
help(out);
|
|
if (!helpMode) {
|
|
out << Console::Red << errorMessage << Console::Reset << "\n";
|
|
}
|
|
std::exit((!helpMode && hasError) ? EXIT_FAILURE : EXIT_SUCCESS);
|
|
}
|
|
return 0;
|
|
}
|
|
int errorExit(std::string forcedError, std::ostream& out=std::cerr) const {
|
|
if (hasError) return errorExit(out); // Argument errors take priority
|
|
out << Console::Red << forcedError << Console::Reset << "\n";
|
|
std::exit(EXIT_FAILURE);
|
|
return 0;
|
|
}
|
|
int errorCommand(std::string message="", std::ostream& out=std::cerr) const {
|
|
if (keywordOptions.size()) {
|
|
// We expected a command, but didn't match on any
|
|
if (helpMode) return errorExit(out);
|
|
if (index >= argc) help(out);
|
|
if (message.length() == 0) {
|
|
message = (index < argc) ? std::string("Unknown command: ") + argv[index] : "Missing command";
|
|
}
|
|
errorExit(message, out);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
template<typename T=std::string>
|
|
T arg(std::string name, std::string longName, T defaultValue) {
|
|
consumeFlags();
|
|
if (index < argc) clearKeywords();
|
|
parsedCommand += std::string(" [") + name + "]";
|
|
argDetails.push_back(Keywords{name, longName, false});
|
|
|
|
if (index >= argc) return defaultValue;
|
|
return valueFromString<T>(argv[index++]);
|
|
}
|
|
|
|
template<typename T=std::string>
|
|
T arg(std::string name, std::string longName="") {
|
|
consumeFlags();
|
|
if (index < argc) clearKeywords();
|
|
parsedCommand += std::string(" <") + name + ">";
|
|
argDetails.push_back(Keywords{name, longName, false});
|
|
|
|
if (index >= argc) {
|
|
if (longName.length() > 0) {
|
|
setError("Missing " + longName + " <" + name + ">");
|
|
} else {
|
|
setError("Missing argument <" + name + ">");
|
|
}
|
|
return T();
|
|
}
|
|
|
|
return valueFromString<T>(argv[index++]);
|
|
}
|
|
|
|
bool command(std::string keyword, std::string description="", bool isHelp=false) {
|
|
consumeFlags();
|
|
if (index == 1) {
|
|
helpCommand();
|
|
}
|
|
if (index < argc && !keyword.compare(argv[index])) {
|
|
clearKeywords();
|
|
index++;
|
|
if (!isHelp) parsedCommand += std::string(" ") + keyword;
|
|
return true;
|
|
}
|
|
keywordOptions.push_back(Keywords{keyword, description, isHelp});
|
|
return false;
|
|
}
|
|
bool helpCommand(std::string keyword="help") {
|
|
if (!checkedHelpCommand && index == 1) {
|
|
keywordOptions.push_back(Keywords{keyword, "", true});
|
|
if (index < argc && !keyword.compare(argv[index])) {
|
|
index++;
|
|
helpMode = true;
|
|
}
|
|
}
|
|
checkedHelpCommand = true;
|
|
return helpMode;
|
|
}
|
|
|
|
template<typename T=std::string>
|
|
T flag(std::string key, std::string description, T defaultValue) {
|
|
consumeFlags();
|
|
if (!hasFlag(key, description)) return defaultValue;
|
|
|
|
auto iterator = flagMap.find(key);
|
|
return valueFromString<T>(iterator->second.c_str());
|
|
}
|
|
template<typename T=std::string>
|
|
T flag(std::string key, T defaultValue) {
|
|
consumeFlags();
|
|
if (!hasFlag(key, "")) return defaultValue;
|
|
|
|
auto iterator = flagMap.find(key);
|
|
return valueFromString<T>(iterator->second.c_str());
|
|
}
|
|
template<typename T=std::string>
|
|
T flag(std::string key) {
|
|
return flag<T>(key, T());
|
|
}
|
|
bool hasFlag(std::string key, std::string description="") {
|
|
consumeFlags();
|
|
auto iterator = flagSet.find(key);
|
|
if (iterator == flagSet.end()) {
|
|
flagSet.insert(key);
|
|
flagOptions.push_back(Keywords{key, description, false});
|
|
} else if (description.length() > 0) {
|
|
bool found = false;
|
|
for (auto &option : flagOptions) {
|
|
if (option.keyword == key) {
|
|
option.description = description;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
flagOptions.push_back(Keywords{key, description, false});
|
|
}
|
|
}
|
|
|
|
auto mapIterator = flagMap.find(key);
|
|
return mapIterator != flagMap.end();
|
|
}
|
|
bool helpFlag(std::string key, std::string description="shows this help") {
|
|
consumeFlags();
|
|
hasFlag(key, description);
|
|
auto iterator = flagMap.find(key);
|
|
helpMode = (iterator != flagMap.end());
|
|
return helpMode;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
std::string SimpleArgs::valueFromString(const char *arg) {
|
|
return arg;
|
|
}
|
|
template<>
|
|
const char * SimpleArgs::valueFromString(const char *arg) {
|
|
return arg;
|
|
}
|
|
template<>
|
|
int SimpleArgs::valueFromString(const char *arg) {
|
|
return std::stoi(arg);
|
|
}
|
|
template<>
|
|
long SimpleArgs::valueFromString(const char *arg) {
|
|
return std::stol(arg);
|
|
}
|
|
template<>
|
|
unsigned long SimpleArgs::valueFromString(const char *arg) {
|
|
return std::stoul(arg);
|
|
}
|
|
template<>
|
|
float SimpleArgs::valueFromString(const char *arg) {
|
|
return std::stof(arg);
|
|
}
|
|
template<>
|
|
double SimpleArgs::valueFromString(const char *arg) {
|
|
return std::stod(arg);
|
|
}
|