413inline auto tokenize(std::span<const std::string_view> args,
414 OptionLookup option_nargs, CommandLookup is_command,
416 TokenizeResult result;
419 const auto find_prefix = [&](std::string_view tok) -> std::uint8_t {
420 for (std::uint8_t pi = 0;
421 pi < static_cast<uint8_t>(cfg.option_prefixes().size()); ++pi) {
422 const auto& p = cfg.option_prefixes()[pi];
423 if (tok.size() > p.size() && tok.starts_with(p)) {
428 if (tok.size() > cfg.short_option_prefix().size() &&
429 tok.starts_with(cfg.short_option_prefix())) {
436 const auto prefix_view = [&](std::uint8_t p) -> std::string_view {
442 const auto push = [&](
TokenType type, std::string_view text, std::size_t pos,
443 std::uint8_t prefix =
kNoPrefix) ->
void {
444 result.tokens.push_back({
452 bool post_separator =
false;
455 while (i < args.size()) {
456 const std::string_view tok = args[i];
459 if (!post_separator && tok == cfg.end_of_options_separator()) {
460 post_separator =
true;
466 if (post_separator) {
472 const auto prefix = find_prefix(tok);
473 const auto prefix_sv = prefix_view(prefix);
481 const auto sep = cfg.inline_value_separator();
482 const auto eq_pos = tok.find(sep);
483 const bool has_inline_val = (eq_pos != std::string_view::npos);
484 const std::string_view opt_sv =
485 has_inline_val ? tok.substr(0, eq_pos) : tok;
486 const auto nargs_opt = option_nargs(opt_sv);
490 const std::string_view body = opt_sv.substr(prefix_sv.size());
492 bool advanced_i =
false;
493 const std::string_view char_prefix = tok.substr(0, prefix_sv.size());
494 std::string char_full{char_prefix};
495 char_full.push_back(
'\0');
496 for (std::size_t k = 0; k < body.size(); ++k) {
497 const std::string_view char_bare =
498 tok.substr(prefix_sv.size() + k, 1);
499 char_full.back() = body[k];
500 const auto char_nargs_opt = option_nargs(char_full);
501 if (!char_nargs_opt) {
505 const bool is_flag = (
nargs.min == 0 &&
nargs.max == 0);
508 if (k + 1 < body.size()) {
514 while (i < args.size()) {
515 const std::string_view next = args[i];
516 if (next == cfg.end_of_options_separator())
break;
517 if (is_command(next))
break;
518 if (
nargs.max != -1 && count >=
nargs.max)
break;
520 const auto next_sep = next.find(sep);
521 const std::string_view next_opt =
522 next_sep != std::string_view::npos
523 ? next.substr(0, next_sep)
525 if (option_nargs(next_opt).has_value())
break;
526 if (count >= 1 ||
nargs.min == 0)
break;
532 if (count <
nargs.min) {
535 detail::value_count_message(
nargs.min, count));
541 while (i < args.size()) {
542 const std::string_view next = args[i];
543 if (next == cfg.end_of_options_separator())
break;
544 if (is_command(next))
break;
545 if (
nargs.max != -1 && count >=
nargs.max)
break;
547 const auto next_sep = next.find(sep);
548 const std::string_view next_opt =
549 next_sep != std::string_view::npos
550 ? next.substr(0, next_sep)
552 if (option_nargs(next_opt).has_value())
break;
553 if (count >= 1 ||
nargs.min == 0)
break;
559 if (count <
nargs.min) {
562 detail::value_count_message(
nargs.min, count));
569 if (!advanced_i) ++i;
576 const bool is_flag = (
nargs.min == 0 &&
nargs.max == 0);
583 if (has_inline_val) {
586 "flag does not accept a value");
590 detail::inline_value_count_message(
nargs.min));
600 if (is_flag)
continue;
603 while (i < args.size()) {
604 const std::string_view next = args[i];
606 if (next == cfg.end_of_options_separator())
break;
607 if (is_command(next))
break;
608 if (
nargs.max != -1 && count >=
nargs.max)
break;
611 const auto next_sep = next.find(sep);
612 const std::string_view next_opt = next_sep != std::string_view::npos
613 ? next.substr(0, next_sep)
615 if (option_nargs(next_opt).has_value())
break;
618 if (count >= 1 ||
nargs.min == 0)
break;
626 if (count <
nargs.min) {
628 detail::value_count_message(
nargs.min, count));
737 template <ArgumentSpec>
750 std::vector<std::string_view> args;
751 args.reserve(
static_cast<std::size_t
>(argc));
752 for (
int i = 0; i < argc; ++i) {
753 args.emplace_back(argv[i]);
758 return this->
parse(std::span<const std::string_view>(args), 1);
774 std::vector<std::string_view> args;
775 args.reserve(
static_cast<std::size_t
>(argc));
776 for (
int i = 0; i < argc; ++i) {
777 args.emplace_back(argv[i]);
782 return this->
parse(std::move(initial),
783 std::span<const std::string_view>(args), 1);
794 auto parse(std::span<const std::string_view> args, std::size_t first_index = 0)
797 parse_body(result, args, first_index);
809 auto parse(std::span<const std::string_view> args,
810 std::string_view
program_name, std::size_t first_index = 0)
814 parse_body(result, args, first_index);
829 auto parse(T initial, std::span<const std::string_view> args,
832 result.
value = std::move(initial);
833 parse_body(result, args, first_index);
847 auto parse(T initial, std::span<const std::string_view> args,
848 std::string_view
program_name, std::size_t first_index = 0)
852 result.
value = std::move(initial);
853 parse_body(result, args, first_index);
862 return program_name_;
870 program_name_ = std::move(name);
875 std::string program_name_ =
"program";
877 static constexpr std::size_t field_count = std::tuple_size_v<
878 std::remove_reference_t<decltype(as_tuple(std::declval<T&>()))>>;
880 auto parse_body(
ParseResult<T>& result, std::span<const std::string_view> args,
881 std::size_t first_index) ->
void {
882 auto lookup = get_option_spec_map(result.value);
885 args.subspan(std::min(first_index, args.size())),
886 [&](std::string_view opt) ->
auto { return lookup.option_nargs(opt); },
887 [&](std::string_view name) ->
auto { return lookup.is_command(name); },
889 if (tokenized.has_error()) {
890 if (tokenized.error.has_position()) {
891 tokenized.error.position +=
static_cast<int>(first_index);
893 result.error = std::move(tokenized.error);
897 const auto& tokens = tokenized.tokens;
898 std::size_t pos_idx = 0, pos_cnt = 0;
900 for (std::size_t i = 0; i < tokens.
size();) {
901 const Token& tok = tokens[i];
904 std::size_t j = i + 1;
908 std::span(tokens).subspan(i + 1, j - i - 1),
909 first_index, tok.position);
910 if (err.has_error()) {
911 result.error = finalize_special_error(std::move(err.error));
917 auto err = dispatch_positional(result.value, pos_idx, pos_cnt, tok.text,
918 tok.position + first_index);
919 if (err.has_error()) {
920 result.error = finalize_special_error(std::move(err.error));
926 auto err = dispatch_command(result.value, tok.text, args,
927 tok.position + first_index + 1);
928 if (err.has_error()) {
929 result.error = finalize_special_error(std::move(err.error));
941 for_each_field(result.value, [](
auto& f) ->
auto {
942 using F = std::remove_cvref_t<decltype(f)>;
943 if constexpr (std::derived_from<F, PositionalTag>) {
944 if (f.provided()) f.on_parse();
948 if (
auto err = apply_env_fallback(result.value); err.has_error()) {
949 result.error = std::move(err.error);
953 if (
auto err = validate_required(result.value); err.has_error()) {
954 result.error = std::move(err.error);
958 if (
auto err = validate_relations(result.value); err.has_error()) {
959 result.error = std::move(err.error);
963 if constexpr (
requires(T& t) {
964 { t.constraints() } -> std::same_as<ConstraintResult>;
966 if (
auto cr = result.value.constraints(); !cr.ok()) {
967 result.error = std::move(*cr.error);
1020 static constexpr void for_each_field(T& val, Fn fn) {
1022 [&]<std::size_t... I>(std::index_sequence<I...>)
1024 (fn(std::get<I>(tup)), ...);
1025 }(std::make_index_sequence<
1026 std::tuple_size_v<std::remove_reference_t<
decltype(tup)>>>{});
1030 static constexpr void for_each_field(
const T& val, Fn fn) {
1032 [&]<std::size_t... I>(std::index_sequence<I...>)
1034 (fn(std::get<I>(tup)), ...);
1035 }(std::make_index_sequence<
1036 std::tuple_size_v<std::remove_reference_t<
decltype(tup)>>>{});
1057 std::array<CommandEntry, field_count> commands{};
1058 std::size_t option_count{};
1059 std::size_t command_count{};
1061 auto add_long_option(std::string_view prefix, std::string_view name,
1062 Nargs nargs) ->
void {
1063 options[option_count++] = OptionEntry{
1072 auto add_short_option(std::string_view prefix,
char short_name, Nargs nargs)
1074 options[option_count++] = OptionEntry{
1077 .short_name = short_name,
1083 auto add_command(std::string_view name) ->
void {
1084 commands[command_count++] = CommandEntry{.name = name};
1087 [[nodiscard]]
auto option_nargs(std::string_view opt)
const
1088 -> std::optional<Nargs> {
1089 for (std::size_t i = 0; i < option_count; ++i) {
1090 const auto& entry = options[i];
1091 if (!opt.starts_with(entry.prefix)) {
1094 const auto rest = opt.substr(entry.prefix.size());
1095 if (entry.is_short) {
1096 if (rest.size() == 1 && rest.front() == entry.short_name) {
1099 }
else if (rest == entry.name) {
1103 return std::nullopt;
1106 [[nodiscard]]
auto is_command(std::string_view name)
const ->
bool {
1107 for (std::size_t i = 0; i < command_count; ++i) {
1108 if (commands[i].name == name)
return true;
1114 auto get_option_spec_map(
const T& val)
const -> Lookup {
1116 for_each_field(val, [&](
const auto& f) ->
auto {
1117 using F = std::remove_cvref_t<
decltype(f)>;
1118 if constexpr (std::derived_from<F, OptionTag>) {
1120 lookup.add_long_option(p, F::name.view(), F::nargs);
1122 if constexpr (F::short_name !=
'\0') {
1126 }
else if constexpr (std::derived_from<F, CommandTag>) {
1127 lookup.add_command(F::command_name());
1137 auto dispatch_option(T& val, std::string_view prefix, std::string_view bare,
1138 std::span<const Token> vals, std::size_t first_index,
1142 for_each_field(val, [&](
auto& f) ->
auto {
1143 using F = std::remove_cvref_t<
decltype(f)>;
1144 if constexpr (std::derived_from<F, OptionTag>) {
1150 if (p == prefix && F::name.view() == bare) {
1155 if (!match && F::short_name !=
'\0' &&
1157 bare[0] == F::short_name) {
1164 f.notify_option_seen();
1165 if constexpr (F::entry_is_optional) {
1166 res = f.invoke_action(std::nullopt, option_position + first_index,
1169 const std::size_t
nargs = vals.size();
1170 for (std::size_t vi = 0; vi < vals.size(); ++vi) {
1171 if (res.has_error()) {
1174 res = f.invoke_action(vals[vi].text, vals[vi].position + first_index,
1177 if (!res.has_error()) f.on_parse();
1185 static auto dispatch_positional(T& val, std::size_t& pos_idx,
1186 std::size_t& pos_cnt, std::string_view text,
1189 bool invoked =
false;
1190 std::size_t cur = 0;
1191 for_each_field(val, [&](
auto& f) ->
auto {
1192 using F = std::remove_cvref_t<
decltype(f)>;
1193 if constexpr (std::derived_from<F, PositionalTag>) {
1194 if (cur++ != pos_idx || invoked)
return;
1196 res = f.invoke_action(text, position);
1197 if (res.has_value()) {
1199 if (F::nargs.max != -1 &&
1200 pos_cnt >=
static_cast<std::size_t
>(F::nargs.max)) {
1211 .position =
static_cast<int>(position),
1212 .subject = std::string(text)});
1216 auto dispatch_command(T& val, std::string_view name,
1217 std::span<const std::string_view> args,
1221 for_each_field(val, [&](
auto& f) ->
auto {
1222 using F = std::remove_cvref_t<
decltype(f)>;
1223 if constexpr (std::derived_from<F, CommandTag>) {
1224 if (found || F::command_name() != name) {
1231 std::string(F::command_name()));
1232 auto sub = sub_parser.parse(args, first_index);
1233 if (sub.has_error()) {
1236 static_cast<typename F::argument_type&
>(f) = std::move(sub.value);
1245 .position =
static_cast<int>(first_index - 1),
1246 .subject = std::string(name),
1254 for_each_field(val, [&](
auto& f) ->
auto {
1255 using F = std::remove_cvref_t<
decltype(f)>;
1256 if constexpr (std::derived_from<F, OptionTag> ||
1257 std::derived_from<F, PositionalTag>) {
1258 if (res.has_error()) {
1262 !
static_cast<bool>(f)) {
1263 std::string subject;
1264 if constexpr (std::derived_from<F, OptionTag>) {
1265 subject =
"--" + std::string(F::name.view());
1267 subject =
"<positional>";
1272 .subject = std::move(subject),
1280 static auto relation_name_list(
NameList<> names) -> std::string {
1282 for (std::size_t i = 0; i < names.size; ++i) {
1283 if (i != 0) out +=
", ";
1285 out += names.values[i];
1290 static auto relation_group_exists(std::string_view name) ->
bool {
1291 if constexpr (!detail::HasRelations<T>) {
1296 [&](
auto... rels) ->
auto {
1299 using R = std::remove_cvref_t<
decltype(rels)>;
1300 if constexpr (std::same_as<R, GroupRelation>) {
1301 if (rels.name == name) found =
true;
1306 T::relations.items);
1311 static auto relation_operand_label(std::string_view operand) -> std::string {
1312 if (relation_group_exists(operand)) {
1313 return std::string(operand);
1315 return "--" + std::string(operand);
1318 static auto relation_operand_present(T& val, std::string_view name) ->
bool {
1319 bool found_field =
false;
1320 bool present =
false;
1321 for_each_field(val, [&](
auto& f) ->
auto {
1322 using F = std::remove_cvref_t<
decltype(f)>;
1323 if constexpr (std::derived_from<F, OptionTag> ||
1324 std::derived_from<F, CommandTag>) {
1325 if (!found_field && F::name.view() == name) {
1327 present = f.provided();
1331 if (found_field)
return present;
1333 if constexpr (detail::HasRelations<T>) {
1334 bool found_group =
false;
1335 bool group_present =
false;
1337 [&](
auto... rels) ->
auto {
1340 using R = std::remove_cvref_t<
decltype(rels)>;
1341 if constexpr (std::same_as<R, GroupRelation>) {
1342 if (!found_group && rels.name == name) {
1344 group_present =
true;
1345 for (
auto member : rels.members) {
1346 group_present = group_present &&
1347 relation_operand_present(val, member);
1354 T::relations.items);
1355 return group_present;
1361 if constexpr (!detail::HasRelations<T>) {
1366 [&](
auto... rels) ->
auto {
1369 if (res.has_error())
return;
1370 using R = std::remove_cvref_t<
decltype(rels)>;
1371 if constexpr (std::same_as<R, GroupRelation>) {
1372 std::size_t count = 0;
1373 for (
auto member : rels.members) {
1374 if (relation_operand_present(val, member)) ++count;
1376 if (count != 0 && count != rels.members.size) {
1380 .subject = std::string(rels.name),
1381 .detail =
"arguments must be provided together: " +
1382 relation_name_list(rels.members),
1385 }
else if constexpr (std::same_as<R, ConflictsRelation>) {
1386 if (relation_operand_present(val, rels.left) &&
1387 relation_operand_present(val, rels.right)) {
1391 .subject = relation_operand_label(rels.left) +
", " +
1392 relation_operand_label(rels.right),
1394 "these arguments cannot be provided together",
1397 }
else if constexpr (std::same_as<R, DependsOnRelation>) {
1398 if (relation_operand_present(val, rels.source) &&
1399 !relation_operand_present(val, rels.target)) {
1403 .subject = relation_operand_label(rels.source),
1405 "requires " + relation_operand_label(rels.target),
1412 T::relations.items);
1419 for_each_field(val, [&](
auto& f) ->
auto {
1420 using F = std::remove_cvref_t<
decltype(f)>;
1421 if constexpr (std::derived_from<F, OptionTag>) {
1422 if (res.has_error() || f.provided() || f.get_param().env.empty())
return;
1424 char* raw_buf =
nullptr;
1425 std::size_t raw_len = 0;
1426 _dupenv_s(&raw_buf, &raw_len, std::string(f.get_param().env).c_str());
1427 std::unique_ptr<char,
decltype(&free)> raw_guard(raw_buf, &free);
1428 const char* raw = raw_buf;
1430 const char* raw = std::getenv(std::string(f.get_param().env).c_str());
1433 if constexpr (F::nargs.min == 0 && F::nargs.max == 0) {
1434 res = f.invoke_action(std::nullopt, 0, 0, 0,
false);
1436 res = f.invoke_action(std::string_view(raw), 0, 1, 0,
false);
1438 if (!res.has_error()) f.on_parse();