diff --git a/README.md b/README.md index 2ac64c7..7093f0b 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ * [Construct a JSON object from a filename argument](#construct-a-json-object-from-a-filename-argument) * [Positional Arguments with Compound Toggle Arguments](#positional-arguments-with-compound-toggle-arguments) * [Restricting the set of values for an argument](#restricting-the-set-of-values-for-an-argument) + * [Using `option=value` syntax](#using-optionvalue-syntax) * [CMake Integration](#cmake-integration) * [Supported Toolchains](#supported-toolchains) * [Contributing](#contributing) @@ -934,6 +935,42 @@ foo@bar:/home/dev/$ ./main fex baz ``` +## Using `option=value` syntax + +```cpp +#include "argparse.hpp" +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + program.add_argument("--foo").implicit_value(true).default_value(false); + program.add_argument("--bar"); + + try { + program.parse_args(argc, argv); + } + catch (const std::runtime_error& err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + if (program.is_used("--foo")) { + std::cout << "--foo: " << std::boolalpha << program.get("--foo") << "\n"; + } + + if (program.is_used("--bar")) { + std::cout << "--bar: " << program.get("--bar") << "\n"; + } +} +``` + +```console +foo@bar:/home/dev/$ ./test --bar=BAR --foo +--foo: true +--bar: BAR +``` + ## CMake Integration Use the latest argparse in your CMake project without copying any content. diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index f8d84f9..8235ba8 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -1226,10 +1226,42 @@ public: } private: + /* + * Pre-process this argument list. Anything starting with "--", that + * contains an =, where the prefix before the = has an entry in the + * options table, should be split. + */ + std::vector + preprocess_arguments(const std::vector &raw_arguments) { + std::vector arguments; + for (const auto &arg : raw_arguments) { + // Check that: + // - We don't have an argument named exactly this + // - The argument starts with "--" + // - The argument contains a "=" + std::size_t eqpos = arg.find("="); + if (m_argument_map.find(arg) == m_argument_map.end() && + arg.rfind("--", 0) == 0 && eqpos != std::string::npos) { + // Get the name of the potential option, and check it exists + std::string opt_name = arg.substr(0, eqpos); + if (m_argument_map.find(opt_name) != m_argument_map.end()) { + // This is the name of an option! Split it into two parts + arguments.push_back(std::move(opt_name)); + arguments.push_back(arg.substr(eqpos + 1)); + continue; + } + } + // If we've fallen through to here, then it's a standard argument + arguments.push_back(arg); + } + return arguments; + } + /* * @throws std::runtime_error in case of any invalid argument */ - void parse_args_internal(const std::vector &arguments) { + void parse_args_internal(const std::vector &raw_arguments) { + auto arguments = preprocess_arguments(raw_arguments); if (m_program_name.empty() && !arguments.empty()) { m_program_name = arguments.front(); } @@ -1294,7 +1326,8 @@ private: * Like parse_args_internal but collects unused args into a vector */ std::vector - parse_known_args_internal(const std::vector &arguments) { + parse_known_args_internal(const std::vector &raw_arguments) { + auto arguments = preprocess_arguments(raw_arguments); std::vector unknown_arguments{}; diff --git a/include/argparse/test b/include/argparse/test deleted file mode 100755 index e8d4e65..0000000 Binary files a/include/argparse/test and /dev/null differ diff --git a/include/argparse/test.cpp b/include/argparse/test.cpp deleted file mode 100644 index 8d96354..0000000 --- a/include/argparse/test.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "argparse.hpp" -#include - -int main(int argc, char *argv[]) { - argparse::ArgumentParser program("test"); - program.add_argument("--foo").implicit_value(true).default_value(false); - program.add_argument("bar"); - - auto unknown_args = - program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"}); - - assert(program.get("--foo") == true); - assert(program.get("bar") == std::string{"BAR"}); - assert((unknown_args == std::vector{"--badger", "spam"})); -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b57da7d..dd7aecc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -48,6 +48,7 @@ file(GLOB ARGPARSE_TEST_SOURCES test_version.cpp test_subparsers.cpp test_parse_known_args.cpp + test_equals_form.cpp ) set_source_files_properties(main.cpp PROPERTIES diff --git a/test/test_equals_form.cpp b/test/test_equals_form.cpp new file mode 100644 index 0000000..81c31f8 --- /dev/null +++ b/test/test_equals_form.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +using doctest::test_suite; + +TEST_CASE("Basic --value=value" * test_suite("equals_form")) { + argparse::ArgumentParser parser("test"); + parser.add_argument("--long"); + parser.parse_args({"test", "--long=value"}); + std::string result{parser.get("--long")}; + REQUIRE(result == "value"); +} + +TEST_CASE("Fallback = in regular option name" * test_suite("equals_form")) { + argparse::ArgumentParser parser("test"); + parser.add_argument("--long=mislead"); + parser.parse_args({"test", "--long=mislead", "value"}); + std::string result{parser.get("--long=mislead")}; + REQUIRE(result == "value"); +} + +TEST_CASE("Duplicate =-named and standard" * test_suite("equals_form")) { + argparse::ArgumentParser parser("test"); + parser.add_argument("--long=mislead"); + parser.add_argument("--long").default_value(std::string{"NO_VALUE"}); + parser.parse_args({"test", "--long=mislead", "value"}); + std::string result{parser.get("--long=mislead")}; + REQUIRE(result == "value"); + std::string result2{parser.get("--long")}; + REQUIRE(result2 == "NO_VALUE"); +} + +TEST_CASE("Basic --value=value with nargs(2)" * test_suite("equals_form")) { + argparse::ArgumentParser parser("test"); + parser.add_argument("--long").nargs(2); + parser.parse_args({"test", "--long=value1", "value2"}); + REQUIRE((parser.get>("--long") == + std::vector{"value1", "value2"})); +} \ No newline at end of file