initial support for mutually exclusive argument groups

master
Daniel Kolesa 2017-05-23 18:07:34 +02:00
parent b0fa895a49
commit 0e9deab322
1 changed files with 144 additions and 26 deletions

View File

@ -44,7 +44,8 @@ struct arg_error: std::runtime_error {
enum class arg_type { enum class arg_type {
OPTIONAL = 0, OPTIONAL = 0,
POSITIONAL, POSITIONAL,
GROUP GROUP,
MUTUALLY_EXCLUSIVE_GROUP
}; };
enum class arg_value { enum class arg_value {
@ -62,7 +63,9 @@ struct arg_description {
virtual arg_type type() const = 0; virtual arg_type type() const = 0;
protected: protected:
virtual arg_description *find_arg(string_range, std::optional<arg_type>) { virtual arg_description *find_arg(
string_range, std::optional<arg_type>, bool
) {
return nullptr; return nullptr;
} }
@ -116,6 +119,7 @@ struct arg_optional: arg_argument {
friend struct basic_arg_parser; friend struct basic_arg_parser;
friend struct arg_description_container; friend struct arg_description_container;
friend struct arg_group; friend struct arg_group;
friend struct arg_mutually_exclusive_group;
arg_type type() const { arg_type type() const {
return arg_type::OPTIONAL; return arg_type::OPTIONAL;
@ -187,7 +191,9 @@ protected:
p_names.emplace_back(name2); p_names.emplace_back(name2);
} }
arg_description *find_arg(string_range name, std::optional<arg_type> tp) { arg_description *find_arg(
string_range name, std::optional<arg_type> tp, bool
) {
if (tp && (*tp != type())) { if (tp && (*tp != type())) {
return nullptr; return nullptr;
} }
@ -278,7 +284,9 @@ protected:
p_name(name) p_name(name)
{} {}
arg_description *find_arg(string_range name, std::optional<arg_type> tp) { arg_description *find_arg(
string_range name, std::optional<arg_type> tp, bool
) {
if ((tp && (*tp != type())) || (name != ostd::citer(p_name))) { if ((tp && (*tp != type())) || (name != ostd::citer(p_name))) {
return nullptr; return nullptr;
} }
@ -325,9 +333,11 @@ protected:
p_name(name) p_name(name)
{} {}
arg_description *find_arg(string_range name, std::optional<arg_type> tp) { arg_description *find_arg(
string_range name, std::optional<arg_type> tp, bool parsing
) {
for (auto &opt: p_opts) { for (auto &opt: p_opts) {
if (auto *p = opt->find_arg(name, tp); p) { if (auto *p = opt->find_arg(name, tp, parsing); p) {
return p; return p;
} }
} }
@ -339,6 +349,63 @@ private:
std::vector<std::unique_ptr<arg_optional>> p_opts; std::vector<std::unique_ptr<arg_optional>> p_opts;
}; };
struct arg_mutually_exclusive_group: arg_description {
friend struct arg_description_container;
arg_type type() const {
return arg_type::MUTUALLY_EXCLUSIVE_GROUP;
}
template<typename ...A>
arg_optional &add_optional(A &&...args) {
arg_optional *o = new arg_optional(std::forward<A>(args)...);
return *p_opts.emplace_back(o);
}
auto iter() const {
return ostd::citer(p_opts);
}
bool required() const {
return p_required;
}
protected:
arg_mutually_exclusive_group(bool required = false):
p_required(required)
{}
arg_description *find_arg(
string_range name, std::optional<arg_type> tp, bool parsing
) {
string_range used;
for (auto &opt: p_opts) {
if (auto *p = opt->find_arg(name, tp, parsing); p) {
if (parsing && !used.empty()) {
throw arg_error{format(
appender<std::string>(),
"argument '%s' not allowed with argument '%s'",
name, used
).get()};
}
return p;
}
if (opt->used()) {
for (auto &n: opt->get_names()) {
if (n.size() > used.size()) {
used = n;
}
}
}
}
return nullptr;
}
private:
std::vector<std::unique_ptr<arg_optional>> p_opts;
bool p_required;
};
struct arg_description_container { struct arg_description_container {
template<typename ...A> template<typename ...A>
arg_optional &add_optional(A &&...args) { arg_optional &add_optional(A &&...args) {
@ -358,6 +425,16 @@ struct arg_description_container {
return static_cast<arg_group &>(*p_opts.emplace_back(p)); return static_cast<arg_group &>(*p_opts.emplace_back(p));
} }
template<typename ...A>
arg_mutually_exclusive_group &add_mutually_exclusive_group(A &&...args) {
arg_description *p = new arg_mutually_exclusive_group(
std::forward<A>(args)...
);
return static_cast<arg_mutually_exclusive_group &>(
*p_opts.emplace_back(p)
);
}
auto iter() const { auto iter() const {
return ostd::citer(p_opts); return ostd::citer(p_opts);
} }
@ -366,9 +443,9 @@ protected:
arg_description_container() {} arg_description_container() {}
template<typename AT> template<typename AT>
AT &find_arg(string_range name) { AT &find_arg(string_range name, bool parsing) {
for (auto &p: p_opts) { for (auto &p: p_opts) {
auto *pp = p->find_arg(name, std::nullopt); auto *pp = p->find_arg(name, std::nullopt, parsing);
if (!pp) { if (!pp) {
continue; continue;
} }
@ -452,6 +529,33 @@ public:
} }
} }
for (auto &p: iter()) { for (auto &p: iter()) {
if (p->type() == arg_type::MUTUALLY_EXCLUSIVE_GROUP) {
auto &mgrp = static_cast<arg_mutually_exclusive_group &>(*p);
if (!mgrp.required()) {
continue;
}
std::vector<string_range> names;
for (auto &mp: mgrp.iter()) {
string_range cn;
for (auto &n: mp->get_names()) {
if (n.size() > cn.size()) {
cn = n;
}
}
if (!cn.empty()) {
names.push_back(cn);
}
if (mp->used()) {
goto loopcont;
}
}
throw arg_error{format(
appender<std::string>(),
"one of the arguments %('%s'%|, %) is required", names
).get()};
loopcont:
continue;
}
if (p->type() != arg_type::POSITIONAL) { if (p->type() != arg_type::POSITIONAL) {
continue; continue;
} }
@ -480,7 +584,7 @@ public:
} }
arg_argument &get(string_range name) { arg_argument &get(string_range name) {
return find_arg<arg_argument>(name); return find_arg<arg_argument>(name, false);
} }
string_range get_progname() const { string_range get_progname() const {
@ -515,7 +619,7 @@ private:
args.pop_front(); args.pop_front();
std::string arg{argr}; std::string arg{argr};
auto &desc = find_arg<arg_optional>(arg); auto &desc = find_arg<arg_optional>(arg, true);
auto needs = desc.needs_value(); auto needs = desc.needs_value();
auto nargs = desc.get_nargs(); auto nargs = desc.get_nargs();
@ -670,17 +774,27 @@ struct default_help_formatter {
template<typename OutputRange> template<typename OutputRange>
void format_options(OutputRange &out) { void format_options(OutputRange &out) {
std::size_t opt_namel = 0, pos_namel = 0, grp_namel = 0; std::size_t opt_namel = 0, pos_namel = 0, grp_namel = 0;
std::vector<arg_optional *> allopt;
std::vector<arg_positional *> allpos;
for (auto &p: p_parser.iter()) { for (auto &p: p_parser.iter()) {
auto cs = counting_sink(noop_sink<char>()); auto cs = counting_sink(noop_sink<char>());
switch (p->type()) { switch (p->type()) {
case arg_type::OPTIONAL: case arg_type::OPTIONAL: {
format_option(cs, static_cast<arg_optional &>(*p)); auto &opt = static_cast<arg_optional &>(*p);
format_option(cs, opt);
opt_namel = std::max(opt_namel, cs.get_written()); opt_namel = std::max(opt_namel, cs.get_written());
allopt.push_back(&opt);
break; break;
case arg_type::POSITIONAL: }
format_option(cs, static_cast<arg_positional &>(*p)); case arg_type::POSITIONAL: {
auto &opt = static_cast<arg_positional &>(*p);
format_option(cs, opt);
pos_namel = std::max(pos_namel, cs.get_written()); pos_namel = std::max(pos_namel, cs.get_written());
allpos.push_back(&opt);
break; break;
}
case arg_type::GROUP: case arg_type::GROUP:
for (auto &sp: static_cast<arg_group &>(*p).iter()) { for (auto &sp: static_cast<arg_group &>(*p).iter()) {
auto ccs = cs; auto ccs = cs;
@ -688,6 +802,16 @@ struct default_help_formatter {
grp_namel = std::max(grp_namel, ccs.get_written()); grp_namel = std::max(grp_namel, ccs.get_written());
} }
break; break;
case arg_type::MUTUALLY_EXCLUSIVE_GROUP:
for (auto &sp: static_cast<
arg_mutually_exclusive_group &
>(*p).iter()) {
auto ccs = cs;
format_option(ccs, *sp);
opt_namel = std::max(opt_namel, ccs.get_written());
allopt.push_back(sp.get());
}
break;
default: default:
break; break;
} }
@ -709,14 +833,11 @@ struct default_help_formatter {
} }
}; };
if (pos_namel) { if (!allpos.empty()) {
format(out, "\nPositional arguments:\n"); format(out, "\nPositional arguments:\n");
for (auto &p: p_parser.iter()) { for (auto p: allpos) {
if (p->type() != arg_type::POSITIONAL) {
continue;
}
format(out, " "); format(out, " ");
auto &parg = static_cast<arg_positional &>(*p); auto &parg = *p;
auto cr = counting_sink(out); auto cr = counting_sink(out);
format_option(cr, parg); format_option(cr, parg);
out = std::move(cr.get_range()); out = std::move(cr.get_range());
@ -724,14 +845,11 @@ struct default_help_formatter {
} }
} }
if (opt_namel) { if (!allopt.empty()) {
format(out, "\nOptional arguments:\n"); format(out, "\nOptional arguments:\n");
for (auto &p: p_parser.iter()) { for (auto &p: allopt) {
if (p->type() != arg_type::OPTIONAL) {
continue;
}
format(out, " "); format(out, " ");
auto &oarg = static_cast<arg_optional &>(*p); auto &oarg = *p;
auto cr = counting_sink(out); auto cr = counting_sink(out);
format_option(cr, oarg); format_option(cr, oarg);
out = std::move(cr.get_range()); out = std::move(cr.get_range());