#include "./parser.hpp" #include "./args.hpp" #include 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; using namespace std::string_view_literals; if (contains ("--help"sv, cruft::view (argv, argc))) { usage (argc, argv, stdout); exit (EXIT_SUCCESS); } 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 static void usage ( int argc, char const * const* argv, std::vector const &positional, std::vector 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); }