53 using Row = std::pair<std::string, std::string>;
54 using Rows = std::vector<Row>;
57 Cli(std::string prog, std::string descr = {})
58 : prog_(
std::move(prog))
59 , descr_(
std::move(descr)) {}
66 Cli&
opt(T& target, std::string hint = {}, std::string sname = {}, std::string lname = {}, std::string descr = {}) {
67 static_assert(Is_Flag<T> || std::is_same_v<T, std::string> || Vec<T>::is
68 || Is_Num<T> || std::is_invocable_v<T&, std::string>,
69 "cannot bind this type to an option");
70 auto& o = opts_.emplace_back(std::move(sname), std::move(lname), std::move(hint), std::move(descr), grp_);
71 assert(Is_Flag<T> != o.takes_value() &&
"a flag takes no <hint>, anything else needs one");
72 assert(!(Is_Flag<T> && o.is_arg()) &&
"a flag needs a name");
73 assert((o.sname.empty() || o.sname.starts_with(
'-')) &&
"a short name starts with `-`");
74 assert((o.lname.empty() || o.lname.starts_with(
"--")) &&
"a long name starts with `--`");
76 o.dflt = dflt(target);
77 o.set = [&target](std::string_view s) {
return assign(target, s); };
81 Cli&
opt(
bool& target, std::string sname = {}, std::string lname = {}, std::string descr = {}) {
82 return opt<bool>(target,
"", std::move(sname), std::move(lname), std::move(descr));
88 Cli&
arg(T& target, std::string hint, std::string descr = {}) {
89 opt(target, std::move(hint), {}, {}, std::move(descr));
90 return opts_.back().dflt.clear(), *
this;
94 Cli&
help(
bool& target, std::string sname =
"-h", std::string lname =
"--help") {
95 return opt(target, std::move(sname), std::move(lname),
"Display this help and exit.");
100 assert(!opts_.empty() &&
"no option to apply a cardinality to");
101 return opts_.back().min = min, opts_.back().max = max, *
this;
105 Cli&
grp(std::string name) {
return grp_ = std::move(name), *
this; }
111 return sections_.emplace_back(std::move(title), std::move(head), std::move(rows)), *
this;
115 Cli&
epilog(std::string s) {
return epilog_ = std::move(s), *
this; }
119 std::optional<std::string>
parse(
int argc,
const char*
const* argv);
120 void help(std::ostream&)
const;
126 template<
class T>
struct Vec {
using Elem = T;
static constexpr bool is =
false; };
127 template<
class T>
struct Vec<
std::vector<T>> {
using Elem = T;
static constexpr bool is =
true; };
131 static constexpr bool Is_Num = std::is_integral_v<T> && !std::is_same_v<T, bool>;
135 static constexpr bool Is_Flag
136 = std::is_same_v<T, bool> || (std::is_invocable_v<T&, bool> && !std::is_invocable_v<T&, std::string>);
140 static std::string assign(T& t, std::string_view s) {
141 if constexpr (std::is_same_v<T, bool>) {
143 }
else if constexpr (std::is_same_v<T, std::string>) {
145 }
else if constexpr (Vec<T>::is) {
146 typename Vec<T>::Elem elem{};
147 if (
auto err = assign(elem, s); !err.empty())
return err;
148 t.emplace_back(std::move(elem));
149 }
else if constexpr (Is_Num<T>) {
150 auto begin = s.data(), end = begin + s.size();
151 if (
auto [ptr, ec] = std::from_chars(begin, end, t); ec != std::errc{} || ptr != end)
152 return std::format(
"'{}' is not a number", s);
153 }
else if constexpr (std::is_invocable_r_v<std::string, T&, std::string>) {
154 return t(std::string(s));
155 }
else if constexpr (std::is_invocable_v<T&, std::string>) {
165 static std::string dflt(
const T& t) {
166 if constexpr (std::is_same_v<T, std::string>)
168 else if constexpr (Is_Num<T>)
169 return std::format(
"{}", t);
175 std::string names(
bool pad =
false)
const;
176 std::string spec(
bool pad =
false)
const;
177 size_t width()
const {
return spec(
true).size(); }
178 bool is_arg()
const {
return sname.empty() && lname.empty(); }
179 bool takes_value()
const {
return !hint.empty(); }
180 std::string_view kind()
const {
return is_arg() ?
"argument" :
"option"; }
181 std::string_view label()
const {
return !lname.empty() ? lname : sname.empty() ? hint : sname; }
183 std::string sname, lname, hint, descr, grp, dflt;
184 std::function<std::string(std::string_view)> set;
187 size_t max = std::numeric_limits<size_t>::max();
192 std::string title, head;
196 std::string usage()
const;
199 std::vector<std::string_view> grps()
const;
201 Opt* find(std::string_view name);
203 std::string prog_, descr_, epilog_, grp_;
204 std::vector<Opt> opts_;
205 std::vector<Section> sections_;
Cli & section(std::string title, std::string head={}, Rows rows={})
A titled table of term/description rows that are not options - ENVIRONMENT, plugin arguments,...
Cli & help(bool &target, std::string sname="-h", std::string lname="--help")
The help flag - named -h/--help unless sname / lname say otherwise.
Cli & arg(T &target, std::string hint, std::string descr={})
A positional argument; hint names it in the help as <hint>.
Cli & opt(T &target, std::string hint={}, std::string sname={}, std::string lname={}, std::string descr={})
An option bound to target and named sname and/or lname - "-o" and "--output".
std::optional< std::string > parse(int argc, const char *const *argv)
Parses argc/argv; returns the error message - and nothing at all if all went well.
constexpr std::uint64_t pad(std::uint64_t offset, std::uint64_t align) noexcept
Rounds offset up to the next multiple of align.