libcruft-util/cmdopt2/parser.cpp

235 lines
6.0 KiB
C++
Raw Normal View History

#include "./parser.hpp"
#include "./arg.hpp"
using cruft::cmdopt2::parser;
///////////////////////////////////////////////////////////////////////////////
cruft::cmdopt2::positional&
parser::add (positional const &arg)&
{
return m_positional.emplace_back (std::move (arg));
}
//-----------------------------------------------------------------------------
cruft::cmdopt2::keyword&
parser::add (keyword const &arg)&
{
return m_keyword.emplace_back (std::move (arg));
}
///////////////////////////////////////////////////////////////////////////////
int
parser::parse_named (int argc, const char *const *argv) const
{
auto cursor = argv[0];
if (!cursor or !*cursor)
return 0;
if (*cursor++ != '-')
return 0;
if (!*cursor)
throw std::runtime_error ("missing argument name");
if (*cursor == '-') {
++cursor;
if (!*cursor)
throw std::runtime_error ("missing long argument name");
return parse_long (argc, argv);
} else {
return parse_short (argc, argv);
}
unreachable ();
}
//-----------------------------------------------------------------------------
int
parser::parse_long (int argc, const char *const *argv) const
{
CHECK (argc >= 1);
CHECK (strlen (argv[0]) >= 2);
CHECK (argv[0][0] == '-');
CHECK (argv[0][1] == '-');
auto const eq = strchr (argv[0], '=');
std::string_view const key (argv[0] + 2, eq ?: strlen (argv[0]) + argv[0]);
auto const pos = std::find_if (
m_keyword.begin (),
m_keyword.end (),
[&] (auto const &arg)
{
return arg.long_ and *arg.long_ == key;
});
if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown long argument");
if (eq) {
(*pos->acceptor1) (eq + 1);
return 1;
} else {
if (pos->acceptor0) {
CHECK (!pos->acceptor1);
(*pos->acceptor0) ();
return 1;
}
CHECK (pos->acceptor1);
if (argc < 2)
throw std::runtime_error ("Missing long arg value");
if (not argv[1] or not argv[1][0] or argv[1][0] == '-')
throw std::runtime_error ("Missing long arg value");
(*pos->acceptor1) (argv[1]);
return 2;
}
unreachable ();
}
//-----------------------------------------------------------------------------
int
parser::parse_short (int argc, const char *const *argv) const
{
(void)argc;
CHECK (argc >= 1);
CHECK (strlen (argv[0]) >= 2);
CHECK (argv[0][0] == '-');
CHECK (argv[0][1] != '-');
auto const len = strlen (argv[0]);
if (len < 1)
throw std::runtime_error ("Missing short arguments");
// Handle single short args with a potential for an associated value
if (len == 2) {
auto const pos = std::find_if (
m_keyword.begin (),
m_keyword.end (),
[&] (auto const &arg)
{
return arg.short_ and *arg.short_ == argv[0][1];
});
if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown short argument");
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;
}
// Handle strings of short arguments, eg: "-vvv"
for (std::size_t i = 1; i < len; ++i) {
auto const pos = std::find_if (
m_keyword.begin (),
m_keyword.end (),
[&] (auto const &arg)
{
return arg.short_ and *arg.short_ == argv[0][1];
});
if (pos == m_keyword.end ())
throw std::runtime_error ("Unknown short argument");
if (pos->acceptor1)
throw std::runtime_error ("Missing short argument value");
(*pos->acceptor0) ();
}
return 1;
}
//-----------------------------------------------------------------------------
int
parser::parse (int const argc, const char *const *argv)
{
if (argc <= 1)
return argc;
int arg_cursor = 1;
int pos_cursor = 0;
while (arg_cursor != argc) {
auto const arg = argv[arg_cursor];
if (!arg or !*arg)
break;
if (*arg == '-') {
arg_cursor += parse_named (argc - arg_cursor, argv + arg_cursor);
continue;
}
(*m_positional[pos_cursor].acceptor1) (argv[arg_cursor++]);
}
return arg_cursor;
}
///////////////////////////////////////////////////////////////////////////////
template <typename OutputT>
static void
usage (
int argc,
char const * const* argv,
std::vector<cruft::cmdopt2::positional> const &positional,
std::vector<cruft::cmdopt2::keyword> const &keyword,
OutputT &output
) {
fmt::print (output, "Usage:");
if (argc > 0)
fmt::print (output, " {}", argv[0]);
static char constexpr OPTIONAL[2] { '[', ']' };
static char constexpr REQUIRED[2] { '<', '>' };
for (auto const &arg: keyword) {
auto const &delimiters = arg.required ? REQUIRED : OPTIONAL;
if (arg.short_)
fmt::print (output, " {}-{}{}", delimiters[0], '-', *arg.short_, delimiters[1]);
if (arg.long_)
fmt::print (output, " {}--{}{}", delimiters[0], "--", *arg.long_, delimiters[1]);
}
for (auto const &arg: positional) {
auto const &delimiters = arg.required ? REQUIRED : OPTIONAL;
fmt::print (output, " {}{}{}", delimiters[0], arg.name, delimiters[1]);
}
fmt::print (output, "\n");
}
//-----------------------------------------------------------------------------
void
parser::usage (int argc, char const * const* argv, std::ostream &os) const
{
::usage (argc, argv, m_positional, m_keyword, os);
}
//-----------------------------------------------------------------------------
void
parser::usage (int argc, char const * const* argv, FILE *fp) const
{
::usage (argc, argv, m_positional, m_keyword, fp);
}