#include "./parser.hpp" #include "./args.hpp" #include using cruft::cmdopt2::parser; /////////////////////////////////////////////////////////////////////////////// parser::parser (char const *_description) : m_description (_description) { ; } /////////////////////////////////////////////////////////////////////////////// cruft::cmdopt2::positional_t& parser::add (positional_t const &arg)& { return m_positional.emplace_back (std::move (arg)); } //----------------------------------------------------------------------------- cruft::cmdopt2::keyword_t& parser::add (keyword_t const &arg)& { return m_keyword.emplace_back (std::move (arg)); } /////////////////////////////////////////////////////////////////////////////// int parser::parse_named (int argc, const char *const *argv, int *found) 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, found); } else { return parse_short (argc, argv, found); } unreachable (); } //----------------------------------------------------------------------------- int parser::parse_long (int argc, const char *const *argv, int *found) 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"); found[std::distance (m_keyword.begin (), pos)] = true; 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, int *found) 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"); found[std::distance (m_keyword.begin (), pos)] = true; if (pos->acceptor0) { CHECK (!pos->acceptor1); (*pos->acceptor0) (); return 1; } 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" 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"); found[std::distance (m_keyword.begin (), pos)] = true; if (pos->acceptor1) throw std::runtime_error ("Missing short argument value"); if (pos->acceptor0) (*pos->acceptor0) (); } return 1; } //----------------------------------------------------------------------------- int parser::parse (int const argc, const char *const *argv) { std::vector found_positional (m_positional.size (), 0); std::vector found_keyword (m_keyword.size (), 0); 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, found_keyword.data ()); continue; } found_positional[pos_cursor] = true; auto &pos_arg = m_positional[pos_cursor]; if (pos_arg.acceptor1) (*pos_arg.acceptor1) (argv[arg_cursor]); arg_cursor++; } for (int i = 0, last = int (m_positional.size ()); 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 = int (m_keyword.size ()); 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; } /////////////////////////////////////////////////////////////////////////////// void parser::usage (int argc, char const * const* argv, FILE *fp) const { fmt::print (fp, FMT_STRING("{}\nUsage:"), m_description); if (argc > 0) fmt::print (fp, " {}", 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_) { fmt::print (fp, FMT_STRING (" {}-{}"), delimiters[0], *arg.short_); if (arg.acceptor1) fmt::print (fp, FMT_STRING (" <{}>"), arg.name); fmt::print (fp, FMT_STRING ("{}"), delimiters[1]); } if (arg.long_) { fmt::print (fp, FMT_STRING (" {}--{}"), delimiters[0], *arg.long_); if (arg.acceptor1) fmt::print (fp, FMT_STRING (" <{}>"), arg.name); fmt::print (fp, FMT_STRING ("{}"), delimiters[1]); } } for (auto const &arg: m_positional) { auto const &delimiters = arg.required () ? REQUIRED : OPTIONAL; fmt::print (fp, FMT_STRING (" {}{}{}"), delimiters[0], arg.name, delimiters[1]); } fmt::print (fp, "\n"); }