feat: add arg parser feature but not finished

This commit is contained in:
yyc12345 2024-07-28 22:42:16 +08:00
parent f997990af6
commit 35318505e4
7 changed files with 481 additions and 49 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.23)
project(YYCC
VERSION 1.1.0
VERSION 1.2.0
LANGUAGES CXX
)

204
src/ArgParser.cpp Normal file
View File

@ -0,0 +1,204 @@
#include "ArgParser.hpp"
#include "EncodingHelper.hpp"
#if YYCC_OS == 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 = 0; i < argc; ++i) {
if (argv[i] != nullptr)
args.emplace_back(yycc_u8string(YYCC::EncodingHelper::ToUTF8(argv[i])));
}
return ArgumentList(std::move(args));
}
#if YYCC_OS == 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 = 0; i < argc; ++i) {
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_ArgumentsIterator(m_Arguments.begin()) {}
void ArgumentList::Prev() {
if (m_ArgumentsIterator == m_Arguments.begin())
throw std::runtime_error("attempt to move on the head of iterator.");
--m_ArgumentsIterator;
}
void ArgumentList::Next() {
if (IsEOF()) throw std::runtime_error("attempt to move on the tail of iterator.");
++m_ArgumentsIterator;
}
static bool IsLongName(const yycc_u8string& param, yycc_u8string_view* name_part) {
if (param.find(AbstractArgument::DOUBLE_DASH) != 0u) return false;
if (name_part != nullptr)
*name_part = yycc_u8string_view(param).substr(2u);
return true;
}
static bool IsShortName(const yycc_u8string& param, yycc_char8_t* name_part) {
if (param.size() != 2u ||
param[0] != AbstractArgument::DASH ||
param[1] == AbstractArgument::DASH ||
param[1] < AbstractArgument::MIN_SHORT_NAME || param[1] > AbstractArgument::MAX_SHORT_NAME) {
return false;
}
if (name_part != nullptr)
*name_part = param[1];
return true;
}
bool ArgumentList::IsSwitch(
bool* is_long_name = nullptr,
yycc_u8string_view* long_name = nullptr,
yycc_char8_t* short_name = nullptr) const {
// get argument first
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
const auto& param = *m_ArgumentsIterator;
// check long name first, then check short name
if (IsLongName(param, long_name)) {
if (is_long_name != nullptr) *is_long_name = true;
return true;
}
if (IsShortName(param, short_name)) {
if (is_long_name != nullptr) *is_long_name = false;
return true;
}
// not matched
return false;
}
bool ArgumentList::IsValue() const { return !IsSwitch(); }
bool ArgumentList::IsEOF() const { return m_ArgumentsIterator == m_Arguments.end(); }
void ArgumentList::Reset() { m_ArgumentsIterator = m_Arguments.begin(); }
#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('~');
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(NO_SHORT_NAME), m_Description(), m_ArgumentExample(),
m_IsOptional(is_optional), m_IsCaptured(false) {
// try to assign long name
if (long_name != nullptr) m_LongName = long_name;
// try to assign short name
if (short_name == AbstractArgument::DASH ||
short_name < AbstractArgument::MIN_SHORT_NAME ||
short_name > AbstractArgument::MAX_SHORT_NAME) {
throw std::invalid_argument("given short name character is invalid.");
}
m_ShortName = short_name;
// check short name and long name
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) {
return false; //todo
}
void OptionContext::Reset() {
for (auto* arg : m_Arguments) {
// clear user data and unset captured
arg->Reset();
arg->SetCaptured(false);
}
}
#pragma endregion
}

176
src/ArgParser.hpp Normal file
View File

@ -0,0 +1,176 @@
#pragma once
#include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include "EncodingHelper.hpp"
#include <functional>
#include <vector>
#include <map>
#include <stdexcept>
namespace YYCC::ArgParser {
class ArgumentList {
public:
static ArgumentList CreateFromStd(int argc, char* argv[]);
#if YYCC_OS == YYCC_OS_WINDOWS
static ArgumentList CreateFromWin32();
#endif
private:
ArgumentList(std::vector<yycc_u8string>&& arguments);
public:
void Prev();
void Next();
bool IsSwitch(
bool* is_long_name = nullptr,
yycc_u8string_view* long_name = nullptr,
yycc_char8_t* short_name = nullptr) const;
bool IsValue() const;
bool IsEOF() const;
void Reset();
private:
std::vector<yycc_u8string> m_Arguments;
std::vector<yycc_u8string>::const_iterator m_ArgumentsIterator;
};
class AbstractArgument {
friend class OptionContext;
public:
static const yycc_u8string DOUBLE_DASH;
static const yycc_char8_t DASH;
static const yycc_char8_t NO_SHORT_NAME;
static const yycc_char8_t MIN_SHORT_NAME;
static const yycc_char8_t MAX_SHORT_NAME;
public:
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();
protected:
virtual bool Parse(ArgumentList& al) = 0;
virtual void Reset() = 0;
public:
bool HasLongName() const;
const yycc_u8string& GetLongName() const;
bool HasShortName() const;
yycc_char8_t GetShortName() const;
bool HasDescription() const;
const yycc_u8string& GetDescription() const;
bool HasArgumentExample() const;
const yycc_u8string& GetArgumentExample() const;
bool IsOptional() const;
private:
yycc_u8string m_LongName;
yycc_char8_t m_ShortName;
yycc_u8string m_Description;
yycc_u8string m_ArgumentExample;
bool m_IsOptional;
public:
bool IsCaptured() const;
private:
void SetCaptured(bool is_captured);
bool m_IsCaptured;
};
class OptionContext {
public:
OptionContext(
const yycc_char8_t* summary, const yycc_char8_t* description,
std::initializer_list<AbstractArgument*> arguments);
~OptionContext();
public:
bool Parse(ArgumentList* al);
void Reset();
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
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:
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() {}
public:
_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 {} // todo
virtual void Reset() override {}// todo
protected:
_Ty m_Data;
Constraints::Constraint<_Ty> m_Constraint;
};
class SwitchArgument : public AbstractArgument {
public:
SwitchArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr) :
AbstractArgument(long_name, short_name, description, argument_example, true), m_Data(false) {} // bool switch must be optional, because it is false if no given switch.
virtual ~SwitchArgument() {}
public:
bool Get() const { return m_Data; }
protected:
virtual bool Parse(ArgumentList& al) override { m_Data = true; }
virtual void Reset() override { m_Data = false; }
protected:
bool m_Data;
};
class StringArgument : public AbstractArgument {
public:
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_view> constraint = Constraints::Constraint<yycc_u8string_view> {}) :
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
virtual ~StringArgument() {}
public:
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 {} // todo
virtual void Reset() override {}// todo
protected:
yycc_u8string m_Data;
Constraints::Constraint<yycc_u8string_view> m_Constraint;
};
#pragma endregion
}

View File

@ -5,6 +5,7 @@ target_sources(YYCCommonplace
PRIVATE
# Sources
COMHelper.cpp
ArgParser.cpp
ConfigManager.cpp
ConsoleHelper.cpp
DialogHelper.cpp
@ -23,7 +24,9 @@ FILE_SET HEADERS
FILES
# Headers
# Common headers
Constraints.hpp
COMHelper.hpp
ArgParser.hpp
ConfigManager.hpp
ConsoleHelper.hpp
DialogHelper.hpp

View File

@ -1,6 +1,7 @@
#pragma once
#include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include <memory>
#include <vector>
#include <map>
@ -17,46 +18,6 @@
*/
namespace YYCC::ConfigManager {
/**
* @brief The constraint applied to settings to limit its stored value.
* @tparam _Ty The internal data type stroed in corresponding setting.
*/
template<typename _Ty>
struct Constraint {
using CheckFct_t = std::function<bool(const _Ty&)>;
//using CorrectFct_t = std::function<_Ty(const _Ty&)>;
CheckFct_t m_CheckFct;
//CorrectFct_t m_CorrectFct;
bool IsValid() const {
return m_CheckFct != nullptr/* && m_CorrectFct != nullptr*/;
}
};
/**
* @brief The namespace containing functions generating common used constraint.
*/
namespace ConstraintPresets {
/**
* @brief Get constraint for arithmetic values by minimum and maximum value range.
* @tparam _Ty The underlying arithmetic type.
* @param[in] min_value The minimum value of range (inclusive).
* @param[in] max_value The maximum value of range (inclusive).
* @return The generated constraint instance which can be directly applied.
*/
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_enum_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
Constraint<_Ty> GetNumberRangeConstraint(_Ty min_value, _Ty max_value) {
if (min_value > max_value)
throw std::invalid_argument("invalid min max value for NumberRangeConstraint");
return Constraint<_Ty> {
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
};
}
}
/// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential to create custom setting.
class AbstractSetting {
@ -114,7 +75,7 @@ namespace YYCC::ConfigManager {
private:
std::vector<uint8_t> m_RawData;
};
/// @brief Settings manager and config file reader writer.
class CoreManager {
public:
@ -166,10 +127,12 @@ namespace YYCC::ConfigManager {
* @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting.
*/
NumberSetting(const yycc_char8_t* name, _Ty default_value, Constraint<_Ty> constraint = Constraint<_Ty> {}) :
NumberSetting(
const yycc_char8_t* 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() {}
/// @brief Get stored data in setting.
_Ty Get() const { return m_Data; }
/**
@ -208,7 +171,7 @@ namespace YYCC::ConfigManager {
}
_Ty m_Data, m_DefaultData;
Constraint<_Ty> m_Constraint;
Constraints::Constraint<_Ty> m_Constraint;
};
/// @brief String type setting
@ -220,13 +183,15 @@ namespace YYCC::ConfigManager {
* @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting.
*/
StringSetting(const yycc_char8_t* name, const yycc_u8string_view& default_value, Constraint<yycc_u8string_view> constraint = Constraint<yycc_u8string_view> {}) :
StringSetting(
const yycc_char8_t* name, const yycc_u8string_view& default_value,
Constraints::Constraint<yycc_u8string_view> constraint = Constraints::Constraint<yycc_u8string_view> {}) :
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
m_Data = default_value;
m_DefaultData = default_value;
}
virtual ~StringSetting() {}
/// @brief Get reference to stored string.
const yycc_u8string& Get() const { return m_Data; }
/**
@ -279,7 +244,7 @@ namespace YYCC::ConfigManager {
}
yycc_u8string m_Data, m_DefaultData;
Constraint<yycc_u8string_view> m_Constraint;
Constraints::Constraint<yycc_u8string_view> m_Constraint;
};
#pragma endregion

84
src/Constraints.hpp Normal file
View File

@ -0,0 +1,84 @@
#pragma once
#include "YYCCInternal.hpp"
#include <functional>
#include <stdexcept>
#include <set>
#include <initializer_list>
/**
* @brief The namespace containing constraint declaration
* and functions generating common used constraint.
*/
namespace YYCC::Constraints {
/**
* @brief The constraint applied to settings to limit its stored value.
* @tparam _Ty The internal data type stroed in corresponding setting.
*/
template<typename _Ty>
struct Constraint {
/// @brief Return true if value is legal, otherwise false.
using CheckFct_t = std::function<bool(const _Ty&)>;
/// @brief The function pointer used for checking whether given value is valid.
CheckFct_t m_CheckFct;
/**
* @brief Check whether this Constraint is valid for using.
* @return
* True if this Constraint is valid, otherwise false.
* You should not use this Constraint if this function return false.
*/
bool IsValid() const {
return m_CheckFct != nullptr;
}
};
/**
* @brief Get constraint for arithmetic or enum values by minimum and maximum value range.
* @tparam _Ty An arithmetic or enum type (except bool) of underlying stored value.
* @param[in] min_value The minimum value of range (inclusive).
* @param[in] max_value The maximum value of range (inclusive).
* @return The generated constraint instance which can be directly applied.
*/
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
Constraint<_Ty> GetMinMaxRangeConstraint(_Ty min_value, _Ty max_value) {
if (min_value > max_value)
throw std::invalid_argument("invalid min max value for NumberRangeConstraint");
return Constraint<_Ty> {
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
};
}
/**
* @brief Get constraint for enum values by enumerate all possible values.
* @tparam _Ty An enum type (except bool) of underlying stored value.
* @param[in] il An initializer list storing all possible values.
* @return The generated constraint instance which can be directly applied.
*/
template<typename _Ty, std::enable_if_t<std::is_enum_v<_Ty>, int> = 0>
Constraint<_Ty> GetEnumEnumerationConstraint(const std::initializer_list<_Ty>& il) {
std::set<_Ty> data(il);
return Constraint<_Ty> {
[data](const _Ty& val) -> bool { return data.find(val) != data.end(); }
};
}
/**
* @brief Get constraint for string values by enumerate all possible values.
* @param[in] il An initializer list storing all possible values.
* @return The generated constraint instance which can be directly applied.
* @remarks
* Caller must make sure that the string view passed in initializer list is valid until this Constraint life time gone.
* Becasue this generator will not copy your given string view into string.
*/
Constraint<yycc_u8string_view> GetStringEnumerationConstraint(const std::initializer_list<yycc_u8string_view>& il) {
std::set<yycc_u8string_view> data(il);
return Constraint<yycc_u8string_view> {
[data](const yycc_u8string_view& val) -> bool { return data.find(val) != data.end(); }
};
}
}

View File

@ -402,7 +402,7 @@ namespace YYCCTestbench {
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::ConfigManager::ConstraintPresets::GetNumberRangeConstraint<float>(-1.0f, 1.0f)),
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