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 {
std::string name;
std::optional<std::string> description;
bool required = false;
bool required_ = false;
using acceptor1_t = std::function<void(std::string_view)>;
std::optional<acceptor1_t> acceptor1;
@ -75,6 +75,18 @@ namespace cruft::cmdopt2 {
res.acceptor1 = _acceptor;
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
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];
if (!cursor or !*cursor)
@ -40,9 +40,9 @@ parser::parse_named (int argc, const char *const *argv) const
++cursor;
if (!*cursor)
throw std::runtime_error ("missing long argument name");
return parse_long (argc, argv);
return parse_long (argc, argv, found);
} else {
return parse_short (argc, argv);
return parse_short (argc, argv, found);
}
unreachable ();
@ -51,7 +51,7 @@ parser::parse_named (int argc, const char *const *argv) const
//-----------------------------------------------------------------------------
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 (strlen (argv[0]) >= 2);
@ -71,6 +71,8 @@ parser::parse_long (int argc, const char *const *argv) const
if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown long argument");
found[std::distance (m_keyword.begin (), pos)] = true;
if (eq) {
(*pos->acceptor1) (eq + 1);
return 1;
@ -98,7 +100,7 @@ parser::parse_long (int argc, const char *const *argv) const
//-----------------------------------------------------------------------------
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;
CHECK (argc >= 1);
@ -123,17 +125,23 @@ parser::parse_short (int argc, const char *const *argv) const
if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown short argument");
found[std::distance (m_keyword.begin (), pos)] = true;
if (pos->acceptor0) {
CHECK (!pos->acceptor1);
(*pos->acceptor0) ();
return 1;
}
CHECK (!pos->acceptor0);
if (argc < 2)
throw std::runtime_error ("Missing short argument value");
(*pos->acceptor1) (argv[1]);
return 2;
if (pos->acceptor1) {
CHECK (!pos->acceptor0);
if (argc < 2)
throw std::runtime_error ("Missing short argument value");
(*pos->acceptor1) (argv[1]);
return 2;
}
return 1;
}
// 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 ())
throw std::runtime_error ("Unknown short argument");
found[std::distance (m_keyword.begin (), pos)] = true;
if (pos->acceptor1)
throw std::runtime_error ("Missing short argument value");
(*pos->acceptor0) ();
if (pos->acceptor0)
(*pos->acceptor0) ();
}
return 1;
@ -162,8 +172,8 @@ parser::parse_short (int argc, const char *const *argv) const
int
parser::parse (int const argc, const char *const *argv)
{
if (argc <= 1)
return argc;
std::vector<int> found_positional (m_positional.size (), 0);
std::vector<int> found_keyword (m_keyword.size (), 0);
using namespace std::string_view_literals;
if (contains ("--help"sv, cruft::view (argv, argc))) {
@ -180,13 +190,28 @@ parser::parse (int const argc, const char *const *argv)
break;
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;
}
(*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;
}
@ -209,7 +234,7 @@ usage (
static char constexpr REQUIRED[2] { '<', '>' };
for (auto const &arg: keyword) {
auto const &delimiters = arg.required ? REQUIRED : OPTIONAL;
auto const &delimiters = arg.required () ? REQUIRED : OPTIONAL;
if (arg.short_) {
fmt::print (output, FMT_STRING (" {}-{}"), delimiters[0], *arg.short_);
@ -227,7 +252,7 @@ usage (
}
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]);
}

View File

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

View File

@ -93,10 +93,13 @@ test_combinations (cruft::TAP::logger &tap)
qux = decltype(qux) {};
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 (
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 (), " ")
);
}
@ -171,21 +174,61 @@ static void test_presence (cruft::TAP::logger &tap)
count = 0;
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 (
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 (), " ")
);
}
}
///////////////////////////////////////////////////////////////////////////////
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 ()
{
cruft::TAP::logger tap;
test_combinations (tap);
test_presence (tap);
test_required (tap);
return tap.status ();
}