diff options
author | Jelle Raaijmakers <jelle@gmta.nl> | 2021-06-07 21:20:35 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-06-08 11:30:58 +0200 |
commit | d7126fbbc2b03d005370ffb8d1195c6d11f11ec9 (patch) | |
tree | 8cc2d96cc33f97aa8fd62b38509402e1a2bd8de9 | |
parent | 0b0bce78f60c54bd9d91dedc37258a6d75cf5053 (diff) | |
download | serenity-d7126fbbc2b03d005370ffb8d1195c6d11f11ec9.zip |
LibCore/ArgsParser: Learn how to stop on first non-option
We need this for utilities like `env`, that do not gain anything by
parsing the options passed to the command they are supposed to
execute.
-rw-r--r-- | Tests/LibCore/TestLibCoreArgsParser.cpp | 176 | ||||
-rw-r--r-- | Userland/Libraries/LibCore/ArgsParser.cpp | 3 | ||||
-rw-r--r-- | Userland/Libraries/LibCore/ArgsParser.h | 2 |
3 files changed, 181 insertions, 0 deletions
diff --git a/Tests/LibCore/TestLibCoreArgsParser.cpp b/Tests/LibCore/TestLibCoreArgsParser.cpp index 8292e20369..3e60a8f6ef 100644 --- a/Tests/LibCore/TestLibCoreArgsParser.cpp +++ b/Tests/LibCore/TestLibCoreArgsParser.cpp @@ -206,4 +206,180 @@ TEST_CASE(positional_vector_string_argument) EXPECT_EQ(values[1], "two"); } } + +TEST_CASE(combination_of_bool_options_with_positional_vector_string) +{ + // Bool options (given) and positional arguments (given) + // Expected: all arguments fill as given + bool bool_opt1 = false; + bool bool_opt2 = false; + Vector<String> positionals = {}; + auto parser_result = run_parser({ "app", "-b", "-c", "one", "two" }, [&](auto& parser) { + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, true); + EXPECT_EQ(bool_opt2, true); + EXPECT_EQ(positionals.size(), 2u); + if (positionals.size() == 2u) { + EXPECT_EQ(positionals[0], "one"); + EXPECT_EQ(positionals[1], "two"); + } + + // Bool options (missing) and positional arguments (given) + // Expected: only the positional arguments are filled + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "one", "two" }, [&](auto& parser) { + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, false); + EXPECT_EQ(bool_opt2, false); + EXPECT_EQ(positionals.size(), 2u); + if (positionals.size() == 2u) { + EXPECT_EQ(positionals[0], "one"); + EXPECT_EQ(positionals[1], "two"); + } + + // Bool options (given) and positional arguments (missing) + // Expected: only the bool options are filled + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "-b", "-c" }, [&](auto& parser) { + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, true); + EXPECT_EQ(bool_opt2, true); + EXPECT_EQ(positionals.size(), 0u); + + // Bool options (missing) and positional arguments (given) using double dash + // Expected: the bool options are interpreted as positional arguments + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "--", "-b", "-c" }, [&](auto& parser) { + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, false); + EXPECT_EQ(bool_opt2, false); + EXPECT_EQ(positionals.size(), 2u); + if (positionals.size() == 2u) { + EXPECT_EQ(positionals[0], "-b"); + EXPECT_EQ(positionals[1], "-c"); + } + + // Bool options (one given) and positional arguments (one given) using double dash + // Expected: bool_opt1 is set, one positional is added + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "-b", "--", "-c" }, [&](auto& parser) { + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, true); + EXPECT_EQ(bool_opt2, false); + EXPECT_EQ(positionals.size(), 1u); + if (positionals.size() == 1u) { + EXPECT_EQ(positionals[0], "-c"); + } + + // Bool options (three given, one incorrect) and positional arguments (missing) + // Expected: parser fails + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "-b", "-d", "-c" }, [&](auto& parser) { + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No); + }); + EXPECT_EQ(parser_result, false); +}; + +TEST_CASE(stop_on_first_non_option) +{ + // Do not stop on first non-option; arguments in correct order + // Expected: bool options are set and one positional argument is filled + bool bool_opt1 = false; + bool bool_opt2 = false; + Vector<String> positionals = {}; + auto parser_result = run_parser({ "app", "-b", "-c", "one" }, [&](auto& parser) { + parser.set_stop_on_first_non_option(false); + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, true); + EXPECT_EQ(bool_opt2, true); + EXPECT_EQ(positionals.size(), 1u); + if (positionals.size() == 1u) + EXPECT_EQ(positionals[0], "one"); + + // Do not stop on first non-option; arguments in wrong order + // Expected: parser chokes on the positional argument + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "-b", "one", "-c" }, [&](auto& parser) { + parser.set_stop_on_first_non_option(false); + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes); + }); + EXPECT_EQ(parser_result, false); + + // Stop on first non-option; arguments in correct order + // Expected: bool options are set and one positional argument is filled + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "-b", "-c", "one" }, [&](auto& parser) { + parser.set_stop_on_first_non_option(true); + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, true); + EXPECT_EQ(bool_opt2, true); + EXPECT_EQ(positionals.size(), 1u); + if (positionals.size() == 1u) + EXPECT_EQ(positionals[0], "one"); + + // Stop on first non-option; arguments in wrong order + // Expected: bool_opt1 is set, other arguments are filled as positional arguments + bool_opt1 = false; + bool_opt2 = false; + positionals = {}; + parser_result = run_parser({ "app", "-b", "one", "-c" }, [&](auto& parser) { + parser.set_stop_on_first_non_option(true); + parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b'); + parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c'); + parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes); + }); + EXPECT_EQ(parser_result, true); + EXPECT_EQ(bool_opt1, true); + EXPECT_EQ(bool_opt2, false); + EXPECT_EQ(positionals.size(), 2u); + if (positionals.size() == 2u) { + EXPECT_EQ(positionals[0], "one"); + EXPECT_EQ(positionals[1], "-c"); + } } diff --git a/Userland/Libraries/LibCore/ArgsParser.cpp b/Userland/Libraries/LibCore/ArgsParser.cpp index 5dbec84b7e..c858bea4f0 100644 --- a/Userland/Libraries/LibCore/ArgsParser.cpp +++ b/Userland/Libraries/LibCore/ArgsParser.cpp @@ -42,6 +42,9 @@ bool ArgsParser::parse(int argc, char* const* argv, FailureBehavior failure_beha Vector<option> long_options; StringBuilder short_options_builder; + if (m_stop_on_first_non_option) + short_options_builder.append('+'); + int index_of_found_long_option = -1; // Tell getopt() to reset its internal state, and start scanning from optind = 1. diff --git a/Userland/Libraries/LibCore/ArgsParser.h b/Userland/Libraries/LibCore/ArgsParser.h index 4e88e8af06..42a082ece8 100644 --- a/Userland/Libraries/LibCore/ArgsParser.h +++ b/Userland/Libraries/LibCore/ArgsParser.h @@ -56,6 +56,7 @@ public: bool parse(int argc, char* const* argv, FailureBehavior failure_behavior = FailureBehavior::PrintUsageAndExit); // *Without* trailing newline! void set_general_help(const char* help_string) { m_general_help = help_string; }; + void set_stop_on_first_non_option(bool stop_on_first_non_option) { m_stop_on_first_non_option = stop_on_first_non_option; } void print_usage(FILE*, const char* argv0); void add_option(Option&&); @@ -81,6 +82,7 @@ private: bool m_show_help { false }; const char* m_general_help { nullptr }; + bool m_stop_on_first_non_option { false }; }; } |