arguments.h 8.4 KB

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