blob: fe3dd71ce8c04fa9ee30e60fd0a6e7789a15c440 [file] [log] [blame]
Austin Engcc2516a2023-10-17 20:57:54 +00001// Copyright 2023 The Dawn & Tint Authors
Ben Clayton3ee81bb2023-05-31 18:06:38 +00002//
Austin Engcc2516a2023-10-17 20:57:54 +00003// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions are met:
Ben Clayton3ee81bb2023-05-31 18:06:38 +00005//
Austin Engcc2516a2023-10-17 20:57:54 +00006// 1. Redistributions of source code must retain the above copyright notice, this
7// list of conditions and the following disclaimer.
Ben Clayton3ee81bb2023-05-31 18:06:38 +00008//
Austin Engcc2516a2023-10-17 20:57:54 +00009// 2. Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12//
13// 3. Neither the name of the copyright holder nor the names of its
14// contributors may be used to endorse or promote products derived from
15// this software without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Ben Clayton3ee81bb2023-05-31 18:06:38 +000027
dan sinclair22b4dd22023-07-21 00:40:07 +000028#ifndef SRC_TINT_UTILS_CLI_CLI_H_
29#define SRC_TINT_UTILS_CLI_CLI_H_
Ben Clayton3ee81bb2023-05-31 18:06:38 +000030
31#include <deque>
32#include <optional>
33#include <string>
34#include <utility>
35
dan sinclair22b4dd22023-07-21 00:40:07 +000036#include "src/tint/utils/containers/vector.h"
37#include "src/tint/utils/macros/compiler.h"
38#include "src/tint/utils/memory/block_allocator.h"
39#include "src/tint/utils/result/result.h"
Ben Clayton16be11d2023-08-01 00:37:35 +000040#include "src/tint/utils/strconv/parse_num.h"
dan sinclair22b4dd22023-07-21 00:40:07 +000041#include "src/tint/utils/text/string.h"
Ben Clayton3ee81bb2023-05-31 18:06:38 +000042
dan sinclairbae54e72023-07-28 15:01:54 +000043namespace tint::cli {
Ben Clayton3ee81bb2023-05-31 18:06:38 +000044
45/// Alias is a fluent-constructor helper for Options
46struct Alias {
47 /// The alias to apply to an Option
48 std::string value;
49
50 /// @param option the option to apply the alias to
51 template <typename T>
52 void Apply(T& option) {
53 option.alias = value;
54 }
55};
56
57/// ShortName is a fluent-constructor helper for Options
58struct ShortName {
59 /// The short-name to apply to an Option
60 std::string value;
61
62 /// @param option the option to apply the short name to
63 template <typename T>
64 void Apply(T& option) {
65 option.short_name = value;
66 }
67};
68
69/// Parameter is a fluent-constructor helper for Options
70struct Parameter {
71 /// The parameter name to apply to an Option
72 std::string value;
73
74 /// @param option the option to apply the parameter name to
75 template <typename T>
76 void Apply(T& option) {
77 option.parameter = value;
78 }
79};
80
81/// Default is a fluent-constructor helper for Options
82template <typename T>
83struct Default {
84 /// The default value to apply to an Option
85 T value;
86
87 /// @param option the option to apply the default value to
88 template <typename O>
89 void Apply(O& option) {
90 option.default_value = value;
91 }
92};
93
94/// Deduction guide for Default
95template <typename T>
96Default(T) -> Default<T>;
97
98/// Option is the base class for all command line options
99class Option {
100 public:
101 /// An alias to std::string, used to hold error messages.
102 using Error = std::string;
103
Ben Clayton72b95a52023-11-01 14:16:29 +0000104 /// Constructor
105 Option();
106
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000107 /// Destructor
108 virtual ~Option();
109
110 /// @return the name of the option, without any leading hyphens.
111 /// Example: 'help'
112 virtual std::string Name() const = 0;
113
114 /// @return the alias name of the option, without any leading hyphens. (optional)
115 /// Example: 'flag'
116 virtual std::string Alias() const = 0;
117
118 /// @return the shorter name of the option, without any leading hyphens. (optional)
119 /// Example: 'h'
120 virtual std::string ShortName() const = 0;
121
122 /// @return a string describing the parameter that the option expects.
123 /// Empty represents no expected parameter.
124 virtual std::string Parameter() const = 0;
125
126 /// @return a description of the option.
127 /// Example: 'shows this message'
128 virtual std::string Description() const = 0;
129
130 /// @return the default value of the option, or an empty string if there is no default value.
131 virtual std::string DefaultValue() const = 0;
132
133 /// Sets the option value to the default (called before arguments are parsed)
134 virtual void SetDefault() = 0;
135
136 /// Parses the option's arguments from the list of command line arguments, removing the consumed
137 /// arguments before returning. @p arguments will have already had the option's name consumed
138 /// before calling.
139 /// @param arguments the queue of unprocessed arguments. Parse() may take from the front of @p
140 /// arguments.
141 /// @return empty Error if successfully parsed, otherwise an error string.
142 virtual Error Parse(std::deque<std::string_view>& arguments) = 0;
143
144 protected:
145 /// An empty string, used to represent no-error.
146 static constexpr const char* Success = "";
147
148 /// @param expected the expected value(s) for the option
149 /// @return an Error message for a missing argument
150 Error ErrMissingArgument(std::string expected) const {
151 Error err = "missing value for option '--" + Name() + "'";
152 if (!expected.empty()) {
153 err += "Expected: " + expected;
154 }
155 return err;
156 }
157
158 /// @param got the argument value provided
159 /// @param reason the reason the argument is invalid (optional)
160 /// @return an Error message for an invalid argument
161 Error ErrInvalidArgument(std::string_view got, std::string reason) const {
162 Error err = "invalid value '" + std::string(got) + "' for option '--" + Name() + "'";
163 if (!reason.empty()) {
164 err += "\n" + reason;
165 }
166 return err;
167 }
Ben Clayton72b95a52023-11-01 14:16:29 +0000168
169 private:
170 Option(const Option&) = delete;
171 Option& operator=(const Option&) = delete;
172 Option(Option&&) = delete;
173 Option& operator=(Option&&) = delete;
174};
175
176/// Options for OptionSet::Parse
177struct ParseOptions {
178 /// If true, then unknown flags will be ignored instead of treated as an error
179 bool ignore_unknown = false;
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000180};
181
182/// OptionSet is a set of Options, which can parse the command line arguments.
183class OptionSet {
184 public:
185 /// Unconsumed is a list of unconsumed command line arguments
dan sinclairbae54e72023-07-28 15:01:54 +0000186 using Unconsumed = Vector<std::string_view, 8>;
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000187
188 /// Constructs and returns a new Option to be owned by the OptionSet
189 /// @tparam T the Option type
190 /// @tparam ARGS the constructor argument types
191 /// @param args the constructor arguments
192 /// @return the constructed Option
193 template <typename T, typename... ARGS>
194 T& Add(ARGS&&... args) {
195 return *options.Create<T>(std::forward<ARGS>(args)...);
196 }
197
198 /// Prints to @p out the description of all the command line options.
199 /// @param out the output stream
David Neto50cd4f32024-06-24 22:14:34 +0000200 /// @param show_equals_form if true, show the '--option=value' syntax
201 /// instead of '--option value'
202 void ShowHelp(std::ostream& out, bool show_equals_form = false);
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000203
204 /// Parses all the options in @p options.
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000205 /// @param arguments the command line arguments, excluding the initial executable name
Ben Clayton72b95a52023-11-01 14:16:29 +0000206 /// @param parse_options the optional parser options
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000207 /// @return a Result holding a list of arguments that were not consumed as options
Ben Clayton72b95a52023-11-01 14:16:29 +0000208 Result<Unconsumed> Parse(VectorRef<std::string_view> arguments,
209 const ParseOptions& parse_options = {});
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000210
211 private:
212 /// The list of options to parse
dan sinclairbae54e72023-07-28 15:01:54 +0000213 BlockAllocator<Option, 1024> options;
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000214};
215
216/// ValueOption is an option that accepts a single value
217template <typename T>
218class ValueOption : public Option {
219 static constexpr bool is_bool = std::is_same_v<T, bool>;
220 static constexpr bool is_number =
221 !is_bool && (std::is_integral_v<T> || std::is_floating_point_v<T>);
222 static constexpr bool is_string = std::is_same_v<T, std::string>;
223 static_assert(is_bool || is_number || is_string, "unsupported data type");
224
225 public:
226 /// The name of the option, without any leading hyphens.
227 std::string name;
228 /// The alias name of the option, without any leading hyphens.
229 std::string alias;
230 /// The shorter name of the option, without any leading hyphens.
231 std::string short_name;
232 /// A description of the option.
233 std::string description;
234 /// The default value.
235 std::optional<T> default_value;
236 /// The option value. Populated with Parse().
237 std::optional<T> value;
238 /// A string describing the name of the option's value.
239 std::string parameter = "value";
240
241 /// Constructor
242 ValueOption() = default;
243
244 /// Constructor
245 /// @param option_name the option name
246 /// @param option_description the option description
247 /// @param settings a number of fluent-constructor values that configure the option
248 /// @see ShortName, Parameter, Default
249 template <typename... SETTINGS>
250 ValueOption(std::string option_name, std::string option_description, SETTINGS&&... settings)
251 : name(std::move(option_name)), description(std::move(option_description)) {
252 (settings.Apply(*this), ...);
253 }
254
255 std::string Name() const override { return name; }
256
257 std::string Alias() const override { return alias; }
258
259 std::string ShortName() const override { return short_name; }
260
261 std::string Parameter() const override { return parameter; }
262
263 std::string Description() const override { return description; }
264
265 std::string DefaultValue() const override {
266 return default_value.has_value() ? ToString(*default_value) : "";
267 }
268
269 void SetDefault() override { value = default_value; }
270
271 Error Parse(std::deque<std::string_view>& arguments) override {
272 TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
273
274 if (arguments.empty()) {
275 if constexpr (is_bool) {
276 // Treat as flag (--blah)
277 value = true;
278 return Success;
279 } else {
280 return ErrMissingArgument(parameter);
281 }
282 }
283
284 auto arg = arguments.front();
285
286 if constexpr (is_number) {
dan sinclair37e9d112023-11-20 13:18:28 +0000287 auto result = strconv::ParseNumber<T>(arg);
Ben Clayton89274f72024-01-03 10:53:42 +0000288 if (result == tint::Success) {
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000289 value = result.Get();
290 arguments.pop_front();
291 return Success;
292 }
dan sinclair37e9d112023-11-20 13:18:28 +0000293 if (result.Failure() == strconv::ParseNumberError::kResultOutOfRange) {
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000294 return ErrInvalidArgument(arg, "value out of range");
295 }
296 return ErrInvalidArgument(arg, "failed to parse value");
297 } else if constexpr (is_string) {
298 value = arg;
299 arguments.pop_front();
300 return Success;
301 } else if constexpr (is_bool) {
302 if (arg == "true") {
303 value = true;
304 arguments.pop_front();
305 return Success;
306 }
307 if (arg == "false") {
308 value = false;
309 arguments.pop_front();
310 return Success;
311 }
312 // Next argument is assumed to be another option, or unconsumed argument.
313 // Treat as flag (--blah)
314 value = true;
315 return Success;
316 }
317
318 TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
319 }
320};
321
322/// BoolOption is an alias to ValueOption<bool>
323using BoolOption = ValueOption<bool>;
324
325/// StringOption is an alias to ValueOption<std::string>
326using StringOption = ValueOption<std::string>;
327
328/// EnumName is a pair of enum value and name.
329/// @tparam ENUM the enum type
330template <typename ENUM>
331struct EnumName {
332 /// Constructor
333 EnumName() = default;
334
335 /// Constructor
336 /// @param v the enum value
337 /// @param n the name of the enum value
338 EnumName(ENUM v, std::string n) : value(v), name(std::move(n)) {}
339
340 /// the enum value
341 ENUM value;
342 /// the name of the enum value
343 std::string name;
344};
345
346/// Deduction guide for EnumName
347template <typename ENUM>
348EnumName(ENUM, std::string) -> EnumName<ENUM>;
349
350/// EnumOption is an option that accepts an enumerator of values
351template <typename ENUM>
352class EnumOption : public Option {
353 public:
354 /// The name of the option, without any leading hyphens.
355 std::string name;
356 /// The alias name of the option, without any leading hyphens.
357 std::string alias;
358 /// The shorter name of the option, without any leading hyphens.
359 std::string short_name;
360 /// A description of the option.
361 std::string description;
362 /// The enum options as a pair of enum value to name
dan sinclairbae54e72023-07-28 15:01:54 +0000363 Vector<EnumName<ENUM>, 8> enum_names;
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000364 /// The default value.
365 std::optional<ENUM> default_value;
366 /// The option value. Populated with Parse().
367 std::optional<ENUM> value;
368
369 /// Constructor
370 EnumOption() = default;
371
372 /// Constructor
373 /// @param option_name the option name
374 /// @param option_description the option description
375 /// @param names The enum options as a pair of enum value to name
376 /// @param settings a number of fluent-constructor values that configure the option
377 /// @see ShortName, Parameter, Default
378 template <typename... SETTINGS>
379 EnumOption(std::string option_name,
380 std::string option_description,
dan sinclairbae54e72023-07-28 15:01:54 +0000381 VectorRef<EnumName<ENUM>> names,
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000382 SETTINGS&&... settings)
383 : name(std::move(option_name)),
384 description(std::move(option_description)),
385 enum_names(std::move(names)) {
386 (settings.Apply(*this), ...);
387 }
388
389 std::string Name() const override { return name; }
390
391 std::string ShortName() const override { return short_name; }
392
393 std::string Alias() const override { return alias; }
394
395 std::string Parameter() const override { return PossibleValues("|"); }
396
397 std::string Description() const override { return description; }
398
399 std::string DefaultValue() const override {
400 for (auto& enum_name : enum_names) {
401 if (enum_name.value == default_value) {
402 return enum_name.name;
403 }
404 }
405 return "";
406 }
407
408 void SetDefault() override { value = default_value; }
409
410 Error Parse(std::deque<std::string_view>& arguments) override {
411 if (arguments.empty()) {
412 return ErrMissingArgument("one of: " + PossibleValues(", "));
413 }
414 auto& arg = arguments.front();
415 for (auto& enum_name : enum_names) {
416 if (enum_name.name == arg) {
417 value = enum_name.value;
418 arguments.pop_front();
419 return Success;
420 }
421 }
422 return ErrInvalidArgument(arg, "Must be one of: " + PossibleValues(", "));
423 }
424
425 /// @param delimiter the delimiter between each enum option
426 /// @returns the accepted enum names delimited with @p delimiter
427 std::string PossibleValues(std::string delimiter) const {
428 std::string out;
429 for (auto& enum_name : enum_names) {
430 if (!out.empty()) {
431 out += delimiter;
432 }
433 out += enum_name.name;
434 }
435 return out;
436 }
437};
438
dan sinclairbae54e72023-07-28 15:01:54 +0000439} // namespace tint::cli
Ben Clayton3ee81bb2023-05-31 18:06:38 +0000440
dan sinclair22b4dd22023-07-21 00:40:07 +0000441#endif // SRC_TINT_UTILS_CLI_CLI_H_