arguments.h 11 KB

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