From 97ce63b79dc26dc4c421953b059afa9e7208fabf Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Wed, 14 Feb 2024 12:54:42 +0100 Subject: [PATCH 1/2] [MISC] Check that parse was not called --- include/sharg/parser.hpp | 26 +++++++++++++ test/unit/parser/parser_design_error_test.cpp | 38 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index 3d7a8726..86784230 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -250,6 +250,7 @@ class parser && std::invocable void add_option(option_type & value, config const & config) { + check_parse_not_called("add_option"); verify_option_config(config); // copy variables into the lambda because the calls are pushed to a stack @@ -276,6 +277,7 @@ class parser requires std::invocable void add_flag(bool & value, config const & config) { + check_parse_not_called("add_flag"); verify_flag_config(config); if (value) @@ -318,6 +320,7 @@ class parser && std::invocable void add_positional_option(option_type & value, config const & config) { + check_parse_not_called("add_positional_option"); verify_positional_option_config(config); if constexpr (detail::is_container_option) @@ -545,6 +548,8 @@ class parser */ void add_section(std::string const & title, bool const advanced_only = false) { + check_parse_not_called("add_section"); + std::visit( [&title, advanced_only](auto & f) { @@ -564,6 +569,8 @@ class parser */ void add_subsection(std::string const & title, bool const advanced_only = false) { + check_parse_not_called("add_subsection"); + std::visit( [&title, advanced_only](auto & f) { @@ -584,6 +591,8 @@ class parser */ void add_line(std::string const & text, bool is_paragraph = false, bool const advanced_only = false) { + check_parse_not_called("add_line"); + std::visit( [&text, is_paragraph, advanced_only](auto & f) { @@ -613,6 +622,8 @@ class parser */ void add_list_item(std::string const & key, std::string const & desc, bool const advanced_only = false) { + check_parse_not_called("add_list_item"); + std::visit( [&key, &desc, advanced_only](auto & f) { @@ -985,6 +996,21 @@ class parser if (!config.default_message.empty()) throw design_error{"A positional option may not have a default message because it is always required."}; } + + /*!\brief Throws a sharg::design_error if parse() was already called. + * \param[in] function_name The name of the function that was called after parse(). + * \throws sharg::design_error if parse() was already called + * \details + * This function is used when calling functions which have no effect (add_line, add_option, ...) or unexpected + * behavior (add_subcommands) after parse() was called. + * Has no effect when parse() encounters a special format (help, version, ...), since those will terminate + * the program. + */ + inline void check_parse_not_called(std::string_view const function_name) const + { + if (parse_was_called) + throw design_error{detail::to_string(function_name.data(), " may only be used before calling parse().")}; + } }; } // namespace sharg diff --git a/test/unit/parser/parser_design_error_test.cpp b/test/unit/parser/parser_design_error_test.cpp index 308a4826..268d47b8 100644 --- a/test/unit/parser/parser_design_error_test.cpp +++ b/test/unit/parser/parser_design_error_test.cpp @@ -282,3 +282,41 @@ TEST(parse_test, subcommand_parser_error) EXPECT_THROW((top_level_parser.add_positional_option(flag_value, sharg::config{})), sharg::design_error); } } + +TEST(parse_test, not_allowed_after_parse) +{ + int32_t value{}; + bool flag{}; + + std::vector const arguments{"./parser_test", "-i", "3"}; + sharg::parser parser{"test_parser", arguments}; + parser.add_option(value, sharg::config{.short_id = 'i'}); + EXPECT_NO_THROW(parser.parse()); + + auto check_error = [](auto call_fn, std::string const function_name) + { + try + { + call_fn(); + FAIL(); + } + catch (sharg::design_error const & exception) + { + EXPECT_EQ(function_name + " may only be used before calling parse().", exception.what()); + } + catch (...) + { + FAIL(); + } + }; + + // clang-format off + check_error([&parser, &value]() { parser.add_option(value, sharg::config{.short_id = 'i'}); }, "add_option"); + check_error([&parser, &flag]() { parser.add_flag(flag, sharg::config{.short_id = 'i'}); }, "add_flag"); + check_error([&parser, &value]() { parser.add_positional_option(value, sharg::config{}); }, "add_positional_option"); + check_error([&parser]() { parser.add_section(""); }, "add_section"); + check_error([&parser]() { parser.add_subsection(""); }, "add_subsection"); + check_error([&parser]() { parser.add_line(""); }, "add_line"); + check_error([&parser]() { parser.add_list_item("", ""); }, "add_list_item"); + // clang-format on +} From e0a68265d6a98309de663986005ce77065af25d5 Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Wed, 14 Feb 2024 13:02:56 +0100 Subject: [PATCH 2/2] [DOC] Update throws documentation --- include/sharg/parser.hpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index 86784230..8428170c 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -240,7 +240,10 @@ class parser * * The `config.validator` must be applicable to the given output variable (\p value). * - * \throws sharg::design_error + * \throws sharg::design_error if sharg::parser::parse was already called. + * \throws sharg::design_error if the option is required and has a default_message. + * \throws sharg::design_error if the option identifier was already used. + * \throws sharg::design_error if the option identifier is not a valid identifier. * * \details * \stableapi{Since version 1.0.} @@ -268,7 +271,10 @@ class parser * \param[in, out] value The variable which shows if the flag is turned off (default) or on. * \param[in] config A configuration object to customise the sharg::parser behaviour. See sharg::config. * - * \throws sharg::design_error + * \throws sharg::design_error if sharg::parser::parse was already called. + * \throws sharg::design_error if `value` is true. + * \throws sharg::design_error if the option identifier was already used. + * \throws sharg::design_error if the option identifier is not a valid identifier. * * \details * \stableapi{Since version 1.0.} @@ -307,7 +313,11 @@ class parser * \param[in, out] value The variable in which to store the given command line argument. * \param[in] config Customise the sharg::parser behaviour. See sharg::positional_config. * - * \throws sharg::design_error + * \throws sharg::design_error if sharg::parser::parse was already called. + * \throws sharg::design_error if the option has a short or long identifier. + * \throws sharg::design_error if the option is advanced or hidden. + * \throws sharg::design_error if the option has a default_message. + * \throws sharg::design_error if there already is a positional list option. * * \details * @@ -540,6 +550,7 @@ class parser /*!\brief Adds an help page section to the sharg::parser. * \param[in] title The title of the section. * \param[in] advanced_only If set to true, the section only shows when the user requested the advanced help page. + * \throws sharg::design_error if sharg::parser::parse was already called. * \details * * This only affects the help page and other output formats. @@ -561,6 +572,7 @@ class parser /*!\brief Adds an help page subsection to the sharg::parser. * \param[in] title The title of the subsection. * \param[in] advanced_only If set to true, the section only shows when the user requested the advanced help page. + * \throws sharg::design_error if sharg::parser::parse was already called. * \details * * This only affects the help page and other output formats. @@ -583,6 +595,7 @@ class parser * \param[in] text The text to print. * \param[in] is_paragraph Whether to insert as paragraph or just a line (Default: false). * \param[in] advanced_only If set to true, the section only shows when the user requested the advanced help page. + * \throws sharg::design_error if sharg::parser::parse was already called. * \details * If the line is not a paragraph (false), only one line break is appended, otherwise two line breaks are appended. * This only affects the help page and other output formats. @@ -605,6 +618,7 @@ class parser * \param[in] key The key of the key-value pair of the list item. * \param[in] desc The value of the key-value pair of the list item. * \param[in] advanced_only If set to true, the section only shows when the user requested the advanced help page. + * \throws sharg::design_error if sharg::parser::parse was already called. * * \details *