Explorar o código

bugfix: don't allow mixing of rest() and action()
docs: add doxygen comments to various functions

Sam Jaffe %!s(int64=3) %!d(string=hai) anos
pai
achega
3a5f782dc8
Modificáronse 2 ficheiros con 87 adicións e 12 borrados
  1. 70 8
      include/program_args/arguments.h
  2. 17 4
      include/program_args/arguments_impl.hpp

+ 70 - 8
include/program_args/arguments.h

@@ -7,6 +7,9 @@
 
 namespace program {
 
+/**
+ * @brief A string-literal object that requires input arguments to be two or more characters
+ */
 struct LongArg {
   template <size_t N> constexpr LongArg(char const (&str)[N]) : str(str) {
     static_assert(N > 1, "cannot create longargs with 0 or 1 character");
@@ -33,20 +36,72 @@ public:
   }
 
 protected:
+  /**
+   * @brief Define an "action"/"subcommand"/"verb" to be executed. An example of this pattern
+   * would be how git visualizes sub-commands in its interface, even though in implementation
+   * it appears different.
+   * @invariant Will fail to compile if bound to something that isn't a subtype of Arguments
+   * @param name A name of the action for usage messages
+   * @param description An optional description object
+   * @return A binding object that can implicitly cast to a subtype of Arguments
+   *
+   * @throws ArgumentMixingError if both argument() and action() have been invoked in this
+   */
   auto action(LongArg name, std::string const &description = "");
+  
+  /**
+   * @brief Define an argument to be passed in to this program
+   *
+   * @param index The 0-based index of this argument in the program args array.
+   * @param name A name of the argument for usage messages
+   * @param description An optional description object
+   * @return A binding object that will write its data in to an object of arity 1
+   *
+   * @throws ArgumentMixingError if both argument() and action() have been invoked in this
+   * @throws ArgumentStructureError if this argument is required, but a prior argument was optional
+   * @throws IllegalPositionError if index is already in use in this
+   * @throws IllegalPositionError if no argument is available at index
+   */
   auto argument(size_t index, LongArg name,
                 std::string const & description = "");
-  std::vector<std::string> rest(LongArg name = "args") {
-    rest_name_ = name;
-    size_t const i = std::min(arguments.size(), argument_names.size());
-    return {arguments.begin() + i, arguments.end()};
-  }
-  auto flag(LongArg name, std::string const & description = "");
+  /**
+   * @brief Return all unbound arguments
+   * @param name An optional name to describe these arguments
+   * @param description An optional description object
+   * @returns All unbound arguments in {@sa Arguments::arguments}
+
+   * @throws ArgumentMixingError if both argument() and action() have been invoked in this
+   * @throws ArgumentStructureError if rest is called with multiple names
+   */
+  std::vector<std::string> rest(LongArg name = "args",
+                                std::string const & description = "");
+  
+  /**
+   * @brief Define a flag (option with an arity of zero).
+   * If a flag binds to a boolean and has a name, then t will be invertable with "--no-" prefix.
+   * If a flag binds to an integer, then it increments the value with each repetition.
+   * @invariant One or both of name and abbrev must be provided
+   * @param name The name of the flag, to be parsed from the commandline with a "--" prefix
+   * @param abbrev A single-character version of a flag, to be parsed from the commandline
+   * with a '-' prefix
+   * @param description An optional description object
+   */
   auto flag(LongArg name, char abbrev, std::string const & description = "");
+  auto flag(LongArg name, std::string const & description = "");
   auto flag(char abbrev, std::string const & description = "");
-  auto option(LongArg name, std::string const & description = "");
+  
+  /**
+   * @brief Define an option. If an option is repeated, then it can be used to fill out multiple
+   * entries in a map or vector.
+   * @invariant One or both of name and abbrev must be provided
+   * @param name The name of the option, to be parsed from the commandline with a "--" prefix
+   * @param abbrev A single-character version of an option, to be parsed from the commandline
+   * with a '-' prefix
+   * @param description An optional description object
+   */
   auto option(LongArg name, char abbrev,
               std::string const & description = "");
+  auto option(LongArg name, std::string const & description = "");
   auto option(char abbrev, std::string const & description = "");
 
 private:
@@ -54,14 +109,21 @@ private:
   void add_options(std::string const & name, char abbrev,
                    std::string const & description,
                    std::vector<std::string> aliases = {});
+  
+  bool has_actions() const { return !action_descriptions.empty(); }
+  bool has_arguments() const { return !(rest_name.empty() && argument_names.empty()); }
+  
   option_id id(std::string const & arg) const;
   option_id id(char arg) const { return id({'-', arg}); }
+  
   bool is_option(std::string const & arg) const;
   bool is_option(char arg) const { return is_option({'-', arg}); }
+  
   bool is_flag(std::string const & arg) const;
   bool is_flag(char arg) const { return is_flag({'-', arg}); }
 
 private:
+  // A helper for what limited introspection is required for handling actions in test
   friend struct ArgumentTestHelper;
   friend int main(int, char const * const *);
   
@@ -70,7 +132,7 @@ private:
   using make_hook_t = std::function<main_callback_t(size_t, char const * const *)>;
   // Metadata variables
   bool primed_{false};
-  std::string rest_name_;
+  std::string rest_name;
   std::map<option_id, std::string> argument_descriptions;
   std::map<size_t, option_id> argument_names;
   std::map<option_id, std::string> option_descriptions;

+ 17 - 4
include/program_args/arguments_impl.hpp

@@ -36,7 +36,7 @@ template <typename Impl> Arguments<Impl>::Action::operator bool() const {
 
 template <typename Impl> bool Arguments<Impl>::Action::primed() const {
   if (self->primed_) { return true; }
-  if (!self->argument_names.empty()) { throw ArgumentMixingError(); }
+  if (self->has_arguments()) { throw ArgumentMixingError(); }
   self->action_descriptions.emplace(name, description);
   return false;
 }
@@ -73,7 +73,7 @@ auto Arguments<Impl>::Argument::operator=(T && value) {
 
 template <typename Impl> bool Arguments<Impl>::Argument::primed() const {
   if (self->primed_) { return true; }
-  if (!self->actions.empty()) { throw ArgumentMixingError(); }
+  if (self->has_actions()) { throw ArgumentMixingError(); }
   if (is_optional) {
     self->optional_from = std::min(self->optional_from, index);
   } else if (self->optional_from < index) {
@@ -224,8 +224,8 @@ template <typename Impl> void Arguments<Impl>::usage() const {
   for (auto & [index, name] : argument_names) {
     std::cout << " " << (index == optional_from ? "[" : "") << name;
   }
-  if (rest_name_.size()) {
-    std::cout << " [" << rest_name_ << "...]";
+  if (rest_name.size()) {
+    std::cout << " [" << rest_name << "...]";
   }
   if (optional_from != no_optional_args) { std::cout << "]"; }
   std::cout << "\nArgument Arguments:\n";
@@ -282,6 +282,19 @@ auto Arguments<Impl>::argument(size_t index, LongArg name,
   return Argument{this, index, false, name, description};
 }
 
+template <typename Impl>
+std::vector<std::string> Arguments<Impl>::rest(LongArg name,
+                                               std::string const & description) {
+  if (has_actions()) { throw ArgumentMixingError(); }
+  if (!rest_name.empty() && rest_name != name.str) {
+    throw ArgumentStructureError("duplicate rest() parameter", name);
+  }
+  rest_name = name;
+  argument_descriptions.emplace(name, description);
+  size_t const i = std::min(arguments.size(), argument_names.size());
+  return {arguments.begin() + i, arguments.end()};
+}
+
 template <typename Impl>
 auto Arguments<Impl>::flag(LongArg name, std::string const & description) {
   return Flag{this, name, 0, description, false};