Skip to content

Commit

Permalink
Merge pull request #240 from eseiler/misc/call_parse
Browse files Browse the repository at this point in the history
[MISC] Check that parse was not called
  • Loading branch information
eseiler authored Feb 14, 2024
2 parents 4b75957 + e0a6826 commit dccd47d
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 3 deletions.
46 changes: 43 additions & 3 deletions include/sharg/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.}
Expand All @@ -250,6 +253,7 @@ class parser
&& std::invocable<validator_type, option_type>
void add_option(option_type & value, config<validator_type> 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
Expand All @@ -267,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.}
Expand All @@ -276,6 +283,7 @@ class parser
requires std::invocable<validator_type, bool>
void add_flag(bool & value, config<validator_type> const & config)
{
check_parse_not_called("add_flag");
verify_flag_config(config);

if (value)
Expand Down Expand Up @@ -305,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
*
Expand All @@ -318,6 +330,7 @@ class parser
&& std::invocable<validator_type, option_type>
void add_positional_option(option_type & value, config<validator_type> const & config)
{
check_parse_not_called("add_positional_option");
verify_positional_option_config(config);

if constexpr (detail::is_container_option<option_type>)
Expand Down Expand Up @@ -537,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.
Expand All @@ -545,6 +559,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)
{
Expand All @@ -556,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.
Expand All @@ -564,6 +581,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)
{
Expand All @@ -576,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.
Expand All @@ -584,6 +604,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)
{
Expand All @@ -596,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
*
Expand All @@ -613,6 +636,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)
{
Expand Down Expand Up @@ -985,6 +1010,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
38 changes: 38 additions & 0 deletions test/unit/parser/parser_design_error_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> 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
}

0 comments on commit dccd47d

Please sign in to comment.