diff --git a/examples/main.cpp b/examples/main.cpp index 1d8edca..be48e42 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -14,6 +14,8 @@ int main(int argc, char const *argv[]) { .Flg<"disable-content-trust">({.help = "Skip image verification"}) .Flg<"quiet", "q">({.help = "Supress verbose output"}); + parse(p, std::span{argv, argc}); + // p.SetValue<"age">(28); // auto age = p.GetValue<"name">(); // fmt::print("name: [{}]\n", age.value_or(std::string())); diff --git a/include/experimental/parsing.hpp b/include/experimental/parsing.hpp index a945b86..721712c 100644 --- a/include/experimental/parsing.hpp +++ b/include/experimental/parsing.hpp @@ -3,9 +3,11 @@ #include #include +#include #include #include #include +#include #include "converters.hpp" #include "exceptions.hpp" @@ -14,6 +16,10 @@ #include "experimental/string_list.hpp" #include "experimental/type_list.hpp" +// +---------------------+ +// | fwd decls | +// +---------------------+ + struct ParsedOption { std::string_view name; // no string and empty string mean different things here @@ -30,4 +36,170 @@ constexpr std::string_view get_if_short_flags(std::string_view const) noexcept; constexpr std::string_view get_if_long_flag(std::string_view const) noexcept; constexpr ParsedOption try_parse_option(std::string_view const) noexcept; +// +-----------------------+ +// | main parsing code | +// +-----------------------+ + +struct ArgsView { + std::vector positionals; + std::map> options; +}; + +struct ArgMap { + std::string_view exec_path{}; + std::map args; + + std::any operator[](std::string_view name) const { + if (!args.contains(name)) throw opzioni::ArgumentNotFound(name); + return args.at(name); + } + + template + T as(std::string_view name) const { + auto const arg = (*this)[name]; + return std::any_cast(arg); + } + + bool has(std::string_view name) const noexcept { return args.contains(name); } + + auto size() const noexcept { return this->args.size(); } +}; + +template +struct ArgParser; + +template +struct ArgParser, TypeList> { + + std::string_view exec_path{}; + std::map args; + Program, TypeList> const &program; + + ArgParser(Program, TypeList> const &program) : program(program) {} + + ArgMap operator()(std::span args) { + ArgMap map; + map.exec_path = args[0]; + return map; + } + + auto get_args_view(std::span args) { + ArgsView view; + if (args.size() > 0) { + std::size_t current_positional_idx = 0; + for (std::size_t index = 1; index < args.size();) { + auto const arg = std::string_view(args[index]); + if (is_dash_dash(arg)) { + // +1 to ignore the dash-dash + auto const rest = args.subspan(index + 1); + view.positionals.reserve(view.positionals.size() + rest.size()); + for (auto const &str : rest) { + view.positionals.emplace_back(str); + } + current_positional_idx += rest.size(); + index = args.size(); + // } else if (auto cmd = is_command(program, arg); cmd != nullptr) { + // index += assign_command(map, args.subspan(index), *cmd); + } else if (looks_positional(arg)) { + view.positionals.emplace_back(arg); + ++current_positional_idx; + ++index; + // } else if (auto const flags = is_short_flags(program, arg); !flags.empty()) { + // index += assign_many_flags(program, map, flags); + // } else if (auto const flag = is_long_flag(program, arg); !flag.empty()) { + // index += assign_flag(program, map, flag); + // } else if (auto const option = is_option(program, arg); option.has_value()) { + // index += assign_option(program, map, args.subspan(index), *option); + } else { + throw opzioni::UnknownArgument(arg); + } + } + } + return view; + } +}; + +template +auto parse(Program, TypeList> const &program, std::span args) { + auto result = ArgParser, TypeList>(program); + auto const i = result.get_args_view(args); + std::print("positionals:\n"); + for (auto &&p : i.positionals) { + std::print("{} ", p); + } + std::print("\n"); + // auto map = parse_args(program, args); + // check_contains_required(program, map); + return result; +} + +// +---------------------+ +// | parsing helpers | +// +---------------------+ + +constexpr bool is_dash_dash(std::string_view const whole_arg) noexcept { + return whole_arg.length() == 2 && whole_arg[0] == '-' && whole_arg[1] == '-'; +} + +constexpr bool looks_positional(std::string_view const whole_arg) noexcept { + auto const num_of_dashes = whole_arg.find_first_not_of('-'); + return num_of_dashes == 0 || (whole_arg.length() == 1 && num_of_dashes == std::string_view::npos); +} + +constexpr std::string_view get_if_short_flags(std::string_view const whole_arg) noexcept { + auto const num_of_dashes = whole_arg.find_first_not_of('-'); + auto const flags = whole_arg.substr(1); + if (num_of_dashes == 1 && flags.length() >= 1) + return flags; + return {}; +} + +constexpr std::string_view get_if_long_flag(std::string_view const whole_arg) noexcept { + auto const name = whole_arg.substr(2); + auto const num_of_dashes = whole_arg.find_first_not_of('-'); + if (num_of_dashes == 2 && name.length() >= 2) + return name; + return {}; +} + +constexpr ParsedOption try_parse_option(std::string_view const whole_arg) noexcept { + auto const num_of_dashes = whole_arg.find_first_not_of('-'); + auto const eq_idx = whole_arg.find('=', num_of_dashes); + bool const has_equals = eq_idx != std::string_view::npos; + if (num_of_dashes == 1) { + // short option, e.g. `-O` + auto const name = whole_arg.substr(1, 1); + if (has_equals) { + if (whole_arg.length() > 3) { + // has equals followed by some value, e.g. `-O=2` + return {name, whole_arg.substr(3)}; + } + // should this `-O=` be handled like this? + return {name, ""}; + } + + if (whole_arg.length() > 2) { + // only followed by some value, e.g. `-O2` + return {name, whole_arg.substr(2)}; + } + + // case left: has no value (next CLI argument could be it) + return {name, std::nullopt}; + } + + if (num_of_dashes == 2 && whole_arg.length() > 3) { + // long option, e.g. `--name` + if (has_equals) { + auto const name = whole_arg.substr(2, eq_idx - 2); + auto const value = whole_arg.substr(eq_idx + 1); + return {name, value}; + } + // has no value (long options cannot have "glued" values like `-O2`; next CLI argument could be it) + return {whole_arg.substr(2), std::nullopt}; + } + + // not an option + return {"", std::nullopt}; +} + #endif \ No newline at end of file diff --git a/src/experimental/parsing.cpp b/src/experimental/parsing.cpp index ca9a0b9..4ebc5c2 100644 --- a/src/experimental/parsing.cpp +++ b/src/experimental/parsing.cpp @@ -1,72 +1 @@ -#include - #include "experimental/parsing.hpp" - -// +---------------------+ -// | parsing helpers | -// +---------------------+ - -constexpr bool is_dash_dash(std::string_view const whole_arg) noexcept { - return whole_arg.length() == 2 && whole_arg[0] == '-' && whole_arg[1] == '-'; -} - -constexpr bool looks_positional(std::string_view const whole_arg) noexcept { - auto const num_of_dashes = whole_arg.find_first_not_of('-'); - return num_of_dashes == 0 || (whole_arg.length() == 1 && num_of_dashes == std::string_view::npos); -} - -constexpr std::string_view get_if_short_flags(std::string_view const whole_arg) noexcept { - auto const num_of_dashes = whole_arg.find_first_not_of('-'); - auto const flags = whole_arg.substr(1); - if (num_of_dashes == 1 && flags.length() >= 1) - return flags; - return {}; -} - -constexpr std::string_view get_if_long_flag(std::string_view const whole_arg) noexcept { - auto const name = whole_arg.substr(2); - auto const num_of_dashes = whole_arg.find_first_not_of('-'); - if (num_of_dashes == 2 && name.length() >= 2) - return name; - return {}; -} - -constexpr ParsedOption try_parse_option(std::string_view const whole_arg) noexcept { - auto const num_of_dashes = whole_arg.find_first_not_of('-'); - auto const eq_idx = whole_arg.find('=', num_of_dashes); - bool const has_equals = eq_idx != std::string_view::npos; - if (num_of_dashes == 1) { - // short option, e.g. `-O` - auto const name = whole_arg.substr(1, 1); - if (has_equals) { - if (whole_arg.length() > 3) { - // has equals followed by some value, e.g. `-O=2` - return {name, whole_arg.substr(3)}; - } - // should this `-O=` be handled like this? - return {name, ""}; - } - - if (whole_arg.length() > 2) { - // only followed by some value, e.g. `-O2` - return {name, whole_arg.substr(2)}; - } - - // case left: has no value (next CLI argument could be it) - return {name, std::nullopt}; - } - - if (num_of_dashes == 2 && whole_arg.length() > 3) { - // long option, e.g. `--name` - if (has_equals) { - auto const name = whole_arg.substr(2, eq_idx - 2); - auto const value = whole_arg.substr(eq_idx + 1); - return {name, value}; - } - // has no value (long options cannot have "glued" values like `-O2`; next CLI argument could be it) - return {whole_arg.substr(2), std::nullopt}; - } - - // not an option - return {"", std::nullopt}; -}