From 598aae69aec6661739d6244e1ab3ef1da410f1ff Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 31 Jul 2024 12:08:30 +0800 Subject: [PATCH] doc: update documentation --- doc/src/arg_parser.dox | 66 ++++++++-- src/ArgParser.cpp | 2 +- src/ArgParser.hpp | 285 ++++++++++++++++++++++++++++++++++++---- src/ConfigManager.hpp | 8 +- src/ExceptionHelper.hpp | 1 + testbench/main.cpp | 14 +- 6 files changed, 334 insertions(+), 42 deletions(-) diff --git a/doc/src/arg_parser.dox b/doc/src/arg_parser.dox index e3b9993..d44be68 100644 --- a/doc/src/arg_parser.dox +++ b/doc/src/arg_parser.dox @@ -48,7 +48,7 @@ if (test.m_StringArgument.IsCaptured()) auto val = test.m_StringArgument.Get(); \endcode -These code can resolve following command line +These code can resolve following command line: \code{.sh} exec -i 114514 -f 2.0 --string fuck -b --clamped-float 0.5 @@ -62,38 +62,82 @@ For convenience, we define following terms used in this article. \section arg_parser__argument Argument +Argument is the leaf of argument parser. +It has the same position as setting in universal config manager. + \subsection arg_parser__argument__presets Argument Presets -\subsubsection arg_parser__argument__presets__number Number Argument +Like setting in universal config manager, +we also provide various common used argument presets. +Current'y we support following argument presets: -\subsubsection arg_parser__argument__presets__string String Argument +\li NumberArgument: The argument storing arithmetic type (except \c bool) inside. Such as -i 114514 in example. +\li StringArgument: The argument storing string inside. Such as --string fuck in example. +\li SwitchArgument: The argument storing nothing. It is just a simple switch. Such as -b in example. -\subsubsection arg_parser__argument__presets__switch Switch Argument +When constructing these argument, +you need provide one from long name or short name, or both of them. +Short name is the argument starting with dash and long name starts with double dash. +You don't need add dash or double dash prefix when providing these names. +Please note only ASCII characters, which can be displayed on screen, can be used in these names. -Switch argument must be optional argument. +Optionally, you can provide description when constructing, +which will tell user how this switch does and more infomation about this switch. +And, you can add an example to tell user which value is valid. + +Next, you can specify an argument to be optional. +Optional argument can be absent in command line. +Oppositely, non-optional argument must be presented in command line, +otherwise parser will return false to indicate an error. +For checking whether an optional argument is specified, +please call AbstractArgument::IsCaptured(). + +Last, you can optionally assign a constraint to it, +to help argument limit its value. + +However SwitchArgument must be optional argument. Because it is true if user specify it explicit it, and will be false if user do not give this flag. - -Switch argument also doesn't contain any value. -Because it is just a switch. -It can not hold any value. +SwitchArgument doesn't have constraint features, +because it doesn't store any value inside. +Thus no need to limit this. \subsection arg_parser__argument__custom Custom Argument +In most cases, the combination use of argument presets and constraints is enough. +However, if you still are urge to create your personal argument, +please inherit AbstractArgument and implement essential class functions. +For the class functions you need to implement, +please refer to our argument presets. + \section arg_parser__argument_list Argument List +Argument list is a struct used by parser for parsing. +It is a higher wrapper of a simple list containing argument items. +We provide 2 ways to get argument list. + +\li ArgumentList::CreateFromStd: Create argument list from standard C main function parameters. +\li ArgumentList::CreateFromWin32: Create argument list from Win32 functions in Windows. +You should use this function in Windows instead of ArgumentList::CreateFromStd. +Because the command line passed in standard C main function has encoding issue in Windows. +Use this function you will fetch correct argument list especially command including non-ASCII characters. + +Please note the first argument in given command line will be stripped. +Because in most cases it point to the executable self, +and should not be seen as the part of argument list. + \section arg_parser__option_context Option Context Please note any unknow argument will let the parser return false. This is different with other argument parsers. In other common argument parsers, -they will collect all unknow argument ad positional argument, +they will collect all unknow argument as positional argument, or just simply ignore them. OptionContext also will not add \c -h or \c --help switch automatically. This is also differnent with other parsers. You should manually add it. -However, OptionContext provide a universal help print function. +However, OptionContext provide a universal help print function, OptionContext::Help. You can directly call it to output help text if you needed (fail to parse or user order help). \section arg_parser__limitation Limitation diff --git a/src/ArgParser.cpp b/src/ArgParser.cpp index 89594d8..998a6da 100644 --- a/src/ArgParser.cpp +++ b/src/ArgParser.cpp @@ -115,7 +115,7 @@ namespace YYCC::ArgParser { return true; } - bool ArgumentList::IsParameter(yycc_u8string* val) const { + bool ArgumentList::IsValue(yycc_u8string* val) const { bool is_value = !IsSwitch(); if (is_value && val != nullptr) *val = *m_ArgumentsIterator; diff --git a/src/ArgParser.hpp b/src/ArgParser.hpp index ecdf2cd..d2b300c 100644 --- a/src/ArgParser.hpp +++ b/src/ArgParser.hpp @@ -9,35 +9,144 @@ #include #include +/** + * @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 YYCC_OS == 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&& arguments); public: + /// @brief Default copy constructor ArgumentList(const ArgumentList&) = default; + /// @brief Default copy assigner ArgumentList& operator=(const ArgumentList&) = default; + /// @brief Default move constructor ArgumentList(ArgumentList&&) = default; + /// @brief Default move assigner ArgumentList& operator=(ArgumentList&&) = default; 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; - bool IsParameter(yycc_u8string* val = 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: @@ -45,40 +154,112 @@ namespace YYCC::ArgParser { std::vector::const_iterator m_ArgumentsIterator; }; + /** + * @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; - 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; + 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(); + // ===== 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; @@ -86,24 +267,52 @@ namespace YYCC::ArgParser { 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 arguments); ~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: @@ -116,10 +325,24 @@ namespace YYCC::ArgParser { }; #pragma region Argument Presets - + + /** + * @brief Arithmetic (integral, floating point. except bool) type argument + * @tparam _Ty The internal stored type belongs to arithmetic type. + */ template && !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, @@ -129,6 +352,7 @@ namespace YYCC::ArgParser { virtual ~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; @@ -139,7 +363,7 @@ namespace YYCC::ArgParser { // try get corresponding value yycc_u8string strval; al.Next(); - if (al.IsEOF() || !al.IsParameter(&strval)) { + if (al.IsEOF() || !al.IsValue(&strval)) { al.Prev(); return false; } @@ -160,32 +384,44 @@ namespace YYCC::ArgParser { 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), m_Data(false) {} + AbstractArgument(long_name, short_name, description, nullptr, true) {} virtual ~SwitchArgument() {} - public: - bool Get() const { return m_Data; } - protected: - virtual bool Parse(ArgumentList& al) override { - m_Data = true; - return true; - } - virtual void Reset() override { m_Data = false; } - - protected: - bool m_Data; + 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, @@ -195,6 +431,7 @@ namespace YYCC::ArgParser { virtual ~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; @@ -204,7 +441,7 @@ namespace YYCC::ArgParser { virtual bool Parse(ArgumentList& al) override { // try get corresponding value al.Next(); - if (al.IsEOF() || !al.IsParameter(&m_Data)) { + if (al.IsEOF() || !al.IsValue(&m_Data)) { al.Prev(); return false; } diff --git a/src/ConfigManager.hpp b/src/ConfigManager.hpp index b5ed94c..ba2da98 100644 --- a/src/ConfigManager.hpp +++ b/src/ConfigManager.hpp @@ -19,7 +19,7 @@ namespace YYCC::ConfigManager { /// @brief The base class of every setting. - /// @details Programmer can inherit this class and implement essential to create custom setting. + /// @details Programmer can inherit this class and implement essential functions to create custom setting. class AbstractSetting { friend class CoreManager; public: @@ -42,19 +42,19 @@ namespace YYCC::ConfigManager { // User Implementations protected: - /// @brief User implemented custom load functions + /// @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 functions + /// @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 functions + /// @brief User implemented custom reset function /// @remarks In this function, programmer should reset its internal variable to default value. virtual void UserReset() = 0; diff --git a/src/ExceptionHelper.hpp b/src/ExceptionHelper.hpp index 67d0790..ab73f40 100644 --- a/src/ExceptionHelper.hpp +++ b/src/ExceptionHelper.hpp @@ -40,6 +40,7 @@ namespace YYCC::ExceptionHelper { * (for convenient debugging of developer when reporting bugs.) * * This function usually is called at the start of program. + * @param[in] callback User defined callback called when unhandled exception happened. nullptr if no callback. */ void Register(ExceptionCallback callback = nullptr); /** diff --git a/testbench/main.cpp b/testbench/main.cpp index c98a703..16b7383 100644 --- a/testbench/main.cpp +++ b/testbench/main.cpp @@ -544,7 +544,11 @@ auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeo PREPARE_DATA("exec", "-i", "114514"); Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); - Assert(!test.m_BoolArgument.Get(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); + Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); + Assert(!test.m_FloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); + Assert(!test.m_StringArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); + Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); + Assert(!test.m_ClampedFloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); test.m_OptionContext.Reset(); } // no argument @@ -565,6 +569,12 @@ auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeo Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); test.m_OptionContext.Reset(); } + // dplicated assign + { + PREPARE_DATA("exec", "-i", "114514" "--int", "114514"); + Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); + test.m_OptionContext.Reset(); + } // extra useless argument { PREPARE_DATA("exec", "-i", "114514" "1919810"); @@ -584,7 +594,7 @@ auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeo Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); Assert(test.m_FloatArgument.IsCaptured() && test.m_FloatArgument.Get() == 2.0f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); Assert(test.m_StringArgument.IsCaptured() && test.m_StringArgument.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); - Assert(test.m_BoolArgument.IsCaptured() && test.m_BoolArgument.Get(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); + Assert(test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); Assert(test.m_ClampedFloatArgument.IsCaptured() && test.m_ClampedFloatArgument.Get() == 0.5f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse")); test.m_OptionContext.Reset(); }