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

Document and use Argument.scan where possible #121

Merged
merged 3 commits into from
Aug 25, 2021
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
53 changes: 42 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ int main(int argc, char *argv[]) {

program.add_argument("square")
.help("display the square of a given integer")
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

try {
program.parse_args(argc, argv);
Expand Down Expand Up @@ -81,7 +81,7 @@ $ ./main 15
Here's what's happening:

* The ```add_argument()``` method is used to specify which command-line options the program is willing to accept. In this case, I’ve named it square so that it’s in line with its function.
* Command-line arguments are strings. Inorder to square the argument and print the result, we need to convert this argument to a number. In order to do this, we use the ```.action``` method and provide a lambda function that tries to convert user input into an integer.
* Command-line arguments are strings. To square the argument and print the result, we need to convert this argument to a number. In order to do this, we use the ```.scan``` method to convert user input into an integer.
* We can get the value stored by the parser for a given argument using ```parser.get<T>(key)``` method.

### Optional Arguments
Expand Down Expand Up @@ -209,12 +209,12 @@ argparse::ArgumentParser program;

program.add_argument("integer")
.help("Input number")
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

program.add_argument("floats")
.help("Vector of floats")
.nargs(4)
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

try {
program.parse_args(argc, argv);
Expand Down Expand Up @@ -243,7 +243,7 @@ argparse::ArgumentParser program("test");

program.add_argument("square")
.help("display the square of a given number")
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

program.add_argument("--verbose")
.default_value(false)
Expand Down Expand Up @@ -326,7 +326,7 @@ auto files = program.get<std::vector<std::string>>("--input_files"); // {"confi
auto files = program.get<std::list<std::string>>("--input_files"); // {"config.yml", "System.xml"}
```

Using ```.action```, one can quickly build a list of desired value types from command line arguments. Here's an example:
Using ```.scan```, one can quickly build a list of desired value types from command line arguments. Here's an example:

```cpp
argparse::ArgumentParser program("main");
Expand All @@ -335,7 +335,7 @@ program.add_argument("--query_point")
.help("3D query point")
.nargs(3)
.default_value(std::vector<double>{0.0, 0.0, 0.0})
.action([](const std::string& value) { return std::stod(value); });
.scan<'g', double>();

try {
program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2
Expand Down Expand Up @@ -367,7 +367,7 @@ program.add_argument("-b")
program.add_argument("-c")
.nargs(2)
.default_value(std::vector<float>{0.0f, 0.0f})
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

try {
program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47
Expand Down Expand Up @@ -406,6 +406,37 @@ Here's what's happening:
- argv is further parsed to identify the inputs mapped to ```-c```.
- If argparse cannot find any arguments to map to c, then c defaults to {0.0, 0.0} as defined by ```.default_value```

### Converting to Numeric Types

For inputs, users can express a primitive type for the value.

The ```.scan<Shape, T>``` method attempts to convert the incoming `std::string` to `T` following the `Shape` conversion specifier. An `std::invalid_argument` or `std::range_error` exception is thrown for errors.

```cpp
program.add_argument("-x")
.scan<'d', int>();

program.add_argument("scale")
.scan<'g', double>();
```

`Shape` specifies what the input "looks like", and the type template argument specifies the return value of the predefined action. Acceptable types are floating point (i.e float, double, long double) and integral (i.e. signed char, short, int, long, long long).

The grammar follows `std::from_chars`, but does not exactly duplicate it. For example, hexadecimal numbers may begin with `0x` or `0X` and numbers with a leading zero may be handled as octal values.

| Shape | interpretation |
| :--------: | ----------------------------------------- |
| 'a' or 'A' | hexadecimal floating point |
| 'e' or 'E' | scientific notation (floating point) |
| 'f' or 'F' | fixed notation (floating point) |
| 'g' or 'G' | general form (either fixed or scientific) |
| | |
| 'd' | decimal |
| 'i' | `std::from_chars` grammar with base == 0 |
| 'o' | octal (unsigned) |
| 'u' | decimal (unsigned) |
| 'x' or 'X' | hexadecimal (unsigned) |

### Gathering Remaining Arguments

`argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
Expand Down Expand Up @@ -521,7 +552,7 @@ Sometimes, several parsers share a common set of arguments. Rather than repeatin
argparse::ArgumentParser parent_parser("main");
parent_parser.add_argument("--parent")
.default_value(0)
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

argparse::ArgumentParser foo_parser("foo");
foo_parser.add_argument("foo");
Expand Down Expand Up @@ -570,7 +601,7 @@ argparse::ArgumentParser program("test");

program.add_argument("numbers")
.nargs(3)
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

program.add_argument("-a")
.default_value(false)
Expand All @@ -582,7 +613,7 @@ program.add_argument("-b")

program.add_argument("-c")
.nargs(2)
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

program.add_argument("--files")
.nargs(3);
Expand Down
2 changes: 1 addition & 1 deletion test/test_append.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ TEST_CASE("Two int .append" * test_suite("append")) {
argparse::ArgumentParser program("test");
program.add_argument("--factor")
.append()
.action([](auto s) { return stoi(s); });
.scan<'i', int>();
program.parse_args({ "test", "--factor", "2", "--factor", "5" });
auto result { program.get<std::vector<int>>("--factor") };
REQUIRE(result.at(0) == 2);
Expand Down
10 changes: 5 additions & 5 deletions test/test_compound_arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ TEST_CASE("Parse compound toggle arguments with implicit values and nargs" *

program.add_argument("-c")
.nargs(2)
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

program.add_argument("--input_files")
.nargs(3);
Expand All @@ -65,7 +65,7 @@ TEST_CASE("Parse compound toggle arguments with implicit values and nargs and "

program.add_argument("numbers")
.nargs(3)
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

program.add_argument("-a")
.default_value(false)
Expand All @@ -77,7 +77,7 @@ TEST_CASE("Parse compound toggle arguments with implicit values and nargs and "

program.add_argument("-c")
.nargs(2)
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

program.add_argument("--input_files")
.nargs(3);
Expand All @@ -99,7 +99,7 @@ TEST_CASE("Parse out-of-order compound arguments" *

program.add_argument("-c")
.nargs(2)
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

program.parse_args({ "./main", "-cab", "3.14", "2.718" });

Expand All @@ -126,7 +126,7 @@ TEST_CASE("Parse out-of-order compound arguments. Second variation" *
program.add_argument("-c")
.nargs(2)
.default_value(std::vector<float>{0.0f, 0.0f})
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

program.parse_args({"./main", "-cb"});

Expand Down
2 changes: 1 addition & 1 deletion test/test_invalid_arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ TEST_CASE("Parse unknown optional argument" *

bfm.add_argument("-m", "--mem")
.default_value(64ULL)
.action([](const std::string& val) { return std::stoull(val); })
.scan<'u', unsigned long long>()
.help("memory in MB to give the VMM when loading");

REQUIRE_THROWS(bfm.parse_args({ "./test.exe", "-om" }));
Expand Down
2 changes: 1 addition & 1 deletion test/test_issue_37.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ TEST_CASE("Issues with implicit values #37" * test_suite("implicit_values")) {
m_bfm.add_argument("-m", "--mem")
.default_value(100)
.required()
.action([](const std::string &val) { return std::stoull(val); })
.scan<'u', unsigned long long>()
.help("memory in MB to give the VMM when loading");
m_bfm.parse_args({ "test", "-l", "blah", "-d", "-u" });

Expand Down
12 changes: 6 additions & 6 deletions test/test_negative_numbers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ TEST_CASE("Parse negative integer" * test_suite("positional_arguments")) {

program.add_argument("number")
.help("Input number")
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

program.parse_args({"./main", "-1"});
REQUIRE(program.get<int>("number") == -1);
Expand All @@ -29,7 +29,7 @@ TEST_CASE("Parse negative integers into a vector" *
program.add_argument("number")
.help("Input number")
.nargs(3)
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

program.parse_args({"./main", "-1", "-2", "3"});
REQUIRE(program["number"] == std::vector<int>{-1, -2, 3});
Expand All @@ -44,7 +44,7 @@ TEST_CASE("Parse negative float" * test_suite("positional_arguments")) {

program.add_argument("number")
.help("Input number")
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();

program.parse_args({"./main", "-1.0"});
REQUIRE(program.get<float>("number") == -1.0);
Expand All @@ -61,7 +61,7 @@ TEST_CASE("Parse negative floats into a vector" *
program.add_argument("number")
.help("Input number")
.nargs(3)
.action([](const std::string& value) { return std::stod(value); });
.scan<'g', double>();

program.parse_args({"./main", "-1.001", "-2.002", "3.003"});
REQUIRE(program["number"] == std::vector<double>{-1.001, -2.002, 3.003});
Expand All @@ -76,7 +76,7 @@ TEST_CASE("Parse numbers in E notation" * test_suite("positional_arguments")) {

program.add_argument("number")
.help("Input number")
.action([](const std::string& value) { return std::stod(value); });
.scan<'g', double>();

program.parse_args({"./main", "-1.2e3"});
REQUIRE(program.get<double>("number") == -1200.0);
Expand All @@ -92,7 +92,7 @@ TEST_CASE("Parse numbers in E notation (capital E)" *

program.add_argument("number")
.help("Input number")
.action([](const std::string& value) { return std::stod(value); });
.scan<'g', double>();

program.parse_args({"./main", "-1.32E4"});
REQUIRE(program.get<double>("number") == -13200.0);
Expand Down
3 changes: 1 addition & 2 deletions test/test_optional_arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ TEST_CASE("Parse optional arguments of many values" *
test_suite("optional_arguments")) {
GIVEN("a program that accepts an optional argument of many values") {
argparse::ArgumentParser program("test");
program.add_argument("-i").remaining().action(
[](const std::string &value) { return std::stoi(value); });
program.add_argument("-i").remaining().scan<'i', int>();

WHEN("provided no argument") {
THEN("the program accepts it but gets nothing") {
Expand Down
2 changes: 1 addition & 1 deletion test/test_parent_parsers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TEST_CASE("Add parent to multiple parent parsers" *
argparse::ArgumentParser parent_parser("main");
parent_parser.add_argument("--parent")
.default_value(0)
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();

argparse::ArgumentParser foo_parser("foo");
foo_parser.add_argument("foo");
Expand Down
23 changes: 11 additions & 12 deletions test/test_parse_args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ TEST_CASE("Parse a string argument without default value" *
TEST_CASE("Parse an int argument with value" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--count")
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();
program.parse_args({ "test", "--count", "5" });
REQUIRE(program.get<int>("--count") == 5);
}
Expand All @@ -58,15 +58,15 @@ TEST_CASE("Parse an int argument with default value" *
argparse::ArgumentParser program("test");
program.add_argument("--count")
.default_value(2)
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();
program.parse_args({ "test", "--count" });
REQUIRE(program.get<int>("--count") == 2);
}

TEST_CASE("Parse a float argument with value" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--ratio")
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();
program.parse_args({ "test", "--ratio", "5.6645" });
REQUIRE(program.get<float>("--ratio") == 5.6645f);
}
Expand All @@ -76,15 +76,15 @@ TEST_CASE("Parse a float argument with default value" *
argparse::ArgumentParser program("test");
program.add_argument("--ratio")
.default_value(3.14f)
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();
program.parse_args({ "test", "--ratio" });
REQUIRE(program.get<float>("--ratio") == 3.14f);
}

TEST_CASE("Parse a double argument with value" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--ratio")
.action([](const std::string& value) { return std::stod(value); });
.scan<'g', double>();
program.parse_args({ "test", "--ratio", "5.6645" });
REQUIRE(program.get<double>("--ratio") == 5.6645);
}
Expand All @@ -94,7 +94,7 @@ TEST_CASE("Parse a double argument with default value" *
argparse::ArgumentParser program("test");
program.add_argument("--ratio")
.default_value(3.14)
.action([](const std::string& value) { return std::stod(value); });
.scan<'g', double>();
program.parse_args({ "test", "--ratio" });
REQUIRE(program.get<double>("--ratio") == 3.14);
}
Expand All @@ -103,7 +103,7 @@ TEST_CASE("Parse a vector of integer arguments" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--vector")
.nargs(5)
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();
program.parse_args({ "test", "--vector", "1", "2", "3", "4", "5" });
auto vector = program.get<std::vector<int>>("--vector");
REQUIRE(vector.size() == 5);
Expand All @@ -118,7 +118,7 @@ TEST_CASE("Parse a vector of float arguments" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--vector")
.nargs(5)
.action([](const std::string& value) { return std::stof(value); });
.scan<'g', float>();
program.parse_args({ "test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5" });
auto vector = program.get<std::vector<float>>("--vector");
REQUIRE(vector.size() == 5);
Expand All @@ -132,7 +132,7 @@ TEST_CASE("Parse a vector of float arguments" * test_suite("parse_args")) {
TEST_CASE("Parse a vector of float without default value" *
test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--vector").scan<'f', float>().nargs(3);
program.add_argument("--vector").scan<'g', float>().nargs(3);

WHEN("no value is provided") {
program.parse_args({"test"});
Expand Down Expand Up @@ -164,7 +164,7 @@ TEST_CASE("Parse a vector of double arguments" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--vector")
.nargs(5)
.action([](const std::string& value) { return std::stod(value); });
.scan<'g', double>();
program.parse_args({ "test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5" });
auto vector = program.get<std::vector<double>>("--vector");
REQUIRE(vector.size() == 5);
Expand All @@ -178,8 +178,7 @@ TEST_CASE("Parse a vector of double arguments" * test_suite("parse_args")) {
TEST_CASE("Parse a vector of string arguments" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--vector")
.nargs(5)
.action([](const std::string& value) { return value; });
.nargs(5);
program.parse_args({ "test", "--vector", "abc", "def", "ghi", "jkl", "mno" });
auto vector = program.get<std::vector<std::string>>("--vector");
REQUIRE(vector.size() == 5);
Expand Down
4 changes: 2 additions & 2 deletions test/test_positional_arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ TEST_CASE("Parse positional arguments with optional arguments" *
program.add_argument("input");
program.add_argument("output").nargs(2);
program.add_argument("--num_iterations")
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();
program.parse_args({ "test", "rocket.mesh", "--num_iterations", "15", "thrust_profile.csv", "output.mesh" });
REQUIRE(program.get<int>("--num_iterations") == 15);
REQUIRE(program.get("input") == "rocket.mesh");
Expand All @@ -48,7 +48,7 @@ TEST_CASE("Parse positional arguments with optional arguments in the middle" *
program.add_argument("input");
program.add_argument("output").nargs(2);
program.add_argument("--num_iterations")
.action([](const std::string& value) { return std::stoi(value); });
.scan<'i', int>();
REQUIRE_THROWS(program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "--num_iterations", "15", "output.mesh" }));
}

Expand Down