cmdopt2: use required
flag
This commit is contained in:
parent
a63f4fef44
commit
a54ed01dcb
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user