You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

518 lines
18KB

  1. #include "./argument_parser.hpp"
  2. /// XXX: Refactor this after pulling debate:: out of dds
  3. #include <dds/dym.hpp>
  4. #include <boost/leaf/error.hpp>
  5. #include <boost/leaf/exception.hpp>
  6. #include <boost/leaf/on_error.hpp>
  7. #include <fmt/color.h>
  8. #include <fmt/format.h>
  9. #include <neo/scope.hpp>
  10. #include <set>
  11. using strv = std::string_view;
  12. using namespace debate;
  13. namespace {
  14. struct parse_engine {
  15. debate::detail::parser_state& state;
  16. const argument_parser* bottom_parser;
  17. // Keep track of how many positional arguments we have seen
  18. int positional_index = 0;
  19. // Keep track of what we've seen
  20. std::set<const argument*> seen{};
  21. auto current_arg() const noexcept { return state.current_arg(); }
  22. auto at_end() const noexcept { return state.at_end(); }
  23. void shift() noexcept { return state.shift(); }
  24. void see(const argument& arg) {
  25. auto did_insert = seen.insert(&arg).second;
  26. if (!did_insert && !arg.can_repeat) {
  27. BOOST_LEAF_THROW_EXCEPTION(invalid_repitition("Invalid repitition"));
  28. }
  29. }
  30. void run() {
  31. auto _ = boost::leaf::on_error([this] { return e_argument_parser{*bottom_parser}; });
  32. while (!at_end()) {
  33. parse_another();
  34. }
  35. // Parsed everything successfully. Cool.
  36. finalize();
  37. }
  38. std::optional<std::string> find_nearest_arg_spelling(std::string_view given) const noexcept {
  39. std::vector<std::string> candidates;
  40. // Only match arguments of the corrent type
  41. auto parser = bottom_parser;
  42. while (parser) {
  43. for (auto& arg : parser->arguments()) {
  44. for (auto& l : arg.long_spellings) {
  45. candidates.push_back("--" + l);
  46. }
  47. for (auto& s : arg.short_spellings) {
  48. candidates.push_back("-" + s);
  49. }
  50. }
  51. parser = parser->parent().pointer();
  52. }
  53. if (bottom_parser->subparsers()) {
  54. auto&& grp = *bottom_parser->subparsers();
  55. for (auto& p : grp._p_subparsers) {
  56. candidates.push_back(p.name);
  57. }
  58. }
  59. return dds::did_you_mean(given, candidates);
  60. }
  61. void parse_another() {
  62. auto given = current_arg();
  63. auto did_parse = try_parse_given(given);
  64. if (!did_parse) {
  65. neo_defer {
  66. auto dym = find_nearest_arg_spelling(given);
  67. if (dym) {
  68. boost::leaf::current_error().load(e_did_you_mean{*dym});
  69. }
  70. };
  71. BOOST_LEAF_THROW_EXCEPTION(unrecognized_argument("Unrecognized argument"),
  72. e_arg_spelling{std::string(given)});
  73. }
  74. }
  75. bool try_parse_given(const strv given) {
  76. if (given.size() < 2 || given[0] != '-') {
  77. if (try_parse_positional(given)) {
  78. return true;
  79. }
  80. return try_parse_subparser(given);
  81. } else if (given[1] == '-') {
  82. // Two hyphens is a long argument
  83. return try_parse_long(given.substr(2), given);
  84. } else {
  85. // A single hyphen, shorthand argument(s)
  86. return try_parse_short(given.substr(1), given);
  87. }
  88. }
  89. /*
  90. ## ####### ## ## ######
  91. ## ## ## ### ## ## ##
  92. ## ## ## #### ## ##
  93. ## ## ## ## ## ## ## ####
  94. ## ## ## ## #### ## ##
  95. ## ## ## ## ### ## ##
  96. ######## ####### ## ## ######
  97. */
  98. bool try_parse_long(strv tail, const strv given) {
  99. if (tail == "help") {
  100. BOOST_LEAF_THROW_EXCEPTION(help_request());
  101. }
  102. auto argset = bottom_parser;
  103. while (argset) {
  104. if (try_parse_long_1(*argset, tail, given)) {
  105. return true;
  106. }
  107. argset = argset->parent().pointer();
  108. }
  109. return false;
  110. }
  111. bool try_parse_long_1(const argument_parser& argset, strv tail, const strv) {
  112. for (const argument& cand : argset.arguments()) {
  113. auto matched = cand.try_match_long(tail);
  114. if (matched.empty()) {
  115. continue;
  116. }
  117. tail.remove_prefix(matched.size());
  118. shift();
  119. auto long_arg = fmt::format("--{}", matched);
  120. auto _ = boost::leaf::on_error(e_argument{cand}, e_arg_spelling{long_arg});
  121. see(cand);
  122. return dispatch_long(cand, tail, long_arg);
  123. }
  124. // None of the arguments matched
  125. return false;
  126. }
  127. bool dispatch_long(const argument& arg, strv tail, strv given) {
  128. if (arg.nargs == 0) {
  129. if (!tail.empty()) {
  130. // We should not have a value
  131. BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Argument does not expect a value"),
  132. e_wrong_val_num{1});
  133. }
  134. // Just a switch. Dispatch
  135. arg.action(given, given);
  136. return true;
  137. }
  138. // We expect at least one value
  139. neo_assert(invariant,
  140. tail.empty() || tail[0] == '=',
  141. "Invalid argparsing state",
  142. tail,
  143. given);
  144. if (!tail.empty()) {
  145. // Given with an '=', as in: '--long-option=value'
  146. tail.remove_prefix(1);
  147. // The remainder is a single value
  148. if (arg.nargs > 1) {
  149. BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Invalid number of values"),
  150. e_wrong_val_num{1});
  151. }
  152. arg.action(tail, given);
  153. } else {
  154. // Trailing words are arguments
  155. for (auto i = 0; i < arg.nargs; ++i) {
  156. if (at_end()) {
  157. BOOST_LEAF_THROW_EXCEPTION(invalid_arguments(
  158. "Invalid number of argument values"),
  159. e_wrong_val_num{i});
  160. }
  161. arg.action(current_arg(), given);
  162. shift();
  163. }
  164. }
  165. return true;
  166. }
  167. /*
  168. ###### ## ## ####### ######## ########
  169. ## ## ## ## ## ## ## ## ##
  170. ## ## ## ## ## ## ## ##
  171. ###### ######### ## ## ######## ##
  172. ## ## ## ## ## ## ## ##
  173. ## ## ## ## ## ## ## ## ##
  174. ###### ## ## ####### ## ## ##
  175. */
  176. bool try_parse_short(strv tail, const strv given) {
  177. if (tail == "h") {
  178. BOOST_LEAF_THROW_EXCEPTION(help_request());
  179. }
  180. auto argset = bottom_parser;
  181. while (argset) {
  182. auto new_tail = try_parse_short_1(*argset, tail, given);
  183. if (new_tail == tail) {
  184. // No characters were consumed...
  185. argset = argset->parent().pointer();
  186. } else {
  187. // Got one argument. Re-seek back to the bottom-most active parser
  188. argset = bottom_parser;
  189. tail = new_tail;
  190. }
  191. if (tail.empty()) {
  192. // We parsed the full group
  193. return true;
  194. }
  195. }
  196. // Did not match anything...
  197. return false;
  198. }
  199. strv try_parse_short_1(const argument_parser& argset, const strv tail, const strv) {
  200. for (const argument& cand : argset.arguments()) {
  201. auto matched = cand.try_match_short(tail);
  202. if (matched.empty()) {
  203. continue;
  204. }
  205. auto short_tail = tail.substr(matched.size());
  206. std::string short_arg = fmt::format("-{}", matched);
  207. auto _ = boost::leaf::on_error(e_argument{cand}, e_arg_spelling{short_arg});
  208. see(cand);
  209. return dispatch_short(cand, short_tail, short_arg);
  210. }
  211. // Didn't match anything. Return the original group unmodified
  212. return tail;
  213. }
  214. strv dispatch_short(const argument& arg, strv tail, strv spelling) {
  215. if (!arg.nargs) {
  216. // Just a switch. Consume a single character
  217. arg.action("", spelling);
  218. return tail;
  219. } else if (arg.nargs == 1) {
  220. // Want one value
  221. if (tail.empty()) {
  222. // The next argument is the value
  223. shift();
  224. if (at_end()) {
  225. BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Expected a value"));
  226. }
  227. arg.action(current_arg(), spelling);
  228. shift();
  229. // We consumed the whole group, so return empty as the remaining:
  230. return "";
  231. } else {
  232. // Consume the remainder of the argument as the value
  233. arg.action(tail, spelling);
  234. shift();
  235. return "";
  236. }
  237. } else {
  238. // Consume the next arguments
  239. if (!tail.empty()) {
  240. BOOST_LEAF_THROW_EXCEPTION(invalid_arguments(
  241. "Wrong number of argument values given"),
  242. e_wrong_val_num{1});
  243. }
  244. shift();
  245. for (auto i = 0; i < arg.nargs; ++i) {
  246. if (at_end()) {
  247. BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Wrong number of argument values"),
  248. e_wrong_val_num{i});
  249. }
  250. arg.action(current_arg(), spelling);
  251. shift();
  252. }
  253. return "";
  254. }
  255. }
  256. /*
  257. ######## ####### ###### #### ######## #### ####### ## ## ### ##
  258. ## ## ## ## ## ## ## ## ## ## ## ### ## ## ## ##
  259. ## ## ## ## ## ## ## ## ## ## #### ## ## ## ##
  260. ######## ## ## ###### ## ## ## ## ## ## ## ## ## ## ##
  261. ## ## ## ## ## ## ## ## ## ## #### ######### ##
  262. ## ## ## ## ## ## ## ## ## ## ## ### ## ## ##
  263. ## ####### ###### #### ## #### ####### ## ## ## ## ########
  264. */
  265. bool try_parse_positional(strv given) {
  266. int pos_idx = 0;
  267. for (auto& arg : bottom_parser->arguments()) {
  268. if (!arg.is_positional()) {
  269. continue;
  270. }
  271. if (pos_idx != this->positional_index) {
  272. // Not yet
  273. ++pos_idx;
  274. continue;
  275. }
  276. // We've found the next one that needs a value
  277. neo_assert(expects,
  278. arg.nargs == 1,
  279. "Positional arguments must have their nargs=1. For more than one "
  280. "positional, use multiple positional arguments objects.",
  281. arg.nargs,
  282. given,
  283. positional_index);
  284. // Just invoke the action
  285. auto _ = boost::leaf::on_error(e_arg_spelling{arg.preferred_spelling()});
  286. see(arg);
  287. arg.action(given, given);
  288. if (!arg.can_repeat) {
  289. // This argument isn't repeatable. Advance past it
  290. ++this->positional_index;
  291. // If an arg is repeatable, it will always be the "next positional" to parse,
  292. // and subsequent positionals are inherently unreachable.
  293. }
  294. shift();
  295. return true;
  296. }
  297. // No one accepted the value. We do not follow the chain of subcommands for positionals
  298. return false;
  299. }
  300. /*
  301. ###### ## ## ######## ######## ### ######## ###### ######## ########
  302. ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  303. ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  304. ###### ## ## ######## ######## ## ## ######## ###### ###### ########
  305. ## ## ## ## ## ## ######### ## ## ## ## ## ##
  306. ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
  307. ###### ####### ######## ## ## ## ## ## ###### ######## ## ##
  308. */
  309. bool try_parse_subparser(const strv given) {
  310. if (!bottom_parser->subparsers()) {
  311. return false;
  312. }
  313. auto& group = *bottom_parser->subparsers();
  314. for (auto& cand : group._p_subparsers) {
  315. if (cand.name == given) {
  316. // This parser is now the bottom of the chain
  317. if (group.action) {
  318. group.action(given, group.valname);
  319. }
  320. if (cand.action) {
  321. cand.action();
  322. }
  323. bottom_parser = &cand._p_parser;
  324. positional_index = 0;
  325. shift();
  326. return true;
  327. }
  328. }
  329. return false;
  330. }
  331. /*
  332. ######## #### ## ## ### ## #### ######## ########
  333. ## ## ### ## ## ## ## ## ## ##
  334. ## ## #### ## ## ## ## ## ## ##
  335. ###### ## ## ## ## ## ## ## ## ## ######
  336. ## ## ## #### ######### ## ## ## ##
  337. ## ## ## ### ## ## ## ## ## ##
  338. ## #### ## ## ## ## ######## #### ######## ########
  339. */
  340. void finalize() {
  341. auto argset = bottom_parser;
  342. while (argset) {
  343. finalize(*argset);
  344. argset = argset->parent().pointer();
  345. }
  346. if (bottom_parser->subparsers() && bottom_parser->subparsers()->required) {
  347. BOOST_LEAF_THROW_EXCEPTION(missing_required("Expected a subcommand"));
  348. }
  349. }
  350. void finalize(const argument_parser& argset) {
  351. for (auto& arg : argset.arguments()) {
  352. if (arg.required && !seen.contains(&arg)) {
  353. BOOST_LEAF_THROW_EXCEPTION(missing_required("Required argument is missing"),
  354. e_argument{arg});
  355. }
  356. }
  357. }
  358. };
  359. } // namespace
  360. void debate::detail::parser_state::run(const argument_parser& bottom) {
  361. parse_engine{*this, &bottom}.run();
  362. }
  363. argument& argument_parser::add_argument(argument arg) noexcept {
  364. _arguments.push_back(std::move(arg));
  365. return _arguments.back();
  366. }
  367. subparser_group& argument_parser::add_subparsers(subparser_group grp) noexcept {
  368. _subparsers.emplace(std::move(grp));
  369. _subparsers->_p_parent_ = this;
  370. return *_subparsers;
  371. }
  372. argument_parser& subparser_group::add_parser(subparser sub) {
  373. _p_subparsers.push_back(std::move(sub));
  374. auto& p = _p_subparsers.back()._p_parser;
  375. p._parent = _p_parent_;
  376. return p;
  377. }
  378. std::string argument_parser::usage_string(std::string_view progname) const noexcept {
  379. std::string subcommand_suffix;
  380. auto tail_parser = this;
  381. while (tail_parser) {
  382. for (auto& arg : tail_parser->arguments()) {
  383. if (arg.is_positional() && arg.required && tail_parser != this) {
  384. subcommand_suffix = " " + arg.preferred_spelling() + subcommand_suffix;
  385. }
  386. }
  387. if (!tail_parser->_name.empty()) {
  388. subcommand_suffix = " " + tail_parser->_name + subcommand_suffix;
  389. }
  390. tail_parser = tail_parser->_parent.pointer();
  391. }
  392. auto ret = fmt::format("Usage: {}{}", progname, subcommand_suffix);
  393. auto indent = ret.size() + 1;
  394. if (indent > 40) {
  395. ret.push_back('\n');
  396. indent = 10;
  397. ret.append(indent, ' ');
  398. }
  399. std::size_t col = indent;
  400. for (auto& arg : _arguments) {
  401. auto synstr = arg.syntax_string();
  402. if (col + synstr.size() > 79 && col > indent) {
  403. ret.append("\n");
  404. ret.append(indent - 1, ' ');
  405. col = indent - 1;
  406. }
  407. ret.append(" " + synstr);
  408. col += synstr.size() + 1;
  409. }
  410. if (subparsers()) {
  411. std::string subcommand_str = " {";
  412. auto& subs = subparsers()->_p_subparsers;
  413. for (auto it = subs.cbegin(); it != subs.cend();) {
  414. subcommand_str.append(it->name);
  415. ++it;
  416. if (it != subs.cend()) {
  417. subcommand_str.append(",");
  418. }
  419. }
  420. subcommand_str.append("}");
  421. if (col + subcommand_str.size() > 79 && col > indent) {
  422. ret.append("\n");
  423. ret.append(indent - 1, ' ');
  424. }
  425. ret.append(subcommand_str);
  426. }
  427. return ret;
  428. }
  429. std::string argument_parser::help_string(std::string_view progname) const noexcept {
  430. std::string ret;
  431. ret = usage_string(progname);
  432. ret.append("\n\n");
  433. if (!_description.empty()) {
  434. ret.append(_description);
  435. ret.append("\n\n");
  436. }
  437. bool any_required = false;
  438. for (auto& arg : arguments()) {
  439. if (!arg.required) {
  440. continue;
  441. }
  442. if (!any_required) {
  443. ret.append("required arguments:\n\n");
  444. }
  445. any_required = true;
  446. ret.append(arg.help_string());
  447. ret.append("\n");
  448. }
  449. bool any_non_required = false;
  450. for (auto& arg : arguments()) {
  451. if (arg.required) {
  452. continue;
  453. }
  454. if (!any_non_required) {
  455. ret.append("optional arguments:\n\n");
  456. }
  457. any_non_required = true;
  458. ret.append(arg.help_string());
  459. ret.append("\n");
  460. }
  461. if (subparsers()) {
  462. ret.append("Subcommands:\n\n");
  463. if (!subparsers()->description.empty()) {
  464. ret.append(fmt::format(" {}\n\n", subparsers()->description));
  465. }
  466. for (auto& sub : subparsers()->_p_subparsers) {
  467. ret.append(fmt::format(fmt::emphasis::bold, "{}", sub.name));
  468. ret.append("\n ");
  469. ret.append(sub.help);
  470. ret.append("\n\n");
  471. }
  472. }
  473. return ret;
  474. }