arguments.h 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #pragma once
  2. #include <map>
  3. #include <set>
  4. #include <string>
  5. #include <vector>
  6. #include "program_args/any.h"
  7. namespace program {
  8. /**
  9. * @brief A string-literal object that requires input arguments to be two or
  10. * more characters
  11. */
  12. struct LongArg {
  13. template <size_t N> constexpr LongArg(char const (&str)[N]) : str(str) {
  14. static_assert(N > 1, "cannot create longargs with 0 or 1 character");
  15. }
  16. operator std::string const &() const { return str; }
  17. std::string str;
  18. };
  19. template <typename Impl> class Arguments {
  20. private:
  21. using option_id = std::string;
  22. struct Action;
  23. struct Argument;
  24. struct Flag;
  25. struct Option;
  26. template <typename, typename> struct WithDefault;
  27. public:
  28. Arguments() = default;
  29. Arguments(int argc, char const * const * const argv)
  30. : Arguments(argc, argv, (void const *)nullptr) {}
  31. std::vector<std::string> args() const {
  32. return {arguments.begin() + argument_names.size(), arguments.end()};
  33. }
  34. // protected:
  35. template <typename T> Arguments(T const * parent) : parent_(parent) {}
  36. template <typename T>
  37. Arguments(int argc, char const * const * const argv, T const * parent);
  38. template <typename T> T const * parent() const { return parent_.get<T>(); }
  39. protected:
  40. /**
  41. * @brief Define an "action"/"subcommand"/"verb" to be executed. An example of
  42. * this pattern would be how git visualizes sub-commands in its interface,
  43. * even though in implementation it appears different.
  44. * @invariant Will fail to compile if bound to something that isn't a subtype
  45. * of Arguments
  46. * @param name A name of the action for usage messages
  47. * @param description An optional description object
  48. * @return A binding object that can implicitly cast to a subtype of Arguments
  49. *
  50. * @throws ArgumentMixingError if both argument() and action() have been
  51. * invoked in this
  52. */
  53. auto action(LongArg name, std::string const & description = "");
  54. /**
  55. * @brief Define an argument to be passed in to this program
  56. *
  57. * @param index The 0-based index of this argument in the program args array.
  58. * @param name A name of the argument for usage messages
  59. * @param description An optional description object
  60. * @return A binding object that will write its data in to an object of arity
  61. * 1
  62. *
  63. * @throws ArgumentMixingError if both argument() and action() have been
  64. * invoked in this
  65. * @throws ArgumentStructureError if this argument is required, but a prior
  66. * argument was optional
  67. * @throws IllegalPositionError if index is already in use in this
  68. * @throws IllegalPositionError if no argument is available at index
  69. */
  70. auto argument(size_t index, LongArg name,
  71. std::string const & description = "");
  72. /**
  73. * @brief Return all unbound arguments
  74. * @param name An optional name to describe these arguments
  75. * @param description An optional description object
  76. * @returns All unbound arguments in {@sa Arguments::arguments}
  77. * @throws ArgumentMixingError if both argument() and action() have been
  78. invoked in this
  79. * @throws ArgumentStructureError if rest is called with multiple names
  80. */
  81. std::vector<std::string> rest(LongArg name = "args",
  82. std::string const & description = "");
  83. /**
  84. * @brief Define a flag (option with an arity of zero).
  85. * If a flag binds to a boolean and has a name, then t will be invertable with
  86. * "--no-" prefix. If a flag binds to an integer, then it increments the value
  87. * with each repetition.
  88. * @invariant One or both of name and abbrev must be provided
  89. * @param name The name of the flag, to be parsed from the commandline with a
  90. * "--" prefix
  91. * @param abbrev A single-character version of a flag, to be parsed from the
  92. * commandline with a '-' prefix
  93. * @param description An optional description object
  94. */
  95. auto flag(LongArg name, char abbrev, std::string const & description = "");
  96. auto flag(LongArg name, std::string const & description = "");
  97. auto flag(char abbrev, std::string const & description = "");
  98. /**
  99. * @brief Define an option. If an option is repeated, then it can be used to
  100. * fill out multiple entries in a map or vector.
  101. * @invariant One or both of name and abbrev must be provided
  102. * @param name The name of the option, to be parsed from the commandline with
  103. * a "--" prefix
  104. * @param abbrev A single-character version of an option, to be parsed from
  105. * the commandline with a '-' prefix
  106. * @param description An optional description object
  107. */
  108. auto option(LongArg name, char abbrev, std::string const & description = "");
  109. auto option(LongArg name, std::string const & description = "");
  110. auto option(char abbrev, std::string const & description = "");
  111. private:
  112. void usage() const;
  113. void add_options(std::string const & name, char abbrev,
  114. std::string const & description,
  115. std::vector<std::string> aliases = {});
  116. bool has_actions() const { return !action_descriptions.empty(); }
  117. bool has_arguments() const {
  118. return !(rest_name.empty() && argument_names.empty());
  119. }
  120. option_id id(std::string const & arg) const;
  121. option_id id(char arg) const { return id({'-', arg}); }
  122. bool is_option(std::string const & arg) const;
  123. bool is_option(char arg) const { return is_option({'-', arg}); }
  124. bool is_flag(std::string const & arg) const;
  125. bool is_flag(char arg) const { return is_flag({'-', arg}); }
  126. private:
  127. // A helper for what limited introspection is required for handling actions in
  128. // test
  129. friend struct ArgumentTestHelper;
  130. friend int main(int, char const * const *);
  131. private:
  132. using main_callback_t = std::function<int(Impl const &)>;
  133. using make_hook_t =
  134. std::function<main_callback_t(size_t, char const * const *)>;
  135. // Metadata variables
  136. bool primed_{false};
  137. detail::Any parent_;
  138. std::string rest_name;
  139. std::map<option_id, std::string> argument_descriptions;
  140. std::map<size_t, option_id> argument_names;
  141. std::map<option_id, std::string> option_descriptions;
  142. std::map<std::string, option_id> option_names;
  143. std::set<option_id> flag_names;
  144. std::map<std::string, make_hook_t> actions;
  145. std::map<std::string, std::string> action_descriptions;
  146. // Data/Output variables
  147. std::string program;
  148. main_callback_t main_callback{nullptr};
  149. constexpr static size_t const no_optional_args{~0ul};
  150. size_t optional_from{no_optional_args};
  151. std::vector<std::string> arguments;
  152. std::map<std::string, std::vector<std::string>> options;
  153. std::map<std::string, int> flags;
  154. };
  155. template <typename Impl> struct Arguments<Impl>::Action {
  156. Arguments<Impl> * self;
  157. std::string name;
  158. std::string description;
  159. template <typename T> operator T() const;
  160. bool primed() const;
  161. explicit operator bool() const;
  162. };
  163. template <typename Impl> struct Arguments<Impl>::Argument {
  164. Arguments<Impl> * self;
  165. size_t index;
  166. bool is_optional;
  167. std::string name;
  168. std::string description;
  169. template <typename T> auto operator=(T && value);
  170. template <typename T> operator T() const;
  171. bool primed() const;
  172. explicit operator bool() const;
  173. };
  174. template <typename Impl> struct Arguments<Impl>::Flag {
  175. Arguments<Impl> * self;
  176. std::string name;
  177. char abbrev;
  178. std::string description;
  179. bool default_value;
  180. auto operator=(bool && value);
  181. operator bool() const;
  182. template <typename T> operator T() const;
  183. bool primed(bool inv) const;
  184. };
  185. template <typename Impl> struct Arguments<Impl>::Option {
  186. Arguments<Impl> * self;
  187. std::string name;
  188. char abbrev;
  189. std::string description;
  190. template <typename T> auto operator=(std::initializer_list<T> value);
  191. template <typename T> auto operator=(T && value);
  192. template <typename T> operator T() const;
  193. bool primed() const;
  194. explicit operator bool() const;
  195. };
  196. template <typename Impl>
  197. template <typename B, typename V>
  198. struct Arguments<Impl>::WithDefault {
  199. B impl;
  200. V default_value;
  201. template <typename T> operator T() const;
  202. };
  203. }
  204. #include "arguments_impl.hpp"
  205. template <typename Args, typename Action>
  206. int typed_main(Args const &, Action const &);
  207. #define TYPED_MAIN(tname) \
  208. int typed_main(tname const & args); \
  209. int main(int argc, char const * const * const argv) try { \
  210. tname args(argc, argv); \
  211. return (args.main_callback) ? args.main_callback(args) : typed_main(args); \
  212. } catch (program::ProgramArgumentsError const & pae) { \
  213. std::cerr << "Error in program argument handling: " << pae.what() << "\n"; \
  214. } \
  215. int typed_main(tname const & args)
  216. #define PROGRAM_DEFER(...) [this]() { return __VA_ARGS__; }