5 #include <initializer_list>
10 #include <string_view>
11 #include <system_error>
12 #include <type_traits>
15 using namespace std::literals
;
17 enum class OptionValueKind
27 char short_name
= '\0';
28 std::string_view long_name
= "", description
= "";
29 bool required
= false;
30 bool all_other_args_not_required
= false;
31 OptionValueKind value_kind
= OptionValueKind::None
;
32 std::function
<void(OptionsParser
&parser
,
33 std::optional
<std::string_view
> value
)>
35 bool has_short_name() const
37 return short_name
!= '\0';
39 bool has_long_name() const
41 return !long_name
.empty();
43 friend std::ostream
&operator<<(std::ostream
&os
, const Option
&option
)
45 if (option
.has_long_name())
47 os
<< "--" << option
.long_name
;
49 else if (option
.has_short_name())
51 os
<< "-" << option
.short_name
;
63 friend class OptionsParser
;
66 std::vector
<Option
> options
;
67 std::map
<char, std::size_t> short_options
;
68 std::map
<std::string_view
, std::size_t> long_options
;
71 Options(std::initializer_list
<Option
> options_
)
72 : options(options_
), short_options(), long_options()
74 for (std::size_t i
= 0; i
< options
.size(); i
++)
76 auto &option
= options
[i
];
77 if (option
.has_short_name())
78 short_options
[option
.short_name
] = i
;
79 if (option
.has_long_name())
80 long_options
[option
.long_name
] = i
;
85 std::vector
<std::string_view
> parse(const char *const *argv
) const;
88 class OptionsParser final
91 const Options
&options
;
92 const char *const *argv
;
93 std::string_view argv0
;
94 std::vector
<bool> seen_options
;
95 bool check_required
= true;
96 std::optional
<std::size_t> current_option_index
;
99 const Option
¤t_option()
101 static const Option default_option
{};
102 if (current_option_index
)
104 return options
.options
[*current_option_index
];
108 return default_option
;
113 OptionsParser(const Options
&options
, const char *const *argv
)
114 : options(options
), argv(argv
+ 1),
115 argv0(argv
[0] ? argv
[0] : "<none>"),
116 seen_options(options
.options
.size(), false), current_option_index()
120 static const char *null
[] = {nullptr};
122 help_and_exit("missing program name");
127 void parse_option(std::size_t option_index
, std::string_view prefix
,
128 std::string_view name
,
129 std::optional
<std::string_view
> value
)
131 auto &option
= options
.options
[option_index
];
132 switch (option
.value_kind
)
134 case OptionValueKind::None
:
137 help_and_exit("value not allowed for ", prefix
, name
);
140 case OptionValueKind::Required
:
149 help_and_exit("missing value for ", prefix
, name
);
154 seen_options
[option_index
] = true;
155 if (option
.all_other_args_not_required
)
156 check_required
= false;
157 current_option_index
= option_index
;
158 option
.parse_value(*this, value
);
159 current_option_index
= std::nullopt
;
163 std::vector
<std::string_view
> parse()
165 std::vector
<std::string_view
> retval
;
166 constexpr auto npos
= std::string_view::npos
;
169 std::string_view arg
= *argv
++;
170 static constexpr std::string_view long_prefix
= "--"sv
;
171 static constexpr std::string_view short_prefix
= "-"sv
;
172 if (arg
.rfind(long_prefix
, 0) == 0) // it starts with `--`
174 arg
.remove_prefix(long_prefix
.size());
175 auto eq
= arg
.find('=');
180 if (arg
.empty()) // just `--`
184 retval
.push_back(*argv
++);
188 auto name
= arg
.substr(0, eq
);
189 std::optional
<std::string_view
> value
;
192 value
= arg
.substr(eq
+ 1);
194 auto iter
= options
.long_options
.find(name
);
195 if (iter
== options
.long_options
.end())
197 help_and_exit("unknown option: ", long_prefix
, name
);
199 auto option_index
= iter
->second
;
200 parse_option(option_index
, long_prefix
, name
, value
);
203 else if (arg
.rfind(short_prefix
, 0) == 0) // it starts with `-`
205 arg
.remove_prefix(short_prefix
.size());
206 if (arg
.empty()) // just `-`
208 retval
.push_back(short_prefix
);
213 auto name
= arg
.substr(0, 1);
214 arg
.remove_prefix(1);
215 auto iter
= options
.short_options
.find(name
[0]);
216 if (iter
== options
.short_options
.end())
218 help_and_exit("unknown option: ", short_prefix
, name
);
220 auto option_index
= iter
->second
;
221 std::optional
<std::string_view
> value
;
222 switch (options
.options
[option_index
].value_kind
)
224 case OptionValueKind::None
:
226 case OptionValueKind::Required
:
227 auto eq
= arg
.rfind('=', 0);
230 value
= arg
.substr(eq
+ 1);
232 else if (!arg
.empty())
239 parse_option(option_index
, short_prefix
, name
, value
);
245 retval
.push_back(arg
);
250 for (std::size_t i
= 0; i
< options
.options
.size(); i
++)
252 auto &option
= options
.options
[i
];
253 if (option
.required
&& !seen_options
[i
])
255 help_and_exit("missing required option ", option
);
261 template <typename
... Options
>
262 [[noreturn
]] void help_and_exit(const Options
&...error_msg
) const
264 auto &os
= sizeof...(error_msg
) == 0 ? std::cout
: std::cerr
;
265 if (sizeof...(error_msg
) != 0)
267 ((os
<< "Error: ") << ... << error_msg
) << std::endl
;
269 os
<< "Usage: " << argv0
;
270 for (auto &option
: options
.options
)
273 if (!option
.required
)
278 if (option
.has_short_name())
280 os
<< "-" << option
.short_name
;
283 if (option
.has_long_name())
285 os
<< sep
<< "--" << option
.long_name
;
287 switch (option
.value_kind
)
289 case OptionValueKind::None
:
291 // TODO: case OptionValueKind::Optional:
292 case OptionValueKind::Required
:
296 if (!option
.required
)
301 os
<< std::endl
<< "Options:" << std::endl
;
302 for (auto &option
: options
.options
)
305 if (option
.has_short_name())
307 os
<< "-" << option
.short_name
;
310 if (option
.has_long_name())
312 os
<< sep
<< "--" << option
.long_name
;
314 os
<< " " << option
.description
<< std::endl
;
316 std::exit(sizeof...(error_msg
) == 0 ? 0 : 1);
318 template <typename Int
>
319 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
320 std::optional
<std::string_view
> value
, Int
&i_value
)
325 help_and_exit("missing value for ", current_option());
329 if (0 == str
.rfind("0x", 0) || 0 == str
.rfind("0X", 0))
332 str
.remove_prefix(2);
334 else if (0 == str
.rfind("0o", 0) || 0 == str
.rfind("0O", 0))
337 str
.remove_prefix(2);
339 else if (0 == str
.rfind("0b", 0) || 0 == str
.rfind("0B", 0))
342 str
.remove_prefix(2);
344 std::from_chars_result result
= std::from_chars(
345 str
.data(), str
.data() + str
.size(), i_value
, base
);
346 if (result
.ptr
!= str
.data() + str
.size())
348 result
.ec
= std::errc::invalid_argument
;
350 if (result
.ec
== std::errc::result_out_of_range
)
352 help_and_exit("value out of range: ", current_option(), "=",
355 else if (result
.ec
!= std::errc())
357 help_and_exit("invalid value for: ", current_option());
360 template <typename Int
>
361 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
362 std::optional
<std::string_view
> value
, std::optional
<Int
> &i_value
,
363 bool required
= true)
365 if (!required
&& !value
)
367 i_value
= std::nullopt
;
371 this->parse_int(value
, i_value
.value());
375 inline std::vector
<std::string_view
> Options::parse(
376 const char *const *argv
) const
378 return OptionsParser(*this, argv
).parse();
381 int main(int, char **argv
)
388 .description
= "Display usage and exit.",
389 .all_other_args_not_required
= true,
390 .parse_value
= [](OptionsParser
&parser
,
391 auto) { parser
.help_and_exit(); },
393 Option
{.short_name
= 'j',
394 .long_name
= "thread-count",
395 .description
= "Number of threads to run on",
396 .value_kind
= OptionValueKind::Required
,
398 [&](OptionsParser
&parser
, auto value
) {
399 parser
.parse_int(value
, config
.thread_count
);
401 Option
{.short_name
= 'n',
402 .long_name
= "iter-count",
403 .description
= "Number of iterations to run per thread",
404 .value_kind
= OptionValueKind::Required
,
406 [&](OptionsParser
&parser
, auto value
) {
407 parser
.parse_int(value
, config
.iteration_count
);
410 OptionsParser
parser(options
, argv
);
411 auto args
= parser
.parse();
414 parser
.help_and_exit("unexpected argument");
416 // TODO: invoke benchmarks