cmdopt2: use required flag

This commit is contained in:
Danny Robson 2022-03-18 15:04:45 +10:00
parent a63f4fef44
commit a54ed01dcb
4 changed files with 106 additions and 26 deletions

View File

@ -22,7 +22,7 @@ namespace cruft::cmdopt2 {
struct argument { struct argument {
std::string name; std::string name;
std::optional<std::string> description; std::optional<std::string> description;
bool required = false; bool required_ = false;
using acceptor1_t = std::function<void(std::string_view)>; using acceptor1_t = std::function<void(std::string_view)>;
std::optional<acceptor1_t> acceptor1; std::optional<acceptor1_t> acceptor1;
@ -75,6 +75,18 @@ namespace cruft::cmdopt2 {
res.acceptor1 = _acceptor; res.acceptor1 = _acceptor;
return res; return res;
} }
bool required (void) const
{
return required_;
}
BaseT required (bool val) const
{
BaseT res = get ();
res.required_ = val;
return res;
}
}; };

View File

@ -25,7 +25,7 @@ parser::add (keyword const &arg)&
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
int int
parser::parse_named (int argc, const char *const *argv) const parser::parse_named (int argc, const char *const *argv, int *found) const
{ {
auto cursor = argv[0]; auto cursor = argv[0];
if (!cursor or !*cursor) if (!cursor or !*cursor)
@ -40,9 +40,9 @@ parser::parse_named (int argc, const char *const *argv) const
++cursor; ++cursor;
if (!*cursor) if (!*cursor)
throw std::runtime_error ("missing long argument name"); throw std::runtime_error ("missing long argument name");
return parse_long (argc, argv); return parse_long (argc, argv, found);
} else { } else {
return parse_short (argc, argv); return parse_short (argc, argv, found);
} }
unreachable (); unreachable ();
@ -51,7 +51,7 @@ parser::parse_named (int argc, const char *const *argv) const
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
int int
parser::parse_long (int argc, const char *const *argv) const parser::parse_long (int argc, const char *const *argv, int *found) const
{ {
CHECK (argc >= 1); CHECK (argc >= 1);
CHECK (strlen (argv[0]) >= 2); CHECK (strlen (argv[0]) >= 2);
@ -71,6 +71,8 @@ parser::parse_long (int argc, const char *const *argv) const
if (pos == m_keyword.end ()) if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown long argument"); throw std::runtime_error ("Unknown long argument");
found[std::distance (m_keyword.begin (), pos)] = true;
if (eq) { if (eq) {
(*pos->acceptor1) (eq + 1); (*pos->acceptor1) (eq + 1);
return 1; return 1;
@ -98,7 +100,7 @@ parser::parse_long (int argc, const char *const *argv) const
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
int int
parser::parse_short (int argc, const char *const *argv) const parser::parse_short (int argc, const char *const *argv, int *found) const
{ {
(void)argc; (void)argc;
CHECK (argc >= 1); CHECK (argc >= 1);
@ -123,17 +125,23 @@ parser::parse_short (int argc, const char *const *argv) const
if (pos == m_keyword.end ()) if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown short argument"); throw std::runtime_error ("Unknown short argument");
found[std::distance (m_keyword.begin (), pos)] = true;
if (pos->acceptor0) { if (pos->acceptor0) {
CHECK (!pos->acceptor1); CHECK (!pos->acceptor1);
(*pos->acceptor0) (); (*pos->acceptor0) ();
return 1; return 1;
} }
CHECK (!pos->acceptor0); if (pos->acceptor1) {
if (argc < 2) CHECK (!pos->acceptor0);
throw std::runtime_error ("Missing short argument value"); if (argc < 2)
(*pos->acceptor1) (argv[1]); throw std::runtime_error ("Missing short argument value");
return 2; (*pos->acceptor1) (argv[1]);
return 2;
}
return 1;
} }
// Handle strings of short arguments, eg: "-vvv" // Handle strings of short arguments, eg: "-vvv"
@ -147,11 +155,13 @@ parser::parse_short (int argc, const char *const *argv) const
}); });
if (pos == m_keyword.end ()) if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown short argument"); throw std::runtime_error ("Unknown short argument");
found[std::distance (m_keyword.begin (), pos)] = true;
if (pos->acceptor1) if (pos->acceptor1)
throw std::runtime_error ("Missing short argument value"); throw std::runtime_error ("Missing short argument value");
(*pos->acceptor0) (); if (pos->acceptor0)
(*pos->acceptor0) ();
} }
return 1; return 1;
@ -162,8 +172,8 @@ parser::parse_short (int argc, const char *const *argv) const
int int
parser::parse (int const argc, const char *const *argv) parser::parse (int const argc, const char *const *argv)
{ {
if (argc <= 1) std::vector<int> found_positional (m_positional.size (), 0);
return argc; std::vector<int> found_keyword (m_keyword.size (), 0);
using namespace std::string_view_literals; using namespace std::string_view_literals;
if (contains ("--help"sv, cruft::view (argv, argc))) { if (contains ("--help"sv, cruft::view (argv, argc))) {
@ -180,13 +190,28 @@ parser::parse (int const argc, const char *const *argv)
break; break;
if (*arg == '-') { if (*arg == '-') {
arg_cursor += parse_named (argc - arg_cursor, argv + arg_cursor); arg_cursor += parse_named (argc - arg_cursor, argv + arg_cursor, found_keyword.data ());
continue; continue;
} }
(*m_positional[pos_cursor].acceptor1) (argv[arg_cursor++]); found_positional[pos_cursor] = true;
auto &pos_arg = m_positional[pos_cursor];
if (pos_arg.acceptor1)
(*pos_arg.acceptor1) (argv[arg_cursor++]);
} }
for (int i = 0, last = std::ssize (m_positional); i < last; ++i)
if (m_positional[i].required () and !found_positional[i])
throw std::runtime_error (
fmt::format ("Missing required position argument {}", m_positional[i].name)
);
for (int i = 0, last = std::ssize (m_keyword); i < last; ++i)
if (m_keyword[i].required () and !found_keyword[i])
throw std::runtime_error (
fmt::format ("Missing required named argument {}", m_keyword[i].name)
);
return arg_cursor; return arg_cursor;
} }
@ -209,7 +234,7 @@ usage (
static char constexpr REQUIRED[2] { '<', '>' }; static char constexpr REQUIRED[2] { '<', '>' };
for (auto const &arg: keyword) { for (auto const &arg: keyword) {
auto const &delimiters = arg.required ? REQUIRED : OPTIONAL; auto const &delimiters = arg.required () ? REQUIRED : OPTIONAL;
if (arg.short_) { if (arg.short_) {
fmt::print (output, FMT_STRING (" {}-{}"), delimiters[0], *arg.short_); fmt::print (output, FMT_STRING (" {}-{}"), delimiters[0], *arg.short_);
@ -227,7 +252,7 @@ usage (
} }
for (auto const &arg: positional) { for (auto const &arg: positional) {
auto const &delimiters = arg.required ? REQUIRED : OPTIONAL; auto const &delimiters = arg.required () ? REQUIRED : OPTIONAL;
fmt::print (output, FMT_STRING (" {}{}{}"), delimiters[0], arg.name, delimiters[1]); fmt::print (output, FMT_STRING (" {}{}{}"), delimiters[0], arg.name, delimiters[1]);
} }

View File

@ -17,7 +17,7 @@
namespace cruft::cmdopt2 { namespace cruft::cmdopt2 {
class parser { class parser {
public: public:
int parse (int argc, char const* const* argv); int parse [[nodiscard]] (int argc, char const* const* argv);
positional& add (positional const&) &; positional& add (positional const&) &;
keyword& add (keyword const&) &; keyword& add (keyword const&) &;
@ -26,9 +26,9 @@ namespace cruft::cmdopt2 {
void usage (int argc, char const * const* argv, std::ostream&) const; void usage (int argc, char const * const* argv, std::ostream&) const;
private: private:
int parse_named (int argc, char const* const* argv) const; int parse_named (int argc, char const* const* argv, int*) const;
int parse_short (int argc, char const* const* argv) const; int parse_short (int argc, char const* const* argv, int*) const;
int parse_long (int argc, char const* const* argv) const; int parse_long (int argc, char const* const* argv, int*) const;
std::vector<positional> m_positional; std::vector<positional> m_positional;
std::vector<keyword> m_keyword; std::vector<keyword> m_keyword;

View File

@ -93,10 +93,13 @@ test_combinations (cruft::TAP::logger &tap)
qux = decltype(qux) {}; qux = decltype(qux) {};
verbose = false; verbose = false;
p.parse (int (t.args.size ()), t.args.data ()); auto const consumed = p.parse (int (t.args.size ()), t.args.data ());
tap.expect ( tap.expect (
foo == t.foo and bar == t.bar and qux == t.qux, consumed == int (t.args.size ())
and foo == t.foo
and bar == t.bar
and qux == t.qux,
"{}", fmt::join (t.args.begin (), t.args.end (), " ") "{}", fmt::join (t.args.begin (), t.args.end (), " ")
); );
} }
@ -171,21 +174,61 @@ static void test_presence (cruft::TAP::logger &tap)
count = 0; count = 0;
present = false; present = false;
p.parse (int (t.args.size ()), t.args.data ()); auto const consumed = p.parse (int (t.args.size ()), t.args.data ());
tap.expect ( tap.expect (
count == t.count and present == t.present, consumed == int (t.args.size ())
and count == t.count
and present == t.present,
"{}", fmt::join (t.args.begin (), t.args.end (), " ") "{}", fmt::join (t.args.begin (), t.args.end (), " ")
); );
} }
} }
///////////////////////////////////////////////////////////////////////////////
static void
test_required (cruft::TAP::logger &tap)
{
static const struct {
std::vector<char const*> args;
bool success;
} TESTS[] = {
{ .args = { "cmd", }, .success = false },
{ .args = { "cmd", "-y" }, .success = true },
{ .args = { "cmd", "-n" }, .success = false },
};
using namespace cruft::cmdopt2;
parser p;
p.add (keyword::create ("y").flag ('y').required (true ).ignore ());
p.add (keyword::create ("n").flag ('n').required (false).ignore ());
for (auto const &t: TESTS) {
bool success;
try {
auto const consumed = p.parse (int (t.args.size ()), t.args.data ());
bool const completed = consumed == int (t.args.size ());
success = completed == t.success;
} catch (...) {
success = not t.success;
}
tap.expect (
success,
"{}",
fmt::join (t.args.begin (), t.args.end (), " ")
);
}
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
int main () int main ()
{ {
cruft::TAP::logger tap; cruft::TAP::logger tap;
test_combinations (tap); test_combinations (tap);
test_presence (tap); test_presence (tap);
test_required (tap);
return tap.status (); return tap.status ();
} }