refactor: remove legacy code.
- all 1.x legacy code were removed because all features has been migrated.
This commit is contained in:
@ -1,348 +0,0 @@
|
||||
#include "ArgParser.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "ConsoleHelper.hpp"
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <processenv.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::ArgParser {
|
||||
|
||||
#pragma region Arguments List
|
||||
|
||||
ArgumentList ArgumentList::CreateFromStd(int argc, char* argv[]) {
|
||||
std::vector<yycc_u8string> args;
|
||||
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
|
||||
if (argv[i] != nullptr)
|
||||
args.emplace_back(yycc_u8string(YYCC::EncodingHelper::ToUTF8(argv[i])));
|
||||
}
|
||||
return ArgumentList(std::move(args));
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
ArgumentList ArgumentList::CreateFromWin32() {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw
|
||||
|
||||
// prepare list
|
||||
std::vector<yycc_u8string> args;
|
||||
|
||||
// try fetching from Win32 functions
|
||||
int argc;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (argv != NULL) {
|
||||
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
|
||||
if (argv[i] != nullptr) {
|
||||
yycc_u8string u8_argv;
|
||||
if (YYCC::EncodingHelper::WcharToUTF8(argv[i], u8_argv))
|
||||
args.emplace_back(std::move(u8_argv));
|
||||
}
|
||||
}
|
||||
}
|
||||
LocalFree(argv);
|
||||
|
||||
// return result
|
||||
return ArgumentList(std::move(args));
|
||||
}
|
||||
#endif
|
||||
|
||||
ArgumentList::ArgumentList(std::vector<yycc_u8string>&& arguments) :
|
||||
m_Arguments(arguments), m_ArgumentsCursor(0u) {}
|
||||
|
||||
void ArgumentList::Prev() {
|
||||
if (m_ArgumentsCursor == 0u)
|
||||
throw std::runtime_error("attempt to move on the head of iterator.");
|
||||
--m_ArgumentsCursor;
|
||||
}
|
||||
|
||||
void ArgumentList::Next() {
|
||||
if (IsEOF()) throw std::runtime_error("attempt to move on the tail of iterator.");
|
||||
++m_ArgumentsCursor;
|
||||
}
|
||||
|
||||
const yycc_u8string& ArgumentList::Argument() const {
|
||||
if (IsEOF()) throw std::runtime_error("attempt to get data on the tail of iterator.");
|
||||
return m_Arguments[m_ArgumentsCursor];
|
||||
}
|
||||
|
||||
bool ArgumentList::IsSwitch(bool* is_long_name, yycc_u8string* long_name, yycc_char8_t* short_name) const {
|
||||
// check eof first
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
// check long name first, then check short name
|
||||
if (IsLongNameSwitch(long_name)) {
|
||||
if (is_long_name != nullptr) *is_long_name = true;
|
||||
return true;
|
||||
}
|
||||
if (IsShortNameSwitch(short_name)) {
|
||||
if (is_long_name != nullptr) *is_long_name = false;
|
||||
return true;
|
||||
}
|
||||
// not matched
|
||||
return false;
|
||||
}
|
||||
bool ArgumentList::IsLongNameSwitch(yycc_u8string* name_part) const {
|
||||
// fetch current parameter
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
|
||||
// find double slash
|
||||
if (param.find(AbstractArgument::DOUBLE_DASH) != 0u) return false;
|
||||
// check gotten long name
|
||||
yycc_u8string_view long_name = yycc_u8string_view(param).substr(2u);
|
||||
if (!AbstractArgument::IsLegalLongName(long_name)) return false;
|
||||
// set checked long name if possible and return
|
||||
if (name_part != nullptr)
|
||||
*name_part = long_name;
|
||||
return true;
|
||||
}
|
||||
bool ArgumentList::IsShortNameSwitch(yycc_char8_t* name_part) const {
|
||||
// fetch current parameter
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
|
||||
// if the length is not exactly equal to 2,
|
||||
// or it not starts with dash,
|
||||
// it is impossible a short name
|
||||
if (param.size() != 2u || param[0] != AbstractArgument::DASH) return false;
|
||||
// check gotten short name
|
||||
yycc_char8_t short_name = param[1];
|
||||
if (!AbstractArgument::IsLegalShortName(short_name)) return false;
|
||||
// set checked short name if possible and return
|
||||
if (name_part != nullptr)
|
||||
*name_part = short_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArgumentList::IsValue(yycc_u8string* val) const {
|
||||
bool is_value = !IsSwitch();
|
||||
if (is_value && val != nullptr)
|
||||
*val = m_Arguments[m_ArgumentsCursor];
|
||||
return is_value;
|
||||
}
|
||||
|
||||
bool ArgumentList::IsEOF() const { return m_ArgumentsCursor >= m_Arguments.size(); }
|
||||
|
||||
void ArgumentList::Reset() { m_ArgumentsCursor = 0u; }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Abstract Argument
|
||||
|
||||
const yycc_u8string AbstractArgument::DOUBLE_DASH = YYCC_U8("--");
|
||||
const yycc_char8_t AbstractArgument::DASH = YYCC_U8_CHAR('-');
|
||||
const yycc_char8_t AbstractArgument::NO_SHORT_NAME = YYCC_U8_CHAR(0);
|
||||
const yycc_char8_t AbstractArgument::MIN_SHORT_NAME = YYCC_U8_CHAR('!');
|
||||
const yycc_char8_t AbstractArgument::MAX_SHORT_NAME = YYCC_U8_CHAR('~');
|
||||
|
||||
bool AbstractArgument::IsLegalShortName(yycc_char8_t short_name) {
|
||||
if (short_name == AbstractArgument::DASH || // dash is not allowed
|
||||
short_name < AbstractArgument::MIN_SHORT_NAME || short_name > AbstractArgument::MAX_SHORT_NAME) { // non-display ASCII chars are not allowed
|
||||
return false;
|
||||
}
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
bool AbstractArgument::IsLegalLongName(const yycc_u8string_view& long_name) {
|
||||
// empty is not allowed
|
||||
if (long_name.empty()) return false;
|
||||
// non-display ASCII chars are not allowed
|
||||
for (const auto& val : long_name) {
|
||||
if (val < AbstractArgument::MIN_SHORT_NAME || val > AbstractArgument::MAX_SHORT_NAME)
|
||||
return false;
|
||||
}
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractArgument::AbstractArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description, const yycc_char8_t* argument_example,
|
||||
bool is_optional) :
|
||||
m_LongName(), m_ShortName(AbstractArgument::NO_SHORT_NAME), m_Description(), m_ArgumentExample(),
|
||||
m_IsOptional(is_optional), m_IsCaptured(false) {
|
||||
|
||||
// try to assign long name and check it
|
||||
if (long_name != nullptr) {
|
||||
m_LongName = long_name;
|
||||
if (!AbstractArgument::IsLegalLongName(m_LongName))
|
||||
throw std::invalid_argument("Given long name is invalid.");
|
||||
}
|
||||
// try to assign short name and check it
|
||||
if (short_name != AbstractArgument::NO_SHORT_NAME) {
|
||||
m_ShortName = short_name;
|
||||
if (!AbstractArgument::IsLegalShortName(m_ShortName))
|
||||
throw std::invalid_argument("Given short name is invalid.");
|
||||
}
|
||||
// check short name and long name existence
|
||||
if (!HasShortName() && !HasLongName())
|
||||
throw std::invalid_argument("you must specify an one of long name or short name.");
|
||||
|
||||
// try to assign other string values
|
||||
if (description != nullptr) m_Description = description;
|
||||
if (argument_example != nullptr) m_ArgumentExample = argument_example;
|
||||
}
|
||||
|
||||
AbstractArgument::~AbstractArgument() {}
|
||||
|
||||
bool AbstractArgument::HasLongName() const { return !m_LongName.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetLongName() const { return m_LongName; }
|
||||
bool AbstractArgument::HasShortName() const { return m_ShortName != NO_SHORT_NAME; }
|
||||
yycc_char8_t AbstractArgument::GetShortName() const { return m_ShortName; }
|
||||
bool AbstractArgument::HasDescription() const { return !m_Description.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetDescription() const { return m_Description; }
|
||||
bool AbstractArgument::HasArgumentExample() const { return !m_ArgumentExample.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetArgumentExample() const { return m_ArgumentExample; }
|
||||
bool AbstractArgument::IsOptional() const { return m_IsOptional; }
|
||||
|
||||
bool AbstractArgument::IsCaptured() const { return m_IsCaptured; }
|
||||
void AbstractArgument::SetCaptured(bool is_captured) { m_IsCaptured = is_captured; }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Option Context
|
||||
|
||||
OptionContext::OptionContext(
|
||||
const yycc_char8_t* summary, const yycc_char8_t* description,
|
||||
std::initializer_list<AbstractArgument*> arguments) :
|
||||
m_Summary(), m_Description() {
|
||||
// assign summary and description
|
||||
if (summary != nullptr) m_Summary = summary;
|
||||
if (description != nullptr) m_Description = description;
|
||||
|
||||
// insert argument list and check them
|
||||
for (auto* arg : arguments) {
|
||||
// insert into long name map if necessary
|
||||
if (arg->HasLongName()) {
|
||||
auto result = m_LongNameMap.try_emplace(arg->GetLongName(), arg);
|
||||
if (!result.second) throw std::invalid_argument("duplicated long name!");
|
||||
}
|
||||
// insert into short name map if necessary
|
||||
if (arg->HasShortName()) {
|
||||
auto result = m_ShortNameMap.try_emplace(arg->GetShortName(), arg);
|
||||
if (!result.second) throw std::invalid_argument("duplicated short name!");
|
||||
}
|
||||
// insert into argument list
|
||||
m_Arguments.emplace_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
OptionContext::~OptionContext() {}
|
||||
|
||||
bool OptionContext::Parse(ArgumentList& al) {
|
||||
// reset argument list first
|
||||
al.Reset();
|
||||
|
||||
// prepare variables and start loop
|
||||
yycc_u8string long_name;
|
||||
yycc_char8_t short_name;
|
||||
bool is_long_name;
|
||||
while (!al.IsEOF()) {
|
||||
// if we can not find any switches, return with error
|
||||
if (!al.IsSwitch(&is_long_name, &long_name, &short_name)) return false;
|
||||
|
||||
// find corresponding argument by long name or short name.
|
||||
// if we can not find it, return with error.
|
||||
AbstractArgument* arg;
|
||||
if (is_long_name) {
|
||||
auto finder = m_LongNameMap.find(long_name);
|
||||
if (finder == m_LongNameMap.end()) return false;
|
||||
arg = finder->second;
|
||||
} else {
|
||||
auto finder = m_ShortNameMap.find(short_name);
|
||||
if (finder == m_ShortNameMap.end()) return false;
|
||||
arg = finder->second;
|
||||
}
|
||||
|
||||
// if this argument has been captured, raise error
|
||||
if (arg->IsCaptured()) return false;
|
||||
// call user parse function of found argument
|
||||
if (arg->Parse(al)) {
|
||||
// success. mark it is captured
|
||||
arg->SetCaptured(true);
|
||||
} else {
|
||||
// failed, return error
|
||||
return false;
|
||||
}
|
||||
|
||||
// move to next argument
|
||||
al.Next();
|
||||
}
|
||||
|
||||
// after processing all argument,
|
||||
// we should check whether all non-optional argument are captured.
|
||||
for (const auto* arg : m_Arguments) {
|
||||
if (!arg->IsOptional() && !arg->IsCaptured())
|
||||
return false;
|
||||
}
|
||||
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
|
||||
void OptionContext::Reset() {
|
||||
for (auto* arg : m_Arguments) {
|
||||
// clear user data and unset captured
|
||||
arg->Reset();
|
||||
arg->SetCaptured(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionContext::Help() const {
|
||||
// print summary and description if necessary
|
||||
if (!m_Summary.empty())
|
||||
YYCC::ConsoleHelper::WriteLine(m_Summary.c_str());
|
||||
if (!m_Description.empty())
|
||||
YYCC::ConsoleHelper::WriteLine(m_Description.c_str());
|
||||
|
||||
// blank line
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(""));
|
||||
|
||||
// print argument list
|
||||
for (const auto* arg : m_Arguments) {
|
||||
yycc_u8string argstr;
|
||||
|
||||
// print indent
|
||||
argstr += YYCC_U8("\t");
|
||||
// print optional head
|
||||
bool is_optional = arg->IsOptional();
|
||||
if (is_optional) argstr += YYCC_U8("[");
|
||||
|
||||
// switch name
|
||||
bool short_name = arg->HasShortName(), long_name = arg->HasLongName();
|
||||
if (short_name) {
|
||||
argstr += YYCC_U8("-");
|
||||
argstr += arg->GetShortName();
|
||||
}
|
||||
if (long_name) {
|
||||
if (short_name) argstr += YYCC_U8(", ");
|
||||
argstr += YYCC_U8("--");
|
||||
argstr += arg->GetLongName();
|
||||
}
|
||||
|
||||
// argument example
|
||||
if (arg->HasArgumentExample()) {
|
||||
argstr += YYCC_U8(" ");
|
||||
argstr += arg->GetArgumentExample();
|
||||
}
|
||||
|
||||
// optional tail
|
||||
if (is_optional) argstr += YYCC_U8("]");
|
||||
|
||||
// argument description
|
||||
if (arg->HasDescription()) {
|
||||
// eol and double indent
|
||||
argstr += YYCC_U8("\n\t\t");
|
||||
// description
|
||||
argstr += arg->GetDescription();
|
||||
}
|
||||
|
||||
// write into console
|
||||
YYCC::ConsoleHelper::WriteLine(argstr.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
||||
@ -1,465 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "Constraints.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "ParserHelper.hpp"
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
/**
|
||||
* @brief Universal argument parser.
|
||||
* @details
|
||||
* For how to use this namespace, please see \ref arg_parser.
|
||||
*/
|
||||
namespace YYCC::ArgParser {
|
||||
|
||||
/**
|
||||
* @brief The advanced wrapper of the list containing command line arguments.
|
||||
* @details
|
||||
* This class is used by OptionContext and argument class internally for convenience.
|
||||
* It should not be constrcuted directly.
|
||||
* Programmer should choose proper static creation function to create instance of this class.
|
||||
*/
|
||||
class ArgumentList {
|
||||
public:
|
||||
/**
|
||||
* @brief Create argument list from the parameters of standard C main function.
|
||||
* @param[in] argc The argument count passed to standard C main function.
|
||||
* @param[in] argv The argument value passed to standard C main function.
|
||||
* @return Extracted argument list instance.
|
||||
* @remarks
|
||||
* First item in command line will be stripped,
|
||||
* because in most cases it points to executable self
|
||||
* and should not be seen as a part of arguments.
|
||||
*/
|
||||
static ArgumentList CreateFromStd(int argc, char* argv[]);
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
/**
|
||||
* @brief Create argument list from Win32 function.
|
||||
* @details
|
||||
* @return Extracted argument list instance.
|
||||
* @remarks
|
||||
* First item in command line will be stripped,
|
||||
* because in most cases it points to executable self
|
||||
* and should not be seen as a part of arguments.
|
||||
* \par
|
||||
* Programmer should use this function instead of CreateFromStd(),
|
||||
* because that function involve encoding issue on Windows, especially command line including non-ASCII chars.
|
||||
* Only this function guaranteen that return correct argument list on Windows.
|
||||
*/
|
||||
static ArgumentList CreateFromWin32();
|
||||
#endif
|
||||
private:
|
||||
/**
|
||||
* @brief Constructor of ArgumentList used internally.
|
||||
* @param[in] arguments
|
||||
* Underlying argument list.
|
||||
* This argument list should remove first executable name before passing it to there.
|
||||
*/
|
||||
ArgumentList(std::vector<yycc_u8string>&& arguments);
|
||||
public:
|
||||
YYCC_DEF_CLS_COPY_MOVE(ArgumentList);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Move to previous argument.
|
||||
* @exception std::runtime_error Try moving at the head of argument list.
|
||||
*/
|
||||
void Prev();
|
||||
/**
|
||||
* @brief Move to next argument.
|
||||
* @exception std::runtime_error Try moving at the tail of argument list.
|
||||
*/
|
||||
void Next();
|
||||
/**
|
||||
* @brief Get the string of current argument.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return The constant reference to the string of current argument.
|
||||
*/
|
||||
const yycc_u8string& Argument() const;
|
||||
/**
|
||||
* @brief Check whether current argument is a option / switch.
|
||||
* @param[out] is_long_name
|
||||
* It will be set true if this argument is long name, otherwise short name.
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @param[out] long_name
|
||||
* The container holding matched long name if it is (double dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @param[out] short_name
|
||||
* The variable holding matched short name if it is (dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return
|
||||
* True if it is, otherwise false.
|
||||
* If this function return false, all given parameters are in undefined status.
|
||||
*/
|
||||
bool IsSwitch(
|
||||
bool* is_long_name = nullptr,
|
||||
yycc_u8string* long_name = nullptr,
|
||||
yycc_char8_t* short_name = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether current argument is a value.
|
||||
* @param[out] val
|
||||
* The variable holding value if it is.
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsValue(yycc_u8string* val = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether we are at the tail of argument list.
|
||||
* @details
|
||||
* Please note EOF is a special state that you can not fetch data from it.
|
||||
* EOF is the next element of the last element of argument list.
|
||||
* It more like \c end() in most C++ container.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsEOF() const;
|
||||
/**
|
||||
* @brief Reset cursor to the head of argument list for reuse.
|
||||
*/
|
||||
void Reset();
|
||||
private:
|
||||
/**
|
||||
* @brief Check whether current argument is long name option / switch.
|
||||
* @details This function is used by IsSwitch() internally.
|
||||
* @param[out] name_part
|
||||
* The container holding matched long name if it is (double dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsLongNameSwitch(yycc_u8string* name_part = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether current argument is short name option / switch.
|
||||
* @details This function is used by IsSwitch() internally.
|
||||
* @param[out] name_part
|
||||
* The variable holding matched short name if it is (dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsShortNameSwitch(yycc_char8_t* name_part = nullptr) const;
|
||||
|
||||
private:
|
||||
std::vector<yycc_u8string> m_Arguments;
|
||||
size_t m_ArgumentsCursor;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The base class of every argument.
|
||||
* @details Programmer can inherit this class and implement essential functions to create custom argument.
|
||||
*/
|
||||
class AbstractArgument {
|
||||
friend class OptionContext;
|
||||
|
||||
// Long name and short name constants and checker.
|
||||
public:
|
||||
static const yycc_u8string DOUBLE_DASH; ///< The constant value representing double dash (\c --)
|
||||
static const yycc_char8_t DASH; ///< The constant value representing dash (\c -)
|
||||
static const yycc_char8_t NO_SHORT_NAME; ///< The constant value representing that there is not short value.
|
||||
static const yycc_char8_t MIN_SHORT_NAME; ///< The constant value representing the minimum value of valid ASCII chars in short and long name.
|
||||
static const yycc_char8_t MAX_SHORT_NAME; ///< The constant value representing the maximum value of valid ASCII chars in short and long name.
|
||||
/**
|
||||
* @brief Check whether given short name is valid.
|
||||
* @details
|
||||
* An ASCII code of valid short name
|
||||
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
|
||||
* It also can not be #DASH.
|
||||
* @param[in] short_name Short name for checking.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
static bool IsLegalShortName(yycc_char8_t short_name);
|
||||
/**
|
||||
* @brief Check whether given long name is valid.
|
||||
* @details
|
||||
* An ASCII code of every item in valid long name
|
||||
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
|
||||
* However it can be #DASH. This is different with short name.
|
||||
* @param[in] long_name Long name for checking.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
static bool IsLegalLongName(const yycc_u8string_view& long_name);
|
||||
|
||||
// Constructor & destructor
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional
|
||||
* True if this argument is optional argument.
|
||||
* Optional argument can be absent in argument list.
|
||||
* Non-optional argument must be presented in argument list,
|
||||
* otherwise parser will fail.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
AbstractArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name = AbstractArgument::NO_SHORT_NAME,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false);
|
||||
virtual ~AbstractArgument();
|
||||
YYCC_DEL_CLS_COPY_MOVE(AbstractArgument);
|
||||
|
||||
// ===== User Implementation =====
|
||||
protected:
|
||||
/**
|
||||
* @brief User implemented custom parse function
|
||||
* @param[in] al The argument list for parsing.
|
||||
* @return True if parse is success, otherwise false.
|
||||
* @remarks
|
||||
* When enter this function, argument list points to switch self.
|
||||
* After success parsing, you should point it to the argument this function last accepted.
|
||||
* For exmaple, for command line "-i 114514",
|
||||
* when enter this function, this argument list point to "-i",
|
||||
* and you should set it to "114514" when exiting this function.
|
||||
*/
|
||||
virtual bool Parse(ArgumentList& al) = 0;
|
||||
/**
|
||||
* @brief User implemented custom reset function
|
||||
* @remarks
|
||||
* In this function, user should claer its stored value if is has.
|
||||
* You don't need clar capture state. That is done by library self.
|
||||
*/
|
||||
virtual void Reset() = 0;
|
||||
|
||||
// ===== Basic Infos =====
|
||||
public:
|
||||
/// @brief Check whether this argument specify long name.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasLongName() const;
|
||||
/// @brief Get specified long name.
|
||||
/// @return Specified long name.
|
||||
const yycc_u8string& GetLongName() const;
|
||||
/// @brief Check whether this argument specify short name.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasShortName() const;
|
||||
/// @brief Get specified short name.
|
||||
/// @return Specified short name.
|
||||
yycc_char8_t GetShortName() const;
|
||||
/// @brief Check whether this argument specify description.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasDescription() const;
|
||||
/// @brief Get specified description.
|
||||
/// @return Specified description.
|
||||
const yycc_u8string& GetDescription() const;
|
||||
/// @brief Check whether this argument specify example.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasArgumentExample() const;
|
||||
/// @brief Get specified example.
|
||||
/// @return Specified example.
|
||||
const yycc_u8string& GetArgumentExample() const;
|
||||
/// @brief Check whether this argument is optional.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool IsOptional() const;
|
||||
private:
|
||||
yycc_u8string m_LongName;
|
||||
yycc_char8_t m_ShortName;
|
||||
yycc_u8string m_Description;
|
||||
yycc_u8string m_ArgumentExample;
|
||||
bool m_IsOptional;
|
||||
|
||||
// ===== Capture State =====
|
||||
public:
|
||||
/// @brief Check whether this argument has been captured.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool IsCaptured() const;
|
||||
private:
|
||||
/**
|
||||
* @brief Set capture state of this argument.
|
||||
* @details This function is used internally by OptionContext.
|
||||
* @param[in] is_captured New states of captured.
|
||||
*/
|
||||
void SetCaptured(bool is_captured);
|
||||
bool m_IsCaptured;
|
||||
};
|
||||
|
||||
/// @brief The core of argument parser, also manage all arguments.
|
||||
class OptionContext {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct option context.
|
||||
* @param[in] summary The summary of this application which will be printed in help text.
|
||||
* @param[in] description The description of this application which will be printed in help text.
|
||||
* @param[in] arguments The initializer list including pointers to all arguments.
|
||||
*/
|
||||
OptionContext(
|
||||
const yycc_char8_t* summary, const yycc_char8_t* description,
|
||||
std::initializer_list<AbstractArgument*> arguments);
|
||||
~OptionContext();
|
||||
YYCC_DEL_CLS_COPY_MOVE(OptionContext);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Start a parse.
|
||||
* @param[in] al The reference to ArgumentList for parsing.
|
||||
* @return
|
||||
* True if success, otherwise false.
|
||||
* If this function return false, you should not visit any arguments it managed.
|
||||
*/
|
||||
bool Parse(ArgumentList& al);
|
||||
/**
|
||||
* @brief Reset all managed argument to default state thus you can start another parsing.
|
||||
*/
|
||||
void Reset();
|
||||
/**
|
||||
* @brief Print help text in \c stdout.
|
||||
*/
|
||||
void Help() const;
|
||||
|
||||
private:
|
||||
yycc_u8string m_Summary;
|
||||
yycc_u8string m_Description;
|
||||
|
||||
std::vector<AbstractArgument*> m_Arguments;
|
||||
std::map<yycc_u8string, AbstractArgument*> m_LongNameMap;
|
||||
std::map<yycc_char8_t, AbstractArgument*> m_ShortNameMap;
|
||||
};
|
||||
|
||||
#pragma region Argument Presets
|
||||
|
||||
/**
|
||||
* @brief Arithmetic (integral, floating point. except bool) type argument
|
||||
* @tparam _Ty The internal stored type belongs to arithmetic type.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
class NumberArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an arithmetic argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional True if this argument is optional argument.
|
||||
* @param[in] constraint The constraint applied to this argument.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
NumberArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false,
|
||||
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
|
||||
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
|
||||
virtual ~NumberArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(NumberArgument);
|
||||
|
||||
public:
|
||||
/// @brief Get stored data in argument.
|
||||
_Ty Get() const {
|
||||
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
|
||||
return m_Data;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override {
|
||||
// try get corresponding value
|
||||
yycc_u8string strval;
|
||||
al.Next();
|
||||
if (al.IsEOF() || !al.IsValue(&strval)) {
|
||||
al.Prev();
|
||||
return false;
|
||||
}
|
||||
// try parsing value
|
||||
if (!YYCC::ParserHelper::TryParse<_Ty>(strval, m_Data)) return false;
|
||||
// check constraint
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
virtual void Reset() override {
|
||||
std::memset(&m_Data, 0, sizeof(m_Data));
|
||||
}
|
||||
|
||||
protected:
|
||||
_Ty m_Data;
|
||||
Constraints::Constraint<_Ty> m_Constraint;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A simple switch type argument which do not store any value.
|
||||
*/
|
||||
class SwitchArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an switch argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
SwitchArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr) :
|
||||
// bool switch must be optional, because it is false if no given switch.
|
||||
// bool switch doesn't have argument, so it doesn't have example property.
|
||||
AbstractArgument(long_name, short_name, description, nullptr, true) {}
|
||||
virtual ~SwitchArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(SwitchArgument);
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override { return true; } // simply return true because no value to store.
|
||||
virtual void Reset() override {} // nothing need to be reset.
|
||||
};
|
||||
|
||||
/// @brief String type argument
|
||||
class StringArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor a string argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional True if this argument is optional argument.
|
||||
* @param[in] constraint The constraint applied to this argument.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
StringArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false,
|
||||
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
|
||||
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
|
||||
virtual ~StringArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(StringArgument);
|
||||
|
||||
public:
|
||||
/// @brief Get stored data in argument.
|
||||
const yycc_u8string& Get() const {
|
||||
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
|
||||
return m_Data;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override {
|
||||
// try get corresponding value
|
||||
al.Next();
|
||||
if (al.IsEOF() || !al.IsValue(&m_Data)) {
|
||||
al.Prev();
|
||||
return false;
|
||||
}
|
||||
// check constraint
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
virtual void Reset() override {
|
||||
m_Data.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
yycc_u8string m_Data;
|
||||
Constraints::Constraint<yycc_u8string> m_Constraint;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
}
|
||||
@ -1,186 +0,0 @@
|
||||
#include "ConfigManager.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "EnumHelper.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace YYCC::ConfigManager {
|
||||
|
||||
#pragma region Abstract Setting
|
||||
|
||||
AbstractSetting::AbstractSetting(const yycc_u8string_view& name) : m_Name(name), m_RawData() {
|
||||
if (m_Name.empty())
|
||||
throw std::invalid_argument("the name of setting should not be empty");
|
||||
}
|
||||
|
||||
AbstractSetting::~AbstractSetting() {}
|
||||
|
||||
const yycc_u8string& AbstractSetting::GetName() const { return m_Name; }
|
||||
|
||||
void AbstractSetting::ResizeData(size_t new_size) { m_RawData.resize(new_size); }
|
||||
const void* AbstractSetting::GetDataPtr() const { return m_RawData.data(); }
|
||||
void* AbstractSetting::GetDataPtr() { return m_RawData.data(); }
|
||||
size_t AbstractSetting::GetDataSize() const { return m_RawData.size(); }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Core Manager
|
||||
|
||||
CoreManager::CoreManager(
|
||||
const yycc_u8string_view& cfg_file_path,
|
||||
uint64_t version_identifier,
|
||||
std::initializer_list<AbstractSetting*> settings) :
|
||||
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
|
||||
// Mark: no need to check cfg file path
|
||||
// it will be checked at creating file handle
|
||||
|
||||
// assign settings
|
||||
for (auto* setting : settings) {
|
||||
auto result = m_Settings.try_emplace(setting->GetName(), setting);
|
||||
if (!result.second) {
|
||||
// if not inserted because duplicated, raise exception
|
||||
throw std::invalid_argument("Duplicated setting name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoadResult CoreManager::Load() {
|
||||
// prepare result variables
|
||||
ConfigLoadResult ret = ConfigLoadResult::OK;
|
||||
|
||||
// reset all settings first
|
||||
Reset();
|
||||
|
||||
// get file handle
|
||||
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("rb")));
|
||||
if (fs.get() == nullptr) {
|
||||
// if we fail to get, it means that we do not have corresponding cfg file.
|
||||
// all settings should be reset to default value.
|
||||
ret = ConfigLoadResult::Created;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// fetch version info
|
||||
uint64_t version_info;
|
||||
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
|
||||
ret = ConfigLoadResult::Created;
|
||||
return ret;
|
||||
}
|
||||
// check version
|
||||
// if read version is greater than we expected,
|
||||
// it means that this cfg file is created by the program higer than this.
|
||||
// we should not read anything from it.
|
||||
// however, for compaitibility reason, we allow read old cfg data.
|
||||
if (version_info > m_VersionIdentifier) {
|
||||
ret = ConfigLoadResult::ForwardNew;
|
||||
return ret;
|
||||
} else if (version_info < m_VersionIdentifier) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
|
||||
}
|
||||
|
||||
// fetch setting item from file
|
||||
yycc_u8string name_cache;
|
||||
while (true) {
|
||||
// try fetch setting name
|
||||
// fetch name length
|
||||
size_t name_length;
|
||||
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
|
||||
// we also check whether reach EOF at there.
|
||||
if (std::feof(fs.get())) break;
|
||||
else {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
// fetch name body
|
||||
name_cache.resize(name_length);
|
||||
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get setting data length
|
||||
size_t data_length;
|
||||
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get matched setting first
|
||||
const auto& found = m_Settings.find(name_cache);
|
||||
if (found != m_Settings.end()) {
|
||||
// found. read data for it
|
||||
found->second->ResizeData(data_length);
|
||||
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
// call user defined load function
|
||||
// if fail to parse, reset to default value
|
||||
if (!found->second->UserLoad()) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
|
||||
found->second->UserReset();
|
||||
}
|
||||
} else {
|
||||
// fail to find. skip this unknown setting
|
||||
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CoreManager::Save() {
|
||||
// get file handle
|
||||
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("wb")));
|
||||
// if we fail to get, return false.
|
||||
if (fs == nullptr) return false;
|
||||
|
||||
// write config data
|
||||
uint64_t version_info = m_VersionIdentifier;
|
||||
if (std::fwrite(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info))
|
||||
return false;
|
||||
|
||||
// iterate all data for writing
|
||||
for (const auto& pair : m_Settings) {
|
||||
// do user defined save
|
||||
// if failed, skip this setting
|
||||
if (!pair.second->UserSave())
|
||||
continue;
|
||||
|
||||
// write setting name
|
||||
// write name length
|
||||
size_t name_length = pair.first.size();
|
||||
if (std::fwrite(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length))
|
||||
return false;
|
||||
// write name body
|
||||
if (std::fwrite(pair.first.c_str(), 1u, name_length, fs.get()) != name_length)
|
||||
return false;
|
||||
|
||||
// write setting daat
|
||||
// write data length
|
||||
size_t data_length = pair.second->GetDataSize();
|
||||
if (std::fwrite(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length))
|
||||
return false;
|
||||
// write data body
|
||||
if (std::fwrite(pair.second->GetDataPtr(), 1u, data_length, fs.get()) != data_length)
|
||||
return false;
|
||||
}
|
||||
|
||||
// all settings done, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
void CoreManager::Reset() {
|
||||
for (const auto& pair : m_Settings) {
|
||||
pair.second->UserReset();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
||||
@ -1,269 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "Constraints.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
/**
|
||||
* @brief Universal configuration manager
|
||||
* @details For how to use this namespace, please see \ref config_manager.
|
||||
*/
|
||||
namespace YYCC::ConfigManager {
|
||||
|
||||
/**
|
||||
* @brief The load result of loading config.
|
||||
*/
|
||||
enum class ConfigLoadResult {
|
||||
OK = 0, ///< Success load configs.
|
||||
Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
|
||||
ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
|
||||
Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
|
||||
BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
|
||||
ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
|
||||
};
|
||||
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
|
||||
|
||||
/// @brief The base class of every setting.
|
||||
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
|
||||
class AbstractSetting {
|
||||
friend class CoreManager;
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a setting
|
||||
* @param[in] name The name of this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
AbstractSetting(const yycc_u8string_view& name);
|
||||
virtual ~AbstractSetting();
|
||||
YYCC_DEL_CLS_COPY_MOVE(AbstractSetting);
|
||||
|
||||
// Name interface
|
||||
public:
|
||||
/// @brief Get name of this setting.
|
||||
/// @details Name was used in storing setting in file.
|
||||
const yycc_u8string& GetName() const;
|
||||
private:
|
||||
yycc_u8string m_Name;
|
||||
|
||||
// User Implementations
|
||||
protected:
|
||||
/// @brief User implemented custom load function
|
||||
/// @remarks
|
||||
/// In this function, programmer should read data from internal buffer
|
||||
/// and store it to its own another internal variables.
|
||||
/// @return True if success, otherwise false.
|
||||
virtual bool UserLoad() = 0;
|
||||
/// @brief User implemented custom save function
|
||||
/// @remarks
|
||||
/// In this function, programmer should write data,
|
||||
/// which is stored in another variavle by it own, to internal buffer.
|
||||
/// @return True if success, otherwise false.
|
||||
virtual bool UserSave() = 0;
|
||||
/// @brief User implemented custom reset function
|
||||
/// @remarks In this function, programmer should reset its internal variable to default value.
|
||||
virtual void UserReset() = 0;
|
||||
|
||||
// Buffer related functions
|
||||
protected:
|
||||
/// @brief Resize internal buffer to given size.
|
||||
/// @remarks It is usually used in UserSave.
|
||||
/// @param[in] new_size The new size of internal buffer.
|
||||
void ResizeData(size_t new_size);
|
||||
/// @brief Get data pointer to internal buffer.
|
||||
/// @remarks It is usually used in UserLoad.
|
||||
const void* GetDataPtr() const;
|
||||
/// @brief Get mutable data pointer to internal buffer.
|
||||
/// @remarks It is usually used in UserSave.
|
||||
void* GetDataPtr();
|
||||
/// @brief Get the length of internal buffer.
|
||||
size_t GetDataSize() const;
|
||||
private:
|
||||
std::vector<uint8_t> m_RawData;
|
||||
};
|
||||
|
||||
/// @brief Settings manager and config file reader writer.
|
||||
class CoreManager {
|
||||
public:
|
||||
/**
|
||||
* @brief Build core manager.
|
||||
* @param[in] cfg_file_path The path to config file.
|
||||
* @param[in] version_identifier The identifier of version. Higher is newer. Lower config will try doing migration.
|
||||
* @param[in] settings An initializer list containing pointers to all managed settings.
|
||||
*/
|
||||
CoreManager(
|
||||
const yycc_u8string_view& cfg_file_path,
|
||||
uint64_t version_identifier,
|
||||
std::initializer_list<AbstractSetting*> settings);
|
||||
~CoreManager() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(CoreManager);
|
||||
|
||||
// Core functions
|
||||
public:
|
||||
/// @brief Load settings from file.
|
||||
/// @details Before loading, all settings will be reset to default value first.
|
||||
/// @return What happend when loading config. This function always success.
|
||||
ConfigLoadResult Load();
|
||||
/// @brief Save settings to file.
|
||||
/// @return True if success, otherwise false.
|
||||
bool Save();
|
||||
/// @brief Reset all settings to default value.
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
yycc_u8string m_CfgFilePath;
|
||||
uint64_t m_VersionIdentifier;
|
||||
std::map<yycc_u8string, AbstractSetting*> m_Settings;
|
||||
};
|
||||
|
||||
#pragma region Setting Presets
|
||||
|
||||
/**
|
||||
* @brief Arithmetic (integral, floating point, bool) and enum type setting
|
||||
* @tparam _Ty The internal stored type belongs to arithmetic type.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> || std::is_enum_v<_Ty>, int> = 0>
|
||||
class NumberSetting : public AbstractSetting {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct arithmetic type setting.
|
||||
* @param[in] name The name of this setting.
|
||||
* @param[in] default_value The default value of this setting.
|
||||
* @param[in] constraint The constraint applied to this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
NumberSetting(
|
||||
const yycc_u8string_view& name, _Ty default_value,
|
||||
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
|
||||
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {}
|
||||
virtual ~NumberSetting() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(NumberSetting);
|
||||
|
||||
/// @brief Get stored data in setting.
|
||||
_Ty Get() const { return m_Data; }
|
||||
/**
|
||||
* @brief Set data to setting.
|
||||
* @param[in] new_data The new data.
|
||||
* @return True if success, otherwise false (given value is invalid)
|
||||
*/
|
||||
bool Set(_Ty new_data) {
|
||||
// validate data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data))
|
||||
return false;
|
||||
// assign data
|
||||
m_Data = new_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool UserLoad() override {
|
||||
// read data
|
||||
if (sizeof(m_Data) != GetDataSize())
|
||||
return false;
|
||||
m_Data = *reinterpret_cast<const _Ty*>(GetDataPtr());
|
||||
// check data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
virtual bool UserSave() override {
|
||||
// write data
|
||||
ResizeData(sizeof(m_Data));
|
||||
*reinterpret_cast<_Ty*>(GetDataPtr()) = m_Data;
|
||||
return true;
|
||||
}
|
||||
virtual void UserReset() override {
|
||||
m_Data = m_DefaultData;
|
||||
}
|
||||
|
||||
_Ty m_Data, m_DefaultData;
|
||||
Constraints::Constraint<_Ty> m_Constraint;
|
||||
};
|
||||
|
||||
/// @brief String type setting
|
||||
class StringSetting : public AbstractSetting {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct string setting
|
||||
* @param[in] name The name of this setting.
|
||||
* @param[in] default_value The default value of this setting.
|
||||
* @param[in] constraint The constraint applied to this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
StringSetting(
|
||||
const yycc_u8string_view& name, const yycc_u8string_view& default_value,
|
||||
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
|
||||
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
|
||||
m_Data = default_value;
|
||||
m_DefaultData = default_value;
|
||||
}
|
||||
virtual ~StringSetting() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(StringSetting);
|
||||
|
||||
/// @brief Get reference to stored string.
|
||||
const yycc_u8string& Get() const { return m_Data; }
|
||||
/**
|
||||
* @brief Set string data to setting.
|
||||
* @param[in] new_data The new string data.
|
||||
* @return True if success, otherwise false (given value is invalid)
|
||||
*/
|
||||
bool Set(const yycc_u8string_view& new_data) {
|
||||
// check data validation
|
||||
yycc_u8string new_data_cache(new_data);
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data_cache))
|
||||
return false;
|
||||
// assign data
|
||||
m_Data = std::move(new_data_cache);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool UserLoad() override {
|
||||
// read string length
|
||||
size_t string_length;
|
||||
if (GetDataSize() < sizeof(string_length))
|
||||
return false;
|
||||
string_length = *reinterpret_cast<const size_t*>(GetDataPtr());
|
||||
// read string body
|
||||
if (GetDataSize() != sizeof(string_length) + string_length)
|
||||
return false;
|
||||
m_Data.assign(
|
||||
reinterpret_cast<const yycc_char8_t*>(static_cast<const uint8_t*>(GetDataPtr()) + sizeof(string_length)),
|
||||
string_length
|
||||
);
|
||||
// check data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
virtual bool UserSave() override {
|
||||
// allocate result buffer
|
||||
size_t string_length = m_Data.size();
|
||||
ResizeData(sizeof(string_length) + string_length);
|
||||
// get pointer
|
||||
uint8_t* ptr = static_cast<uint8_t*>(GetDataPtr());
|
||||
// assign string length
|
||||
*reinterpret_cast<size_t*>(ptr) = string_length;
|
||||
// assign string body
|
||||
std::memcpy(ptr + sizeof(string_length), m_Data.data(), string_length);
|
||||
return true;
|
||||
}
|
||||
virtual void UserReset() override {
|
||||
m_Data = m_DefaultData;
|
||||
}
|
||||
|
||||
yycc_u8string m_Data, m_DefaultData;
|
||||
Constraints::Constraint<yycc_u8string> m_Constraint;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
}
|
||||
@ -1,193 +0,0 @@
|
||||
#include <YYCCommonplace.hpp>
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
namespace Console = YYCC::ConsoleHelper;
|
||||
|
||||
namespace YYCCTestbench {
|
||||
|
||||
static void Assert(bool condition, const YYCC::yycc_char8_t* description) {
|
||||
if (condition) {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("OK: %s")), description);
|
||||
} else {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Failed: %s\n")), description);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void ConsoleTestbench() {
|
||||
// UTF8 Output Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Output Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
Console::FormatLine(YYCC_U8("\t%s"), strl.c_str());
|
||||
}
|
||||
|
||||
// UTF8 Input Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Input Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
Console::FormatLine(YYCC_U8("\tPlease type: %s"), strl.c_str());
|
||||
Console::Write(YYCC_U8("\t> "));
|
||||
|
||||
YYCC::yycc_u8string gotten(Console::ReadLine());
|
||||
if (gotten == strl) Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("\tMatched! Got: %s")), gotten.c_str());
|
||||
else Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("\tNOT Matched! Got: %s")), gotten.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void ExceptionTestbench() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
|
||||
MessageBoxW(
|
||||
NULL,
|
||||
YYCC::EncodingHelper::UTF8ToWchar(
|
||||
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
|
||||
).c_str(),
|
||||
L"Fatal Error", MB_OK + MB_ICONERROR
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Perform a div zero exception.
|
||||
#if defined (YYCC_DEBUG_UE_FILTER)
|
||||
// Reference: https://stackoverflow.com/questions/20981982/is-it-possible-to-debug-unhandledexceptionfilters-with-a-debugger
|
||||
__try {
|
||||
// all of code normally inside of main or WinMain here...
|
||||
int i = 1, j = 0;
|
||||
int k = i / j;
|
||||
} __except (YYCC::ExceptionHelper::DebugCallUExceptionImpl(GetExceptionInformation())) {
|
||||
OutputDebugStringW(L"executed filter function\n");
|
||||
}
|
||||
#else
|
||||
int i = 1, j = 0;
|
||||
int k = i / j;
|
||||
#endif
|
||||
|
||||
YYCC::ExceptionHelper::Unregister();
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
enum class TestEnum : int8_t {
|
||||
Test1, Test2, Test3
|
||||
};
|
||||
|
||||
class TestConfigManager {
|
||||
public:
|
||||
TestConfigManager() :
|
||||
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
|
||||
m_FloatSetting(YYCC_U8("float-setting"), 0.0f),
|
||||
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
|
||||
m_BoolSetting(YYCC_U8("bool-setting"), false),
|
||||
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
|
||||
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
|
||||
&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting
|
||||
}) {}
|
||||
~TestConfigManager() {}
|
||||
|
||||
void PrintSettings() {
|
||||
Console::WriteLine(YYCC_U8("Config Manager Settings:"));
|
||||
|
||||
Console::FormatLine(YYCC_U8("\tint-setting: %" PRIi32), m_IntSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_FloatSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tstring-setting: %s"), m_StringSetting.Get().c_str());
|
||||
|
||||
Console::FormatLine(YYCC_U8("\tbool-setting: %s"), m_BoolSetting.Get() ? YYCC_U8("true") : YYCC_U8("false"));
|
||||
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_ClampedFloatSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tenum-setting: %" PRIi8), static_cast<std::underlying_type_t<TestEnum>>(m_EnumSetting.Get()));
|
||||
}
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
|
||||
YYCC::ConfigManager::StringSetting m_StringSetting;
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<bool> m_BoolSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_ClampedFloatSetting;
|
||||
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
|
||||
|
||||
YYCC::ConfigManager::CoreManager m_CoreManager;
|
||||
};
|
||||
|
||||
static void ConfigManagerTestbench() {
|
||||
// init cfg manager
|
||||
TestConfigManager test;
|
||||
|
||||
// test constraint works
|
||||
Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::Constraints::Constraint"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::Constraints::Constraint"));
|
||||
|
||||
// test modify settings
|
||||
#define TEST_MACRO(member_name, set_val) { \
|
||||
Assert(test.member_name.Set(set_val), YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
|
||||
Assert(test.member_name.Get() == set_val, YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(m_IntSetting, INT32_C(114));
|
||||
TEST_MACRO(m_FloatSetting, 2.0f);
|
||||
TEST_MACRO(m_StringSetting, YYCC_U8("fuck"));
|
||||
TEST_MACRO(m_BoolSetting, true);
|
||||
TEST_MACRO(m_ClampedFloatSetting, 0.5f);
|
||||
TEST_MACRO(m_EnumSetting, TestEnum::Test2);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// test save
|
||||
test.PrintSettings();
|
||||
Assert(test.m_CoreManager.Save(), YYCC_U8("YYCC::ConfigManager::CoreManager::Save"));
|
||||
|
||||
// test reset
|
||||
test.m_CoreManager.Reset();
|
||||
test.PrintSettings();
|
||||
Assert(test.m_IntSetting.Get() == INT32_C(0), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_FloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_StringSetting.Get() == YYCC_U8(""), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_BoolSetting.Get() == false, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
|
||||
// test load
|
||||
YYCC::ConfigManager::ConfigLoadResult wrong_result = YYCC::EnumHelper::Merge(
|
||||
YYCC::ConfigManager::ConfigLoadResult::ItemError,
|
||||
YYCC::ConfigManager::ConfigLoadResult::BrokenFile
|
||||
);
|
||||
Assert(!YYCC::EnumHelper::Has(test.m_CoreManager.Load(), wrong_result), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
test.PrintSettings();
|
||||
Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_StringSetting.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_BoolSetting.Get() == true, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.5f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_EnumSetting.Get() == TestEnum::Test2, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
|
||||
#error "The YYCC library used when compiling is not match code expected, this may cause build error."
|
||||
#error "If you trust it, please annotate these preprocessor statement, otherwise please contact developer."
|
||||
#endif
|
||||
|
||||
// common testbench
|
||||
// normal
|
||||
YYCCTestbench::EncodingTestbench();
|
||||
YYCCTestbench::StringTestbench();
|
||||
YYCCTestbench::ParserTestbench();
|
||||
YYCCTestbench::WinFctTestbench();
|
||||
YYCCTestbench::StdPatchTestbench();
|
||||
YYCCTestbench::EnumHelperTestbench();
|
||||
YYCCTestbench::VersionMacroTestbench();
|
||||
// advanced
|
||||
YYCCTestbench::ConfigManagerTestbench();
|
||||
YYCCTestbench::ArgParserTestbench(argc, argv);
|
||||
|
||||
// testbench which may terminal app or ordering input
|
||||
YYCCTestbench::ConsoleTestbench();
|
||||
YYCCTestbench::DialogTestbench();
|
||||
YYCCTestbench::ExceptionTestbench();
|
||||
}
|
||||
Reference in New Issue
Block a user