diff --git a/README.md b/README.md index 35d4479..fa6f120 100644 --- a/README.md +++ b/README.md @@ -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); @@ -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(key)``` method. ### Optional Arguments @@ -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); @@ -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) @@ -326,7 +326,7 @@ auto files = program.get>("--input_files"); // {"confi auto files = program.get>("--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"); @@ -335,7 +335,7 @@ program.add_argument("--query_point") .help("3D query point") .nargs(3) .default_value(std::vector{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 @@ -367,7 +367,7 @@ program.add_argument("-b") program.add_argument("-c") .nargs(2) .default_value(std::vector{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 @@ -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``` 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: @@ -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"); @@ -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) @@ -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); diff --git a/test/test_append.cpp b/test/test_append.cpp index bd868ef..005bd92 100644 --- a/test/test_append.cpp +++ b/test/test_append.cpp @@ -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>("--factor") }; REQUIRE(result.at(0) == 2); diff --git a/test/test_compound_arguments.cpp b/test/test_compound_arguments.cpp index 7f67547..dd19344 100644 --- a/test/test_compound_arguments.cpp +++ b/test/test_compound_arguments.cpp @@ -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); @@ -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) @@ -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); @@ -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" }); @@ -126,7 +126,7 @@ TEST_CASE("Parse out-of-order compound arguments. Second variation" * program.add_argument("-c") .nargs(2) .default_value(std::vector{0.0f, 0.0f}) - .action([](const std::string& value) { return std::stof(value); }); + .scan<'g', float>(); program.parse_args({"./main", "-cb"}); diff --git a/test/test_invalid_arguments.cpp b/test/test_invalid_arguments.cpp index a51be95..9750a39 100644 --- a/test/test_invalid_arguments.cpp +++ b/test/test_invalid_arguments.cpp @@ -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" })); diff --git a/test/test_issue_37.cpp b/test/test_issue_37.cpp index 1ac3b7f..caa80a4 100644 --- a/test/test_issue_37.cpp +++ b/test/test_issue_37.cpp @@ -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" }); diff --git a/test/test_negative_numbers.cpp b/test/test_negative_numbers.cpp index 4bac6ec..0e8ddb0 100644 --- a/test/test_negative_numbers.cpp +++ b/test/test_negative_numbers.cpp @@ -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("number") == -1); @@ -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{-1, -2, 3}); @@ -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("number") == -1.0); @@ -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{-1.001, -2.002, 3.003}); @@ -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("number") == -1200.0); @@ -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("number") == -13200.0); diff --git a/test/test_optional_arguments.cpp b/test/test_optional_arguments.cpp index a31d5df..4d50254 100644 --- a/test/test_optional_arguments.cpp +++ b/test/test_optional_arguments.cpp @@ -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") { diff --git a/test/test_parent_parsers.cpp b/test/test_parent_parsers.cpp index 2077265..301309f 100644 --- a/test/test_parent_parsers.cpp +++ b/test/test_parent_parsers.cpp @@ -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"); diff --git a/test/test_parse_args.cpp b/test/test_parse_args.cpp index 6ca55a6..a3a0452 100644 --- a/test/test_parse_args.cpp +++ b/test/test_parse_args.cpp @@ -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("--count") == 5); } @@ -58,7 +58,7 @@ 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("--count") == 2); } @@ -66,7 +66,7 @@ TEST_CASE("Parse an int argument with default value" * 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("--ratio") == 5.6645f); } @@ -76,7 +76,7 @@ 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("--ratio") == 3.14f); } @@ -84,7 +84,7 @@ TEST_CASE("Parse a float argument with default value" * 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("--ratio") == 5.6645); } @@ -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("--ratio") == 3.14); } @@ -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>("--vector"); REQUIRE(vector.size() == 5); @@ -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>("--vector"); REQUIRE(vector.size() == 5); @@ -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"}); @@ -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>("--vector"); REQUIRE(vector.size() == 5); @@ -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>("--vector"); REQUIRE(vector.size() == 5); diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index 3b2b85c..f0d936c 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -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("--num_iterations") == 15); REQUIRE(program.get("input") == "rocket.mesh"); @@ -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" })); }