Skip to content

Commit

Permalink
feat: create args view with positionals
Browse files Browse the repository at this point in the history
  • Loading branch information
ggabriel96 committed Feb 3, 2025
1 parent 4ea81b6 commit 717cfcd
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 71 deletions.
2 changes: 2 additions & 0 deletions examples/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<char const *>{argv, argc});

// p.SetValue<"age">(28);
// auto age = p.GetValue<"name">();
// fmt::print("name: [{}]\n", age.value_or(std::string()));
Expand Down
172 changes: 172 additions & 0 deletions include/experimental/parsing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

#include <any>
#include <map>
#include <print>
#include <span>
#include <string>
#include <string_view>
#include <vector>

#include "converters.hpp"
#include "exceptions.hpp"
Expand All @@ -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
Expand All @@ -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<std::string_view> positionals;
std::map<std::string_view, std::vector<std::string_view>> options;
};

struct ArgMap {
std::string_view exec_path{};
std::map<std::string_view, std::any> args;

std::any operator[](std::string_view name) const {
if (!args.contains(name)) throw opzioni::ArgumentNotFound(name);
return args.at(name);
}

template <typename T>
T as(std::string_view name) const {
auto const arg = (*this)[name];
return std::any_cast<T>(arg);
}

bool has(std::string_view name) const noexcept { return args.contains(name); }

auto size() const noexcept { return this->args.size(); }
};

template <typename...>
struct ArgParser;

template <fixed_string... ArgNames, typename... ArgTypes>
struct ArgParser<StringList<ArgNames...>, TypeList<ArgTypes...>> {

std::string_view exec_path{};
std::map<std::string_view, std::any> args;
Program<StringList<ArgNames...>, TypeList<ArgTypes...>> const &program;

ArgParser(Program<StringList<ArgNames...>, TypeList<ArgTypes...>> const &program) : program(program) {}

ArgMap operator()(std::span<char const *> args) {
ArgMap map;
map.exec_path = args[0];
return map;
}

auto get_args_view(std::span<char const *> 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 <fixed_string... ArgNames, typename... ArgTypes>
auto parse(Program<StringList<ArgNames...>, TypeList<ArgTypes...>> const &program, std::span<char const *> args) {
auto result = ArgParser<StringList<ArgNames...>, TypeList<ArgTypes...>>(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
71 changes: 0 additions & 71 deletions src/experimental/parsing.cpp
Original file line number Diff line number Diff line change
@@ -1,72 +1 @@
#include <format>

#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};
}

0 comments on commit 717cfcd

Please sign in to comment.