49template<
class T>
Opt opt(T&);
50template<
class T>
Opt opt(T&, std::string);
51template<
class T>
Opt arg(T&, std::string);
65inline constexpr bool always_false =
false;
73struct Elem<
std::vector<T>> {
78inline constexpr bool is_vec =
false;
80inline constexpr bool is_vec<std::vector<T>> =
true;
83inline constexpr bool is_num = std::is_integral_v<T> && !std::is_same_v<T, bool>;
87std::string scan(T& t, std::string_view s) {
88 if constexpr (std::is_same_v<T, std::string>) {
90 }
else if constexpr (is_vec<T>) {
91 typename Elem<T>::type elem{};
92 if (
auto err = scan(elem, s); !err.empty())
return err;
93 t.emplace_back(std::move(elem));
94 }
else if constexpr (is_num<T>) {
95 auto begin = s.data(), end = begin + s.size();
96 if (
auto [ptr, ec] = std::from_chars(begin, end, t); ec != std::errc{} || ptr != end)
97 return std::format(
"'{}' is not a number", s);
98 }
else if constexpr (std::is_invocable_r_v<std::string, T&, std::string>) {
99 return t(std::string(s));
100 }
else if constexpr (std::is_invocable_v<T&, std::string>) {
103 static_assert(always_false<T>,
"cannot bind this type to an option that takes a value");
109std::function<std::string()> dflt(T& t) {
110 if constexpr (std::is_same_v<T, std::string>)
111 return [&t] {
return t; };
112 else if constexpr (is_num<T>)
113 return [&t] {
return std::format(
"{}", t); };
124 Opt&
operator[](std::string name) {
return names_.emplace_back(std::move(name)), *
this; }
127 Opt&
operator()(std::string descr) {
return descr_ = std::move(descr), *
this; }
130 Opt&
cardinality(
size_t min,
size_t max) {
return min_ = min, max_ = max, *
this; }
134 std::string names()
const;
135 size_t width()
const;
136 std::string_view label()
const {
return names_.empty() ? std::string_view(hint_) : names_.back(); }
137 std::string_view kind()
const {
return names_.empty() ?
"argument" :
"option"; }
139 std::vector<std::string> names_;
140 std::string hint_, descr_, group_;
141 std::function<std::string(std::string_view)> set_;
142 std::function<std::string()> dflt_;
146 size_t max_ = std::numeric_limits<size_t>::max();
151 template<
class T>
friend Opt opt(T&);
152 template<
class T>
friend Opt opt(T&, std::string);
153 template<
class T>
friend Opt arg(T&, std::string);
161 o.set_ = [&target](std::string_view) -> std::string {
162 if constexpr (std::is_same_v<T, bool>)
175 o.hint_ = std::move(hint);
177 o.multi_ = detail::is_vec<T>;
178 o.set_ = [&target](std::string_view s) {
return detail::scan(target, s); };
179 o.dflt_ = detail::dflt(target);
187 auto o =
opt(target, std::move(hint));
193inline Opt help(
bool& target) {
return opt(target)[
"-h"][
"--help"](
"Display this help and exit."); }
199 Cli(std::string prog, std::string descr = {})
200 : prog_(
std::move(prog))
201 , descr_(
std::move(descr)) {}
204 if (o.group_.empty()) o.group_ = group_;
205 opts_.emplace_back(std::move(o));
220 Cli&
section(std::string title, std::string head, std::vector<std::pair<std::string, std::string>> rows) {
221 return sections_.emplace_back(std::move(title), std::move(head), std::move(rows)), *
this;
225 Cli&
epilog(std::string s) {
return epilog_ = std::move(s), *
this; }
229 std::optional<std::string>
parse(
int argc,
const char*
const* argv);
230 void help(std::ostream&)
const;
234 std::string usage()
const;
237 std::vector<std::string_view> groups()
const;
239 Opt* find(std::string_view name);
242 std::string title, head;
243 std::vector<std::pair<std::string, std::string>> rows;
246 std::string prog_, descr_, epilog_, group_;
247 std::vector<Opt> opts_;
248 std::vector<Section> sections_;
void help(std::ostream &) const
Renders the help for a terminal.
void markdown(std::ostream &) const
Renders the same information as Doxygen-flavored Markdown tables.
Cli(std::string prog, std::string descr={})
Cli & operator|(Group g) &
friend std::ostream & operator<<(std::ostream &os, const Cli &cli)
Cli & epilog(std::string s)
Text printed below the option list.
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.
Cli && operator|(Opt o) &&
Cli && operator|(Group g) &&
Cli & section(std::string title, std::string head, std::vector< std::pair< std::string, std::string > > rows)
A titled table of term/description rows that are not Opts - ENVIRONMENT, plugin arguments,...
One option or positional argument; build one with fe::cli::opt, fe::cli::arg, or fe::cli::help.
friend Opt arg(T &, std::string)
A positional argument; hint names it in the help as <hint>.
Opt & cardinality(size_t min, size_t max)
This Opt must occur at least min and at most max times.
friend Opt opt(T &)
A flag: sets target to true - or invokes it with true - each time it occurs.
Opt & operator()(std::string descr)
Sets the description shown in the help.
Opt & operator[](std::string name)
Adds name - "-o" for a short, "--output" for a long one.
A small command-line parser for a single command - no subcommands.
Group group(std::string name)
Opens a section named name that all following Cli options are listed under.
Opt help(bool &target)
The -h/--help flag; chain ["-?"] to give it further names.
Opt opt(T &)
A flag: sets target to true - or invokes it with true - each time it occurs.
Opt arg(T &, std::string)
A positional argument; hint names it in the help as <hint>.
Starts a new section in the help output; see fe::cli::group.