aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor C. Richberger <taywee@gmx.com>2016-05-11 22:44:34 -0400
committerTaylor C. Richberger <taywee@gmx.com>2016-05-11 22:44:34 -0400
commit27f92acf2aa914efaf691f26518f61319537ab1c (patch)
tree5ef1c18abcfddabea96f4a3fab6a8c593d123514
parentbump version (diff)
parentfinish mapping types (diff)
downloadargs.hxx-27f92acf2aa914efaf691f26518f61319537ab1c.tar.xz
Merge branch '17-add-mapping-types' into 'master' 4.1.0
17 add mapping types Closes #17 See merge request !11
-rw-r--r--Doxyfile2
-rw-r--r--README.md52
-rw-r--r--args.hxx244
-rw-r--r--test.cxx52
4 files changed, 334 insertions, 16 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 a1abbcf..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:
@@ -492,7 +496,7 @@ Argument 'numbers' received invalid value type 'a'
```cpp
#include <iostream>
#include <tuple>
-#include <args.hxx>
+
std::istream& operator>>(std::istream& is, std::tuple<int, int>& ints)
{
is >> std::get<0>(ints);
@@ -501,6 +505,8 @@ std::istream& operator>>(std::istream& is, std::tuple<int, int>& ints)
return is;
}
+#include <args.hxx>
+
void DoublesReader(const std::string &name, const std::string &value, std::tuple<double, double> &destination)
{
size_t commapos = 0;
@@ -865,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<std::string, MappingEnum> 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<std::string, MappingEnum> dmf(parser, "DMF", "Maps string to an enum", args::Matcher{"dmf"}, map);
+ args::MapFlag<std::string, MappingEnum> mf(parser, "MF", "Maps string to an enum", args::Matcher{"mf"}, map);
+ args::MapFlag<std::string, MappingEnum, ToLowerReader> cimf(parser, "CIMF", "Maps string to an enum case-insensitively", args::Matcher{"cimf"}, map);
+ args::MapFlagList<std::string, MappingEnum> mfl(parser, "MFL", "Maps string to an enum list", args::Matcher{"mfl"}, map);
+ args::MapPositional<std::string, MappingEnum> mp(parser, "MP", "Maps string to an enum", map);
+ args::MapPositionalList<std::string, MappingEnum> mpl(parser, "MPL", "Maps string to an enum list", map);
+ parser.ParseArgs(std::vector<std::string>{"--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>{MappingEnum::bar, MappingEnum::green, MappingEnum::bar}));
+ REQUIRE(mp);
+ REQUIRE((args::get(mp) == MappingEnum::foo));
+ REQUIRE(mpl);
+ REQUIRE((args::get(mpl) == std::vector<MappingEnum>{MappingEnum::red, MappingEnum::def}));
+ REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--mf=YeLLoW"}), args::MapError);
+}
+```
diff --git a/args.hxx b/args.hxx
index 9198afc..9dfe9d5 100644
--- a/args.hxx
+++ b/args.hxx
@@ -32,6 +32,7 @@
#include <string>
#include <tuple>
#include <vector>
+#include <unordered_map>
#include <unordered_set>
#include <type_traits>
@@ -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
@@ -378,7 +386,7 @@ namespace args
virtual std::tuple<std::string, std::string> GetDescription(const std::string &shortPrefix, const std::string &longPrefi, const std::string &shortSeparator, const std::string &longSeparator) const override
{
std::tuple<std::string, std::string> description;
- std::get<0>(description) = name;
+ std::get<0>(description) = Name();
std::get<1>(description) = help;
return description;
}
@@ -451,7 +459,7 @@ namespace args
virtual std::tuple<std::string, std::string> GetDescription(const std::string &shortPrefix, const std::string &longPrefix, const std::string &shortSeparator, const std::string &longSeparator) const override
{
std::tuple<std::string, std::string> description;
- const std::vector<std::string> flagStrings(matcher.GetFlagStrings(shortPrefix, longPrefix, name, shortSeparator, longSeparator));
+ const std::vector<std::string> flagStrings(matcher.GetFlagStrings(shortPrefix, longPrefix, Name(), shortSeparator, longSeparator));
std::ostringstream flagstream;
for (auto it = std::begin(flagStrings); it != std::end(flagStrings); ++it)
{
@@ -469,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()
{
@@ -564,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<PosBase *>(child);
+ PositionalBase *next = dynamic_cast<PositionalBase *>(child);
Group *group = dynamic_cast<Group *>(child);
if (group)
{
@@ -687,7 +695,7 @@ namespace args
for (const auto &child: children)
{
const Group *group = dynamic_cast<Group *>(child);
- const PosBase *pos = dynamic_cast<PosBase *>(child);
+ const PositionalBase *pos = dynamic_cast<PositionalBase *>(child);
if (group)
{
std::vector<std::string> groupNames(group->GetPosNames());
@@ -1208,7 +1216,7 @@ namespace args
}
} else
{
- PosBase *pos = GetNextPositional();
+ PositionalBase *pos = GetNextPositional();
if (pos)
{
pos->ParseValue(chunk);
@@ -1455,6 +1463,111 @@ namespace args
{
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 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 K, typename T, void (*Reader)(const std::string &, const std::string &, K&) = ValueReader<K>, typename Map = std::unordered_map<K, T>>
+ 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;
+ }
+ };
+
+ /** 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 <typename K, typename T, typename List = std::vector<T>, void (*Reader)(const std::string &, const std::string &, K&) = ValueReader<K>, typename Map = std::unordered_map<K, T>>
+ class MapFlagList : public ValueFlagBase
+ {
+ private:
+ const Map map;
+ List values;
+
+ public:
+
+ 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 ~MapFlagList() {}
+
+ 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);
+ }
+ }
+
+ /** Get the value
+ */
+ List &Get() noexcept
+ {
+ return values;
+ }
+
+ virtual std::string Name() const override
+ {
+ return name + std::string("...");
+ }
};
/** A positional argument class
@@ -1463,12 +1576,12 @@ namespace args
* \tparam Reader The function used to read the argument, taking the name, value, and destination reference
*/
template <typename T, void (*Reader)(const std::string &, const std::string &, T&) = ValueReader<T>>
- 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);
}
@@ -1500,13 +1613,13 @@ namespace args
typename T,
typename List = std::vector<T>,
void (*Reader)(const std::string &, const std::string &, T&) = ValueReader<T>>
- 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);
}
@@ -1532,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 K, typename T, void (*Reader)(const std::string &, const std::string &, K&) = ValueReader<K>, typename Map = std::unordered_map<K, T>>
+ 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 <typename K, typename T, typename List = std::vector<T>, void (*Reader)(const std::string &, const std::string &, K&) = ValueReader<K>, typename Map = std::unordered_map<K, T>>
+ 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 4d4e6ef..b28a378 100644
--- a/test.cxx
+++ b/test.cxx
@@ -341,3 +341,55 @@ TEST_CASE("Required argument separation being violated throws an error", "[args]
REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-b", "test"}), args::ParseError);
REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"--bar", "test"}));
}
+
+enum class MappingEnum
+{
+ def,
+ foo,
+ bar,
+ red,
+ yellow,
+ green
+};
+
+#include <unordered_map>
+#include <algorithm>
+#include <string>
+
+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<std::string, MappingEnum> 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<std::string, MappingEnum> dmf(parser, "DMF", "Maps string to an enum", args::Matcher{"dmf"}, map);
+ args::MapFlag<std::string, MappingEnum> mf(parser, "MF", "Maps string to an enum", args::Matcher{"mf"}, map);
+ args::MapFlag<std::string, MappingEnum, ToLowerReader> cimf(parser, "CIMF", "Maps string to an enum case-insensitively", args::Matcher{"cimf"}, map);
+ args::MapFlagList<std::string, MappingEnum> mfl(parser, "MFL", "Maps string to an enum list", args::Matcher{"mfl"}, map);
+ args::MapPositional<std::string, MappingEnum> mp(parser, "MP", "Maps string to an enum", map);
+ args::MapPositionalList<std::string, MappingEnum> mpl(parser, "MPL", "Maps string to an enum list", map);
+ parser.ParseArgs(std::vector<std::string>{"--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>{MappingEnum::bar, MappingEnum::green, MappingEnum::bar}));
+ REQUIRE(mp);
+ REQUIRE((args::get(mp) == MappingEnum::foo));
+ REQUIRE(mpl);
+ REQUIRE((args::get(mpl) == std::vector<MappingEnum>{MappingEnum::red, MappingEnum::def}));
+ REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--mf=YeLLoW"}), args::MapError);
+}