Skip to content

Commit

Permalink
Builtin types (#12)
Browse files Browse the repository at this point in the history
* First draft version of supporting actions, default arguments and etc:

- removed ParseResult (parsing directly into ArgMap)
- list of supported types in BuiltinTypes
- Arg has std::variant default value member
- Arg stores its action
- implemented very basic assign and append actions
- removed Program::assign_* member functions
- temporarily not assigning default values when setting parsed arguments
- just updated unit tests to pass, gotta re-do them

* only override action_fn is it was not set by user

* Arg is not required by default

* setting required for positionals

* using actions instead of namespace

* Some code organization:

- moved Program to its own .hpp/.cpp files
- types.hpp/.cpp renamed to args.hpp/.cpp, added "section comments"
- opzioni::parsing::parse_option moved to program.hpp, not in parsing namespace anymore
- include cleanup
- removed unused opzioni.cpp and types.test.cpp

* Removed unused TypedConverter

* pseudo-random version bump

* Basic append templated on container too

* testing error messages in parsing.test.cpp

* Usando optional<variant> pro valor padrão

* Atribuindo valores padrão

* Fix unintentional copy

* Testing positional argument for command

* clang-format

* Fix include guards of args.hpp

* fix typo in comment
  • Loading branch information
ggabriel96 committed Aug 29, 2020
1 parent ac64a95 commit c456d5a
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 409 deletions.
33 changes: 17 additions & 16 deletions examples/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,42 @@
#include "opzioni.hpp"

int main(int argc, char const *argv[]) {
using opzioni::actions::append;
using namespace std::string_literals;

fmt::print("argv: {}\n", fmt::join(std::vector(argv, argv + argc), ", "));

opzioni::Program program;
program.pos("name").help("Your name");
program.opt("last-name").help("Your last name");
program.opt("v").help("Level of verbosity");
program.opt("d").help("A double");
program.opt("last-name").help("Your last name").otherwise("default value"s);
program.opt("v").help("Level of verbosity").otherwise(0);
program.opt("d").help("A double").otherwise(7.11);
program.flag("flag").help("Long flag");
program.flag("a").help("Short flag a");
program.flag("b").help("Short flag b");
program.opt("numbers").help("A list of numbers");
program.flag("a").help("Short flag a").otherwise(false);
program.flag("b").help("Short flag b").otherwise(false);
program.opt("n").help("A list of numbers").action(append<int>);

auto subcmd = program.cmd("subcmd").help("Just showing how subcommands work");
auto &subcmd = program.cmd("subcmd").help("Just showing how subcommands work");
subcmd.pos("subname").help("Your name again, please");
subcmd.flag("x").help("A nested flag");
subcmd.flag("x").help("A nested flag").otherwise(false);

auto const args = program(argc, argv);
fmt::print("\nCommand name: {}\n", args.cmd_name);
fmt::print("Number of arguments: {}\n", args.size());
fmt::print("name: {}\n", args["name"].as<std::string>());
fmt::print("last name: {}\n", args.value_or("last-name", ""s));
fmt::print("v: {}\n", args.value_or("v", 0));
fmt::print("d: {}\n", args.value_or("d", 0.0));
fmt::print("name: {}\n", args.as<std::string>("name"));
fmt::print("last name: {}\n", args.as<std::string>("last-name"));
fmt::print("v: {}\n", args.as<int>("v"));
fmt::print("d: {}\n", args.as<double>("d"));
fmt::print("flag: {}\n", args.get_if_else("do something!"s, "flag", "nope"s));
fmt::print("a: {}\n", args.value_or("a", false));
fmt::print("b: {}\n", args.value_or("b", false));
fmt::print("numbers: [{}]\n", fmt::join(args.value_or("numbers", std::vector<int>{}), ", "));
fmt::print("a: {}\n", args.as<bool>("a"));
fmt::print("b: {}\n", args.as<bool>("b"));
fmt::print("n: [{}]\n", fmt::join(args.as<std::vector<int>>("n"), ", "));

if (args.subcmd != nullptr) {
auto subargs = *args.subcmd;
fmt::print("\nCommand name: {}\n", subargs.cmd_name);
fmt::print("Number of arguments: {}\n", subargs.size());
fmt::print("subname: {}\n", subargs["subname"].as<std::string>());
fmt::print("x: {}\n", subargs.value_or("x", false));
fmt::print("x: {}\n", subargs.as<bool>("x"));
}
}
127 changes: 127 additions & 0 deletions include/args.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#ifndef OPZIONI_ARGS_H
#define OPZIONI_ARGS_H

#include "converters.hpp"
#include "memory.hpp"

#include <map>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#include <fmt/format.h>

namespace opzioni {

// +----------------------+
// | forward declarations |
// +----------------------+

struct Arg;
struct ArgMap;

namespace actions {

using signature = void (*)(ArgMap &, Arg const &, std::optional<std::string> const &);

template <typename T> void assign(ArgMap &, Arg const &, std::optional<std::string> const &);

} // namespace actions

// +----------------------------------------+
// | related to type list and builtin types |
// +----------------------------------------+

template <typename...> struct TypeList;

template <typename...> struct VariantOf;
template <typename... Ts> struct VariantOf<TypeList<Ts...>> { using type = std::variant<Ts...>; };

using BuiltinTypes = TypeList<bool, int, double, std::string, std::vector<int>>;
using BuiltinType = VariantOf<BuiltinTypes>::type;

// +-----------+
// | arguments |
// +-----------+

struct ArgValue {
BuiltinType value{};

template <typename T> T as() const { return std::get<T>(value); }
};

struct ArgMap {
ArgValue operator[](std::string name) const {
if (!args.contains(name))
throw UnknownArgument(fmt::format("Could not find argument `{}`", name));
return args.at(name);
}

template <typename T> T get_if_else(T &&desired, std::string name, T &&otherwise) const {
// mainly intended for flags which are not only true or false
if (args.contains(name))
return desired;
else
return otherwise;
}

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

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

std::string cmd_name;
memory::ValuePtr<ArgMap> subcmd;
std::map<std::string, ArgValue> args;
};

struct Arg {
std::string name{};
std::string description{};
bool is_required = false;
std::optional<BuiltinType> default_value{};
actions::signature action_fn = actions::assign<std::string>;

Arg &help(std::string) noexcept;
Arg &required() noexcept;
Arg &action(actions::signature) noexcept;

template <typename T> Arg &otherwise(T value) {
default_value = std::move(value);
is_required = false;
if (action_fn == actions::assign<std::string>)
action_fn = actions::assign<T>;
return *this;
}
};

// +---------------------------+
// | implementation of actions |
// +---------------------------+

namespace actions {

template <typename T> void assign(ArgMap &map, Arg const &arg, std::optional<std::string> const &parsed_value) {
T value = parsed_value.has_value() ? convert<T>(*parsed_value) : std::get<T>(*arg.default_value);
map.args[arg.name] = ArgValue{value};
}

template <typename Elem, typename Container = std::vector<Elem>>
void append(ArgMap &map, Arg const &arg, std::optional<std::string> const &parsed_value) {
Elem value = convert<Elem>(*parsed_value);
if (auto list = map.args.find(arg.name); list != map.args.end()) {
std::get<Container>(list->second.value).push_back(std::move(value));
} else {
map.args.emplace(std::make_pair(arg.name, Container{std::move(value)}));
}
}

} // namespace actions

} // namespace opzioni

#endif // OPZIONI_ARGS_H
3 changes: 0 additions & 3 deletions include/converters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
#include <cerrno>
#include <charconv>
#include <concepts>
#include <functional>
#include <optional>
#include <string>

#include <fmt/format.h>

namespace opzioni {

template <typename TargetType> using TypedConverter = std::function<TargetType(std::string)>;

template <typename TargetType> auto convert(std::string) -> TargetType;

template <Integer Int> auto convert(std::string arg_val) -> Int {
Expand Down
5 changes: 2 additions & 3 deletions include/opzioni.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#ifndef OPZIONI_H
#define OPZIONI_H

#include "types.hpp"

namespace opzioni {} // namespace opzioni
#include "args.hpp"
#include "program.hpp"

#endif // OPZIONI_H
13 changes: 5 additions & 8 deletions include/parsing.hpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
#ifndef OPZIONI_PARSING_H
#define OPZIONI_PARSING_H

#include "args.hpp"
#include "memory.hpp"
#include "types.hpp"
#include "program.hpp"

#include <map>
#include <optional>
#include <set>
#include <span>
#include <string>
#include <variant>
#include <vector>

namespace opzioni {

namespace parsing {

ParsedOption parse_option(std::string const &) noexcept;

struct DashDash {
std::size_t index;
};
Expand Down Expand Up @@ -54,7 +50,7 @@ class ArgumentParser {
public:
ArgumentParser(Program const &program, std::span<char const *> args) : spec(program), args(args) {}

ParseResult operator()();
ArgMap operator()();
std::size_t operator()(DashDash);
std::size_t operator()(Flag);
std::size_t operator()(ManyFlags);
Expand All @@ -64,9 +60,10 @@ class ArgumentParser {
std::size_t operator()(Unknown);

private:
ArgMap map;
Program const &spec;
std::span<char const *> args;
ParseResult result;
std::size_t current_positional_idx{};

alternatives decide_type(std::size_t) const noexcept;
};
Expand Down
58 changes: 58 additions & 0 deletions include/program.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef OPZIONI_PROGRAM_H
#define OPZIONI_PROGRAM_H

#include "args.hpp"
#include "memory.hpp"

#include <map>
#include <optional>
#include <string>
#include <vector>

namespace opzioni {

struct ParsedOption {
std::string name;
std::optional<std::string> value;
};

ParsedOption parse_option(std::string const &) noexcept;

struct Program {
std::string name{};
std::string description{};
std::string epilog{};

std::map<std::string, memory::ValuePtr<Program>> cmds;
std::vector<Arg> positional_args;
std::map<std::string, Arg> flags;
std::map<std::string, Arg> options;

Program() = default;
Program(std::string name) : name(name) {}
Program(std::string name, std::string description, std::string epilog)
: name(name), description(description), epilog(epilog) {}

Program &help(std::string) noexcept;
Program &with_epilog(std::string) noexcept;

ArgMap operator()(int, char const *[]) const;

Arg &pos(std::string);
Arg &opt(std::string);
Arg &flag(std::string);
Program &cmd(std::string);

bool is_flag(std::string const &) const noexcept;
void set_default_values(ArgMap &) const noexcept;

std::optional<std::string> is_positional(std::string const &) const noexcept;
std::optional<std::string> is_long_flag(std::string const &) const noexcept;
std::optional<std::string> is_short_flags(std::string const &) const noexcept;
std::optional<ParsedOption> is_option(std::string const &) const noexcept;
std::optional<decltype(cmds)::const_iterator> is_subcmd(std::string const &) const noexcept;
};

} // namespace opzioni

#endif // OPZIONI_PROGRAM_H

0 comments on commit c456d5a

Please sign in to comment.