483 lines
17 KiB
C++
483 lines
17 KiB
C++
#include "cli.h"
|
|
#include <stdexcept>
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
|
|
namespace cli {
|
|
const std::vector<std::string> trueStrings = { "TRUE", "Y", "YES", "ON", "1" };
|
|
const std::vector<std::string> falseStrings = { "FALSE", "N", "NO", "OFF", "0" };
|
|
|
|
Value::Value() : _type(VAL_TYPE_INVALID) {}
|
|
|
|
Value::Value(const char* value) : _type(VAL_TYPE_STRING) { string = value; }
|
|
Value::Value(const std::string& value) : _type(VAL_TYPE_STRING) { string = value; }
|
|
Value::Value(uint8_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; }
|
|
Value::Value(uint16_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; }
|
|
Value::Value(uint32_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; }
|
|
Value::Value(uint64_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; }
|
|
Value::Value(int8_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; }
|
|
Value::Value(int16_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; }
|
|
Value::Value(int32_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; }
|
|
Value::Value(int64_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; }
|
|
Value::Value(float value) : _type(VAL_TYPE_FLOATING) { floating = value; }
|
|
Value::Value(double value) : _type(VAL_TYPE_FLOATING) { floating = value;; }
|
|
Value::Value(bool value) : _type(VAL_TYPE_BOOLEAN) { boolean = value; }
|
|
|
|
Value::Value(ValueType type, const std::string& str) : _type(type) {
|
|
std::string upperStr;
|
|
|
|
switch (type) {
|
|
case VAL_TYPE_STRING:
|
|
// Copy the string directly
|
|
string = str;
|
|
return;
|
|
case VAL_TYPE_UNSIGNED_INTEGER:
|
|
// Parse signed integer or throw error
|
|
try {
|
|
uinteger = std::stoull(str);
|
|
}
|
|
catch (const std::exception& e) {
|
|
throw std::runtime_error("Expected an unsigned integer value");
|
|
}
|
|
return;
|
|
case VAL_TYPE_SIGNED_INTEGER:
|
|
// Parse signed integer or throw error
|
|
try {
|
|
sinteger = std::stoll(str);
|
|
}
|
|
catch (const std::exception& e) {
|
|
throw std::runtime_error("Expected a signed integer value");
|
|
}
|
|
return;
|
|
case VAL_TYPE_FLOATING:
|
|
// Parse float or throw error
|
|
try {
|
|
floating = std::stod(str);
|
|
}
|
|
catch (const std::exception& e) {
|
|
throw std::runtime_error("Expected a floating point value");
|
|
}
|
|
return;
|
|
case VAL_TYPE_BOOLEAN:
|
|
// Convert to upper case
|
|
for (char c : str) { upperStr += std::toupper(c); }
|
|
|
|
// Check for a true value
|
|
if (std::find(trueStrings.begin(), trueStrings.end(), upperStr) != trueStrings.end()) {
|
|
boolean = true;
|
|
return;
|
|
}
|
|
|
|
// Check for false strings
|
|
if (std::find(falseStrings.begin(), falseStrings.end(), upperStr) != falseStrings.end()) {
|
|
boolean = false;
|
|
return;
|
|
}
|
|
|
|
// Invalid, throw error
|
|
throw std::runtime_error("Expected a boolean value");
|
|
return;
|
|
default:
|
|
throw std::runtime_error("Unsupported type");
|
|
}
|
|
}
|
|
|
|
Value::Value(const Value& b) : _type(b._type) {
|
|
// Copy the member of the designated type
|
|
switch (_type) {
|
|
case VAL_TYPE_STRING:
|
|
string = b.string; break;
|
|
case VAL_TYPE_UNSIGNED_INTEGER:
|
|
uinteger = b.uinteger; break;
|
|
case VAL_TYPE_SIGNED_INTEGER:
|
|
sinteger = b.sinteger; break;
|
|
case VAL_TYPE_FLOATING:
|
|
floating = b.floating; break;
|
|
case VAL_TYPE_BOOLEAN:
|
|
boolean = b.boolean; break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Value::Value(Value&& b) : _type(b._type) {
|
|
// Move the member of the designated type
|
|
switch (_type) {
|
|
case VAL_TYPE_STRING:
|
|
string = std::move(b.string); break;
|
|
case VAL_TYPE_UNSIGNED_INTEGER:
|
|
uinteger = b.uinteger; break;
|
|
case VAL_TYPE_SIGNED_INTEGER:
|
|
sinteger = b.sinteger; break;
|
|
case VAL_TYPE_FLOATING:
|
|
floating = b.floating; break;
|
|
case VAL_TYPE_BOOLEAN:
|
|
boolean = b.boolean; break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Value& Value::operator=(const Value& b) {
|
|
// Update the type
|
|
_type = b._type;
|
|
|
|
// Copy the member of the designated type
|
|
switch (_type) {
|
|
case VAL_TYPE_STRING:
|
|
string = b.string; break;
|
|
case VAL_TYPE_UNSIGNED_INTEGER:
|
|
uinteger = b.uinteger; string.clear(); break;
|
|
case VAL_TYPE_SIGNED_INTEGER:
|
|
sinteger = b.sinteger; string.clear(); break;
|
|
case VAL_TYPE_FLOATING:
|
|
floating = b.floating; string.clear(); break;
|
|
case VAL_TYPE_BOOLEAN:
|
|
boolean = b.boolean; string.clear(); break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Return self
|
|
return *this;
|
|
}
|
|
|
|
Value& Value::operator=(Value&& b) {
|
|
// Update the type
|
|
_type = b._type;
|
|
|
|
// Move the member of the designated type
|
|
switch (_type) {
|
|
case VAL_TYPE_STRING:
|
|
string = std::move(b.string); break;
|
|
case VAL_TYPE_UNSIGNED_INTEGER:
|
|
uinteger = b.uinteger; string.clear(); break;
|
|
case VAL_TYPE_SIGNED_INTEGER:
|
|
sinteger = b.sinteger; string.clear(); break;
|
|
case VAL_TYPE_FLOATING:
|
|
floating = b.floating; string.clear(); break;
|
|
case VAL_TYPE_BOOLEAN:
|
|
boolean = b.boolean; string.clear(); break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Return self
|
|
return *this;
|
|
}
|
|
|
|
Value::operator const std::string&() const {
|
|
if (_type != VAL_TYPE_STRING) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return string;
|
|
}
|
|
|
|
Value::operator uint8_t() const {
|
|
if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return (uint8_t)uinteger;
|
|
}
|
|
|
|
Value::operator uint16_t() const {
|
|
if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return (uint16_t)uinteger;
|
|
}
|
|
|
|
Value::operator uint32_t() const {
|
|
if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return (uint32_t)uinteger;
|
|
}
|
|
|
|
Value::operator uint64_t() const {
|
|
if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return uinteger;
|
|
}
|
|
|
|
Value::operator int8_t() const {
|
|
if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return (int8_t)sinteger;
|
|
}
|
|
|
|
Value::operator int16_t() const {
|
|
if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return (int16_t)sinteger;
|
|
}
|
|
|
|
Value::operator int32_t() const {
|
|
if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return (int32_t)sinteger;
|
|
}
|
|
|
|
Value::operator int64_t() const {
|
|
if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return sinteger;
|
|
}
|
|
|
|
Value::operator float() const {
|
|
if (_type != VAL_TYPE_FLOATING) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return (float)floating;
|
|
}
|
|
|
|
Value::operator double() const {
|
|
if (_type != VAL_TYPE_FLOATING) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return floating;
|
|
}
|
|
|
|
Value::operator bool() const {
|
|
if (_type != VAL_TYPE_BOOLEAN) { throw std::runtime_error("Cannot cast value due to type mismatch"); }
|
|
return boolean;
|
|
}
|
|
|
|
Interface::Interface() {}
|
|
|
|
Interface::Interface(const Interface& b) {
|
|
// Copy all members
|
|
aliases = b.aliases;
|
|
arguments = b.arguments;
|
|
subcommands = b.subcommands;
|
|
}
|
|
|
|
Interface::Interface(Interface&& b) {
|
|
// Move all members
|
|
aliases = std::move(b.aliases);
|
|
arguments = std::move(b.arguments);
|
|
subcommands = std::move(b.subcommands);
|
|
}
|
|
|
|
Interface& Interface::operator=(const Interface& b) {
|
|
// Copy all members
|
|
aliases = b.aliases;
|
|
arguments = b.arguments;
|
|
subcommands = b.subcommands;
|
|
|
|
// Return self
|
|
return *this;
|
|
}
|
|
|
|
Interface& Interface::operator=(Interface&& b) {
|
|
// Move all members
|
|
aliases = std::move(b.aliases);
|
|
arguments = std::move(b.arguments);
|
|
subcommands = std::move(b.subcommands);
|
|
|
|
// Return self
|
|
return *this;
|
|
}
|
|
|
|
void Interface::arg(const std::string& name, char alias, Value defValue, const std::string& description) {
|
|
// Check if an argument with that name already exists
|
|
if (arguments.find(name) != arguments.end()) {
|
|
throw std::runtime_error("An argument with the given name already exists");
|
|
}
|
|
|
|
// If an alias was given
|
|
if (alias) {
|
|
// Check if an alias with that character already exists
|
|
if (aliases.find(alias) != aliases.end()) {
|
|
throw std::runtime_error("An argument with the given alias already exists");
|
|
}
|
|
|
|
// Save the alias
|
|
aliases[alias] = name;
|
|
}
|
|
|
|
// Save the argument
|
|
Argument arg = { defValue, description };
|
|
arguments[name] = arg;
|
|
}
|
|
|
|
void Interface::subcmd(const std::string& name, const Interface& interface, const std::string& description) {
|
|
// Check if a subcommand of that name does not exist yet
|
|
if (subcommands.find(name) != subcommands.end()) {
|
|
throw std::runtime_error("A subcommand with the given name already exists");
|
|
}
|
|
|
|
// Save the interface of the subcommand
|
|
SubCommand scmd = { std::make_shared<Interface>(interface), description };
|
|
subcommands[name] = scmd;
|
|
}
|
|
|
|
Command::Command() {}
|
|
|
|
Command::Command(const std::string& command, const Interface& interface) {
|
|
// Save the command
|
|
this->command = command;
|
|
|
|
// Go through all defined arguments
|
|
for (const auto& [name, arg] : interface.arguments) {
|
|
// Initialize the argument with the default value
|
|
arguments[name] = arg.defValue;
|
|
}
|
|
}
|
|
|
|
Command::Command(const Command& b) {
|
|
// Copy all members
|
|
subcommand = b.subcommand;
|
|
command = b.command;
|
|
arguments = b.arguments;
|
|
values = b.values;
|
|
}
|
|
|
|
Command::Command(Command&& b) {
|
|
// Move all members
|
|
subcommand = std::move(b.subcommand);
|
|
command = std::move(b.command);
|
|
arguments = std::move(b.arguments);
|
|
values = std::move(b.values);
|
|
}
|
|
|
|
Command& Command::operator=(const Command& b) {
|
|
// Copy b since it could be allocated by this->subcommand
|
|
Command bcpy = b;
|
|
|
|
// Move all members from the copy to self
|
|
subcommand = std::move(bcpy.subcommand);
|
|
command = std::move(bcpy.command);
|
|
arguments = std::move(bcpy.arguments);
|
|
values = std::move(bcpy.values);
|
|
|
|
// Return self
|
|
return *this;
|
|
}
|
|
|
|
Command& Command::operator=(Command&& b) {
|
|
// Move b since it could be allocated by this->subcommand
|
|
Command bcpy = std::move(b);
|
|
|
|
// Move all members from the copy to self
|
|
subcommand = std::move(bcpy.subcommand);
|
|
command = std::move(bcpy.command);
|
|
arguments = std::move(bcpy.arguments);
|
|
values = std::move(bcpy.values);
|
|
|
|
// Return self
|
|
return *this;
|
|
}
|
|
|
|
Command::operator const std::string&() const {
|
|
return command;
|
|
}
|
|
|
|
bool Command::operator==(const std::string& b) const {
|
|
return (command == b);
|
|
}
|
|
|
|
bool Command::operator==(const char* b) const {
|
|
return !strcmp(command.c_str(), b);
|
|
}
|
|
|
|
const Value& Command::operator[](const std::string& arg) const {
|
|
// Get value from argument list
|
|
return arguments.at(arg);
|
|
}
|
|
|
|
bool isValidBoolean(const std::string& str) {
|
|
// Convert to upper case
|
|
std::string upperStr;
|
|
for (char c : str) { upperStr += std::toupper(c); }
|
|
return std::find(trueStrings.begin(), trueStrings.end(), upperStr) != trueStrings.end() ||
|
|
std::find(falseStrings.begin(), falseStrings.end(), upperStr) != falseStrings.end();
|
|
}
|
|
|
|
void parseArgument(Command& cmd, std::string& argName, ValueType type, int& argc, char**& argv) {
|
|
// If the argument is a boolean
|
|
if (type == VAL_TYPE_BOOLEAN) {
|
|
// If no value follows it's not a valid boolean
|
|
if (!argc || !isValidBoolean(*argv)) {
|
|
// Assume the value is true
|
|
cmd.arguments[argName] = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Expect a value
|
|
if (!argc) { throw std::runtime_error("Expected a value"); }
|
|
|
|
// Pop the value
|
|
std::string value = *(argv++); argc--;
|
|
|
|
// Parse and set the value
|
|
cmd.arguments[argName] = Value(type, value);
|
|
}
|
|
|
|
Command parse(const Interface& interface, int argc, char** argv) {
|
|
// Pop the command name
|
|
std::string cmdName = *(argv++); argc--;
|
|
|
|
// Initialize the command
|
|
Command cmd(cmdName, interface);
|
|
|
|
// Process until no arguments are left
|
|
while (argc) {
|
|
// Pop the argument
|
|
std::string arg = *(argv++); argc--;
|
|
|
|
// If the argument gives a long name
|
|
if (arg.starts_with("--")) {
|
|
// Verify that the command admits the given argument
|
|
std::string argName = arg.substr(2);
|
|
auto it = interface.arguments.find(argName);
|
|
if (it == interface.arguments.end()) {
|
|
throw std::runtime_error("Unknown argument");
|
|
}
|
|
|
|
// Parse the argument
|
|
parseArgument(cmd, argName, it->second.defValue.type, argc, argv);
|
|
continue;
|
|
}
|
|
|
|
// Otherwise if the argument gives aliases
|
|
if (arg.starts_with("-")) {
|
|
// Iterate through each alias
|
|
for (int i = 1; i < arg.size(); i++) {
|
|
// Get the alias
|
|
char c = arg[i];
|
|
|
|
// Verify that the command admits the given alias
|
|
auto it = interface.aliases.find(c);
|
|
if (it == interface.aliases.end()) {
|
|
throw std::runtime_error("Unknown argument");
|
|
}
|
|
|
|
// Fetch the argument descriptor
|
|
std::string argName = it->second;
|
|
const auto& desc = interface.arguments.at(argName);
|
|
|
|
// If the argument is not at the end of the compound argument
|
|
if (i < arg.size()-1) {
|
|
// if the argument is a boolean
|
|
if (desc.defValue.type == VAL_TYPE_BOOLEAN) {
|
|
// Assume the value is true
|
|
cmd.arguments[argName] = true;
|
|
continue;
|
|
}
|
|
|
|
// Non-boolean arguments are not allowed before the end of the compound argument
|
|
throw std::runtime_error("Non boolean argumentcan only be at the end of a compound argument");
|
|
}
|
|
|
|
// Parse the argument
|
|
parseArgument(cmd, argName, desc.defValue.type, argc, argv);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// If the interface needs a subcommand
|
|
if (!interface.subcommands.empty()) {
|
|
// Verify that the command admits the given subcommand
|
|
auto it = interface.subcommands.find(arg);
|
|
if (it == interface.subcommands.end()) {
|
|
throw std::runtime_error("Unknown sub-command");
|
|
}
|
|
|
|
// Parse the subcommand and finish
|
|
cmd.subcommand = std::make_shared<Command>(parse(*(it->second.iface), ++argc, --argv));
|
|
break;
|
|
}
|
|
|
|
// Just append the argument to the value list
|
|
cmd.values.push_back(arg);
|
|
}
|
|
|
|
// Return the newly parsed command
|
|
return cmd;
|
|
}
|
|
} |