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