diff --git a/CMakeLists.txt b/CMakeLists.txt index dc2b3ec..72f7c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) project(YYCC - VERSION 1.1.0 + VERSION 1.2.0 LANGUAGES CXX ) diff --git a/src/ArgParser.cpp b/src/ArgParser.cpp new file mode 100644 index 0000000..c97d230 --- /dev/null +++ b/src/ArgParser.cpp @@ -0,0 +1,204 @@ +#include "ArgParser.hpp" + +#include "EncodingHelper.hpp" + +#if YYCC_OS == YYCC_OS_WINDOWS +#include "WinImportPrefix.hpp" +#include +#include +#include +#include "WinImportSuffix.hpp" +#endif + +namespace YYCC::ArgParser { + +#pragma region Arguments List + + ArgumentList ArgumentList::CreateFromStd(int argc, char* argv[]) { + std::vector 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 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&& 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 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 + +} diff --git a/src/ArgParser.hpp b/src/ArgParser.hpp new file mode 100644 index 0000000..6321fb2 --- /dev/null +++ b/src/ArgParser.hpp @@ -0,0 +1,176 @@ +#pragma once +#include "YYCCInternal.hpp" + +#include "Constraints.hpp" +#include "EncodingHelper.hpp" +#include +#include +#include +#include + +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&& 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 m_Arguments; + std::vector::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 arguments); + ~OptionContext(); + + public: + bool Parse(ArgumentList* al); + void Reset(); + + private: + yycc_u8string m_Summary; + yycc_u8string m_Description; + + std::vector m_Arguments; + std::map m_LongNameMap; + std::map m_ShortNameMap; + }; + +#pragma region Argument Presets + + template && !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 constraint = Constraints::Constraint {}) : + 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 m_Constraint; + }; + +#pragma endregion + + +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b68e6b..efe6a8d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/ConfigManager.hpp b/src/ConfigManager.hpp index d74cb24..f39edc0 100644 --- a/src/ConfigManager.hpp +++ b/src/ConfigManager.hpp @@ -1,6 +1,7 @@ #pragma once #include "YYCCInternal.hpp" +#include "Constraints.hpp" #include #include #include @@ -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 - struct Constraint { - using CheckFct_t = std::function; - //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 && !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 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 constraint = Constraint {}) : + StringSetting( + const yycc_char8_t* name, const yycc_u8string_view& default_value, + Constraints::Constraint constraint = Constraints::Constraint {}) : 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 m_Constraint; + Constraints::Constraint m_Constraint; }; #pragma endregion diff --git a/src/Constraints.hpp b/src/Constraints.hpp new file mode 100644 index 0000000..02323ba --- /dev/null +++ b/src/Constraints.hpp @@ -0,0 +1,84 @@ +#pragma once +#include "YYCCInternal.hpp" + +#include +#include +#include +#include + +/** + * @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 + struct Constraint { + /// @brief Return true if value is legal, otherwise false. + using CheckFct_t = std::function; + /// @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 && !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, 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 GetStringEnumerationConstraint(const std::initializer_list& il) { + std::set data(il); + return Constraint { + [data](const yycc_u8string_view& val) -> bool { return data.find(val) != data.end(); } + }; + } + +} + diff --git a/testbench/main.cpp b/testbench/main.cpp index 4ae6882..ec26d5c 100644 --- a/testbench/main.cpp +++ b/testbench/main.cpp @@ -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(-1.0f, 1.0f)), + m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::Constraints::GetMinMaxRangeConstraint(-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