Files
vpn/vendor/cli/cli.cpp
AlexandreRouma 4fc730ae06 initial commit
2026-03-14 22:18:46 -04:00

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;
}
}