1 // Copyright 2018-2022 René Ferdinand Rivera Morell
2 // Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
7 #ifndef LYRA_CLI_HPP
8 #define LYRA_CLI_HPP
9
10 #include "lyra/arguments.hpp"
11 #include "lyra/detail/deprecated_parser_customization.hpp"
12 #include "lyra/detail/from_string.hpp"
13 #include "lyra/detail/print.hpp"
14 #include "lyra/exe_name.hpp"
15 #include "lyra/group.hpp"
16 #include "lyra/option_style.hpp"
17
18 #include <type_traits>
19
20 namespace lyra {
21
22 /* tag::reference[]
23
24 [#lyra_cli]
25 = `lyra::cli`
26
27 A Combined parser made up of any two or more other parsers. Creating and using
28 one of these as a basis one can incrementally compose other parsers into this
29 one. For example:
30
31 [source]
32 ----
33 auto cli = lyra::cli();
34 std::string what;
35 float when = 0;
36 std::string where;
37 cli |= lyra::opt(what, "what")["--make-it-so"]("Make it so.").required();
38 cli |= lyra::opt(when. "when")["--time"]("When to do <what>.").optional();
39 cli.add_argument(lyra::opt(where, "where").name("--where")
40 .help("There you are.").optional());
41 ----
42
43 */ // end::reference[]
44 class cli : protected arguments
45 {
46 public:
47 cli() = default;
48
49 // Copy construction, needs to copy the exe name and the composed parsers.
50 cli(const cli & other);
51
52 // Compose the `exe_name` parser.
53 cli & add_argument(exe_name const & exe_name);
54 cli & operator|=(exe_name const & exe_name);
55
56 // Compose a regular parser.
57 cli & add_argument(parser const & p);
58 cli & operator|=(parser const & p);
59
60 // Compose a group, by adding it as a single argument.
61 cli & add_argument(group const & p);
62 cli & operator|=(group const & p);
63
64 // Compose the parsers from another `cli`.
65 cli & add_argument(cli const & other);
66 cli & operator|=(cli const & other);
67
68 // Concat composition.
69 template <typename T>
70 cli operator|(T const & other) const;
71
72 // Result reference wrapper to fetch and convert argument.
73 struct value_result
74 {
75 public:
value_resultlyra::cli::value_result76 explicit value_result(const parser * p)
77 : parser_ref(p)
78 {}
79
80 template <typename T,
81 typename std::enable_if<detail::is_convertible_from_string<
82 typename detail::remove_cvref<T>::type>::value>::
83 type * = nullptr>
operator Tlyra::cli::value_result84 operator T() const
85 {
86 typename detail::remove_cvref<T>::type result {};
87 if (parser_ref)
88 detail::from_string<std::string,
89 typename detail::remove_cvref<T>::type>(
90 parser_ref->get_value(0), result);
91 return result;
92 }
93
94 template <typename T>
operator std::vector<T>lyra::cli::value_result95 operator std::vector<T>() const
96 {
97 std::vector<T> result;
98 if (parser_ref)
99 {
100 for (size_t i = 0; i < parser_ref->get_value_count(); ++i)
101 {
102 T v;
103 if (detail::from_string(parser_ref->get_value(i), v))
104 result.push_back(v);
105 }
106 }
107 return result;
108 }
109
operator std::stringlyra::cli::value_result110 operator std::string() const
111 {
112 if (parser_ref) return parser_ref->get_value(0);
113 return "";
114 }
115
116 private:
117 const parser * parser_ref = nullptr;
118 };
119
120 value_result operator[](const std::string & n);
121
122 cli & style(const option_style & style);
123 cli & style(option_style && style);
124
125 // Stream out generates the help output.
operator <<(std::ostream & os,cli const & parser)126 friend std::ostream & operator<<(std::ostream & os, cli const & parser)
127 {
128 return os << static_cast<const arguments &>(parser);
129 }
130
131 // Parse from arguments.
parse(args const & args) const132 parse_result parse(args const & args) const
133 {
134 if (opt_style)
135 return parse(args, *opt_style);
136 else
137 return parse(args, option_style::posix());
138 }
139 parse_result parse(args const & args, const option_style & style) const;
140
141 // Backward compatability parse() that takes `parser_customization` and
142 // converts to `option_style`.
parse(args const & args,const parser_customization & customize) const143 [[deprecated]] parse_result parse(
144 args const & args, const parser_customization & customize) const
145 {
146 return this->parse(args,
147 option_style(customize.token_delimiters(),
148 customize.option_prefix(), 2, customize.option_prefix(), 1));
149 }
150
151 // Internal..
152
153 using arguments::parse;
154 using arguments::get_named;
155
clone() const156 virtual std::unique_ptr<parser> clone() const override
157 {
158 return std::unique_ptr<parser>(new cli(*this));
159 }
160
161 protected:
162 mutable exe_name m_exeName;
163
get_usage_text(const option_style & style) const164 virtual std::string get_usage_text(
165 const option_style & style) const override
166 {
167 if (!m_exeName.name().empty())
168 return m_exeName.name() + " " + arguments::get_usage_text(style);
169 else
170 // We use an empty exe name as an indicator to remove USAGE text.
171 return "";
172 }
173 };
174
175 /* tag::reference[]
176
177 [#lyra_cli_ctor]
178 == Construction
179
180 end::reference[] */
181
182 /* tag::reference[]
183
184 [#lyra_cli_ctor_default]
185 === Default
186
187 [source]
188 ----
189 cli() = default;
190 ----
191
192 Default constructing a `cli` is the starting point to adding arguments
193 and options for parsing a command line.
194
195 end::reference[] */
196
197 /* tag::reference[]
198
199 [#lyra_cli_ctor_copy]
200 === Copy
201
202 [source]
203 ----
204 cli::cli(const cli& other);
205 ----
206
207 end::reference[] */
cli(const cli & other)208 inline cli::cli(const cli & other)
209 : arguments(other)
210 , m_exeName(other.m_exeName)
211 {}
212
213 /* tag::reference[]
214
215 [#lyra_cli_specification]
216 == Specification
217
218 end::reference[] */
219
220 // ==
221
222 /* tag::reference[]
223 [#lyra_cli_add_argument]
224 === `lyra::cli::add_argument`
225
226 [source]
227 ----
228 cli& cli::add_argument(exe_name const& exe_name);
229 cli& cli::operator|=(exe_name const& exe_name);
230 cli& cli::add_argument(parser const& p);
231 cli& cli::operator|=(parser const& p);
232 cli& cli::add_argument(group const& p);
233 cli& cli::operator|=(group const& p);
234 cli& cli::add_argument(cli const& other);
235 cli& cli::operator|=(cli const& other);
236 ----
237
238 Adds the given argument parser to the considered arguments for this
239 `cli`. Depending on the parser given it will be: recorded as the exe
240 name (for `exe_name` parser), directly added as an argument (for
241 `parser`), or add the parsers from another `cli` to this one.
242
243 end::reference[] */
add_argument(exe_name const & exe_name)244 inline cli & cli::add_argument(exe_name const & exe_name)
245 {
246 m_exeName = exe_name;
247 return *this;
248 }
operator |=(exe_name const & exe_name)249 inline cli & cli::operator|=(exe_name const & exe_name)
250 {
251 return this->add_argument(exe_name);
252 }
add_argument(parser const & p)253 inline cli & cli::add_argument(parser const & p)
254 {
255 arguments::add_argument(p);
256 return *this;
257 }
operator |=(parser const & p)258 inline cli & cli::operator|=(parser const & p)
259 {
260 arguments::add_argument(p);
261 return *this;
262 }
add_argument(group const & other)263 inline cli & cli::add_argument(group const & other)
264 {
265 arguments::add_argument(static_cast<parser const &>(other));
266 return *this;
267 }
operator |=(group const & other)268 inline cli & cli::operator|=(group const & other)
269 {
270 return this->add_argument(other);
271 }
add_argument(cli const & other)272 inline cli & cli::add_argument(cli const & other)
273 {
274 arguments::add_argument(static_cast<arguments const &>(other));
275 return *this;
276 }
operator |=(cli const & other)277 inline cli & cli::operator|=(cli const & other)
278 {
279 return this->add_argument(other);
280 }
281
282 template <typename T>
operator |(T const & other) const283 inline cli cli::operator|(T const & other) const
284 {
285 return cli(*this).add_argument(other);
286 }
287
288 template <typename DerivedT, typename T>
operator |(composable_parser<DerivedT> const & thing,T const & other)289 cli operator|(composable_parser<DerivedT> const & thing, T const & other)
290 {
291 return cli() | static_cast<DerivedT const &>(thing) | other;
292 }
293
294 /* tag::reference[]
295 [#lyra_cli_array_ref]
296 === `lyra::cli::operator[]`
297
298 [source]
299 ----
300 cli::value_result cli::operator[](const std::string & n)
301 ----
302
303 Finds the given argument by either option name or hint name and returns a
304 convertible reference to the value, either the one provided by the user or the
305 default.
306
307 end::reference[] */
operator [](const std::string & n)308 inline cli::value_result cli::operator[](const std::string & n)
309 {
310 return value_result(this->get_named(n));
311 }
312
313 /* tag::reference[]
314 [#lyra_cli_parse]
315 === `lyra::cli::parse`
316
317 [source]
318 ----
319 parse_result cli::parse(
320 args const& args, const option_style& customize) const;
321 ----
322
323 Parses given arguments `args` and optional option style.
324 The result indicates success or failure, and if failure what kind of failure
325 it was. The state of variables bound to options is unspecified and any bound
326 callbacks may have been called.
327
328 end::reference[] */
parse(args const & args,const option_style & style) const329 inline parse_result cli::parse(
330 args const & args, const option_style & style) const
331 {
332 LYRA_PRINT_SCOPE("cli::parse");
333 m_exeName.set(args.exe_name());
334 detail::token_iterator args_tokens(args, style);
335 parse_result result = parse(args_tokens, style);
336 if (result
337 && (result.value().type() == parser_result_type::no_match
338 || result.value().type() == parser_result_type::matched))
339 {
340 if (result.value().have_tokens())
341 {
342 return parse_result::error(result.value(),
343 "Unrecognized token: "
344 + result.value().remainingTokens().argument().name);
345 }
346 }
347 return result;
348 }
349
350 /* tag::reference[]
351 [#lyra_cli_style]
352 === `lyra::cli::style`
353
354 [source]
355 ----
356 lyra::cli & lyra::cli::style(const lyra::option_style & style)
357 lyra::cli & lyra::cli::style(lyra::option_style && style)
358 ----
359
360 Specifies the <<lyra_option_style>> to accept for this instance.
361
362 end::reference[] */
style(const option_style & style)363 inline cli & cli::style(const option_style & style)
364 {
365 opt_style = std::make_shared<option_style>(style);
366 return *this;
367 }
style(option_style && style)368 inline cli & cli::style(option_style && style)
369 {
370 opt_style = std::make_shared<option_style>(std::move(style));
371 return *this;
372 }
373
374 } // namespace lyra
375
376 #endif
377