Commit 60f5fb6a authored by Dominik Charousset's avatar Dominik Charousset

Fix parsing in `extract_opts`, add help generator

Parse arguments correctly when only using a long name. Also allow customizing
the help text by adding an additional factory function argument. Close #268.
parent 118ef549
...@@ -216,6 +216,11 @@ class message { ...@@ -216,6 +216,11 @@ class message {
*/ */
std::string text; std::string text;
/**
* Auto-generated helptext for this item.
*/
std::string helptext;
/** /**
* Returns `true` on a match, `false` otherwise. * Returns `true` on a match, `false` otherwise.
*/ */
...@@ -253,6 +258,8 @@ class message { ...@@ -253,6 +258,8 @@ class message {
struct cli_res; struct cli_res;
using help_factory = std::function<std::string (const std::vector<cli_arg>&)>;
/** /**
* A simplistic interface for using `extract` to parse command line options. * A simplistic interface for using `extract` to parse command line options.
* Usage example: * Usage example:
...@@ -280,8 +287,15 @@ class message { ...@@ -280,8 +287,15 @@ class message {
* // ... * // ...
* } * }
* ~~~ * ~~~
*/ * @param xs List of argument descriptors.
cli_res extract_opts(std::vector<cli_arg> xs) const; * @param f Optional factory function to generate help text
* (overrides the default generator).
* @returns A struct containing remainder
* (i.e. unmatched elements), a set containing the names of all
* used arguments, and the generated help text.
* @throws std::invalid_argument if no name or more than one long name is set
*/
cli_res extract_opts(std::vector<cli_arg> xs, help_factory f = nullptr) const;
/** /**
* Queries whether the element at position `p` is of type `T`. * Queries whether the element at position `p` is of type `T`.
......
...@@ -102,8 +102,10 @@ class message_builder { ...@@ -102,8 +102,10 @@ class message_builder {
/** /**
* @copydoc message::extract_opts * @copydoc message::extract_opts
*/ */
inline message::cli_res extract_opts(std::vector<message::cli_arg> xs) const { inline message::cli_res extract_opts(std::vector<message::cli_arg> xs,
return to_message().extract_opts(std::move(xs)); message::help_factory f
= nullptr) const {
return to_message().extract_opts(std::move(xs), std::move(f));
} }
/** /**
......
...@@ -143,26 +143,53 @@ message message::extract(message_handler handler) const { ...@@ -143,26 +143,53 @@ message message::extract(message_handler handler) const {
return extract_impl(0, handler); return extract_impl(0, handler);
} }
message::cli_res message::extract_opts(std::vector<cli_arg> xs) const { message::cli_res message::extract_opts(std::vector<cli_arg> xs,
std::set<std::string> opts; help_factory f) const {
cli_arg dummy{"help,h", ""}; // add default help item if not specified by user
auto pred = [](const cli_arg& arg) {
return arg.name == "help" || arg.name.compare(0, 5, "help,") == 0;
};
if (std::none_of(xs.begin(), xs.end(), pred)) {
xs.push_back(cli_arg{"help,h,?", "print this text"});
}
std::map<std::string, cli_arg*> shorts; std::map<std::string, cli_arg*> shorts;
std::map<std::string, cli_arg*> longs; std::map<std::string, cli_arg*> longs;
shorts["-h"] = &dummy;
shorts["-?"] = &dummy;
longs["--help"] = &dummy;
for (auto& cliarg : xs) { for (auto& cliarg : xs) {
std::vector<std::string> s; std::vector<std::string> s;
split(s, cliarg.name, is_any_of(","), token_compress_on); split(s, cliarg.name, is_any_of(","), token_compress_on);
if (s.size() == 2 && s.back().size() == 1) { if (s.empty()) {
throw std::invalid_argument("invalid option name: " + cliarg.name);
}
longs["--" + s.front()] = &cliarg; longs["--" + s.front()] = &cliarg;
shorts["-" + s.back()] = &cliarg; for (size_t i = 1; i < s.size(); ++i) {
} else if (s.size() == 1) { if (s[i].size() != 1) {
longs[s.front()] = &cliarg; throw std::invalid_argument("invalid short option name: " + s[i]);
}
shorts["-" + s[i]] = &cliarg;
}
// generate helptext for this item
auto& ht = cliarg.helptext;
if (s.size() == 1) {
ht += "--";
ht += s.front();
} else { } else {
throw std::invalid_argument("invalid option name: " + cliarg.name); ht += "-";
ht += s[1];
ht += " [";
for (size_t i = 2; i < s.size(); ++i) {
ht += "-";
ht += s[i];
ht += ",";
}
ht += "--";
ht += s.front();
ht += "]";
}
if (cliarg.fun) {
ht += " arg";
} }
} }
std::set<std::string> opts;
auto insert_opt_name = [&](const cli_arg* ptr) { auto insert_opt_name = [&](const cli_arg* ptr) {
auto separator = ptr->name.find(','); auto separator = ptr->name.find(',');
if (separator == std::string::npos) { if (separator == std::string::npos) {
...@@ -232,43 +259,24 @@ message::cli_res message::extract_opts(std::vector<cli_arg> xs) const { ...@@ -232,43 +259,24 @@ message::cli_res message::extract_opts(std::vector<cli_arg> xs) const {
return none; return none;
} }
}); });
size_t name_width = 0; std::string helptext;
for (auto& x : xs) { if (f) {
// name field contains either only "--<long_name>" or helptext = f(xs);
// "-<short name> [--<long name>]" depending on whether or not } else {
// a ',' appears in the name auto op = [](size_t tmp, const cli_arg& arg) {
auto nw = x.name.find(',') == std::string::npos return std::max(tmp, arg.helptext.size());
? x.name.size() + 2 // "--<name>" };
: x.name.size() + 5; // "-X [--<name>]" (minus trailing ",X") auto name_width = std::accumulate(xs.begin(), xs.end(), size_t{0}, op);
if (x.fun) {
nw += 4; // trailing " arg"
}
name_width = std::max(name_width, nw);
}
std::ostringstream oss; std::ostringstream oss;
oss << std::left; oss << std::left;
oss << "Allowed options:" << std::endl; oss << "Allowed options:" << std::endl;
for (auto& ca : xs) { for (auto& ca : xs) {
std::string lhs;
auto separator = ca.name.find(',');
if (separator == std::string::npos) {
lhs += "--";
lhs += ca.name;
} else {
lhs += "-";
lhs += ca.name.back();
lhs += " [--";
lhs += ca.name.substr(0, separator);
lhs += "]";
}
if (ca.fun) {
lhs += " arg";
}
oss << " "; oss << " ";
oss.width(static_cast<std::streamsize>(name_width)); oss.width(static_cast<std::streamsize>(name_width));
oss << lhs << " : " << ca.text << std::endl; oss << ca.helptext << " : " << ca.text << std::endl;
}
helptext = oss.str();
} }
auto helptext = oss.str();
if (opts.count("help") == 1) { if (opts.count("help") == 1) {
std::cout << helptext << std::endl; std::cout << helptext << std::endl;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment