From 1b9b9823d4619ef62783e25768b9460dcde55a74 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Wed, 11 May 2016 18:10:53 -0600 Subject: add mapping flag, throw maperror, ensure throwing works properly --- README.md | 4 +++- args.hxx | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.cxx | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1abbcf..18ac5e4 100644 --- a/README.md +++ b/README.md @@ -492,7 +492,7 @@ Argument 'numbers' received invalid value type 'a' ```cpp #include #include -#include + std::istream& operator>>(std::istream& is, std::tuple& ints) { is >> std::get<0>(ints); @@ -501,6 +501,8 @@ std::istream& operator>>(std::istream& is, std::tuple& ints) return is; } +#include + void DoublesReader(const std::string &name, const std::string &value, std::tuple &destination) { size_t commapos = 0; diff --git a/args.hxx b/args.hxx index 9198afc..aade975 100644 --- a/args.hxx +++ b/args.hxx @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -174,6 +175,13 @@ namespace args virtual ~UsageError() {}; }; + class MapError : public Error + { + public: + MapError(const std::string &problem) : Error(problem) {} + virtual ~MapError() {}; + }; + /** An exception that indicates that the user has requested help */ class Help : public Error @@ -1419,6 +1427,52 @@ namespace args } }; + /** A mapping value flag class + * + * \tparam K the type to extract the argument as + * \tparam T the type to store the result map as + * \tparam Reader The function used to read the argument, taking the name, value, and destination reference + */ + template , typename Map = std::unordered_map> + class MapFlag : public ValueFlagBase + { + private: + const Map map; + T value; + + public: + + MapFlag(Group &group, const std::string &name, const std::string &help, Matcher &&matcher, const Map &map, const T &defaultValue = T()): ValueFlagBase(name, help, std::move(matcher)), map(map), value(defaultValue) + { + group.Add(*this); + } + + virtual ~MapFlag() {} + + virtual void ParseValue(const std::string &value) override + { + K key; + Reader(name, value, key); + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; + throw MapError(problem.str()); + } else + { + this->value = it->second; + } + } + + /** Get the value + */ + T &Get() noexcept + { + return value; + } + }; + /** An argument-accepting flag class that pushes the found values into a list * * \tparam T the type to extract the argument as diff --git a/test.cxx b/test.cxx index 4d4e6ef..bcccdf4 100644 --- a/test.cxx +++ b/test.cxx @@ -341,3 +341,56 @@ TEST_CASE("Required argument separation being violated throws an error", "[args] REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-b", "test"}), args::ParseError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"--bar", "test"})); } + +enum class MappingEnum +{ + def, + foo, + bar, + red, + yellow, + green +}; + +#include +#include +#include + +void ToLowerReader(const std::string &name, const std::string &value, std::string &destination) +{ + destination = value; + std::transform(destination.begin(), destination.end(), destination.begin(), ::tolower); +} + +TEST_CASE("Mapping types work as needed", "[args]") +{ + std::unordered_map map{ + {"default", MappingEnum::def}, + {"foo", MappingEnum::foo}, + {"bar", MappingEnum::bar}, + {"red", MappingEnum::red}, + {"yellow", MappingEnum::yellow}, + {"green", MappingEnum::green}}; + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::MapFlag dmf(parser, "DMF", "Maps string to an enum", args::Matcher{"dmf"}, map); + args::MapFlag mf(parser, "MF", "Maps string to an enum", args::Matcher{"mf"}, map); + args::MapFlag cimf(parser, "CIMF", "Maps string to an enum case-insensitively", args::Matcher{"cimf"}, map); + //args::MapFlagList mfl(parser, "MFL", "Maps string to an enum list", args::Matcher{"mfl"}, map); + //args::MapPositional mp(parser, "MP", "Maps string to an enum", args::Matcher{"mp"}, map); + //args::MapPositionalList mpl(parser, "MPL", "Maps string to an enum list", args::Matcher{"mpl"}, map); + //parser.ParseArgs(std::vector{"--mf=red", "--cimf=YeLLoW", "--mfl=bar", "foo", "--mfl=green", "red", "--mfl", "bar", "default"}); + parser.ParseArgs(std::vector{"--mf=red", "--cimf=YeLLoW"}); + REQUIRE_FALSE(dmf); + REQUIRE(args::get(dmf) == MappingEnum::def); + REQUIRE(mf); + REQUIRE(args::get(mf) == MappingEnum::red); + REQUIRE(cimf); + REQUIRE(args::get(cimf) == MappingEnum::yellow); + //REQUIRE(mfl); + //REQUIRE(args::get(mfl) == std::vector{MappingEnum::bar, MappingEnum::green, MappingEnum::bar}); + //REQUIRE(mp); + //REQUIRE(args::get(mp) == MappingEnum::foo); + //REQUIRE(mpl); + //REQUIRE(args::get(mpl) == std::vector{MappingEnum::red, MappingEnum::def}); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--mf=YeLLoW"}), args::MapError); +} -- cgit v1.2.1 From b8870fac3ab825d7d3b42483321f6ba32b67ec8f Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Wed, 11 May 2016 20:40:18 -0600 Subject: finish mapping types --- Doxyfile | 2 +- README.md | 48 ++++++++++++++ args.hxx | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++--------- test.cxx | 21 +++--- 4 files changed, 250 insertions(+), 41 deletions(-) diff --git a/Doxyfile b/Doxyfile index 871141a..171f0eb 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 = 4.0.2 +PROJECT_NUMBER = 4.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/README.md b/README.md index 18ac5e4..f4435d0 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ The code can be downloaded at https://github.com/Taywee/args There are also somewhat extensive examples below. +You can find the complete test cases at +https://github.com/Taywee/args/blob/master/test.cxx, which should very well +describe the usage, as it's built to push the boundaries. + # What does it do? It: @@ -867,3 +871,47 @@ int main(int argc, char **argv) This goes after the options. % ``` + +# Mapping positionals + +I haven't written out a long example for this, but here's the test case you should be able to discern the meaning from: + +```cpp +void ToLowerReader(const std::string &name, const std::string &value, std::string &destination) +{ + destination = value; + std::transform(destination.begin(), destination.end(), destination.begin(), ::tolower); +} + +TEST_CASE("Mapping types work as needed", "[args]") +{ + std::unordered_map map{ + {"default", MappingEnum::def}, + {"foo", MappingEnum::foo}, + {"bar", MappingEnum::bar}, + {"red", MappingEnum::red}, + {"yellow", MappingEnum::yellow}, + {"green", MappingEnum::green}}; + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::MapFlag dmf(parser, "DMF", "Maps string to an enum", args::Matcher{"dmf"}, map); + args::MapFlag mf(parser, "MF", "Maps string to an enum", args::Matcher{"mf"}, map); + args::MapFlag cimf(parser, "CIMF", "Maps string to an enum case-insensitively", args::Matcher{"cimf"}, map); + args::MapFlagList mfl(parser, "MFL", "Maps string to an enum list", args::Matcher{"mfl"}, map); + args::MapPositional mp(parser, "MP", "Maps string to an enum", map); + args::MapPositionalList mpl(parser, "MPL", "Maps string to an enum list", map); + parser.ParseArgs(std::vector{"--mf=red", "--cimf=YeLLoW", "--mfl=bar", "foo", "--mfl=green", "red", "--mfl", "bar", "default"}); + REQUIRE_FALSE(dmf); + REQUIRE(args::get(dmf) == MappingEnum::def); + REQUIRE(mf); + REQUIRE(args::get(mf) == MappingEnum::red); + REQUIRE(cimf); + REQUIRE(args::get(cimf) == MappingEnum::yellow); + REQUIRE(mfl); + REQUIRE((args::get(mfl) == std::vector{MappingEnum::bar, MappingEnum::green, MappingEnum::bar})); + REQUIRE(mp); + REQUIRE((args::get(mp) == MappingEnum::foo)); + REQUIRE(mpl); + REQUIRE((args::get(mpl) == std::vector{MappingEnum::red, MappingEnum::def})); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--mf=YeLLoW"}), args::MapError); +} +``` diff --git a/args.hxx b/args.hxx index aade975..9dfe9d5 100644 --- a/args.hxx +++ b/args.hxx @@ -386,7 +386,7 @@ namespace args virtual std::tuple GetDescription(const std::string &shortPrefix, const std::string &longPrefi, const std::string &shortSeparator, const std::string &longSeparator) const override { std::tuple description; - std::get<0>(description) = name; + std::get<0>(description) = Name(); std::get<1>(description) = help; return description; } @@ -459,7 +459,7 @@ namespace args virtual std::tuple GetDescription(const std::string &shortPrefix, const std::string &longPrefix, const std::string &shortSeparator, const std::string &longSeparator) const override { std::tuple description; - const std::vector flagStrings(matcher.GetFlagStrings(shortPrefix, longPrefix, name, shortSeparator, longSeparator)); + const std::vector flagStrings(matcher.GetFlagStrings(shortPrefix, longPrefix, Name(), shortSeparator, longSeparator)); std::ostringstream flagstream; for (auto it = std::begin(flagStrings); it != std::end(flagStrings); ++it) { @@ -477,14 +477,14 @@ namespace args /** Base class for positional options */ - class PosBase : public NamedBase + class PositionalBase : public NamedBase { protected: bool ready; public: - PosBase(const std::string &name, const std::string &help) : NamedBase(name, help), ready(true) {} - virtual ~PosBase() {} + PositionalBase(const std::string &name, const std::string &help) : NamedBase(name, help), ready(true) {} + virtual ~PositionalBase() {} bool Ready() { @@ -572,13 +572,13 @@ namespace args /** Get the next ready positional, or nullptr if there is none * - * \return the first ready PosBase pointer, or nullptr if there is no match + * \return the first ready PositionalBase pointer, or nullptr if there is no match */ - PosBase *GetNextPositional() + PositionalBase *GetNextPositional() { for (Base *child: children) { - PosBase *next = dynamic_cast(child); + PositionalBase *next = dynamic_cast(child); Group *group = dynamic_cast(child); if (group) { @@ -695,7 +695,7 @@ namespace args for (const auto &child: children) { const Group *group = dynamic_cast(child); - const PosBase *pos = dynamic_cast(child); + const PositionalBase *pos = dynamic_cast(child); if (group) { std::vector groupNames(group->GetPosNames()); @@ -1216,7 +1216,7 @@ namespace args } } else { - PosBase *pos = GetNextPositional(); + PositionalBase *pos = GetNextPositional(); if (pos) { pos->ParseValue(chunk); @@ -1427,11 +1427,55 @@ namespace args } }; + /** An argument-accepting flag class that pushes the found values into a list + * + * \tparam T the type to extract the argument as + * \tparam List the list type that houses the values + * \tparam Reader The function used to read the argument, taking the name, value, and destination reference + */ + template < + typename T, + typename List = std::vector, + void (*Reader)(const std::string &, const std::string &, T&) = ValueReader> + class ValueFlagList : public ValueFlagBase + { + private: + List values; + + public: + + ValueFlagList(Group &group, const std::string &name, const std::string &help, Matcher &&matcher, const List &defaultValues = List()): ValueFlagBase(name, help, std::move(matcher)), values(defaultValues) + { + group.Add(*this); + } + + virtual ~ValueFlagList() {} + + virtual void ParseValue(const std::string &value) override + { + values.emplace_back(); + Reader(name, value, values.back()); + } + + /** Get the values + */ + List &Get() noexcept + { + return values; + } + + virtual std::string Name() const override + { + return name + std::string("..."); + } + }; + /** A mapping value flag class * * \tparam K the type to extract the argument as - * \tparam T the type to store the result map as - * \tparam Reader The function used to read the argument, taking the name, value, and destination reference + * \tparam T the type to store the result as + * \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> class MapFlag : public ValueFlagBase @@ -1473,42 +1517,57 @@ namespace args } }; - /** An argument-accepting flag class that pushes the found values into a list + /** A mapping value flag class * - * \tparam T the type to extract the argument as + * \tparam K the type to extract the argument as + * \tparam T the type to store the result as * \tparam List the list type that houses the values - * \tparam Reader The function used to read the argument, taking the name, value, and destination reference + * \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 T, - typename List = std::vector, - void (*Reader)(const std::string &, const std::string &, T&) = ValueReader> - class ValueFlagList : public ValueFlagBase + template , void (*Reader)(const std::string &, const std::string &, K&) = ValueReader, typename Map = std::unordered_map> + class MapFlagList : public ValueFlagBase { private: + const Map map; List values; public: - ValueFlagList(Group &group, const std::string &name, const std::string &help, Matcher &&matcher, const List &defaultValues = List()): ValueFlagBase(name, help, std::move(matcher)), values(defaultValues) + MapFlagList(Group &group, const std::string &name, const std::string &help, Matcher &&matcher, const Map &map, const List &defaultValues = List()): ValueFlagBase(name, help, std::move(matcher)), map(map), values(defaultValues) { group.Add(*this); } - virtual ~ValueFlagList() {} + virtual ~MapFlagList() {} virtual void ParseValue(const std::string &value) override { - values.emplace_back(); - Reader(name, value, values.back()); + K key; + Reader(name, value, key); + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; + throw MapError(problem.str()); + } else + { + this->values.emplace_back(it->second); + } } - /** Get the values + /** Get the value */ List &Get() noexcept { return values; } + + virtual std::string Name() const override + { + return name + std::string("..."); + } }; /** A positional argument class @@ -1517,12 +1576,12 @@ namespace args * \tparam Reader The function used to read the argument, taking the name, value, and destination reference */ template > - class Positional : public PosBase + class Positional : public PositionalBase { private: T value; public: - Positional(Group &group, const std::string &name, const std::string &help, const T &defaultValue = T()): PosBase(name, help), value(defaultValue) + Positional(Group &group, const std::string &name, const std::string &help, const T &defaultValue = T()): PositionalBase(name, help), value(defaultValue) { group.Add(*this); } @@ -1554,13 +1613,13 @@ namespace args typename T, typename List = std::vector, void (*Reader)(const std::string &, const std::string &, T&) = ValueReader> - class PositionalList : public PosBase + class PositionalList : public PositionalBase { private: List values; public: - PositionalList(Group &group, const std::string &name, const std::string &help, const List &defaultValues = List()): PosBase(name, help), values(defaultValues) + PositionalList(Group &group, const std::string &name, const std::string &help, const List &defaultValues = List()): PositionalBase(name, help), values(defaultValues) { group.Add(*this); } @@ -1586,4 +1645,107 @@ namespace args return values; } }; + + /** A positional argument mapping class + * + * \tparam K the type to extract the argument as + * \tparam T the type to store the result as + * \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> + class MapPositional : public PositionalBase + { + private: + const Map map; + T value; + + public: + + MapPositional(Group &group, const std::string &name, const std::string &help, const Map &map, const T &defaultValue = T()): PositionalBase(name, help), map(map), value(defaultValue) + { + group.Add(*this); + } + + virtual ~MapPositional() {} + + virtual void ParseValue(const std::string &value) override + { + K key; + Reader(name, value, key); + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; + throw MapError(problem.str()); + } else + { + this->value = it->second; + ready = false; + matched = true; + } + } + + /** Get the value + */ + T &Get() noexcept + { + return value; + } + }; + + /** A mapping value flag class + * + * \tparam K the type to extract the argument as + * \tparam T the type to store the result as + * \tparam List the list type that houses the values + * \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> + class MapPositionalList : public PositionalBase + { + private: + const Map map; + List values; + + public: + + MapPositionalList(Group &group, const std::string &name, const std::string &help, const Map &map, const List &defaultValues = List()): PositionalBase(name, help), map(map), values(defaultValues) + { + group.Add(*this); + } + + virtual ~MapPositionalList() {} + + virtual void ParseValue(const std::string &value) override + { + K key; + Reader(name, value, key); + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; + throw MapError(problem.str()); + } else + { + this->values.emplace_back(it->second); + matched = true; + } + } + + /** Get the value + */ + List &Get() noexcept + { + return values; + } + + virtual std::string Name() const override + { + return name + std::string("..."); + } + }; } diff --git a/test.cxx b/test.cxx index bcccdf4..b28a378 100644 --- a/test.cxx +++ b/test.cxx @@ -375,22 +375,21 @@ TEST_CASE("Mapping types work as needed", "[args]") args::MapFlag dmf(parser, "DMF", "Maps string to an enum", args::Matcher{"dmf"}, map); args::MapFlag mf(parser, "MF", "Maps string to an enum", args::Matcher{"mf"}, map); args::MapFlag cimf(parser, "CIMF", "Maps string to an enum case-insensitively", args::Matcher{"cimf"}, map); - //args::MapFlagList mfl(parser, "MFL", "Maps string to an enum list", args::Matcher{"mfl"}, map); - //args::MapPositional mp(parser, "MP", "Maps string to an enum", args::Matcher{"mp"}, map); - //args::MapPositionalList mpl(parser, "MPL", "Maps string to an enum list", args::Matcher{"mpl"}, map); - //parser.ParseArgs(std::vector{"--mf=red", "--cimf=YeLLoW", "--mfl=bar", "foo", "--mfl=green", "red", "--mfl", "bar", "default"}); - parser.ParseArgs(std::vector{"--mf=red", "--cimf=YeLLoW"}); + args::MapFlagList mfl(parser, "MFL", "Maps string to an enum list", args::Matcher{"mfl"}, map); + args::MapPositional mp(parser, "MP", "Maps string to an enum", map); + args::MapPositionalList mpl(parser, "MPL", "Maps string to an enum list", map); + parser.ParseArgs(std::vector{"--mf=red", "--cimf=YeLLoW", "--mfl=bar", "foo", "--mfl=green", "red", "--mfl", "bar", "default"}); REQUIRE_FALSE(dmf); REQUIRE(args::get(dmf) == MappingEnum::def); REQUIRE(mf); REQUIRE(args::get(mf) == MappingEnum::red); REQUIRE(cimf); REQUIRE(args::get(cimf) == MappingEnum::yellow); - //REQUIRE(mfl); - //REQUIRE(args::get(mfl) == std::vector{MappingEnum::bar, MappingEnum::green, MappingEnum::bar}); - //REQUIRE(mp); - //REQUIRE(args::get(mp) == MappingEnum::foo); - //REQUIRE(mpl); - //REQUIRE(args::get(mpl) == std::vector{MappingEnum::red, MappingEnum::def}); + REQUIRE(mfl); + REQUIRE((args::get(mfl) == std::vector{MappingEnum::bar, MappingEnum::green, MappingEnum::bar})); + REQUIRE(mp); + REQUIRE((args::get(mp) == MappingEnum::foo)); + REQUIRE(mpl); + REQUIRE((args::get(mpl) == std::vector{MappingEnum::red, MappingEnum::def})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--mf=YeLLoW"}), args::MapError); } -- cgit v1.2.1