Browse Source

Add custom exceptions with a special handler for them in TYPED_MAIN.
Add more error checks, such as for duplicate options.
Add type coercion.

Sam Jaffe 4 years ago
parent
commit
7602a81184

+ 12 - 7
include/program_args/arguments.h

@@ -18,7 +18,10 @@ public:
   Arguments(int argc, char const * const * const argv);
 
   auto option(std::string const &name, std::string const &description = "");
-  auto argument(size_t index, std::string const &name, std::string const &description = "");
+  auto option(std::string const &name, char abbrev,
+              std::string const &description = "");
+  auto argument(size_t index, std::string const &name,
+                std::string const &description = "");
 
 private:
   void usage() const;
@@ -34,7 +37,7 @@ private:
   // Data variables
   std::string program;
   size_t optional_from{std::numeric_limits<size_t>::max()};
-  std::map<std::string, std::string> options;
+  std::map<std::string, std::vector<std::string>> options;
   std::vector<std::string> arguments;
 };
 
@@ -77,9 +80,11 @@ struct Arguments<Impl>::Positional {
 
 #include "arguments_impl.hpp"
 
-#define TYPED_MAIN(tname)                               \
-  int typed_main(tname const &args);                    \
-  int main(int argc, char const * const * const argv) { \
-    return typed_main(tname(argc, argv));               \
-  }                                                     \
+#define TYPED_MAIN(tname)                                                      \
+  int typed_main(tname const &args);                                           \
+  int main(int argc, char const * const * const argv) try {                    \
+    return typed_main(tname(argc, argv));                                      \
+  } catch(program::ProgramArgumentsError const &pae) {                         \
+    std::cerr << "Error in program argument handling: " << pae.what() << "\n"; \
+  }                                                                            \
   int typed_main(tname const &args)

+ 34 - 26
include/program_args/arguments_impl.hpp

@@ -1,15 +1,17 @@
 #pragma once
 
-#include "options.hpp"
-
 #include <utility>
 
+#include "arguments.h"
+#include "exception.h"
+#include "utilities.h"
+
 namespace program {
 
 template <typename Impl>
 template <typename T>
 Arguments<Impl>::Option::operator T() const {
-  return (*this) ? self->options.at(name) : T();
+  return (*this) ? convert<T>(self->options.at(name)) : T();
 }
 
 template <typename Impl>
@@ -23,16 +25,6 @@ auto Arguments<Impl>::Option::operator=(T &&value) {
   return WithDefault<Option, T>{*this, std::forward<T>(value)};
 }
 
-inline std::string join(std::string const &tok,
-                        std::vector<std::string> const &data) {
-  std::string accum = data.empty() ? "" : data.front();
-  for (size_t i = 1; i < data.size(); ++i) {
-    accum += tok;
-    accum += data[i];
-  }
-  return accum;
-}
-
 template <typename Impl>
 bool Arguments<Impl>::Option::primed() const {
   if (self->primed_) { return true; }
@@ -41,7 +33,9 @@ bool Arguments<Impl>::Option::primed() const {
     aliases.emplace_back(std::string{'-', abbrev});
   }
   for (auto &alias : aliases) {
-    self->option_name_mapping.emplace(alias, name);
+    if (!self->option_name_mapping.emplace(alias, name).second) {
+      throw ArgumentStructureError("Duplicate option string", alias);
+    }
   }
   self->option_descriptions.emplace(join("/", aliases), description);
   return false;
@@ -54,12 +48,16 @@ namespace program {
 template <typename Impl>
 template <typename T>
 Arguments<Impl>::Positional::operator T() const {
-  return (*this) ? self->arguments.at(index) : T();
+  return (*this) ? convert<T>(self->arguments.at(index)) : T();
 }
 
 template <typename Impl>
 Arguments<Impl>::Positional::operator bool() const {
-  return primed(); // TODO(samjaffe): Existance check?
+  bool const good = primed();
+  if (good && self->arguments.size() <= index) {
+    throw IllegalPositionError("No argument provided", index);
+  }
+  return good;
 }
 
 template <typename Impl>
@@ -75,11 +73,11 @@ bool Arguments<Impl>::Positional::primed() const {
   if (is_optional) {
     self->optional_from = std::min(self->optional_from, index);
   } else if (self->optional_from < index) {
-    throw std::logic_error{
-      "Cannot have required positional named '" + name +
-        "' after optional positionals"};
+    throw ArgumentStructureError{"Required positional after optional", name};
+  }
+  if (!self->argument_names.emplace(index, name).second) {
+    throw IllegalPositionError("Duplicate positional", index);
   }
-  self->argument_names.emplace(index, name);
   self->positional_descriptions.emplace(name, description);
   return false;
 }
@@ -94,7 +92,8 @@ Arguments<Impl>::Arguments(int argc, char const * const * const argv) {
   Impl generator;
   *this = static_cast<Arguments const&>(generator);
   if (argument_names.rbegin()->first != argument_names.size() - 1) {
-    throw std::logic_error{"Jagged arguments are not supported"};
+    throw IllegalPositionError("Higher positional than number recorded",
+                               argument_names.rbegin()->first);
   }
 
   primed_ = true;
@@ -102,13 +101,14 @@ Arguments<Impl>::Arguments(int argc, char const * const * const argv) {
   for (size_t i = 1; i < argc; ++i) {
     if (arg_equals("--help")) {
       usage();
-      std::exit(0);
+      std::exit(0); // TODO: ???
     } else if (arg_equals("--")) {
+      // TODO: Special arguments store for passthroughs
       arguments.insert(arguments.end(), &argv[i+1], &argv[argc]);
-      i = argc;
+      break;
     } else if (argv[i][0] == '-') {
-      // TODO(samjaffe): Arity
-      options.emplace(option_name_mapping.at(argv[i]), argv[i+1]);
+      // TODO: Arity
+      options[option_name_mapping.at(argv[i])].emplace_back(argv[i+1]);
       ++i;
     } else {
       arguments.emplace_back(argv[i]);
@@ -136,10 +136,17 @@ void Arguments<Impl>::usage() const {
 }
 
 template <typename Impl>
-auto Arguments<Impl>::option(std::string const &name, std::string const &description) {
+auto Arguments<Impl>::option(std::string const &name,
+                             std::string const &description) {
   return Option{this, name, 0, description};
 }
 
+template <typename Impl>
+auto Arguments<Impl>::option(std::string const &name, char abbrev,
+                             std::string const &description) {
+  return Option{this, name, abbrev, description};
+}
+
 template <typename Impl>
 auto Arguments<Impl>::argument(size_t index, std::string const &name,
                                std::string const &description) {
@@ -147,3 +154,4 @@ auto Arguments<Impl>::argument(size_t index, std::string const &name,
 }
 
 }
+#undef arg_equals

+ 23 - 0
include/program_args/exception.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <stdexcept>
+
+namespace program {
+
+class ProgramArgumentsError : public std::logic_error {
+  using std::logic_error::logic_error;
+};
+
+class ArgumentStructureError : public ProgramArgumentsError {
+public:
+  ArgumentStructureError(std::string const &desc, std::string const &name)
+      : ProgramArgumentsError(desc + " for argument " + name) {}
+};
+
+class IllegalPositionError : public ProgramArgumentsError {
+public:
+  IllegalPositionError(std::string const &desc, size_t index)
+      : ProgramArgumentsError(desc + " at index " + std::to_string(index)) {}
+};
+
+}

+ 50 - 0
include/program_args/utilities.h

@@ -0,0 +1,50 @@
+#pragma once
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+namespace program {
+inline std::string join(std::string const &tok,
+                        std::vector<std::string> const &data) {
+  std::string accum = data.empty() ? "" : data.front();
+  for (size_t i = 1; i < data.size(); ++i) {
+    accum += tok;
+    accum += data[i];
+  }
+  return accum;
+}
+
+template <typename T, typename = void> struct conversion_helper;
+
+template <typename T, typename D> T convert(D const & data) {
+  return conversion_helper<T>{}(data);
+}
+
+template <typename T>
+struct conversion_helper<T, std::enable_if_t<std::is_convertible_v<std::string, T>>> {
+  T operator()(std::string const &str) const { return T(str); }
+  T operator()(std::vector<std::string> const &data) const {
+    return operator()(data.front());
+  }
+};
+
+template <>
+struct conversion_helper<int> {
+  int operator()(std::string const &str) const { return std::stoi(str); }
+  int operator()(std::vector<std::string> const &data) const {
+    return operator()(data.front());
+  }
+};
+
+template <typename T>
+struct conversion_helper<std::vector<T>> : conversion_helper<T> {
+  std::vector<T> operator()(std::vector<std::string> const &data) const {
+    std::vector<T> rval;
+    for (auto &str : data) {
+      rval.emplace_back((*this)(str));
+    }
+    return rval;
+  }
+};
+}