Euphoria
argparse.cc
Go to the documentation of this file.
1 #include "core/argparse.h"
2 
3 #include <iostream>
4 #include <iomanip>
5 #include <algorithm>
6 
7 #include "base/cint.h"
8 #include "base/stringutils.h"
9 #include "base/stringmerger.h"
10 #include "core/table.h"
11 #include "core/wordwrap.h"
12 #include "base/functional.h"
13 
14 
15 /*
16 
17  argparse issues & todos:
18 
19  * less 'passive agressive' --help suggestion. only suggest -h when it's the "first" argument
20 
21  * assert invalid setups and argument name collisions
22 
23  * help should optional expect a argument name to print detailed help
24 
25  * generic OnComplete unit test
26 
27  * instead of .Add("arg").Help("") perhaps change to .Add(Arg{"arg"}.Help("")) so chainging arguments is possible
28 
29  * extra validators combinable with | that adds more conditions (files must exists, strings can be empty, numbers must be in range etc)
30 
31  * support python @ append arguments from file "arguments"
32 
33  * support reading palettes and rgb(a)s from commandline, including common palette files
34 
35  * add real-world tests in unit tests, like fake-git and fake-compiler
36 
37  * replae ' with ` in help output
38 
39  * if bored, get ideas from https://www.npmjs.com/package/commander
40 
41 */
42 
43 
45 {
46  namespace
47  {
48  bool
49  is_optional_argument(const std::string& name)
50  {
51  return name[0] == '-';
52  }
53 
54  bool
55  is_valid_argument(const std::string& str)
56  {
57  if (str.empty())
58  {
59  return false;
60  }
61 
62  constexpr auto valid_argument_name_characters = "abcdefghijklmnopqrstuvwxyz-_0123456789";
63  const auto first_invalid_character = to_lower(str).find_first_not_of(valid_argument_name_characters);
64  if (first_invalid_character != std::string::npos)
65  {
66  return false;
67  }
68 
69  return true;
70  }
71  }
72 
73 
74  FileOutput::FileOutput(const std::string& o) : file(o), single(!(ends_with(o, "/") || ends_with(o, "\\")))
75  {}
76 
77 
78  std::string
80  {
81  if(single)
82  {
83  return file;
84  }
85 
86  index += 1;
87  if(print)
88  {
89  fmt::print("Generating {}...\n", index);
90  }
91 
92  // todo(Gustav): provide option for file extension
93  return fmt::format("{}{:0>5}.png", file, index);
94  }
95 
96 
97  void
99  {
100  if(single) { return; }
101  // std::filesystem::create_directories(file);
102  }
103 
104 
105  std::string to_string(const ParseResult& pr)
106  { return fmt::format("{}({})", from_enum_to_string(pr.internal_type), pr.return_value); }
107 
108  bool
109  operator==(const ParseResult& lhs, const ParseResult& rhs)
110  {
111  return lhs.internal_type == rhs.internal_type && lhs.return_value == rhs.return_value;
112  }
113 
114 
115  bool
116  operator!=(const ParseResult& lhs, const ParseResult& rhs)
117  {
118  return !(lhs == rhs);
119  }
120 
121 
122  NameAndArguments::NameAndArguments(const std::string& n, const std::vector<std::string>& a)
123  : name(n)
124  , arguments(a)
125  {
126  }
127 
128 
130  NameAndArguments::extract(int argc, char* argv[])
131  {
132  auto ret = NameAndArguments{argv[0] , {}};
133 
134  for (int index = 1; index < argc; index += 1)
135  {
136  ret.arguments.emplace_back(argv[index]);
137  }
138 
139  return ret;
140  }
141 
142 
144  {
145  return fmt::format("{} [{}]", args.name, fmt::join(args.arguments, ", "));
146  }
147 
148 
150  : arguments(a)
151  , next_position(0)
152  {
153  }
154 
155 
156  bool
158  {
160  }
161 
162 
163  std::string
165  {
166  if (has_more())
167  {
168  const auto arg = arguments.arguments[next_position];
169  return arg;
170  }
171  else
172  {
173  return "";
174  }
175  }
176 
177 
178  std::string
180  {
181  const auto arg = peek();
182  next_position += 1;
183  return arg;
184  }
185 
186 
187  void
189  {
190  ASSERT(next_position > 0);
191  next_position -= 1;
192  }
193 
194 
195 
196 
197 
198 
199 
200  void
201  ConsolePrinter::print_error(const std::string& line)
202  {
203  std::cerr << line << "\n";
204  }
205 
206 
207  void
208  ConsolePrinter::print_info(const std::string& line)
209  {
210  std::cout << line << "\n";
211  }
212 
213 
214  void
216  (
217  Runner* runner,
218  ParserBase* base,
219  const std::string& error_message
220  )
221  {
222  runner->printer->print_info
223  (
225  (
226  runner->arguments->arguments
227  )
228  );
229  runner->printer->print_error
230  (
231  error_message
232  );
233  }
234 
235 
236  Name::Name(const char* nn)
237  : names(split(nn, ','))
238  {
239  for (auto& n : names)
240  {
241  n = trim(n);
242  }
243 
244  ASSERTX(validate().empty(), validate());
245  }
246 
247 
248  std::string
249  Name::validate() const
250  {
251  if (names.empty()) { return "no names found"; }
252  if (is_optional())
253  {
254  for (const auto& n : names)
255  {
256  if (is_optional_argument(n) == false)
257  {
258  return "arguments are optional but '" + n + "' is not";
259  }
260 
261  if (is_valid_argument(n) == false)
262  {
263  return "optional arguments '" + n + "' is not considered valid";
264  }
265  }
266  }
267  else
268  {
269  const auto n = names[0];
270  if (is_valid_argument(n) == false)
271  {
272  return "positional arguments '" + n + "' is not considered valid";
273  }
274  }
275 
276  return "";
277  }
278 
279 
280  bool
281  Name::is_optional() const
282  {
283  if (names.empty())
284  {
285  return false;
286  }
287 
288  if (names.size() > 1)
289  {
290  return true;
291  }
292 
293  return is_optional_argument(names[0]);
294  }
295 
296 
297 
298 
299 
300 
301  Argument&
302  Argument::set_nargs(const std::string& na)
303  {
304  nargs = na;
305  return *this;
306  }
307 
308 
309  Argument&
310  Argument::set_help(const std::string& h)
311  {
312  help = h;
313  return *this;
314  }
315 
316 
317  Argument&
318  Argument::set_allow_before_positionals()
319  {
320  allow_before_positionals = true;
321  return *this;
322  }
323 
324 
325  ArgumentAndName::ArgumentAndName(const argparse::Name& n, std::shared_ptr<argparse::Argument> a)
326  : name(n)
327  , argument(a)
328  {
329  }
330 
331 
333  : callback_function(cb)
334  {
335  }
336 
337 
338  bool
340  {
341  return false;
342  }
343 
344 
345  std::optional<std::string>
347  {
348  return std::nullopt;
349  }
350 
351 
352 
355  (
356  Runner* runner,
357  const std::string&,
358  ParserBase*
359  )
360  {
361  return callback_function(runner);
362  }
363 
364 
366  : callback_function(cb)
367  , describe_function(d)
368  {
369  }
370 
371 
372  bool
374  {
375  return true;
376  }
377 
378 
379  std::optional<std::string>
381  {
382  return describe_function();
383  }
384 
385 
388  (
389  Runner* runner,
390  const std::string& name,
391  ParserBase* caller
392  )
393  {
394  // eat all arguments!
395  while (runner->arguments->has_more())
396  {
397  auto res = callback_function
398  (
399  runner,
400  name,
401  caller,
402  runner->arguments->read()
403  );
404  if(res != argparse::ok)
405  {
406  return res;
407  }
408  }
409  return argparse::ok;
410  }
411 
412 
414  : callback_function(cb)
415  , describe_function(d)
416  {
417  }
418 
419 
420  bool
422  {
423  return true;
424  }
425 
426 
427  std::optional<std::string>
429  {
430  return describe_function();
431  }
432 
433 
436  (
437  Runner* runner,
438  const std::string& name,
439  ParserBase* caller
440  )
441  {
442  if (runner->arguments->has_more())
443  {
444  auto res = callback_function
445  (
446  runner,
447  name,
448  caller,
449  runner->arguments->read()
450  );
451  return res;
452  }
453  else
454  {
456  (
457  runner,
458  caller,
459  fmt::format("missing value for '{}'", name)
460  );
461  return argparse::error;
462  }
463  }
464 
465 
467  {
468  names.emplace_back(str);
469  }
470 
471 
473  (
474  const SubParserNames& n,
475  const std::string& h,
477  )
478  : names(n)
479  , help(h)
480  , callback(cb)
481  {
482  }
483 
484 
486  (
487  const std::string& t,
488  ParserBase* o
489  )
490  : title(t)
491  , owner(o)
492  {
493  }
494 
495 
496  void
498  (
499  const SubParserNames& names,
500  const std::string& desc,
502  )
503  {
504  auto container = std::make_shared<SubParserContainer>(names, desc, sub);
505  parsers.emplace_back(container);
506  for(const auto& name : names.names)
507  {
508  owner->subparsers.add(name, container);
509  }
510  }
511 
512 
513  void
515  (
516  const SubParserNames& names,
518  )
519  {
520  add(names, "", sub);
521  }
522 
523 
524  ParserBase::ParserBase(const std::string& d)
525  : description(d)
526  {
528  (
529  "-h, --help",
530  std::make_shared<ArgumentNoValue>
531  (
532  [this](Runner* runner)
533  {
534  print_help(runner->printer, runner->arguments->arguments);
535  return argparse::quit;
536  }
537  )
538  )
540  .set_help("show this help message and exit")
541  ;
542  }
543 
544 
545 
546 
547 
548 
549  std::string
551  {
552  auto arg_to_string = [](const ArgumentAndName& aa)
553  {
554  if( aa.name.is_optional() )
555  {
556  // todo(Gustav): include any attributes (vector input)
557  const auto first_name = aa.name.names[0];
558 
559  if(aa.argument->have_nargs())
560  {
561  return fmt::format("[{} {}]", first_name, aa.argument->nargs);
562  }
563  else
564  {
565  return fmt::format("[{}]", first_name);
566  }
567  }
568  else
569  {
570  return to_upper(aa.name.names[0]);
571  }
572  };
573  auto ret = std::vector<std::string>{"usage:", get_calling_name(args)};
574 
575  for(auto& a: optional_argument_list)
576  {
577  if(a.argument->allow_before_positionals == true)
578  {
579  ret.emplace_back(arg_to_string(a));
580  }
581  }
582 
583  for(auto& a: positional_argument_list)
584  {
585  ret.emplace_back(arg_to_string(a));
586  }
587 
588  for(auto& a: optional_argument_list)
589  {
590  if(a.argument->allow_before_positionals == false)
591  {
592  ret.emplace_back(arg_to_string(a));
593  }
594  }
595 
596  if(subparser_groups.empty() == false)
597  {
598  ret.emplace_back("<command> [<args>]");
599  }
600 
601  return string_mergers::space.merge(ret);
602  }
603 
604  namespace
605  {
606  // msvc and clang disagrees on if capturing
607  // constexpr in lambdas is necessary or not
608  constexpr int global_max_name_length = 20;
609  constexpr int global_max_help_length = 80;
610  }
611 
612  void
613  ParserBase::print_help(std::shared_ptr<Printer> printer, const NameAndArguments& args)
614  {
616 
617  // table functions
618  int max_name_length = 0;
619 
620  const auto add = [&max_name_length]
621  (
622  StringTable* table,
623  const std::string& name,
624  const std::string& desc,
625  const std::optional<std::string>& second_line
626  )
627  {
628  const auto values = std::vector<std::string>{name, desc};
629  table->add_row(values);
630  if(second_line)
631  {
632  table->add_row({"", *second_line});
633  }
634 
635  max_name_length = std::min
636  (
637  std::max
638  (
639  max_name_length,
640  c_sizet_to_int(name.length())
641  ),
642  global_max_name_length
643  );
644  };
645 
646  const auto print = [printer, &max_name_length](const StringTable& table)
647  {
648  auto t = StringTable{};
649  for(int y=0; y<table.get_height(); y+=1)
650  {
651  const auto names = get_word_wrapped
652  (
653  table[{0, y}],
654  [max_name_length](const std::string& s)
655  {
656  return c_sizet_to_int(s.size()) <= max_name_length;
657  }
658  );
659  const auto helps = get_word_wrapped
660  (
661  table[{1, y}],
662  [](const std::string& s)
663  {
664  return s.size() <= global_max_help_length;
665  }
666  );
667  const auto rows = zip_longest(names, helps);
668  for(auto [name,help]: rows)
669  {
670  t.add_row({name, help});
671  }
672  }
673  for(int y=0; y<t.get_height(); y+=1)
674  {
675  constexpr auto indent = " ";
676  constexpr auto space = " ";
677 
678  const auto name = t[{0, y}];
679  const auto help = t[{1, y}];
680 
681  const auto name_length = c_sizet_to_int(name.length());
682  if(name_length > max_name_length)
683  {
684  printer->print_info(indent + name);
685  const auto extra_space = std::string(max_name_length, ' ');
686  printer->print_info(indent + extra_space + space + help);
687  }
688  else
689  {
690  const auto extra_space = std::string(max_name_length - name_length, ' ');
691  printer->print_info(indent + name + extra_space + space + help);
692  }
693  }
694  };
695 
696  // collect data
697 
698  auto positionals = StringTable{};
699  for (auto& a : positional_argument_list)
700  {
701  add
702  (
703  &positionals,
704  a.name.names[0],
705  a.argument->help,
706  a.argument->get_second_line()
707  );
708  }
709 
710  auto optionals = StringTable{};
711  for (auto& a : optional_argument_list)
712  {
713  const auto names = string_mergers::comma.merge(a.name.names);
714  const auto names_with_narg = a.argument->have_nargs() == false
715  ? names
716  : fmt::format("{} {}", names, a.argument->nargs)
717  ;
718 
719  const auto desc = a.argument->default_value.empty() == false
720  ? fmt::format("{} (default: {})", a.argument->help, a.argument->default_value)
721  : a.argument->help
722  ;
723 
724  add
725  (
726  &optionals,
727  names_with_narg,
728  desc,
729  a.argument->get_second_line()
730  );
731  }
732 
733  auto subs = std::vector<StringTable>{};
734  for(const auto& group: subparser_groups)
735  {
736  auto sub = StringTable{};
737  for(const auto& parser: group->parsers)
738  {
739  const auto names = string_mergers::comma.merge
740  (
741  parser->names.names
742  );
743  add(&sub, names, parser->help, std::nullopt);
744  }
745  subs.emplace_back(sub);
746  }
747 
748  // print all tables now
749  printer->print_info(generate_usage_string(args));
750  if (description.empty() == false)
751  {
752  printer->print_info("");
753  printer->print_info(description);
754  }
755 
756  if (positionals.get_height() != 0)
757  {
758  printer->print_info("");
759  printer->print_info("positional arguments:");
760  print(positionals);
761  }
762 
763  if(optionals.get_height() != 0)
764  {
765  printer->print_info("");
766  printer->print_info("optional arguments:");
767  print(optionals);
768  }
769 
770  if(!subs.empty())
771  {
772  bool is_first_group = true;
773  printer->print_info("");
774  auto sub = subs.begin();
775  for(const auto& group: subparser_groups)
776  {
777  if(is_first_group == false)
778  {
779  printer->print_info("");
780  }
781 
782  printer->print_info(group->title + ":");
783 
784  print(*sub);
785  sub +=1 ;
786 
787  is_first_group = false;
788  }
789  }
790  }
791 
792 
793  std::string
795  {
796  const auto n = trim_left(name.names[0], "-");
797  return to_upper(n);
798  }
799 
800 
801  Argument&
802  ParserBase::add_argument(const Name& name, std::shared_ptr<Argument> argument)
803  {
804  ASSERT
805  (
807  "It looks like you are adding argument during parsing... "
808  "are you using the wrong parser in a OnComplete?"
809  );
810  argument->nargs = create_default_nargs(name);
811  if (name.is_optional())
812  {
813  for (const auto& n : name.names)
814  {
815  optional_arguments[n] = argument;
816  }
817  optional_argument_list.emplace_back(name, argument);
818  }
819  else
820  {
821  ASSERT(argument->have_nargs());
822  positional_argument_list.emplace_back(name, argument);
823  }
824  return *argument;
825  }
826 
827 
828  Argument&
829  ParserBase::add_void_function(const Name& name, std::function<void()> void_function)
830  {
831  return add_argument(name, std::make_shared<ArgumentNoValue>([void_function](Runner*) {void_function(); return argparse::ok; }));
832  }
833 
834 
835  Argument&
836  ParserBase::set_true(const Name& name, bool* target)
837  {
838  *target = false;
839  return set_const(name, target, true);
840  }
841 
842 
843  Argument&
844  ParserBase::set_false(const Name& name, bool* target)
845  {
846  *target = true;
847  return set_const(name, target, false);
848  }
849 
850 
851  void
853  {
855  }
856 
857 
858  std::shared_ptr<SubParserGroup>
859  ParserBase::add_sub_parsers(const std::string& name)
860  {
862  auto group = std::make_shared<SubParserGroup>(name, this);
863  subparser_groups.emplace_back(group);
864  return group;
865  }
866 
867 
868  std::shared_ptr<Argument>
869  ParserBase::find_argument(const std::string& name) const
870  {
871  const auto found = optional_arguments.find(name);
872  if(found == optional_arguments.end())
873  {
874  return nullptr;
875  }
876  else
877  {
878  return found->second;
879  }
880  }
881 
882 
883  namespace
884  {
885  struct ArgumentParser
886  {
887  ParserBase* base = nullptr;
888  argparse::Runner* runner = nullptr;
890  bool found_subparser = false;
891 
892  [[nodiscard]] bool
893  has_more_positionals() const;
894 
895  [[nodiscard]] std::vector<std::string>
896  get_positionals_left() const;
897 
898  void
899  print_error(const std::string& error_message) const;
900 
901  [[nodiscard]] std::optional<ParseResult>
902  try_parse_important_optional() const;
903 
904  std::optional<ParseResult>
905  parse_one_positional();
906 
907  std::optional<ParseResult>
908  parse_sub_command(const std::string& arg);
909 
910  std::optional<ParseResult>
911  parse_one_optional();
912 
913  std::optional<ParseResult>
914  parse_one_arg();
915  };
916 
917 
918  bool
919  ArgumentParser::has_more_positionals() const
920  {
921  const auto positionals_size = c_sizet_to_int
922  (
924  );
925  return positional_index < positionals_size;
926  }
927 
928 
929  std::vector<std::string>
930  ArgumentParser::get_positionals_left() const
931  {
932  if(has_more_positionals() == false) { return {}; }
933 
934  std::vector<std::string> positionals;
935 
936  const auto& list = base->positional_argument_list;
937  for
938  (
939  auto pos = list.begin()+positional_index;
940  pos != list.end();
941  pos += 1
942  )
943  {
944  positionals.emplace_back(to_upper(pos->name.names[0]));
945  }
946 
947  return positionals;
948  }
949 
950 
951  void
952  ArgumentParser::print_error(const std::string& error_message) const
953  {
954  print_parse_error(runner, base, error_message);
955  }
956 
957 
958  std::optional<ParseResult>
959  ArgumentParser::try_parse_important_optional() const
960  {
961  // first, peek at the next commandline argument
962  // and check for optionals that are allowed before positionals
963  const auto arg = base->find_argument(runner->arguments->peek());
964  if(arg != nullptr && arg->allow_before_positionals)
965  {
966  // we have matched a optional, read and parse it!
967  const auto matched_name = runner->arguments->read();
968  auto arg_parse_result = arg->parse_arguments
969  (
970  runner,
971  matched_name,
972  base
973  );
974  return arg_parse_result;
975  }
976  else
977  {
978  // the peeked argument isn't a important optional
979  // handle positional
980  return std::nullopt;
981  }
982  }
983 
984  std::optional<ParseResult>
985  ArgumentParser::parse_one_positional()
986  {
988  auto arg_parse_result = match.argument->parse_arguments
989  (
990  runner,
991  match.name.names[0],
992  base
993  );
994  if (arg_parse_result != argparse::ok)
995  {
996  return arg_parse_result;
997  }
998  positional_index += 1;
999 
1000  return std::nullopt;
1001  }
1002 
1004  get_parser_style(ParserBase* base, bool from_self=true)
1005  {
1006  if(base == nullptr)
1007  {
1008  return SubParserStyle::greedy;
1009  }
1010 
1011  ParserBase* parent = base->get_parent_or_null();
1012  if(from_self && parent==nullptr)
1013  {
1014  return SubParserStyle::greedy;
1015  }
1016 
1018  {
1019  return get_parser_style(parent, false);
1020  }
1021  else
1022  {
1023  return base->parser_style;
1024  }
1025  }
1026 
1027 
1028  std::optional<ParseResult>
1029  ArgumentParser::parse_sub_command(const std::string& arg)
1030  {
1031  auto match = base->subparsers.match(arg, 3);
1032  if(match.single_match == false)
1033  {
1034  // todo(Gustav): check if this accepts invalid and
1035  // calls on_complete() on invalid input
1036  if(get_parser_style(base) == SubParserStyle::greedy)
1037  {
1038  if(match.names.empty())
1039  {
1040  print_error
1041  (
1042  fmt::format("'{}' was unexpected", arg)
1043  );
1044  return argparse::error;
1045  }
1046 
1047  const auto names = add_quotes_and_combine_with_english_or(match.names);
1048 
1049  // todo(Gustav): switch between 'did you mean' and
1050  // 'could be either' depending on how big the
1051  // edit distance is?
1052  print_error
1053  (
1054  fmt::format("Invalid command '{}', did you mean {}?", arg, names)
1055  );
1056  return argparse::error;
1057  }
1058  else
1059  {
1060  // go back one step
1061  runner->arguments->undo_read();
1062 
1063  if(base->on_complete_function.has_value())
1064  {
1065  return base->on_complete_function.value()();
1066  }
1067 
1068  return argparse::ok;
1069  }
1070  }
1071  found_subparser = true;
1072 
1073  auto container = match.values[0];
1074  const auto calling_name = base->get_calling_name
1075  (
1076  runner->arguments->arguments
1077  );
1078  auto sub = SubParser
1079  {
1080  container->help,
1081  base,
1082  runner,
1083  calling_name + " " + arg
1084  };
1085  const auto subresult = container->callback(&sub);
1086  if
1087  (
1088  subresult == argparse::ok &&
1089  get_parser_style(base) == SubParserStyle::greedy
1090  )
1091  {
1092  // continue here
1093  return std::nullopt;
1094  }
1095  else
1096  {
1097  return subresult;
1098  }
1099  }
1100 
1101  std::optional<ParseResult>
1102  ArgumentParser::parse_one_optional()
1103  {
1104  const auto arg = runner->arguments->read();
1105  if(is_optional_argument(arg))
1106  {
1107  auto match = base->find_argument(arg);
1108  if (match == nullptr)
1109  {
1110  const auto closest_match =
1111  std::min_element
1112  (
1113  base->optional_arguments.begin(),
1114  base->optional_arguments.end(),
1115  [&]
1116  (
1117  const auto& lhs,
1118  const auto& rhs
1119  )
1120  {
1121  // return true if lhs < rhs
1122  return calc_edit_distance(arg, lhs.first) <
1123  calc_edit_distance(arg, rhs.first) ;
1124  }
1125  );
1126 
1127  // todo(Gustav): if we have already detected it,
1128  // change the text to 'invalid argument' but in this case
1129  // it's actually a unknown argument, not a 'invalid' one
1130  if(closest_match == base->optional_arguments.end())
1131  {
1132  print_error
1133  (
1134  fmt::format("unknown argument: '{}'", arg)
1135  );
1136  }
1137  else
1138  {
1139  const auto closest_match_name = closest_match->first;
1140  print_error
1141  (
1142  fmt::format("unknown argument: '{}', did you mean '{}'?", arg, closest_match_name)
1143  );
1144  }
1145 
1146  return argparse::error;
1147  }
1148  auto arg_parse_result = match->parse_arguments
1149  (
1150  runner,
1151  arg,
1152  base
1153  );
1154  if (arg_parse_result != argparse::ok)
1155  {
1156  return arg_parse_result;
1157  }
1158 
1159  return std::nullopt;
1160  }
1161  else
1162  {
1163  // arg doesn't look like a optional argument
1164  // hence it must be a sub command
1165  // or it's a error
1166  return parse_sub_command(arg);
1167  }
1168  }
1169 
1170 
1171  std::optional<ParseResult>
1172  ArgumentParser::parse_one_arg()
1173  {
1174  if (has_more_positionals())
1175  {
1176  // null from tryparse = the parsed optional wasnt important
1177  // null from this = abort this parsing and return to parent
1178  auto opt = try_parse_important_optional();
1179  if(opt.has_value())
1180  {
1181  if(*opt == argparse::ok)
1182  {
1183  // ok... continue parsing
1184  return std::nullopt;
1185  }
1186  else
1187  {
1188  // error... abort!
1189  return opt;
1190  }
1191  }
1192  else
1193  {
1194  // not important optional... parse a positional
1195  return parse_one_positional();
1196  }
1197  }
1198  else
1199  {
1200  // no more positionals... then it can ob ly be optionals
1201  return parse_one_optional();
1202  }
1203  }
1204  }
1205 
1206  ParseResult
1208  {
1210  auto parser = ArgumentParser{this, runner};
1211 
1212  while (runner->arguments->has_more())
1213  {
1214  auto r = parser.parse_one_arg();
1215  if(r)
1216  {
1217  return *r;
1218  }
1219  }
1220 
1221  if (parser.has_more_positionals())
1222  {
1223  const auto missing = parser.get_positionals_left();
1224  const auto text = string_mergers::english_and.merge(missing);
1225  parser.print_error
1226  (
1227  missing.size() == 1
1228  ? fmt::format("Positional {} was not specified.", text)
1229  : fmt::format("Positionals {} were not specified.", text)
1230  );
1231  return argparse::error;
1232  }
1233 
1234  // not sure if should keep or not
1235  // should handle required subparser, but doesn't
1236  if(subparsers.size != 0 && parser.found_subparser == false)
1237  {
1238  parser.print_error("no subparser specified");
1239  return argparse::error;
1240  }
1241 
1242  if(on_complete_function.has_value())
1243  {
1244  return on_complete_function.value()();
1245  }
1246 
1247  return argparse::ok;
1248  }
1249 
1250 
1252  (
1253  const std::string& d,
1254  ParserBase* p,
1255  argparse::Runner* r,
1256  const std::string& cn
1257  )
1258  : ParserBase(d)
1259  , parent(p)
1260  , runner(r)
1261  , calling_name(cn)
1262  {
1263  }
1264 
1265 
1266  ParserBase*
1268  {
1269  return parent;
1270  }
1271 
1272 
1273  std::string
1275  {
1276  return calling_name;
1277  }
1278 
1279 
1280  ParseResult
1282  {
1284  return parse_args(runner);
1285  }
1286 
1287 
1288  Parser::Parser(const std::string& d)
1289  : ParserBase(d)
1290  , printer(std::make_shared<ConsolePrinter>())
1291  {
1292  }
1293 
1294 
1295  ParserBase*
1297  {
1298  return nullptr;
1299  }
1300 
1301 
1302  ParseResult
1304  {
1305  auto reader = ArgumentReader{ args };
1306  auto runner = argparse::Runner{ &reader, printer };
1307  return ParserBase::parse_args(&runner);
1308  }
1309 
1310 
1311  std::optional<int>
1312  Parser::parse(int argc, char* argv[])
1313  {
1314  const auto args = NameAndArguments::extract(argc, argv);
1315  const auto res = parse(args);
1316  if (res == argparse::ok)
1317  {
1318  return std::nullopt;
1319  }
1320  else
1321  {
1322  return res.return_value;
1323  }
1324  }
1325 
1326 
1327  std::string
1329  {
1330  return args.name;
1331  }
1332 
1333 
1334  int
1335  parse_from_main(Parser* parser, int argc, char* argv[])
1336  {
1337  const auto args = NameAndArguments::extract(argc, argv);
1338  const auto res = parser->parse(args);
1339  return res.return_value;
1340  }
1341 }
bool found_subparser
Definition: argparse.cc:890
int positional_index
Definition: argparse.cc:889
argparse::Runner * runner
Definition: argparse.cc:888
ParserBase * base
Definition: argparse.cc:887
#define ASSERTX(x,...)
Definition: assert.h:48
#define ASSERT(x)
Definition: assert.h:29
std::string trim_left(const std::string &string_to_trim, std::string_view trim_characters)
Remove characters from the left, stops at invalid character.
Definition: stringutils.cc:72
std::string to_lower(const std::string &str)
Generate a string containing only lower characters.
Definition: stringutils.cc:143
std::vector< std::string > split(const std::string &s, char delim)
Definition: stringutils.cc:431
std::string to_upper(const std::string &str)
Definition: stringutils.cc:159
std::string trim(const std::string &string_to_trim, std::string_view trim_characters)
Remove characters from both the start and the end.
Definition: stringutils.cc:79
constexpr ParseResult error
no error occurred
Definition: argparse.h:73
std::string create_default_nargs(const Name &name)
Definition: argparse.cc:794
constexpr ParseResult ok
all ok
Definition: argparse.h:76
std::function< ParseResult()> CompleteFunction
Definition: argparse.h:343
bool operator==(const ParseResult &lhs, const ParseResult &rhs)
Definition: argparse.cc:109
int parse_from_main(Parser *parser, int argc, char *argv[])
helper function for parsing directly from main
Definition: argparse.cc:1335
std::string to_string(const ParseResult &pr)
Definition: argparse.cc:105
void print_parse_error(Runner *runner, ParserBase *base, const std::string &error_message)
Definition: argparse.cc:216
constexpr ParseResult quit
all ok, but quit requested
Definition: argparse.h:79
bool operator!=(const ParseResult &lhs, const ParseResult &rhs)
Definition: argparse.cc:116
SubParserStyle
how the subparsing is handled, non-greedy are useful for 'scripting' with subparsers
Definition: argparse.h:399
std::function< ParseResult(SubParser *)> SubParserCallback
Definition: argparse.h:342
std::vector< std::string > get_word_wrapped(const std::string &str, std::function< bool(const std::string &)> fit)
Definition: wordwrap.cc:62
DirPath join(const DirPath &lhs, const DirPath &rhs)
Definition: vfs_path.cc:419
constexpr StringMerger english_and
Definition: stringmerger.h:89
constexpr StringMerger comma
Definition: stringmerger.h:94
constexpr StringMerger space
Definition: stringmerger.h:93
std::string from_enum_to_string(T t)
Definition: enumtostring.h:116
std::vector< std::pair< A, B > > zip_longest(const std::vector< A > &as, const std::vector< B > &bs, A da=A(), B db=B())
Definition: functional.h:34
int c_sizet_to_int(size_t t)
Definition: cint.cc:11
bool ends_with(const std::string &str, char c)
Definition: os.cc:139
std::string add_quotes_and_combine_with_english_or(const std::vector< std::string > &matches)
size2f min(const size2f lhs, const size2f rhs)
Definition: size2.cc:140
size2f max(const size2f lhs, const size2f rhs)
Definition: size2.cc:149
int line
Definition: nlp_sentence.cc:91
String utility functions.
std::string merge(const std::vector< std::string > &strings) const
Definition: stringmerger.cc:11
Idx get_height() const
Definition: table.h:158
void add_row(T d=T())
Definition: table.h:33
named tuple class for argument and name
Definition: argparse.h:244
std::function< ParseResult(Runner *)> CallbackFunction
Definition: argparse.h:255
CallbackFunction callback_function
Definition: argparse.h:257
std::optional< std::string > get_second_line() override
Definition: argparse.cc:346
ParseResult parse_arguments(Runner *runner, const std::string &name, ParserBase *caller) override
Definition: argparse.cc:355
ArgumentNoValue(CallbackFunction cb)
Definition: argparse.cc:332
"file" like api for reading arguments
Definition: argparse.h:117
ArgumentReader(const NameAndArguments &a)
Definition: argparse.cc:149
base class for argument
Definition: argparse.h:199
Argument & set_help(const std::string &h)
Definition: argparse.cc:310
Argument & set_allow_before_positionals()
Definition: argparse.cc:318
void print_error(const std::string &line) override
Definition: argparse.cc:201
void print_info(const std::string &line) override
Definition: argparse.cc:208
FileOutput(const std::string &o)
Definition: argparse.cc:74
std::string get_next_file(bool print=true)
Definition: argparse.cc:79
void create_dir_if_missing() const
Definition: argparse.cc:98
ParseResult parse_arguments(Runner *runner, const std::string &name, ParserBase *caller) override
Definition: argparse.cc:388
std::function< std::optional< std::string >()> DescribeFunction
Definition: argparse.h:317
MultiArgument(CallbackFunction cb, DescribeFunction d)
Definition: argparse.cc:365
DescribeFunction describe_function
Definition: argparse.h:320
std::optional< std::string > get_second_line() override
Definition: argparse.cc:380
CallbackFunction callback_function
Definition: argparse.h:319
std::function< ParseResult(Runner *runner, const std::string &name, ParserBase *caller, const std::string &value) > CallbackFunction
Definition: argparse.h:316
container for arguments passed to main
Definition: argparse.h:98
NameAndArguments(const std::string &n, const std::vector< std::string > &a)
Definition: argparse.cc:122
static NameAndArguments extract(int argc, char *argv[])
Definition: argparse.cc:130
std::vector< std::string > arguments
Definition: argparse.h:100
represents a command line argument optional:
Definition: argparse.h:182
Name(const char *nn)
std::vector< std::string > names
Definition: argparse.h:183
base for the parser, start with Parser and add one or more subparsers
Definition: argparse.h:412
std::vector< std::shared_ptr< SubParserGroup > > subparser_groups
Definition: argparse.h:418
virtual std::string get_calling_name(const NameAndArguments &args)=0
std::map< std::string, std::shared_ptr< Argument > > optional_arguments
Definition: argparse.h:415
std::vector< ArgumentAndName > positional_argument_list
Definition: argparse.h:414
void on_complete(CompleteFunction com)
Definition: argparse.cc:852
std::shared_ptr< Argument > find_argument(const std::string &name) const
Definition: argparse.cc:869
Argument & set_const(const Name &name, T *target, T t)
Definition: argparse.h:443
SubParserStyle parser_style
Definition: argparse.h:421
virtual ParserBase * get_parent_or_null()=0
std::vector< ArgumentAndName > optional_argument_list
Definition: argparse.h:416
void print_help(std::shared_ptr< Printer > printer, const NameAndArguments &args)
Definition: argparse.cc:613
Argument & set_true(const Name &name, bool *target)
Definition: argparse.cc:836
Argument & set_false(const Name &name, bool *target)
Definition: argparse.cc:844
std::optional< CompleteFunction > on_complete_function
Definition: argparse.h:419
Argument & add_void_function(const Name &name, std::function< void()> void_function)
Definition: argparse.cc:829
std::shared_ptr< SubParserGroup > add_sub_parsers(const std::string &name="commands")
Definition: argparse.cc:859
ParserBase(const std::string &d)
Definition: argparse.cc:524
ParseResult parse_args(Runner *runner)
Definition: argparse.cc:1207
Argument & add(const Name &name, T *target, ParseFunction< T > parse_function=default_parse_function< T >)
Definition: argparse.h:453
Argument & add_argument(const Name &name, std::shared_ptr< Argument > argument)
Definition: argparse.cc:802
std::string generate_usage_string(const NameAndArguments &args)
Definition: argparse.cc:550
EnumToStringImplementation< std::shared_ptr< SubParserContainer > > subparsers
Definition: argparse.h:417
root parser. start argumentparsing with this one
Definition: argparse.h:575
ParseResult parse(const NameAndArguments &args)
Definition: argparse.cc:1303
std::shared_ptr< argparse::Printer > printer
Definition: argparse.h:576
ParserBase * get_parent_or_null() override
Definition: argparse.cc:1296
Parser(const std::string &d="")
Definition: argparse.cc:1288
std::string get_calling_name(const NameAndArguments &args) override
Definition: argparse.cc:1328
shared data between parsing functions
Definition: argparse.h:160
DescribeFunction describe_function
Definition: argparse.h:289
ParseResult parse_arguments(Runner *runner, const std::string &name, ParserBase *caller) override
Definition: argparse.cc:436
CallbackFunction callback_function
Definition: argparse.h:288
std::function< std::optional< std::string >()> DescribeFunction
Definition: argparse.h:286
SingleArgument(CallbackFunction cb, DescribeFunction d)
Definition: argparse.cc:413
std::optional< std::string > get_second_line() override
Definition: argparse.cc:428
std::function< ParseResult(Runner *runner, const std::string &name, ParserBase *caller, const std::string &value) > CallbackFunction
Definition: argparse.h:285
SubParserContainer(const SubParserNames &n, const std::string &h, SubParserCallback cb)
Definition: argparse.cc:473
void add(const SubParserNames &names, const std::string &desc, SubParserCallback sub)
Definition: argparse.cc:498
std::vector< std::shared_ptr< SubParserContainer > > parsers
Definition: argparse.h:374
SubParserGroup(const std::string &t, ParserBase *o)
Definition: argparse.cc:486
valid names for a single subparser example: [checkout, co] or [pull]
Definition: argparse.h:348
std::vector< std::string > names
Definition: argparse.h:349
ParserBase * get_parent_or_null() override
Definition: argparse.cc:1267
argparse::Runner * runner
Definition: argparse.h:556
std::string get_calling_name(const NameAndArguments &args) override
Definition: argparse.cc:1274
SubParser(const std::string &d, ParserBase *p, argparse::Runner *r, const std::string &cn)
Definition: argparse.cc:1252
ParseResult on_complete(CompleteFunction com)
Definition: argparse.cc:1281