From f4fd39821ac05baabfe13590e9b71e3088a77bc4 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Thu, 30 Jun 2016 12:15:20 -0600 Subject: add noexcept functionality --- Doxyfile | 2 +- args.hxx | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++------------- test.cxx | 69 ++++++++++++++- 3 files changed, 299 insertions(+), 64 deletions(-) diff --git a/Doxyfile b/Doxyfile index 26d547e..1ba8d5e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "args" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 5.0.0 +PROJECT_NUMBER = 5.1.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/args.hxx b/args.hxx index 6d5be60..5f8dec8 100644 --- a/args.hxx +++ b/args.hxx @@ -36,11 +36,17 @@ #include #include +#ifdef ARGS_TESTNAMESPACE +namespace argstest +{ +#else + /** \namespace args * \brief contains all the functionality of the args library */ namespace args { +#endif /** Getter to grab the value from the argument type. * * If the Get() function of the type returns a reference, so does this, and @@ -139,6 +145,18 @@ namespace args return output; } +#ifdef ARGS_NOEXCEPT + enum class Error + { + None, + Usage, + Parse, + Validation, + Map, + Extra, + Help + }; +#else /** Base error class */ class Error : public std::runtime_error @@ -148,6 +166,15 @@ namespace args virtual ~Error() {}; }; + /** Errors that occur during usage + */ + class UsageError : public Error + { + public: + UsageError(const std::string &problem) : Error(problem) {} + virtual ~UsageError() {}; + }; + /** Errors that occur during regular parsing */ class ParseError : public Error @@ -192,6 +219,7 @@ namespace args Help(const std::string &flag) : Error(flag) {} virtual ~Help() {}; }; +#endif /** A simple unified option type for unified initializer lists for the Matcher class. */ @@ -346,6 +374,9 @@ namespace args protected: bool matched; const std::string help; +#ifdef ARGS_NOEXCEPT + Error error; +#endif public: Base(const std::string &help) : matched(false), help(help) {} @@ -368,10 +399,20 @@ namespace args return description; } - virtual void ResetMatched() + virtual void Reset() noexcept { matched = false; +#ifdef ARGS_NOEXCEPT + error = Error::None; +#endif } + +#ifdef ARGS_NOEXCEPT + virtual Error GetError() const + { + return error; + } +#endif }; /** Base class for all match types that have a name @@ -432,9 +473,13 @@ namespace args { if (extraError && matched) { +#ifdef ARGS_NOEXCEPT + error = Error::Extra; +#else std::ostringstream problem; problem << "Flag '" << flag << "' was passed multiple times, but is only allowed to be passed once"; throw ExtraError(problem.str()); +#endif } matched = true; return this; @@ -448,9 +493,13 @@ namespace args { if (extraError && matched) { +#ifdef ARGS_NOEXCEPT + error = Error::Extra; +#else std::ostringstream problem; problem << "Flag '" << flag << "' was passed multiple times, but is only allowed to be passed once"; throw ExtraError(problem.str()); +#endif } matched = true; return this; @@ -545,33 +594,8 @@ namespace args * \param flag The flag with prefixes stripped * \return the first matching FlagBase pointer, or nullptr if there is no match */ - FlagBase *Match(const std::string &flag) - { - for (Base *child: children) - { - if (FlagBase *flagBase = dynamic_cast(child)) - { - if (FlagBase *match = flagBase->Match(flag)) - { - return match; - } - } else if (Group *group = dynamic_cast(child)) - { - if (FlagBase *match = group->Match(flag)) - { - return match; - } - } - } - return nullptr; - } - - /** Return the first FlagBase that matches flag, or nullptr - * - * \param flag The flag with prefixes stripped - * \return the first matching FlagBase pointer, or nullptr if there is no match - */ - FlagBase *Match(const char flag) + template + FlagBase *Match(const T &flag) { for (Base *child: children) { @@ -655,15 +679,7 @@ namespace args */ std::vector::size_type MatchedChildren() const { - std::vector::size_type sum = 0; - for (const Base * child: children) - { - if (child->Matched()) - { - ++sum; - } - } - return sum; + return std::count_if(std::begin(children), std::end(children), [](const Base *child){return child->Matched();}); } /** Whether or not this group matches validation @@ -732,13 +748,35 @@ namespace args return names; } - virtual void ResetMatched() override + virtual void Reset() noexcept override { for (auto &child: children) { - child->ResetMatched(); + child->Reset(); + } +#ifdef ARGS_NOEXCEPT + error = Error::None; +#endif + } + +#ifdef ARGS_NOEXCEPT + virtual Error GetError() const override + { + if (error != Error::None) + { + return error; + } + + auto it = std::find_if(std::begin(children), std::end(children), [](const Base *child){return child->GetError() != Error::None;}); + if (it == std::end(children)) + { + return Error::None; + } else + { + return (*it)->GetError(); } } +#endif /** Default validators */ @@ -943,9 +981,15 @@ namespace args { if (longseparator.empty()) { - throw std::runtime_error("longseparator can not be set to empty"); +#ifdef ARGS_NOEXCEPT + error = Error::Usage; +#else + throw UsageError("longseparator can not be set to empty"); +#endif + } else + { + this->longseparator = longseparator; } - this->longseparator = longseparator; } /** The terminator that forcibly separates flags from positionals @@ -1111,8 +1155,8 @@ namespace args template It ParseArgs(It begin, It end) { - // Reset all Matched statuses to false, for validation. Don't reset values. - ResetMatched(); + // Reset all Matched statuses and errors + Reset(); bool terminated = false; // Check all arg chunks @@ -1145,18 +1189,28 @@ namespace args argbase->ParseValue(argchunk.substr(separator + longseparator.size())); } else { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag '" << arg << "' was passed a joined argument, but these are disallowed"; throw ParseError(problem.str()); +#endif } } else { ++it; if (it == end) { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag '" << arg << "' requires an argument but received none"; throw ParseError(problem.str()); +#endif } if (allowSeparateLongValue) @@ -1164,16 +1218,26 @@ namespace args argbase->ParseValue(*it); } else { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag '" << arg << "' was passed a separate argument, but these are disallowed"; throw ParseError(problem.str()); +#endif } } } else if (separator != argchunk.npos) { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Passed an argument into a non-argument flag: " << chunk; throw ParseError(problem.str()); +#endif } if (base->KickOut()) @@ -1182,9 +1246,14 @@ namespace args } } else { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag could not be matched: " << arg; throw ParseError(problem.str()); +#endif } // Check short args } else if (!terminated && chunk.find(shortprefix) == 0 && chunk.size() > shortprefix.size()) @@ -1206,18 +1275,28 @@ namespace args argbase->ParseValue(value); } else { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag '" << arg << "' was passed a joined argument, but these are disallowed"; throw ParseError(problem.str()); +#endif } } else { ++it; if (it == end) { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag '" << arg << "' requires an argument but received none"; throw ParseError(problem.str()); +#endif } if (allowSeparateShortValue) @@ -1225,9 +1304,14 @@ namespace args argbase->ParseValue(*it); } else { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag '" << arg << "' was passed a separate argument, but these are disallowed"; throw ParseError(problem.str()); +#endif } } // Because this argchunk is done regardless @@ -1240,9 +1324,14 @@ namespace args } } else { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Flag could not be matched: '" << arg << "'"; throw ParseError(problem.str()); +#endif } } } else @@ -1258,17 +1347,26 @@ namespace args } } else { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; + return it; +#else std::ostringstream problem; problem << "Passed in argument, but no positional arguments were ready to receive it: " << chunk; throw ParseError(problem.str()); +#endif } } } if (!Matched()) { +#ifdef ARGS_NOEXCEPT + error = Error::Validation; +#else std::ostringstream problem; problem << "Group validation failed somewhere!"; throw ValidationError(problem.str()); +#endif } return end; } @@ -1342,7 +1440,12 @@ namespace args { if (FlagBase::Match(arg)) { +#ifdef ARGS_NOEXCEPT + error = Error::Help; +#else throw Help(arg); +#endif + return this; } return nullptr; } @@ -1351,7 +1454,12 @@ namespace args { if (FlagBase::Match(arg)) { +#ifdef ARGS_NOEXCEPT + error = Error::Help; +#else throw Help(std::string(1, arg)); +#endif + return this; } return nullptr; } @@ -1410,17 +1518,22 @@ namespace args * raises a ParseError if there are any characters left. */ template - void ValueReader(const std::string &name, const std::string &value, T &destination) + bool ValueReader(const std::string &name, const std::string &value, T &destination) { std::istringstream ss(value); ss >> destination; if (ss.rdbuf()->in_avail() > 0) { +#ifdef ARGS_NOEXCEPT + return false; +#else std::ostringstream problem; problem << "Argument '" << name << "' received invalid value type '" << value << "'"; throw ParseError(problem.str()); +#endif } + return true; } /** std::string specialization for ValueReader @@ -1429,9 +1542,10 @@ namespace args * it is more efficient to ust copy a string into the destination. */ template <> - void ValueReader(const std::string &name, const std::string &value, std::string &destination) + bool ValueReader(const std::string &name, const std::string &value, std::string &destination) { destination.assign(value); + return true; } /** An argument-accepting flag class @@ -1439,7 +1553,7 @@ namespace args * \tparam T the type to extract the argument as * \tparam Reader The function used to read the argument, taking the name, value, and destination reference */ - template > + template > class ValueFlag : public ValueFlagBase { private: @@ -1456,7 +1570,12 @@ namespace args virtual void ParseValue(const std::string &value) override { - Reader(name, value, this->value); + if (!Reader(name, value, this->value)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } } /** Get the value @@ -1476,7 +1595,7 @@ namespace args template < typename T, typename List = std::vector, - void (*Reader)(const std::string &, const std::string &, T&) = ValueReader> + bool (*Reader)(const std::string &, const std::string &, T&) = ValueReader> class ValueFlagList : public ValueFlagBase { private: @@ -1494,7 +1613,12 @@ namespace args virtual void ParseValue(const std::string &value) override { T v; - Reader(name, value, v); + if (!Reader(name, value, v)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } values.insert(std::end(values), v); } @@ -1518,7 +1642,7 @@ namespace args * \tparam Reader The function used to read the argument into the key type, taking the name, value, and destination reference * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ - template , typename Map = std::unordered_map> + template , typename Map = std::unordered_map> class MapFlag : public ValueFlagBase { private: @@ -1537,13 +1661,22 @@ namespace args virtual void ParseValue(const std::string &value) override { K key; - Reader(name, value, key); + if (!Reader(name, value, key)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } auto it = map.find(key); if (it == std::end(map)) { +#ifdef ARGS_NOEXCEPT + error = Error::Map; +#else std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; throw MapError(problem.str()); +#endif } else { this->value = it->second; @@ -1566,7 +1699,7 @@ namespace args * \tparam Reader The function used to read the argument into the key type, taking the name, value, and destination reference * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ - template , void (*Reader)(const std::string &, const std::string &, K&) = ValueReader, typename Map = std::unordered_map> + template , bool (*Reader)(const std::string &, const std::string &, K&) = ValueReader, typename Map = std::unordered_map> class MapFlagList : public ValueFlagBase { private: @@ -1585,13 +1718,22 @@ namespace args virtual void ParseValue(const std::string &value) override { K key; - Reader(name, value, key); + if (!Reader(name, value, key)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } auto it = map.find(key); if (it == std::end(map)) { +#ifdef ARGS_NOEXCEPT + error = Error::Map; +#else std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; throw MapError(problem.str()); +#endif } else { this->values.emplace_back(it->second); @@ -1616,7 +1758,7 @@ namespace args * \tparam T the type to extract the argument as * \tparam Reader The function used to read the argument, taking the name, value, and destination reference */ - template > + template > class Positional : public PositionalBase { private: @@ -1631,7 +1773,12 @@ namespace args virtual void ParseValue(const std::string &value) override { - Reader(name, value, this->value); + if (!Reader(name, value, this->value)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } ready = false; matched = true; } @@ -1653,7 +1800,7 @@ namespace args template < typename T, typename List = std::vector, - void (*Reader)(const std::string &, const std::string &, T&) = ValueReader> + bool (*Reader)(const std::string &, const std::string &, T&) = ValueReader> class PositionalList : public PositionalBase { private: @@ -1670,7 +1817,12 @@ namespace args virtual void ParseValue(const std::string &value) override { T v; - Reader(name, value, v); + if (!Reader(name, value, v)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } values.insert(std::end(values), v); matched = true; } @@ -1695,7 +1847,7 @@ namespace args * \tparam Reader The function used to read the argument into the key type, taking the name, value, and destination reference * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ - template , typename Map = std::unordered_map> + template , typename Map = std::unordered_map> class MapPositional : public PositionalBase { private: @@ -1714,13 +1866,22 @@ namespace args virtual void ParseValue(const std::string &value) override { K key; - Reader(name, value, key); + if (!Reader(name, value, key)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } auto it = map.find(key); if (it == std::end(map)) { +#ifdef ARGS_NOEXCEPT + error = Error::Map; +#else std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; throw MapError(problem.str()); +#endif } else { this->value = it->second; @@ -1745,7 +1906,7 @@ namespace args * \tparam Reader The function used to read the argument into the key type, taking the name, value, and destination reference * \tparam Map The Map type. Should operate like std::map or std::unordered_map */ - template , void (*Reader)(const std::string &, const std::string &, K&) = ValueReader, typename Map = std::unordered_map> + template , bool (*Reader)(const std::string &, const std::string &, K&) = ValueReader, typename Map = std::unordered_map> class MapPositionalList : public PositionalBase { private: @@ -1764,13 +1925,22 @@ namespace args virtual void ParseValue(const std::string &value) override { K key; - Reader(name, value, key); + if (!Reader(name, value, key)) + { +#ifdef ARGS_NOEXCEPT + error = Error::Parse; +#endif + } auto it = map.find(key); if (it == std::end(map)) { +#ifdef ARGS_NOEXCEPT + error = Error::Map; +#else std::ostringstream problem; problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; throw MapError(problem.str()); +#endif } else { this->values.emplace_back(it->second); diff --git a/test.cxx b/test.cxx index 8113943..b4cc9fc 100644 --- a/test.cxx +++ b/test.cxx @@ -251,11 +251,12 @@ TEST_CASE("Argument groups should nest", "[args]") REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a", "-dg"}), args::ValidationError); } -void DoublesReader(const std::string &name, const std::string &value, std::tuple &destination) +bool DoublesReader(const std::string &name, const std::string &value, std::tuple &destination) { size_t commapos = 0; std::get<0>(destination) = std::stod(value, &commapos); std::get<1>(destination) = std::stod(std::string(value, commapos + 1)); + return true; } TEST_CASE("Custom types work", "[args]") @@ -375,10 +376,11 @@ enum class MappingEnum #include #include -void ToLowerReader(const std::string &name, const std::string &value, std::string &destination) +bool ToLowerReader(const std::string &name, const std::string &value, std::string &destination) { destination = value; std::transform(destination.begin(), destination.end(), destination.begin(), ::tolower); + return true; } TEST_CASE("Mapping types work as needed", "[args]") @@ -522,3 +524,66 @@ TEST_CASE("Kick-out should work via all flags and value flags", "[args]") REQUIRE_FALSE(c3); REQUIRE(d3); } + +#define ARGS_TESTNAMESPACE +#define ARGS_NOEXCEPT +#include + +TEST_CASE("Noexcept mode works as expected", "[args]") +{ + std::unordered_map map{ + {"default", MappingEnum::def}, + {"foo", MappingEnum::foo}, + {"bar", MappingEnum::bar}, + {"red", MappingEnum::red}, + {"yellow", MappingEnum::yellow}, + {"green", MappingEnum::green}}; + + argstest::ArgumentParser parser("This is a test program.", "This goes after the options."); + argstest::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); + argstest::Flag bar(parser, "BAR", "test flag", {'b', "bar"}, true); + argstest::ValueFlag foo(parser, "FOO", "test flag", {'f', "foo"}); + argstest::Group nandgroup(parser, "this group provides nand validation", argstest::Group::Validators::AtMostOne); + argstest::Flag x(nandgroup, "x", "test flag", {'x'}); + argstest::Flag y(nandgroup, "y", "test flag", {'y'}); + argstest::Flag z(nandgroup, "z", "test flag", {'z'}); + argstest::MapFlag mf(parser, "MF", "Maps string to an enum", {"mf"}, map); + parser.ParseArgs(std::vector{"-h"}); + REQUIRE(parser.GetError() == argstest::Error::Help); + parser.ParseArgs(std::vector{"--Help"}); + REQUIRE(parser.GetError() == argstest::Error::Parse); + parser.ParseArgs(std::vector{"--bar=test"}); + REQUIRE(parser.GetError() == argstest::Error::Parse); + parser.ParseArgs(std::vector{"--bar"}); + REQUIRE(parser.GetError() == argstest::Error::None); + parser.ParseArgs(std::vector{"--bar", "-b"}); + REQUIRE(parser.GetError() == argstest::Error::Extra); + + parser.ParseArgs(std::vector{"--foo=7.5"}); + REQUIRE(parser.GetError() == argstest::Error::Parse); + parser.ParseArgs(std::vector{"--foo", "7a"}); + REQUIRE(parser.GetError() == argstest::Error::Parse); + parser.ParseArgs(std::vector{"--foo", "7e4"}); + REQUIRE(parser.GetError() == argstest::Error::Parse); + parser.ParseArgs(std::vector{"--foo"}); + REQUIRE(parser.GetError() == argstest::Error::Parse); + + parser.ParseArgs(std::vector{"--foo=85"}); + REQUIRE(parser.GetError() == argstest::Error::None); + + parser.ParseArgs(std::vector{"this is a test flag again", "1", "this has no positional available"}); + REQUIRE(parser.GetError() == argstest::Error::Parse); + + parser.ParseArgs(std::vector{"-x"}); + REQUIRE(parser.GetError() == argstest::Error::None); + parser.ParseArgs(std::vector{"-xz"}); + REQUIRE(parser.GetError() == argstest::Error::Validation); + parser.ParseArgs(std::vector{"-y"}); + REQUIRE(parser.GetError() == argstest::Error::None); + parser.ParseArgs(std::vector{"-y", "-xz"}); + REQUIRE(parser.GetError() == argstest::Error::Validation); + parser.ParseArgs(std::vector{"--mf", "YeLLoW"}); + REQUIRE(parser.GetError() == argstest::Error::Map); + parser.ParseArgs(std::vector{"--mf", "yellow"}); + REQUIRE(parser.GetError() == argstest::Error::None); +} -- cgit v1.2.1