#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; } /////////////////////////////////////////////////////////////////////////////// void parser::usage (int argc, char const * const* argv, std::ostream &os) const { os << "Usage:"; if (argc > 0) os << ' ' << argv[0]; static char constexpr OPTIONAL[2] { '[', ']' }; static char constexpr REQUIRED[2] { '<', '>' }; for (auto const &arg: m_keyword) { auto const &delimiters = arg.required ? REQUIRED : OPTIONAL; if (arg.short_) os << ' ' << delimiters[0] << '-' << *arg.short_ << delimiters[1]; if (arg.long_) os << ' ' << delimiters[0] << "--" << *arg.long_ << delimiters[1]; } for (auto const &arg: m_positional) { auto const &delimiters = arg.required ? REQUIRED : OPTIONAL; os << ' ' << delimiters[0] << arg.name << delimiters[1]; } os << '\n'; }