From 73f6aa75384b2688c2e94bd8813dc89080cd9319 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Thu, 6 Jun 2019 21:24:32 -0400 Subject: [PATCH] Fixes Issue #24 --- include/argparse.hpp | 44 ++++++++++++++++----- test/CMakeLists.txt | 14 ++++++- test/main.cpp | 1 + test/test_compound_arguments.hpp | 5 ++- test/test_negative_numbers.hpp | 65 ++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 test/test_negative_numbers.hpp diff --git a/include/argparse.hpp b/include/argparse.hpp index 5473109..7411918 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -217,9 +217,30 @@ public: } private: + + static bool is_integer(const std::string& aValue) { + if(aValue.empty() || + ((!isdigit(aValue[0])) && (aValue[0] != '-') && (aValue[0] != '+'))) + return false; + char * tPtr; + strtol(aValue.c_str(), &tPtr, 10); + return (*tPtr == 0); + } + + static bool is_float(const std::string& aValue) { + std::istringstream tStream(aValue); + float tFloat; + // noskipws considers leading whitespace invalid + tStream >> std::noskipws >> tFloat; + // Check the entire string was consumed + // and if either failbit or badbit is set + return tStream.eof() && !tStream.fail(); + } + // If an argument starts with "-" or "--", then it's optional static bool is_optional(const std::string& aName) { - return (!aName.empty() && aName[0] == '-'); + return (!aName.empty() && aName[0] == '-' && + !is_integer(aName) && !is_float(aName)); } static bool is_positional(const std::string& aName) { @@ -464,10 +485,11 @@ class ArgumentParser { */ void parse_args_validate() { // Check if all arguments are parsed - std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap), [](const auto& argPair) { - const auto& [key, arg] = argPair; - arg->validate(); - }); + std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap), + [](const auto& argPair) { + const auto& tArgument = argPair.second; + tArgument->validate(); + }); } // Used by print_help. @@ -475,11 +497,13 @@ class ArgumentParser { if (mArgumentMap.empty()) return 0; std::vector argumentLengths(mArgumentMap.size()); - std::transform(std::begin(mArgumentMap), std::end(mArgumentMap), std::begin(argumentLengths), [](const auto& argPair) { - const auto& [key, arg] = argPair; - return arg->get_arguments_length(); - }); - return *std::max_element(std::begin(argumentLengths), std::end(argumentLengths)); + std::transform(std::begin(mArgumentMap), std::end(mArgumentMap), + std::begin(argumentLengths), [](const auto& argPair) { + const auto& tArgument = argPair.second; + return tArgument->get_arguments_length(); + }); + return *std::max_element(std::begin(argumentLengths), + std::end(argumentLengths)); } std::string mProgramName; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0de4790..d7f6fb3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,18 @@ cmake_minimum_required(VERSION 3.6) project(ARGPARSE) +if(MSVC) + # Force to always compile with W4 + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() +elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + # Update if necessary + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic") +endif() + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() @@ -22,4 +34,4 @@ set_target_properties(ARGPARSE PROPERTIES OUTPUT_NAME tests) set_property(TARGET ARGPARSE PROPERTY CXX_STANDARD 17) # Set ${PROJECT_NAME} as the startup project -set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE) \ No newline at end of file +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE) diff --git a/test/main.cpp b/test/main.cpp index 99ae60d..2885d40 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -9,3 +9,4 @@ #include #include #include +#include diff --git a/test/test_compound_arguments.hpp b/test/test_compound_arguments.hpp index b584246..9c42f59 100644 --- a/test/test_compound_arguments.hpp +++ b/test/test_compound_arguments.hpp @@ -100,6 +100,9 @@ TEST_CASE("Parse out-of-order compound arguments", "[compound_arguments]") { auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get>("-c"); // {3.14f, 2.718f} + REQUIRE(a == true); + REQUIRE(b == true); + REQUIRE(program["-c"] == std::vector{3.14f, 2.718f}); } TEST_CASE("Parse out-of-order compound arguments. Second variation", "[compound_arguments]") { @@ -127,4 +130,4 @@ TEST_CASE("Parse out-of-order compound arguments. Second variation", "[compound_ REQUIRE(a == false); REQUIRE(b == true); REQUIRE(program["-c"] == std::vector{0.0f, 0.0f}); -} \ No newline at end of file +} diff --git a/test/test_negative_numbers.hpp b/test/test_negative_numbers.hpp new file mode 100644 index 0000000..668f117 --- /dev/null +++ b/test/test_negative_numbers.hpp @@ -0,0 +1,65 @@ +#pragma once +#include +#include + +TEST_CASE("Parse negative integer", "[positional_arguments]") { + argparse::ArgumentParser program; + program.add_argument("--verbose", "-v") + .help("enable verbose logging") + .default_value(false) + .implicit_value(true); + + program.add_argument("number") + .help("Input number") + .action([](const std::string& value) { return std::stoi(value); }); + + program.parse_args({"./main", "-1"}); + REQUIRE(program.get("number") == -1); +} + +TEST_CASE("Parse negative integers into a vector", "[positional_arguments]") { + argparse::ArgumentParser program; + program.add_argument("--verbose", "-v") + .help("enable verbose logging") + .default_value(false) + .implicit_value(true); + + program.add_argument("number") + .help("Input number") + .nargs(3) + .action([](const std::string& value) { return std::stoi(value); }); + + program.parse_args({"./main", "-1", "-2", "3"}); + REQUIRE(program["number"] == std::vector{-1, -2, 3}); +} + +TEST_CASE("Parse negative float", "[positional_arguments]") { + argparse::ArgumentParser program; + program.add_argument("--verbose", "-v") + .help("enable verbose logging") + .default_value(false) + .implicit_value(true); + + program.add_argument("number") + .help("Input number") + .action([](const std::string& value) { return std::stof(value); }); + + program.parse_args({"./main", "-1.0"}); + REQUIRE(program.get("number") == -1.0); +} + +TEST_CASE("Parse negative floats into a vector", "[positional_arguments]") { + argparse::ArgumentParser program; + program.add_argument("--verbose", "-v") + .help("enable verbose logging") + .default_value(false) + .implicit_value(true); + + program.add_argument("number") + .help("Input number") + .nargs(3) + .action([](const std::string& value) { return std::stod(value); }); + + program.parse_args({"./main", "-1.001", "-2.002", "3.003"}); + REQUIRE(program["number"] == std::vector{-1.001, -2.002, 3.003}); +}