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