Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow --option=value form of arguments (Refactored #185) #203

Merged
merged 4 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -934,6 +935,42 @@ foo@bar:/home/dev/$ ./main fex
baz
```

## Using `option=value` syntax

```cpp
#include "argparse.hpp"
#include <cassert>

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<bool>("--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.
Expand Down
37 changes: 35 additions & 2 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1226,10 +1226,42 @@ class ArgumentParser {
}

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<std::string>
preprocess_arguments(const std::vector<std::string> &raw_arguments) {
std::vector<std::string> 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<std::string> &arguments) {
void parse_args_internal(const std::vector<std::string> &raw_arguments) {
auto arguments = preprocess_arguments(raw_arguments);
if (m_program_name.empty() && !arguments.empty()) {
m_program_name = arguments.front();
}
Expand Down Expand Up @@ -1294,7 +1326,8 @@ class ArgumentParser {
* Like parse_args_internal but collects unused args into a vector<string>
*/
std::vector<std::string>
parse_known_args_internal(const std::vector<std::string> &arguments) {
parse_known_args_internal(const std::vector<std::string> &raw_arguments) {
auto arguments = preprocess_arguments(raw_arguments);

std::vector<std::string> unknown_arguments{};

Expand Down
Binary file removed include/argparse/test
Binary file not shown.
15 changes: 0 additions & 15 deletions include/argparse/test.cpp

This file was deleted.

1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions test/test_equals_form.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <argparse/argparse.hpp>
#include <doctest.hpp>
#include <iostream>
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<std::vector<std::string>>("--long") ==
std::vector<std::string>{"value1", "value2"}));
}