Riyyi
2 years ago
60 changed files with 2 additions and 8347 deletions
@ -1,568 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include <algorithm> // find_if |
||||
#include <cstddef> // size_t |
||||
#include <cstdint> // uint8_t |
||||
#include <cstdio> // printf |
||||
#include <cstring> // strcmp |
||||
#include <limits> // numeric_limits |
||||
#include <string> // stod, stoi, stoul |
||||
#include <string_view> |
||||
#include <vector> |
||||
|
||||
#include "util/argparser.h" |
||||
|
||||
namespace Util { |
||||
|
||||
ArgParser::ArgParser() |
||||
{ |
||||
} |
||||
|
||||
ArgParser::~ArgParser() |
||||
{ |
||||
} |
||||
|
||||
void ArgParser::printError(char parameter, Error error) |
||||
{ |
||||
char tmp[] { parameter, '\0' }; |
||||
printError(tmp, error, false); |
||||
} |
||||
|
||||
void ArgParser::printError(const char* parameter, Error error, bool longName) |
||||
{ |
||||
if (!m_errorReporting) { |
||||
return; |
||||
} |
||||
|
||||
if (error == Error::OptionInvalid) { |
||||
printf("%s: invalid option -- '%s'\n", m_name, parameter); |
||||
} |
||||
else if (error == Error::OptionUnrecognized) { |
||||
printf("%s: unrecognized option -- '%s'\n", m_name, parameter); |
||||
} |
||||
else if (error == Error::OptionDoesntAllowArgument) { |
||||
printf("%s: option '--%s' doesn't allow an argument\n", m_name, parameter); |
||||
} |
||||
else if (error == Error::OptionRequiresArgument) { |
||||
if (longName) { |
||||
printf("%s: option '--%s' requires an argument\n", m_name, parameter); |
||||
} |
||||
else { |
||||
printf("%s: option requires an argument -- '%s'\n", m_name, parameter); |
||||
} |
||||
} |
||||
else if (error == Error::OptionInvalidArgumentType) { |
||||
if (longName) { |
||||
printf("%s: option '--%s' invalid type\n", m_name, parameter); |
||||
} |
||||
else { |
||||
printf("%s: option invalid type -- '%s'\n", m_name, parameter); |
||||
} |
||||
} |
||||
else if (error == Error::ArgumentExtraOperand) { |
||||
printf("%s: extra operand '%s'\n", m_name, parameter); |
||||
} |
||||
else if (error == Error::ArgumentRequired) { |
||||
printf("%s: missing required argument '%s'\n", m_name, parameter); |
||||
} |
||||
else if (error == Error::ArgumentInvalidType) { |
||||
printf("%s: invalid argument type '%s'\n", m_name, parameter); |
||||
} |
||||
|
||||
// TODO: Print command usage, if it's enabled.
|
||||
} |
||||
|
||||
// Required: directly after || separated by space
|
||||
// Optional: directly after
|
||||
bool ArgParser::parseShortOption(std::string_view option, std::string_view next) |
||||
{ |
||||
bool result = true; |
||||
|
||||
#ifndef NDEBUG |
||||
printf("Parsing short option: '%s'\n", option.data()); |
||||
#endif |
||||
|
||||
char c; |
||||
std::string_view value; |
||||
for (size_t i = 0; i < option.size(); ++i) { |
||||
c = option.at(i); |
||||
|
||||
#ifndef NDEBUG |
||||
printf("short '%c'\n", c); |
||||
#endif |
||||
|
||||
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&c](Option& it) -> bool { |
||||
return it.shortName == c; |
||||
}); |
||||
|
||||
// Option does not exist
|
||||
if (foundOption == m_options.cend()) { |
||||
printError(c, Error::OptionInvalid); |
||||
|
||||
if (m_exitOnFirstError) { |
||||
return false; |
||||
} |
||||
} |
||||
else if (foundOption->requiresArgument == Required::No) { |
||||
// FIXME: Figure out why providing a nullptr breaks the lambda here.
|
||||
result = foundOption->acceptValue(""); |
||||
} |
||||
else if (foundOption->requiresArgument == Required::Yes) { |
||||
value = option.substr(i + 1); |
||||
if (value.empty() && next.empty()) { |
||||
foundOption->error = Error::OptionRequiresArgument; |
||||
printError(c, Error::OptionRequiresArgument); |
||||
result = false; |
||||
} |
||||
else if (!value.empty()) { |
||||
result = foundOption->acceptValue(value.data()); |
||||
if (!result) { |
||||
printError(c, Error::OptionInvalidArgumentType); |
||||
} |
||||
} |
||||
else if (next[0] == '-') { |
||||
foundOption->error = Error::OptionRequiresArgument; |
||||
printError(c, Error::OptionRequiresArgument); |
||||
result = false; |
||||
} |
||||
else { |
||||
result = foundOption->acceptValue(next.data()); |
||||
m_optionIndex++; |
||||
if (!result) { |
||||
printError(c, Error::OptionInvalidArgumentType); |
||||
} |
||||
} |
||||
|
||||
break; |
||||
} |
||||
else if (foundOption->requiresArgument == Required::Optional) { |
||||
value = option.substr(i + 1); |
||||
if (!value.empty()) { |
||||
result = foundOption->acceptValue(value.data()); |
||||
if (!result) { |
||||
printError(c, Error::OptionInvalidArgumentType); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
// Required: directly after, separated by '=' || separated by space
|
||||
// Optional: directly after, separated by '='
|
||||
bool ArgParser::parseLongOption(std::string_view option, std::string_view next) |
||||
{ |
||||
std::string name = std::string(option.substr(0, option.find_first_of('='))); |
||||
std::string_view value = option.substr(option.find_first_of('=') + 1); |
||||
|
||||
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&name](Option& it) -> bool { |
||||
return it.longName && it.longName == name; |
||||
}); |
||||
|
||||
if (foundOption == m_options.cend()) { |
||||
printError(name.data(), Error::OptionUnrecognized); |
||||
return false; |
||||
} |
||||
|
||||
enum class ArgumentProvided : uint8_t { |
||||
No, |
||||
DirectlyAfter, |
||||
Seperated, |
||||
}; |
||||
|
||||
auto argument = ArgumentProvided::No; |
||||
if (name != value || option.find('=') != std::string_view::npos) { |
||||
argument = ArgumentProvided::DirectlyAfter; |
||||
} |
||||
else if (!next.empty() && next[0] != '-') { |
||||
argument = ArgumentProvided::Seperated; |
||||
value = next; |
||||
} |
||||
|
||||
bool result = true; |
||||
|
||||
if (foundOption->requiresArgument == Required::No) { |
||||
if (argument == ArgumentProvided::DirectlyAfter) { |
||||
foundOption->error = Error::OptionDoesntAllowArgument; |
||||
printError(name.data(), Error::OptionDoesntAllowArgument); |
||||
return false; |
||||
} |
||||
|
||||
result = foundOption->acceptValue(""); |
||||
} |
||||
else if (foundOption->requiresArgument == Required::Yes) { |
||||
if (argument == ArgumentProvided::No) { |
||||
foundOption->error = Error::OptionRequiresArgument; |
||||
printError(name.data(), Error::OptionRequiresArgument); |
||||
return false; |
||||
} |
||||
|
||||
result = foundOption->acceptValue(value.data()); |
||||
if (!result) { |
||||
printError(name.data(), Error::OptionInvalidArgumentType); |
||||
} |
||||
|
||||
if (argument == ArgumentProvided::Seperated) { |
||||
m_optionIndex++; |
||||
} |
||||
} |
||||
else if (foundOption->requiresArgument == Required::Optional) { |
||||
if (argument == ArgumentProvided::DirectlyAfter) { |
||||
result = foundOption->acceptValue(value.data()); |
||||
if (!result) { |
||||
printError(name.data(), Error::OptionInvalidArgumentType); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
bool ArgParser::parseArgument(std::string_view argument) |
||||
{ |
||||
bool result = true; |
||||
|
||||
for (;;) { |
||||
// Run out of argument handlers
|
||||
if (m_argumentIndex >= m_arguments.size()) { |
||||
printError(argument.data(), Error::ArgumentExtraOperand); |
||||
return false; |
||||
} |
||||
|
||||
Argument& currentArgument = m_arguments.at(m_argumentIndex); |
||||
result = currentArgument.acceptValue(argument.data()); |
||||
|
||||
if (result) { |
||||
currentArgument.addedValues++; |
||||
if (currentArgument.addedValues >= currentArgument.maxValues) { |
||||
m_argumentIndex++; |
||||
} |
||||
} |
||||
else if (currentArgument.minValues == 0) { |
||||
m_argumentIndex++; |
||||
continue; |
||||
} |
||||
else { |
||||
printError(argument.data(), Error::ArgumentInvalidType); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
bool ArgParser::parse(int argc, const char* argv[]) |
||||
{ |
||||
bool result = true; |
||||
|
||||
// Set looping indices
|
||||
m_optionIndex = 1; |
||||
m_argumentIndex = 0; |
||||
|
||||
// By default parse all '-' prefixed parameters as options
|
||||
m_nonOptionMode = false; |
||||
|
||||
// Get program name
|
||||
m_name = argv[0] + std::string_view(argv[0]).find_last_of('/') + 1; |
||||
|
||||
std::string_view argument; |
||||
std::string_view next; |
||||
for (; m_optionIndex < (size_t)argc; ++m_optionIndex) { |
||||
|
||||
#ifndef NDEBUG |
||||
printf("argv[%zu]: %s\n", m_optionIndex, argv[m_optionIndex]); |
||||
#endif |
||||
|
||||
// Get the current and next parameter
|
||||
argument = argv[m_optionIndex]; |
||||
if (m_optionIndex + 1 < (size_t)argc && argv[m_optionIndex + 1][0] != '-') { |
||||
next = argv[m_optionIndex + 1]; |
||||
} |
||||
else { |
||||
next = {}; |
||||
} |
||||
|
||||
// Stop parsing '-' prefixed parameters as options afer '--'
|
||||
if (argument.compare("--") == 0) { |
||||
m_nonOptionMode = true; |
||||
continue; |
||||
} |
||||
|
||||
// Long Option
|
||||
if (!m_nonOptionMode && argument[0] == '-' && argument[1] == '-') { |
||||
argument = argument.substr(argument.find_first_not_of('-')); |
||||
if (!parseLongOption(argument, next)) { |
||||
result = false; |
||||
} |
||||
} |
||||
// Short Option
|
||||
else if (!m_nonOptionMode && argument[0] == '-') { |
||||
argument = argument.substr(argument.find_first_not_of('-')); |
||||
if (!parseShortOption(argument, next)) { |
||||
result = false; |
||||
} |
||||
} |
||||
// Argument
|
||||
else { |
||||
if (m_stopParsingOnFirstNonOption) { |
||||
m_nonOptionMode = true; |
||||
} |
||||
if (!parseArgument(argument)) { |
||||
result = false; |
||||
} |
||||
} |
||||
|
||||
if (m_exitOnFirstError && !result) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Check any leftover arguments for required
|
||||
for (; m_argumentIndex < m_arguments.size(); ++m_argumentIndex) { |
||||
Argument& currentArgument = m_arguments.at(m_argumentIndex); |
||||
if (currentArgument.minValues > currentArgument.addedValues) { |
||||
result = false; |
||||
printError(currentArgument.name ? currentArgument.name : "", Error::ArgumentRequired); |
||||
} |
||||
} |
||||
|
||||
if (result) { |
||||
return true; |
||||
} |
||||
|
||||
for (auto& option : m_options) { |
||||
if (option.longName && strcmp(option.longName, "help") == 0) { |
||||
printf("Try '%s --help' for more information.\n", m_name); |
||||
break; |
||||
} |
||||
if (option.shortName == 'h') { |
||||
printf("Try '%s -h' for more information.\n", m_name); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void ArgParser::addOption(Option&& option) |
||||
{ |
||||
m_options.push_back(option); |
||||
} |
||||
|
||||
void ArgParser::addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString) |
||||
{ |
||||
addOption({ shortName, longName, nullptr, usageString, manString, Required::No, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||
{ |
||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||
{ |
||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||
{ |
||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||
{ |
||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||
{ |
||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||
{ |
||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addOption(std::vector<std::string>& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||
{ |
||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(values) }); |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void ArgParser::addArgument(Argument&& argument) |
||||
{ |
||||
m_arguments.push_back(argument); |
||||
} |
||||
|
||||
void ArgParser::addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||
} |
||||
|
||||
void ArgParser::addArgument(std::vector<std::string>& values, const char* name, const char* usageString, const char* manString, Required required) |
||||
{ |
||||
size_t minValues = required == Required::Yes ? 1 : 0; |
||||
addArgument({ name, usageString, manString, minValues, values.max_size(), 0, getAcceptFunction(values) }); |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(bool& value) |
||||
{ |
||||
return [&value](const char*) -> bool { |
||||
value = true; |
||||
return true; |
||||
}; |
||||
} |
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(const char*& value) |
||||
{ |
||||
return [&value](const char* input) -> bool { |
||||
value = input; |
||||
return true; |
||||
}; |
||||
} |
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(std::string& value) |
||||
{ |
||||
return [&value](const char* input) -> bool { |
||||
value = input; |
||||
return true; |
||||
}; |
||||
} |
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(std::string_view& value) |
||||
{ |
||||
return [&value](const char* input) -> bool { |
||||
value = input; |
||||
return true; |
||||
}; |
||||
} |
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(int& value) |
||||
{ |
||||
return [&value](const char* input) -> bool { |
||||
const char* validate = input; |
||||
for (; *validate != '\0'; ++validate) { |
||||
// - [0-9]
|
||||
if (*validate != 45 && (*validate < 48 || *validate > 57)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
try { |
||||
value = std::stoi(input); |
||||
return true; |
||||
} |
||||
catch (...) { |
||||
return false; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(unsigned int& value) |
||||
{ |
||||
return [&value](const char* input) -> bool { |
||||
const char* validate = input; |
||||
for (; *validate != '\0'; ++validate) { |
||||
// [0-9]
|
||||
if (*validate < 48 || *validate > 57) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
unsigned long convert = 0; |
||||
try { |
||||
convert = std::stoul(input); |
||||
} |
||||
catch (...) { |
||||
return false; |
||||
} |
||||
|
||||
if (convert <= std::numeric_limits<unsigned int>::max()) { |
||||
value = static_cast<unsigned int>(convert); |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
}; |
||||
} |
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(double& value) |
||||
{ |
||||
return [&value](const char* input) -> bool { |
||||
const char* validate = input; |
||||
for (; *validate != '\0'; ++validate) { |
||||
// . [0-9]
|
||||
if (*validate != 46 && (*validate < 48 || *validate > 57)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
try { |
||||
value = std::stod(input); |
||||
return true; |
||||
} |
||||
catch (...) { |
||||
return false; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
AcceptFunction ArgParser::getAcceptFunction(std::vector<std::string>& value) |
||||
{ |
||||
return [&value](const char* input) -> bool { |
||||
value.push_back(input); |
||||
return true; |
||||
}; |
||||
} |
||||
|
||||
} // namespace Util
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <cstddef> // size_t |
||||
#include <cstdint> // uint8_t |
||||
#include <functional> // function |
||||
#include <string> |
||||
#include <string_view> |
||||
#include <vector> |
||||
|
||||
namespace Util { |
||||
|
||||
using AcceptFunction = std::function<bool(const char*)>; |
||||
|
||||
class ArgParser final { |
||||
public: |
||||
ArgParser(); |
||||
virtual ~ArgParser(); |
||||
|
||||
enum class Required : uint8_t { |
||||
No, |
||||
Yes, |
||||
Optional, |
||||
}; |
||||
|
||||
enum class Error : uint8_t { |
||||
None, |
||||
OptionInvalid, // For short options
|
||||
OptionUnrecognized, // For long options
|
||||
OptionDoesntAllowArgument, |
||||
OptionRequiresArgument, |
||||
OptionInvalidArgumentType, |
||||
ArgumentExtraOperand, |
||||
ArgumentRequired, |
||||
ArgumentInvalidType, |
||||
}; |
||||
|
||||
struct Option { |
||||
char shortName { 0 }; |
||||
const char* longName { nullptr }; |
||||
const char* argumentName { nullptr }; |
||||
const char* usageString { nullptr }; |
||||
const char* manString { nullptr }; |
||||
Required requiresArgument; |
||||
AcceptFunction acceptValue; |
||||
|
||||
Error error = Error::None; |
||||
}; |
||||
|
||||
struct Argument { |
||||
const char* name { nullptr }; |
||||
const char* usageString { nullptr }; |
||||
const char* manString { nullptr }; |
||||
size_t minValues { 0 }; |
||||
size_t maxValues { 1 }; |
||||
size_t addedValues { 0 }; |
||||
AcceptFunction acceptValue; |
||||
}; |
||||
|
||||
bool parse(int argc, const char* argv[]); |
||||
|
||||
void addOption(Option&& option); |
||||
void addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString); |
||||
void addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||
void addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||
void addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||
void addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||
void addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||
void addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||
void addOption(std::vector<std::string>& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||
|
||||
void addArgument(Argument&& argument); |
||||
void addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
void addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
void addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
void addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
void addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
void addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
void addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
void addArgument(std::vector<std::string>& values, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||
|
||||
void setErrorReporting(bool state) { m_errorReporting = state; } |
||||
void setExitOnFirstError(bool state) { m_exitOnFirstError = state; } |
||||
void setStopParsingOnFirstNonOption(bool state) { m_stopParsingOnFirstNonOption = state; } |
||||
|
||||
private: |
||||
void printError(char parameter, Error error); |
||||
void printError(const char* parameter, Error error, bool longName = true); |
||||
bool parseShortOption(std::string_view option, std::string_view next); |
||||
bool parseLongOption(std::string_view option, std::string_view next); |
||||
bool parseArgument(std::string_view argument); |
||||
|
||||
AcceptFunction getAcceptFunction(bool& value); |
||||
AcceptFunction getAcceptFunction(const char*& value); |
||||
AcceptFunction getAcceptFunction(std::string& value); |
||||
AcceptFunction getAcceptFunction(std::string_view& value); |
||||
AcceptFunction getAcceptFunction(int& value); |
||||
AcceptFunction getAcceptFunction(unsigned int& value); |
||||
AcceptFunction getAcceptFunction(double& value); |
||||
AcceptFunction getAcceptFunction(std::vector<std::string>& value); |
||||
|
||||
bool m_errorReporting { true }; |
||||
bool m_exitOnFirstError { true }; |
||||
bool m_stopParsingOnFirstNonOption { false }; |
||||
|
||||
size_t m_optionIndex { 1 }; |
||||
size_t m_argumentIndex { 0 }; |
||||
bool m_nonOptionMode { false }; |
||||
|
||||
const char* m_name { nullptr }; |
||||
std::vector<Option> m_options; |
||||
std::vector<Argument> m_arguments; |
||||
}; |
||||
|
||||
} // namespace Util
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include <cstdint> // int32_t |
||||
#include <filesystem> |
||||
#include <fstream> // ifstream, ios, ofstream |
||||
#include <memory> // make_unique |
||||
#include <string> |
||||
|
||||
#include "util/file.h" |
||||
#include "util/meta/assert.h" |
||||
|
||||
namespace Util { |
||||
|
||||
File::File(const std::string& path) |
||||
: m_path(path) |
||||
{ |
||||
// Create input stream object and open file
|
||||
std::ifstream file(path, std::ios::in); |
||||
VERIFY(file.is_open(), "failed to open file: '{}'", path); |
||||
|
||||
// Get length of the file
|
||||
file.seekg(0, std::ios::end); |
||||
int32_t size = file.tellg(); |
||||
file.seekg(0, std::ios::beg); |
||||
VERIFY(size != -1, "failed to read file length: '{}', path"); |
||||
|
||||
// Allocate memory filled with zeros
|
||||
auto buffer = std::make_unique<char[]>(size); |
||||
|
||||
// Fill buffer with file contents
|
||||
file.read(buffer.get(), size); |
||||
file.close(); |
||||
|
||||
m_data = std::string(buffer.get(), size); |
||||
} |
||||
|
||||
File::~File() |
||||
{ |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
File File::create(const std::string& path) |
||||
{ |
||||
if (!std::filesystem::exists(path)) { |
||||
std::ofstream { path }; |
||||
} |
||||
|
||||
return Util::File(path); |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void File::clear() |
||||
{ |
||||
m_data.clear(); |
||||
} |
||||
|
||||
File& File::append(const std::string& data) |
||||
{ |
||||
m_data.append(data); |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
File& File::replace(size_t index, size_t length, const std::string& data) |
||||
{ |
||||
m_data.replace(index, length, data); |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
File& File::flush() |
||||
{ |
||||
// Create output stream object and open file
|
||||
std::ofstream file(m_path, std::ios::out | std::ios::trunc); |
||||
VERIFY(file.is_open(), "failed to open file: '{}'", m_path); |
||||
|
||||
// Write data to disk
|
||||
file.write(m_data.c_str(), m_data.size()); |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
} // namespace Util
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <string> |
||||
|
||||
namespace Util { |
||||
|
||||
class File { |
||||
public: |
||||
File(const std::string& path); |
||||
virtual ~File(); |
||||
|
||||
static File create(const std::string& path); |
||||
|
||||
void clear(); |
||||
File& append(const std::string& data); |
||||
File& replace(size_t index, size_t length, const std::string& data); |
||||
File& flush(); |
||||
|
||||
const char* c_str() const { return m_data.c_str(); } |
||||
const std::string& data() const { return m_data; } |
||||
const std::string& path() const { return m_path; } |
||||
|
||||
private: |
||||
std::string m_path; |
||||
std::string m_data; |
||||
}; |
||||
|
||||
} // namespace Util
|
@ -1,6 +0,0 @@
|
||||
# -*- yaml -*- |
||||
|
||||
--- |
||||
# FIXME: Figure out why NOLINTBEGIN/NOLINTEND doesnt work |
||||
Checks: -misc-unused-using-decls |
||||
... |
@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include <algorithm> // min |
||||
#include <cstddef> // size_t |
||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t |
||||
#include <iomanip> // setprecision |
||||
#include <ios> // defaultfloat, fixed |
||||
#include <limits> // numeric_limits |
||||
#include <sstream> // stringstream |
||||
#include <string> |
||||
#include <string_view> |
||||
|
||||
#include "util/format/builder.h" |
||||
#include "util/meta/assert.h" |
||||
|
||||
namespace Util::Format { |
||||
|
||||
void Builder::putLiteral(std::string_view literal) |
||||
{ |
||||
for (size_t i = 0; i < literal.length(); ++i) { |
||||
putCharacter(literal[i]); |
||||
if (literal[i] == '{' || literal[i] == '}') { |
||||
++i; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static constexpr std::string numberToString(size_t value, uint8_t base, bool uppercase) |
||||
{ |
||||
std::string result; |
||||
|
||||
if (value > std::numeric_limits<uint32_t>::max()) { |
||||
result.reserve(64); |
||||
} |
||||
else if (value > std::numeric_limits<uint16_t>::max()) { |
||||
result.reserve(32); |
||||
} |
||||
|
||||
constexpr const auto& lookupLowercase = "0123456789abcdef"; |
||||
constexpr const auto& lookupUppercase = "0123456789ABCDEF"; |
||||
|
||||
if (value == 0) { |
||||
result = '0'; |
||||
} |
||||
else if (uppercase) { |
||||
while (value > 0) { |
||||
result.insert(0, 1, lookupUppercase[value % base]); |
||||
value /= base; |
||||
} |
||||
} |
||||
else { |
||||
while (value > 0) { |
||||
result.insert(0, 1, lookupLowercase[value % base]); |
||||
value /= base; |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
void Builder::putU64(size_t value, |
||||
uint8_t base, |
||||
bool uppercase, |
||||
char fill, |
||||
Align align, |
||||
Sign sign, |
||||
bool alternativeForm, |
||||
bool zeroPadding, |
||||
size_t width, |
||||
bool isNegative) const |
||||
{ |
||||
std::string number = numberToString(value, base, uppercase); |
||||
|
||||
// Sign
|
||||
std::string prefix = ""; |
||||
switch (sign) { |
||||
case Sign::None: |
||||
case Sign::Negative: |
||||
if (isNegative) { |
||||
prefix = '-'; |
||||
} |
||||
break; |
||||
case Sign::Both: |
||||
prefix = (isNegative) ? '-' : '+'; |
||||
break; |
||||
case Sign::Space: |
||||
prefix = (isNegative) ? '-' : ' '; |
||||
break; |
||||
default: |
||||
VERIFY_NOT_REACHED(); |
||||
}; |
||||
|
||||
// Alternative form
|
||||
if (alternativeForm) { |
||||
switch (base) { |
||||
case 2: |
||||
prefix += (uppercase) ? "0B" : "0b"; |
||||
break; |
||||
case 8: |
||||
prefix += '0'; |
||||
break; |
||||
case 10: |
||||
break; |
||||
case 16: |
||||
prefix += (uppercase) ? "0X" : "0x"; |
||||
break; |
||||
default: |
||||
VERIFY_NOT_REACHED(); |
||||
} |
||||
} |
||||
|
||||
if (!zeroPadding) { |
||||
number.insert(0, prefix); |
||||
} |
||||
else { |
||||
if (align != Builder::Align::None) { |
||||
number.insert(0, prefix); |
||||
} |
||||
fill = '0'; |
||||
} |
||||
|
||||
size_t length = number.length(); |
||||
if (width < length) { |
||||
m_builder.write(number.data(), length); |
||||
return; |
||||
} |
||||
|
||||
switch (align) { |
||||
case Align::Left: |
||||
m_builder.write(number.data(), length); |
||||
m_builder << std::string(width - length, fill); |
||||
break; |
||||
case Align::Center: { |
||||
size_t half = (width - length) / 2; |
||||
m_builder << std::string(half, fill); |
||||
m_builder.write(number.data(), length); |
||||
m_builder << std::string(width - half - length, fill); |
||||
break; |
||||
} |
||||
case Align::Right: |
||||
m_builder << std::string(width - length, fill); |
||||
m_builder.write(number.data(), length); |
||||
break; |
||||
case Align::None: |
||||
if (zeroPadding) { |
||||
m_builder << prefix; |
||||
m_builder << std::string(width - length - prefix.length(), fill); |
||||
} |
||||
else { |
||||
m_builder << std::string(width - length, fill); |
||||
} |
||||
m_builder.write(number.data(), length); |
||||
break; |
||||
default: |
||||
VERIFY_NOT_REACHED(); |
||||
}; |
||||
} |
||||
|
||||
void Builder::putI64(int64_t value, |
||||
uint8_t base, |
||||
bool uppercase, |
||||
char fill, |
||||
Align align, |
||||
Sign sign, |
||||
bool alternativeForm, |
||||
bool zeroPadding, |
||||
size_t width) const |
||||
{ |
||||
bool isNegative = value < 0; |
||||
value = isNegative ? -value : value; |
||||
putU64(static_cast<uint64_t>(value), base, uppercase, fill, align, sign, alternativeForm, zeroPadding, width, isNegative); |
||||
} |
||||
|
||||
void Builder::putF64(double number, uint8_t precision) const |
||||
{ |
||||
precision = std::min(precision, static_cast<uint8_t>(std::numeric_limits<double>::digits10)); |
||||
|
||||
std::stringstream stream; |
||||
stream |
||||
<< std::fixed << std::setprecision(precision) |
||||
<< number |
||||
<< std::defaultfloat << std::setprecision(6); |
||||
std::string string = stream.str(); |
||||
m_builder << string; |
||||
} |
||||
|
||||
void Builder::putString(std::string_view string, char fill, Align align, size_t width) const |
||||
{ |
||||
size_t length = string.length(); |
||||
if (width < length) { |
||||
m_builder.write(string.data(), length); |
||||
return; |
||||
} |
||||
|
||||
switch (align) { |
||||
case Align::None: |
||||
case Align::Left: |
||||
m_builder.write(string.data(), length); |
||||
m_builder << std::string(width - length, fill); |
||||
break; |
||||
case Align::Center: { |
||||
size_t half = (width - length) / 2; |
||||
m_builder << std::string(half, fill); |
||||
m_builder.write(string.data(), length); |
||||
m_builder << std::string(width - half - length, fill); |
||||
break; |
||||
} |
||||
case Align::Right: |
||||
m_builder << std::string(width - length, fill); |
||||
m_builder.write(string.data(), length); |
||||
break; |
||||
default: |
||||
VERIFY_NOT_REACHED(); |
||||
}; |
||||
} |
||||
|
||||
} // namespace Util::Format
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <cstddef> // size_t |
||||
#include <cstdint> // int32_t, int64_t, uint8_t, uint32_t |
||||
#include <sstream> // stringstream |
||||
#include <string_view> |
||||
|
||||
namespace Util::Format { |
||||
|
||||
class Builder { |
||||
public: |
||||
enum class Align : uint8_t { |
||||
None, |
||||
Left = '<', |
||||
Right = '>', |
||||
Center = '^', |
||||
}; |
||||
|
||||
enum class Sign : uint8_t { |
||||
None, |
||||
Negative = '-', |
||||
Both = '+', |
||||
Space = ' ', |
||||
}; |
||||
|
||||
explicit Builder(std::stringstream& builder) |
||||
: m_builder(builder) |
||||
{ |
||||
} |
||||
|
||||
void putLiteral(std::string_view literal); |
||||
|
||||
void putU64(size_t value, |
||||
uint8_t base = 10, |
||||
bool uppercase = false, |
||||
char fill = ' ', |
||||
Align align = Align::Right, |
||||
Sign sign = Sign::Negative, |
||||
bool alternativeForm = false, |
||||
bool zeroPadding = false, |
||||
size_t width = 0, |
||||
bool isNegative = false) const; |
||||
|
||||
void putI64(int64_t value, |
||||
uint8_t base = 10, |
||||
bool uppercase = false, |
||||
char fill = ' ', |
||||
Align align = Align::Right, |
||||
Sign sign = Sign::Negative, |
||||
bool alternativeForm = false, |
||||
bool zeroPadding = false, |
||||
size_t width = 0) const; |
||||
|
||||
void putF64(double number, uint8_t precision = 6) const; |
||||
void putCharacter(char character) const { m_builder.write(&character, 1); } |
||||
void putString(std::string_view string, char fill = ' ', Align align = Align::Left, size_t width = 0) const; |
||||
|
||||
const std::stringstream& builder() const { return m_builder; } |
||||
std::stringstream& builder() { return m_builder; } |
||||
|
||||
private: |
||||
std::stringstream& m_builder; |
||||
}; |
||||
|
||||
} // namespace Util::Format
|
@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include <cstdint> // uint8_t |
||||
#include <cstdio> // FILE, fputs, stdout |
||||
#include <sstream> // stringstream |
||||
#include <string> |
||||
#include <string_view> |
||||
|
||||
#include "util/format/color.h" |
||||
#include "util/format/format.h" |
||||
#include "util/meta/assert.h" |
||||
|
||||
namespace Util::Format { |
||||
|
||||
TextStyle::TextStyle(Emphasis emphasis) |
||||
: m_emphasis(emphasis) |
||||
{ |
||||
} |
||||
|
||||
TextStyle::TextStyle(bool isForeground, TerminalColor color) |
||||
{ |
||||
if (isForeground) { |
||||
m_foregroundColor = color; |
||||
} |
||||
else { |
||||
m_backgroundColor = color; |
||||
} |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
TextStyle& TextStyle::operator|=(const TextStyle& rhs) |
||||
{ |
||||
if (m_foregroundColor == TerminalColor::None) { |
||||
m_foregroundColor = rhs.m_foregroundColor; |
||||
} |
||||
else { |
||||
VERIFY(rhs.m_foregroundColor == TerminalColor::None, "can't OR a terminal color"); |
||||
} |
||||
|
||||
if (m_backgroundColor == TerminalColor::None) { |
||||
m_backgroundColor = rhs.m_backgroundColor; |
||||
} |
||||
else { |
||||
VERIFY(rhs.m_backgroundColor == TerminalColor::None, "can't OR a terminal color"); |
||||
} |
||||
|
||||
m_emphasis = static_cast<Emphasis>(static_cast<uint8_t>(m_emphasis) | static_cast<uint8_t>(rhs.m_emphasis)); |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
TextStyle fg(TerminalColor foreground) |
||||
{ |
||||
return TextStyle(true, foreground); |
||||
} |
||||
|
||||
TextStyle bg(TerminalColor background) |
||||
{ |
||||
return TextStyle(false, background); |
||||
} |
||||
|
||||
TextStyle operator|(TextStyle lhs, const TextStyle& rhs) |
||||
{ |
||||
return lhs |= rhs; |
||||
} |
||||
|
||||
TextStyle operator|(Emphasis lhs, Emphasis rhs) |
||||
{ |
||||
return TextStyle { lhs } | rhs; |
||||
} |
||||
|
||||
bool operator&(Emphasis lhs, Emphasis rhs) |
||||
{ |
||||
return static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs); |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void setDisplayAttributes(std::stringstream& stream, const TextStyle& style) |
||||
{ |
||||
bool hasForeground = style.foregroundColor() != TerminalColor::None; |
||||
bool hasBackground = style.backgroundColor() != TerminalColor::None; |
||||
bool hasEmphasis = style.emphasis() != Emphasis::None; |
||||
if (!hasForeground && !hasBackground && !hasEmphasis) { |
||||
return; |
||||
} |
||||
|
||||
stream.write("\033[", 2); |
||||
|
||||
if (hasForeground) { |
||||
stream << format("{}", static_cast<uint8_t>(style.foregroundColor())); |
||||
} |
||||
|
||||
if (hasBackground) { |
||||
if (hasForeground) { |
||||
stream.write(";", 1); |
||||
} |
||||
stream << format("{}", static_cast<uint8_t>(style.backgroundColor()) + 10); |
||||
} |
||||
|
||||
stream.write("m", 1); |
||||
|
||||
if (hasEmphasis) { |
||||
|
||||
#define ESCAPE_ATTRIBUTE(escape, attribute) \ |
||||
if (style.emphasis() & escape) { \
|
||||
stream.write("\033[", 2); \
|
||||
stream.write(attribute, 1); \
|
||||
stream.write("m", 1); \
|
||||
} |
||||
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Bold, "1"); |
||||
ESCAPE_ATTRIBUTE(Emphasis::Faint, "2"); |
||||
ESCAPE_ATTRIBUTE(Emphasis::Italic, "3"); |
||||
ESCAPE_ATTRIBUTE(Emphasis::Underline, "4"); |
||||
ESCAPE_ATTRIBUTE(Emphasis::Blink, "5"); |
||||
ESCAPE_ATTRIBUTE(Emphasis::Reverse, "7"); |
||||
ESCAPE_ATTRIBUTE(Emphasis::Conceal, "8"); |
||||
ESCAPE_ATTRIBUTE(Emphasis::Strikethrough, "9"); |
||||
} |
||||
} |
||||
|
||||
void coloredVariadicFormat(std::stringstream& stream, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters) |
||||
{ |
||||
setDisplayAttributes(stream, style); |
||||
variadicFormat(stream, format, parameters); |
||||
stream.write("\033[0m", 4); |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void coloredVariadicPrint(FILE* file, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters) |
||||
{ |
||||
std::stringstream stream; |
||||
setDisplayAttributes(stream, style); |
||||
variadicFormat(stream, format, parameters); |
||||
stream.write("\033[0m", 4); |
||||
|
||||
std::string string = stream.str(); |
||||
fputs(string.c_str(), file); |
||||
} |
||||
|
||||
// -----------------------------------------
|
||||
|
||||
ColorPrintOperatorStyle::ColorPrintOperatorStyle(FILE* file, const TextStyle& style) |
||||
: m_file(file) |
||||
, m_style(style) |
||||
, m_stream() |
||||
, m_builder(m_stream) |
||||
{ |
||||
setDisplayAttributes(m_stream, style); |
||||
} |
||||
|
||||
ColorPrintOperatorStyle::~ColorPrintOperatorStyle() |
||||
{ |
||||
m_stream.write("\033[0m", 4); |
||||
std::string string = m_stream.str(); |
||||
fputs(string.c_str(), m_file); |
||||
} |
||||
|
||||
ColorPrintOperatorStyle print(const TextStyle& style) |
||||
{ |
||||
return ColorPrintOperatorStyle(stdout, style); |
||||
} |
||||
|
||||
ColorPrintOperatorStyle print(FILE* file, const TextStyle& style) |
||||
{ |
||||
return ColorPrintOperatorStyle(file, style); |
||||
} |
||||
|
||||
} // namespace Util::Format
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <cstddef> // size_t |
||||
#include <cstdint> // uint8_t |
||||
#include <cstdio> // FILE, stdout |
||||
#include <sstream> // stringstream |
||||
#include <string_view> |
||||
|
||||
#include "util/format/format.h" |
||||
|
||||
namespace Util::Format { |
||||
|
||||
enum class TerminalColor : uint8_t { |
||||
None = 0, |
||||
Black = 30, |
||||
Red, |
||||
Green, |
||||
Yellow, |
||||
Blue, |
||||
Magenta, |
||||
Cyan, |
||||
White, |
||||
BrightBlack = 90, |
||||
BrightRed, |
||||
BrightGreen, |
||||
BrightYellow, |
||||
BrightBlue, |
||||
BrightMagenta, |
||||
BrightCyan, |
||||
BrightWhite |
||||
}; |
||||
|
||||
// Bit field
|
||||
enum class Emphasis : uint8_t { |
||||
None = 0, // Attribute 0
|
||||
Bold = 1, // Attribute 1
|
||||
Faint = 1 << 1, // Attribute 2
|
||||
Italic = 1 << 2, // Attribute 3
|
||||