sway-wallpaper/src/CliArguments.h
2024-06-03 09:35:53 +02:00

225 lines
5.8 KiB
C++

#pragma once
#include <algorithm>
#include <cctype>
#include <exception>
#include <iomanip>
#include <iostream>
#include <map>
#include <optional>
#include <ostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace CliArguments {
enum Type {
String,
Int,
Bool
};
struct Argument {
std::vector<std::string> aliases;
std::string description;
Type type = String;
short order = 0;
bool required = false;
bool direct = false;
};
class CliArguments {
public:
CliArguments(int argc, char** argv, std::map<std::string, Argument> arguments)
: argc_(argc), argv_(argv), arguments_(arguments) {
registerArgs(argc, argv, arguments);
}
std::string getString(const std::string& argName) const {
auto value = argumentsValue_.find(argName);
if (value == argumentsValue_.end()) {
throw std::logic_error("trying to access non existant argument '" + argName + "'");
}
return value->second;
}
int getInt(const std::string& argName) const {
auto valueStr = getString(argName);
try {
return std::stoi(valueStr);
} catch (std::exception& e) {
throw std::logic_error("trying to access argument '" + argName + "' (value: '" + valueStr + "')" + " as integer");
}
}
int isInt(const std::string& argName) const {
auto valueStr = getString(argName);
for (const auto& ch : valueStr) {
if (!isdigit(ch)) {
return false;
}
}
return true;
}
bool getBool(const std::string& argName) const {
auto valueStr = getString(argName);
if (valueStr != "true" && valueStr != "false") {
throw std::logic_error("trying to access argument '" + argName + "' (value: '" + valueStr + "')" + " as bool");
}
return valueStr == "true";
}
int isBool(const std::string& argName) const {
auto valueStr = getString(argName);
return valueStr == "true" || valueStr == "false";
}
bool exists(const std::string& argName) const {
auto value = argumentsValue_.find(argName);
return value != argumentsValue_.end();
}
void printDebug() const {
for (const auto& [key, arg] : arguments_) {
std::string value = "-";
if (argumentsValue_.find(key) != argumentsValue_.end()) {
value = argumentsValue_.find(key)->second;
}
std::cout << key << ": " << value << std::endl;
}
}
void printHelp() const {
int longestArgLength = 0;
for (const auto& [key, arg] : arguments_) {
if (key.length() > longestArgLength) {
longestArgLength = key.length();
}
}
std::cout << "usage: sway-wallpaper [options]";
for (const auto& arg : directArguments_) {
std::cout << " <" << arg << ">";
}
std::cout << std::endl << std::endl;
std::cout << "required:" << std::endl;
for (const auto& [key, arg] : arguments_) {
if (!arg.direct) {
continue;
}
std::cout << std::left << std::setw(longestArgLength + 10) << (" " + key) << arg.description << std::endl;
}
std::cout << std::endl;
std::cout << "options:" << std::endl;
for (const auto& [key, arg] : arguments_) {
if (arg.direct) {
continue;
}
std::string argWithAliases = " ";
for (const auto& alias : arg.aliases) {
argWithAliases += "-" + alias + ", ";
}
argWithAliases += "--" + key;
std::cout << std::left << std::setw(longestArgLength + 10) << argWithAliases;
std::cout << arg.description << std::endl;
}
}
bool isValid() const {
return error_ == "";
}
const std::string& error() const {
return error_;
}
private:
void registerArgs(int argc, char** argv, std::map<std::string, Argument> arguments) {
for (auto& [key, arg] : arguments) {
if (arg.direct) {
directArguments_.push_back(key);
}
for (auto& alias : arg.aliases) {
aliases_[alias] = key;
}
}
std::ranges::sort(directArguments_, [&](const std::string& s1, const std::string& s2) {
return arguments_[s1].order < arguments_[s2].order;
});
int currentDirectFieldIndex = 0;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg.starts_with("-")) {
auto argName = extractArgName(arg);
if (!argName.has_value()) {
setError("'" + arg + "' is not a valid argument");
return;
}
if (arguments_[argName.value()].type == Bool) {
argumentsValue_[argName.value()] = "true";
return;
}
++i;
if (i >= argc) {
setError("Missing value for argument '" + arg + "'");
return;
}
std::string argValue = std::string(argv[i]);
argumentsValue_[argName.value()] = argValue;
} else if (currentDirectFieldIndex >= directArguments_.size()) {
setError("Too many argument, unknown '" + arg + "'");
return;
} else {
std::string& argName = directArguments_[currentDirectFieldIndex];
argumentsValue_[argName] = arg;
++currentDirectFieldIndex;
}
}
if (currentDirectFieldIndex != directArguments_.size()) {
setError("Missing required arguments");
}
}
std::optional<std::string> extractArgName(const std::string& arg) const {
if (arg.length() > 2 && arg.starts_with("--")) {
std::string argName = arg.substr(2);
if (arguments_.find(argName) == arguments_.end()) {
return std::nullopt;
}
return argName;
} else if (arg.length() == 2 && arg.starts_with("-")) {
std::string argAlias = arg.substr(1);
const auto& alias = aliases_.find(argAlias);
if (alias == aliases_.end()) {
return std::nullopt;
}
std::string argName = alias->second;
return argName;
}
return std::nullopt;
}
void setError(std::string error) {
error_ = error;
}
std::string error_;
int argc_;
char** argv_;
std::vector<std::string> directArguments_;
std::map<std::string, Argument> arguments_;
std::map<std::string, std::string> argumentsValue_;
std::map<std::string, std::string> aliases_;
};
}