Evo C++ Library v0.5.1
commandline.h
Go to the documentation of this file.
1 // Evo C++ Library
2 /* Copyright 2019 Justin Crowell
3 Distributed under the BSD 2-Clause License -- see included file LICENSE.txt for details.
4 */
6 
7 #pragma once
8 #ifndef INCL_evo_config_h
9 #define INCL_evo_config_h
10 
11 #include "strtok.h"
12 #include "string.h"
13 #include "maplist.h"
14 #include "setlist.h"
15 #include "ptr.h"
16 #include "io.h"
17 
18 namespace evo {
23 
25 
135 template<class ConsoleT=Console>
137 public:
139  typedef typename ConsoleT::OutT OutT;
140 
141  static const uint MAXLINE_DEFAULT = 100;
142 
146  struct Option {
148  virtual ~Option() { }
149 
157  virtual Option& default_value(const String& value) = 0;
158 
165  virtual Option& required(bool val=true) = 0;
166 
174  virtual Option& multi(bool val=true) = 0;
175 
181  virtual Option& numeric() = 0;
182 
189  virtual Option& maxlen(uint len) = 0;
190 
197  virtual Option& addchoice(const SubString& value) = 0;
198 
206  template<class T>
207  Option& addchoices(const T& values) {
208  for (SizeT i = 0, sz = values.size(); i < sz; ++i)
209  addchoice(values[i]);
210  return *this;
211  }
212  };
213 
217  struct Command {
219  virtual ~Command() { }
220 
235  virtual Option& add(const String& str) = 0;
236 
242  virtual void addsep() = 0;
243 
252  virtual void addflag(const String& names, const String& key, const String& help) = 0;
253 
262  virtual void addflag(const String& names, const String& help) = 0;
263 
274  virtual Option& addopt(const String& names, const String& key, const String& helpname, const String& help) = 0;
275 
286  virtual Option& addopt(const String& names, const String& helpname, const String& help) = 0;
287 
297  virtual Option& addopt(const String& names, const String& help) = 0;
298 
307  virtual Option& addarg(const String& key, const String& helpname, const String& help) = 0;
308 
317  virtual Option& addarg(const String& key, const String& help) = 0;
318  };
319 
320 private:
321  enum Type {
322  tPOS, // positional argument
323  tFLAG, // flag option without a value
324  tOPTION // option with a single stored value
325  };
326 
327  struct OptionInfo : public Option {
328  Type type; // option/argument type
329  String all; // string with all option variants
330  String key; // key to store option under result map
331  String helpname; // option value name to show with help info
332  String help; // option description shown with help info
333  String default_val; // default option/argument value
334  StrSetList choices; // allowed choice values, ignored if empty
335  ulong bitflags; // additional bit flags
336  uint max_length; // maximum value length (or max digits if numeric)
337 
338  OptionInfo() : type(tPOS), bitflags(0), max_length(0) {
339  }
340 
341  OptionInfo(Type type, const String& key, const String& help) :
342  type(type),
343  key(key),
344  help(help),
345  bitflags(0),
346  max_length(0)
347  { }
348 
349  OptionInfo(Type type, const String& key, const String& helpname, const String& help) :
350  type(type),
351  key(key),
352  helpname(helpname),
353  help(help),
354  bitflags(0),
355  max_length(0)
356  { }
357 
358  OptionInfo(const OptionInfo& src) :
359  type(src.type),
360  all(src.all),
361  key(src.key),
362  helpname(src.helpname),
363  help(src.help),
364  default_val(src.default_val),
365  choices(src.choices),
366  bitflags(src.bitflags),
367  max_length(0)
368  { }
369 
370  Option& default_value(const String& value) {
371  if (type == tOPTION)
372  default_val = value;
373  return *this;
374  }
375 
376  Option& required(bool val=true) {
377  if (val)
378  bitflags |= BITFLAG_REQUIRED;
379  else
380  bitflags &= ~BITFLAG_REQUIRED;
381  return *this;
382  }
383 
384  Option& multi(bool val=true) {
385  if (val)
386  bitflags |= BITFLAG_MULTI_VALUE;
387  else
388  bitflags &= ~BITFLAG_MULTI_VALUE;
389  return *this;
390  }
391 
392  Option& numeric() {
393  bitflags |= BITFLAG_NUMERIC;
394  return *this;
395  }
396 
397  Option& maxlen(uint len) {
398  max_length = len;
399  return *this;
400  }
401 
402  Option& addchoice(const SubString& value) {
403  SubString tok, tmpval(value);
404  while (tmpval.token(tok, ';'))
405  choices.add(tok);
406  return *this;
407  }
408 
409  String& format(String& str, bool shorthand=false) const {
410  if (type == tPOS) {
411  str.clear();
412  const bool required = (bitflags & BITFLAG_REQUIRED);
413  if (!required && !shorthand)
414  str << '[';
415  if (!helpname.empty())
416  str << helpname;
417  else
418  str << "VALUE";
419  if (!shorthand) {
420  if (!required)
421  str << ']';
422  if (bitflags & BITFLAG_MULTI_VALUE)
423  str << "...";
424  }
425  } else {
426  str = all;
427  if (type != tFLAG) {
428  if (!helpname.empty())
429  str << ' ' << helpname;
430  else
431  str << " VALUE";
432  }
433  }
434  return str;
435  }
436 
437  template<class T>
438  void store_flag(T& map) const {
439  String& val = map[key];
440  val.setn(*val.numu() + 1);
441  }
442 
443  template<class T>
444  void store_value(T& map, const SubString& value) const {
445  if (bitflags & BITFLAG_MULTI_VALUE)
446  map[key].addsep(';').add(value);
447  else
448  map[key] = value;
449  }
450 
451  private:
452  OptionInfo& operator=(const OptionInfo& src);
453  };
454 
458  typedef List<OptionInfo> ArgList;
459 
460  struct NullOption : public Option {
461  Option& default_value(const String&) { return *this; }
462  Option& required(bool val=true) { (void)val; return *this; }
463  Option& multi(bool val=true) { (void)val; return *this; }
464  Option& numeric() { return *this; }
465  Option& maxlen(uint) { return *this; }
466  Option& addchoice(const SubString&) { return *this; }
467  };
468 
469  struct CommandInfo : public Command {
470  typedef CommandLineT<ConsoleT> Parent;
471 
472  Parent* parent;
473  String name;
474  String helptext;
475  const OptionMap* parent_options; // pointer to global options when in subcommand, otherwise null
476  OptionMap options;
477  OptionList options_list;
478  ArgList args;
479 
480  CommandInfo() : parent(NULL), parent_options(NULL) {
481  }
482 
483  Option& add(const String& str) {
484  static NullOption null;
485  if (str.empty()) {
486  addsep();
487  return null;
488  }
489 
490  SubString lines(str), line;
491  if (lines.token_line(line)) {
492  StrTok tok(line);
493  if (tok.nextw(' ')) {
494  StrSizeT i;
495  if (tok.value().starts('-')) {
496  // Flag/Option
497  String names(tok.value()), help;
498  SubString value;
499  for (;;) {
500  i = tok.skipws();
501  if (!tok.nextw(' '))
502  break;
503 
504  if (tok.value().starts('-')) {
505  names.addsep(',').add(tok.value());
506  } else {
507  if (i > 2 && (line[i-1] != ' ' || line[i-2] != ' ')) {
508  i = tok.skipws();
509  value = tok.value();
510  }
511  break;
512  }
513  }
514  get_help_text(help, str, i);
515  if (value.empty()) {
516  addflag(names, help);
517  return null;
518  } else
519  return addopt(names, value, help);
520  } else {
521  // Argument
522  SubString key(tok.value());
523  i = tok.skipws();
524  if (tok.nextw(' ') && tok.index() != NONE) {
525  String help;
526  get_help_text(help, str, i);
527  return addarg(key, help);
528  } else
529  return addarg(key, String());
530  }
531  }
532  }
533 
534  parent->show_warning() << "CommandLine::add() ignoring malformed input: " << line << parent->newline_;
535  return null;
536  }
537 
538  void addsep() {
539  OptionInfoShPtr sep;
540  options_list.add(sep);
541  }
542 
543  void addflag(const String& names, const String& key, const String& help) {
544  OptionInfoShPtr arg( new OptionInfo(tFLAG, key, help) );
545  String all;
546  StrTok tok(names);
547  while (tok.next(',')) {
548  options[SubString(tok.value()).stripl('-')] = arg;
549  all.addsep(',').addsep(' ') << tok.value();
550  }
551  arg->all = all;
552  options_list.add(arg);
553  }
554 
555  void addflag(const String& names, const String& help) {
556  SubString key;
557  get_option_key(key, names);
558  if (key.empty())
559  parent->show_warning() << "CommandLine::addflag() ignoring malformed names: " << names << parent->newline_;
560  else
561  addflag(names, key, help);
562  }
563 
564  Option& addopt(const String& names, const String& key, const String& helpname, const String& help) {
565  OptionInfoShPtr arg( new OptionInfo(tOPTION, key, helpname, help) );
566  String all;
567  StrTok tok(names);
568  while (tok.next(',')) {
569  options[SubString(tok.value()).stripl('-')] = arg;
570  all.addsep(',').addsep(' ') << tok.value();
571  }
572  arg->all = all;
573  options_list.add(arg);
574  return *arg;
575  }
576 
577  Option& addopt(const String& names, const String& helpname, const String& help) {
578  SubString key;
579  get_option_key(key, names);
580  if (key.empty()) {
581  parent->show_warning() << "CommandLine::addopt() ignoring malformed names: " << names << parent->newline_;
582  static NullOption null;
583  return null;
584  } else
585  return addopt(names, key, helpname, help);
586  }
587 
588  Option& addopt(const String& names, const String& help) {
589  return addopt(names, "<value>", help);
590  }
591 
592  Option& addarg(const String& key, const String& helpname, const String& help) {
593  OptionInfo& arg = args(args.addnew().iend());
594  arg.key = key;
595  arg.helpname = helpname;
596  arg.help = help;
597  return arg;
598  }
599 
600  Option& addarg(const String& key, const String& help) {
601  return addarg(key, key, help);
602  }
603 
604  // Scan options list and remove outdated items (dups)
605  void option_cleanup() {
606  SubString name_sub;
607  for (SizeT j = options_list.size(); j > 0; ) {
608  const OptionInfo* ptr = options_list[--j].ptr();
609  if (ptr != NULL) {
610  ptr->all.split(',', name_sub);
611  const OptionInfoShPtr* findptr = options.find(name_sub.stripl('-'));
612  if (findptr == NULL || findptr->ptr() != ptr)
613  options_list.remove(j);
614  }
615  }
616  }
617 
618  // Set defaults
619  template <class T>
620  void set_defaults(T& map) {
621  for (typename OptionList::Iter iter(options_list); iter; ++iter) {
622  const OptionInfo* ptr = iter->ptr();
623  if (ptr != NULL && !ptr->default_val.null())
624  map[ptr->key] = ptr->default_val;
625  }
626  }
627 
628  private:
629  void get_option_key(SubString& key, const SubString& names) {
630  StrTok tok(names);
631  while (tok.next(',')) {
632  SubString tokname(tok.value());
633  if (tokname == "-")
634  key = tokname;
635  else if (tokname.starts("--") || (key.empty() && tokname.starts("-")))
636  key = tokname.stripl('-');
637  }
638  }
639 
640  void get_help_text(String& help, const SubString& str, StrSizeT indent=0) {
641  help.set();
642  SubString lines(str), line;
643  if (indent == NONE)
644  lines.token_line(line);
645  for (uint i = 0; lines.token_line(line); ) {
646  if (indent == NONE) {
647  indent = line.findanybut(" ", 1);
648  if (indent == NONE)
649  indent = 0;
650  }
651  if (indent > 0) {
652  if (i > 0) {
653  const StrSizeT maxtrim = line.findanybut(" ", 1);
654  line.triml(indent <= maxtrim ? indent : maxtrim);
655  } else {
656  line.triml(indent);
657  ++i;
658  }
659  }
660  if (!line.stripr().empty())
661  help.addsep('\n').add(line);
662  }
663  }
664  };
665 
667 
668 public:
673  CommandLineT() : con_(ConsoleT::get()), progname_set_(false), maxline_(This::MAXLINE_DEFAULT), noexit_(false), error_(false) {
674  main_.parent = this;
675  }
676 
682  CommandLineT(const String& description) : con_(ConsoleT::get()), progname_set_(false), description_(description), maxline_(This::MAXLINE_DEFAULT), noexit_(false), error_(false) {
683  main_.parent = this;
684  }
685 
689  ConsoleT& get_con() {
690  return con_;
691  }
692 
696  const NewlineValue& get_newline() const {
697  return newline_;
698  }
699 
704  This& set_newline(const NewlineValue& nl) {
705  newline_ = nl;
706  return *this;
707  }
708 
713  This& set_maxline(uint maxline) {
714  const uint MIN_MAXLINE = 40;
715  if (maxline < MIN_MAXLINE)
716  maxline = MIN_MAXLINE;
717  maxline_ = maxline;
718  return *this;
719  }
720 
728  This& set_progname(const String& name) {
729  progname_ = name;
730  progname_set_ = true;
731  return *this;
732  }
733 
740  This& set_epilog(const String& text) {
741  epilog_ = text;
742  return *this;
743  }
744 
749  This& set_noexit(bool val=true) {
750  noexit_ = val;
751  return *this;
752  }
753 
761  This& addver(const String& version_info) {
762  version_ = version_info;
763  return *this;
764  }
765 
780  Option& add(const String& str) {
781  return main_.add(str);
782  }
783 
789  void addsep() {
790  main_.addsep();
791  }
792 
801  void addflag(const String& names, const String& key, const String& help) {
802  return main_.addflag(names, key, help);
803  }
804 
811  void addflag(const String& names, const String& help) {
812  return main_.addflag(names, help);
813  }
814 
825  Option& addopt(const String& names, const String& key, const String& helpname, const String& help) {
826  return main_.addopt(names, key, helpname, help);
827  }
828 
837  Option& addopt(const String& names, const String& helpname, const String& help) {
838  return main_.addopt(names, helpname, help);
839  }
840 
850  Option& addopt(const String& names, const String& help) {
851  return main_.addopt(names, help);
852  }
853 
862  Option& addarg(const String& key, const String& helpname, const String& help) {
863  return main_.addarg(key, helpname, help);
864  }
865 
873  Option& addarg(const String& key, const String& help) {
874  return main_.addarg(key, help);
875  }
876 
884  CommandInfo& addcmd(const String& name, const String& help) {
885  CommandInfo& cmd = subcommands_[name];
886  if (cmd.parent == NULL) {
887  cmd.parent = this;
888  cmd.name = name;
889  cmd.helptext = help;
890  subcommand_list_.add(name);
891  }
892  return cmd;
893  }
894 
900  bool error() const {
901  return error_;
902  }
903 
915  template<class TMap>
916  bool parse(TMap& map, int argc, const char* argv[], int offset=0) {
917  int i = (offset >= 0 ? offset : 0);
918  if (progname_.null() || !progname_set_) {
919  if (i < argc) {
920  // Use first arg for program name
921  Char delim;
922  SubString name;
923  SubString(argv[i]).tokenr_any(name, delim, "/\\", 2);
924  progname_ = name;
925  ++i;
926  }
927  }
928  if (progname_.empty()) {
929  con_.err << "ERROR: CommandLine processing not setup correctly -- No program name set" << newline_;
930  return finish(true);
931  }
932 
933  // Add support for builtin options
934  addflag("-h, --help", "evo_help_", (subcommands_.size() > 0 ? "Show this usage help, or command usage help if after command" : "Show this usage help"));
935  addflag("--help-general", "evo_help_general_", "Show general argument processing help");
936  if (!version_.empty())
937  addflag("--version", "evo_version_", "Show version information");
938  cleanup();
939 
940  // Scan for builtin options first
941  {
942  const CommandInfo* cmd = NULL;
943  for (int j = i; j < argc; ++j) {
944  SubString opt(argv[j]);
945  if (opt.starts("-")) {
946  if (opt == "--") {
947  break;
948  } else if (opt == "-h" || opt == "--help") {
949  print_help(cmd);
950  return finish(false);
951  } else if (opt == "--help-general") {
952  print_help_general();
953  return finish(false);
954  } else if (opt == "--version") {
955  if (!version_.empty()) {
956  con_.out << FmtStringWrap(version_, maxline_).set_newline(newline_);
957  return finish(false);
958  }
959  }
960  } else {
961  const CommandInfo* new_cmd = subcommands_.find(opt);
962  if (new_cmd != NULL)
963  cmd = new_cmd;
964  }
965  }
966  }
967 
968  // Parse arguments
969  main_.set_defaults(map);
970  ParseState state(main_);
971  for (; i < argc; ++i) {
972  if (!parse_arg(state, map, argv[i]))
973  return false;
974  }
975 
976  // Check for missing arguments
977  if (state.current != NULL && state.valnum > 0)
978  ++state.argnum; // current has at least 1 value
979  while (state.argnum < state.cur_args->size()) {
980  if ((*state.cur_args)[state.argnum].bitflags & BITFLAG_REQUIRED) {
981  con_.err << progname_ << ": ERROR: Missing required argument: " << (*state.cur_args)[state.argnum].helpname << newline_;
982  return finish(true);
983  }
984  ++state.argnum;
985  }
986  if (subcommands_.size() > 0 && state.cur_cmd == NULL) {
987  con_.err << progname_ << ": ERROR: Missing required command" << newline_;
988  return finish(true);
989  }
990  return true;
991  }
992 
1003  OutT& show_error() {
1004  if (!progname_.empty())
1005  con_.err << progname_ << ": ";
1006  con_.err << "ERROR: ";
1007  return con_.err;
1008  }
1009 
1019  OutT& show_warning() {
1020  if (!progname_.empty())
1021  con_.err << progname_ << ": ";
1022  con_.err << "WARNING: ";
1023  return con_.err;
1024  }
1025 
1026 private:
1027  static const ulong BITFLAG_REQUIRED = 0x01; // value required (non-flags)
1028  static const ulong BITFLAG_MULTI_VALUE = 0x02; // multiple values allowed (non-flags)
1029  static const ulong BITFLAG_DEFAULT_TRUE = 0x04; // option flag defaults to true (flags)
1030  static const ulong BITFLAG_NUMERIC = 0x08; // validate numeric value
1031 
1032  ConsoleT& con_;
1033  NewlineValue newline_;
1034  String progname_;
1035  bool progname_set_;
1036  String description_;
1037  String epilog_;
1038  String version_;
1039  uint maxline_;
1040  bool noexit_;
1041  bool error_;
1042 
1043  CommandInfo main_;
1044  CommandList subcommands_;
1045  List<String> subcommand_list_;
1046 
1047  // Called to finish parsing and exit if applicable
1048  bool finish(bool err) {
1049  if (!noexit_)
1050  ::exit(1); // COV can't test
1051  error_ = err;
1052  return false;
1053  }
1054 
1055  // Cleanup invalid/dup options
1056  void cleanup() {
1057  main_.option_cleanup();
1058  }
1059 
1060  // State while parsing
1061  struct ParseState {
1062  const OptionInfo* current; // current option/argument, set when expecting at least another value
1063  const OptionInfo* prev_arg; // previous argument with multiple values
1064  const CommandInfo* cur_cmd; // current command, NULL if none
1065  const OptionMap* cur_opts; // current options, either &main_.options or command-specific while in subcommand
1066  const ArgList* cur_args; // current command arguments, either &main_.args or command-specific while in subcommand
1067  uint argnum; // current argument index
1068  uint valnum; // current value index, 0 if no value yet, 1 or more if at at least 1 value found
1069  bool end_options; // true to stop detecting options
1070 
1071  ParseState(const CommandInfo& cmd) : current(NULL), prev_arg(NULL), cur_cmd(NULL), cur_opts(&cmd.options), cur_args(&cmd.args), argnum(0), valnum(0), end_options(false) {
1072  }
1073 
1074  void value_expected(const OptionInfo& info) {
1075  current = &info;
1076  valnum = 0;
1077  }
1078 
1079  void value_stored(const OptionInfo& info) {
1080  if (info.type != tPOS) {
1081  if (prev_arg != NULL) {
1082  current = prev_arg; // return to multi-value argument before option
1083  valnum = 1;
1084  } else {
1085  current = NULL; // single value only
1086  valnum = 0;
1087  }
1088  } else if (info.bitflags & BITFLAG_MULTI_VALUE) {
1089  current = &info; // allow additional values
1090  valnum = 1;
1091  prev_arg = current;
1092  }
1093  }
1094 
1095  void value_added(const OptionInfo& info) {
1096  if (info.type == tPOS) {
1097  if (info.bitflags & BITFLAG_MULTI_VALUE) {
1098  ++valnum;
1099  return;
1100  }
1101  } else if (prev_arg != NULL) {
1102  current = prev_arg; // return to multi-value argument before option
1103  valnum = 1;
1104  }
1105  current = NULL; // single value only
1106  valnum = 0;
1107  }
1108  };
1109 
1110  // Lookup option, try current command options first, fallback to global options
1111  const OptionInfo* option_lookup(const ParseState& state, const SubString& name) const {
1112  const OptionInfoShPtr* result;
1113  if (state.cur_cmd != NULL) {
1114  result = state.cur_cmd->options.find(name);
1115  if (result != NULL)
1116  return result->ptr();
1117  }
1118  result = main_.options.find(name);
1119  if (result != NULL)
1120  return result->ptr();
1121  return NULL;
1122  }
1123 
1124  bool parse_validate(const OptionInfo& info, const SubString& value) {
1125  if (info.bitflags & BITFLAG_NUMERIC) {
1126  StrSizeT i = 0, sz = value.size();
1127  if (i < sz && value[i] == '-')
1128  ++i;
1129  for (StrSizeT digits = 0; i < sz; ++i, ++digits) {
1130  if (value[i] < '0' || value[i] > '9') {
1131  con_.err << progname_ << ": ERROR: Value must be numeric: " << value << newline_;
1132  return finish(true);
1133  }
1134  if (info.max_length > 0 && digits >= info.max_length) {
1135  con_.err << progname_ << ": ERROR: Numeric value too long (max digits: " << info.max_length << "): " << value << newline_;
1136  return finish(true);
1137  }
1138  }
1139  } else if (info.max_length > 0 && value.size() > info.max_length) {
1140  con_.err << progname_ << ": ERROR: Value too long (max length: " << info.max_length << "): " << value << newline_;
1141  return finish(true);
1142  }
1143  if (info.choices.size() > 0 && !info.choices.contains(value)) {
1144  con_.err << progname_ << ": ERROR: Invalid value: " << value << newline_;
1145  return finish(true);
1146  }
1147  return true;
1148  }
1149 
1150  template<class TMap>
1151  bool parse_arg(ParseState& state, TMap& map, const SubString& arg) {
1152  if (!state.end_options) {
1153  if (arg == "-.") {
1154  // Stop current multi-value argument
1155  state.current = NULL;
1156  state.prev_arg = NULL;
1157  return true;
1158  } else if (arg == "--") {
1159  // Stop processing options
1160  state.end_options = true;
1161  state.current = NULL;
1162  return true;
1163  }
1164  }
1165 
1166  if (state.current == NULL && state.prev_arg != NULL) {
1167  // Switch back to previous argument with multiple values
1168  state.current = state.prev_arg;
1169  state.valnum = 1;
1170  }
1171 
1172  if (state.current != NULL) {
1173  if (!state.end_options && arg.starts('-') && state.valnum > 0) {
1174  // Found an option between arguments
1175  if (state.current->type == tPOS && state.current->bitflags & BITFLAG_MULTI_VALUE)
1176  state.prev_arg = state.current; // save multi-value argument to come back to
1177  state.current = NULL;
1178  state.valnum = 0;
1179  } else {
1180  // Value for current option/argument
1181  const OptionInfo& info = *state.current;
1182  if (!parse_validate(info, arg))
1183  return false;
1184  info.store_value(map, arg);
1185  state.value_added(info);
1186  return true;
1187  }
1188  }
1189 
1190  if (!state.end_options) {
1191  // Check for options
1192  SubString str, name, value;
1193  if (arg.starts("--", 2)) {
1194  // Long option
1195  str.set(arg, 2);
1196  const bool found_value = str.split('=', name, value);
1197 
1198  const OptionInfo* infop = option_lookup(state, name);
1199  if (infop == NULL) {
1200  if (name.starts("no-", 3)) {
1201  // Check for --no-* option to reset (remove) option
1202  SubString noname(name, 3);
1203  infop = option_lookup(state, noname);
1204  if (infop != NULL) {
1205  if (found_value) {
1206  con_.err << progname_ << ": ERROR: Value not allowed with reset option: --" << name << newline_;
1207  return finish(true);
1208  }
1209  map.remove(infop->key);
1210  return true;
1211  }
1212  }
1213  con_.err << progname_ << ": ERROR: Unknown option: --" << name << newline_;
1214  return finish(true);
1215  }
1216 
1217  const OptionInfo& info = *infop;
1218  if (info.type == tFLAG) {
1219  if (found_value) {
1220  con_.err << progname_ << ": ERROR: Unexpected value with option: --" << name << newline_;
1221  return finish(true);
1222  }
1223  info.store_flag(map);
1224  } else if (found_value) {
1225  if (!parse_validate(info, value))
1226  return false;
1227  info.store_value(map, value);
1228  state.value_stored(info);
1229  } else
1230  state.value_expected(info);
1231  return true;
1232  } else if (arg.starts('-')) {
1233  // Short options
1234  const SubString args(arg.data() + 1, arg.size() - 1);
1235  for (StrSizeT i = 0, c = args.size(); i < c; ++i) {
1236  if (args[i] == 'h') {
1237  print_help(state.cur_cmd);
1238  return finish(false);
1239  } else {
1240  str.set(args.data() + i, 1);
1241  const OptionInfo* infop = option_lookup(state, str);
1242  if (infop == NULL) {
1243  con_.err << progname_ << ": ERROR: Unknown option: -" << str << newline_;
1244  return finish(true);
1245  }
1246 
1247  const OptionInfo& info = *infop;
1248  if (info.type == tFLAG) {
1249  if (i + 1 < c && args[i + 1] == '=') {
1250  con_.err << progname_ << ": ERROR: Unexpected value with option: -" << args[i] << newline_;
1251  return finish(true);
1252  }
1253  info.store_flag(map);
1254  } else {
1255  if (i + 1 >= c) {
1256  state.value_expected(info);
1257  return true; // Value expected in next argument
1258  }
1259  if (args[i + 1] == '=') {
1260  ++i;
1261  } else if (i > 0) {
1262  str.set(args.data() + i + 1, c - i - 1);
1263  con_.err << progname_ << ": ERROR: Possible typo, use '" << args[i] << '=' << str << "' for clarity when combining short options in: -" << args << newline_;
1264  return finish(true);
1265  }
1266 
1267  str.set(args.data() + i + 1, c - i - 1);
1268  if (!parse_validate(info, str))
1269  return false;
1270  info.store_value(map, str);
1271  state.value_stored(info);
1272  break;
1273  }
1274  }
1275  }
1276  return true;
1277  }
1278  }
1279 
1280  // Argument value
1281  if (state.argnum >= state.cur_args->size()) {
1282  if (subcommands_.size() > 0 && state.cur_cmd == NULL) {
1283  // Sub-command argument
1284  state.cur_cmd = subcommands_.find(arg);
1285  if (state.cur_cmd == NULL) {
1286  con_.err << progname_ << ": ERROR: Unknown command: " << arg << newline_;
1287  return finish(true);
1288  }
1289  map["command"] = arg;
1290  state.current = NULL;
1291  state.prev_arg = NULL;
1292  state.cur_args = &(state.cur_cmd->args);
1293  state.argnum = 0;
1294  state.valnum = 0;
1295  return true;
1296  }
1297 
1298  con_.err << progname_ << ": ERROR: Unexpected argument: " << arg << newline_;
1299  return finish(true);
1300  }
1301 
1302  const OptionInfo& info = (*state.cur_args)[state.argnum];
1303  if (!parse_validate(info, arg))
1304  return false;
1305  info.store_value(map, arg);
1306  state.value_stored(info);
1307  ++state.argnum;
1308  return true;
1309  }
1310 
1311  void print_help_usage(const CommandInfo& cmd, const char* options_prefix="") const {
1312  if (cmd.options.size() > 0)
1313  con_.out << " [" << options_prefix << "options]";
1314  String tmp;
1315  for (typename ArgList::Iter iter(cmd.args); iter; ++iter) {
1316  const OptionInfo& info = *iter;
1317  con_.out << ' ' << info.format(tmp);
1318  }
1319  }
1320 
1321  void print_help_args(const CommandInfo& cmd, const char* options_prefix="") const {
1322  const SubString SEP(" ", 3);
1323  String tmp;
1324  if (cmd.options.size() > 0) {
1325  con_.out << newline_ << options_prefix << "Options:" << newline_;
1326 
1327  const StrSizeT MAX_MAXLEN = 30;
1328  StrSizeT maxlen = 0;
1329  for (typename OptionList::Iter iter(cmd.options_list); iter; ++iter) {
1330  const OptionInfoShPtr& info = *iter;
1331  if (info) {
1332  StrSizeT len = info->format(tmp).size();
1333  if (len > maxlen)
1334  maxlen = len;
1335  }
1336  }
1337  if (maxlen > MAX_MAXLEN)
1338  maxlen = MAX_MAXLEN;
1339 
1340  String help, help_default;
1341  const StrSizeT help_indent = maxlen + SEP.size() + 1;
1342  for (typename OptionList::Iter iter(cmd.options_list); iter; ++iter) {
1343  const OptionInfoShPtr& info = *iter;
1344  if (!info) {
1345  con_.out << newline_;
1346  continue;
1347  }
1348 
1349  con_.out << ' ' << FmtString(info->format(tmp), maxlen);
1350  if (tmp.size() > maxlen)
1351  con_.out << newline_ << FmtChar(' ', maxlen + 1);
1352 
1353  if (info->help.empty()) {
1354  con_.out << newline_;
1355  } else {
1356  help = info->help;
1357  if (!info->default_val.null()) {
1358  // Add default to help, replace ${default} token, or append it
1359  help_default.set() << "[default: " << info->default_val << ']';
1360  if (help.findreplace("${default}", 10, help_default) == 0)
1361  help.addsep(' ') << help_default;
1362  }
1363  con_.out << SEP << FmtStringWrap(help, maxline_ - help_indent).set_indent(help_indent).set_newline(newline_);
1364  }
1365  }
1366  }
1367 
1368  if (cmd.args.size() > 0) {
1369  bool found_help = false;
1370  StrSizeT maxlen = 0;
1371  for (typename ArgList::Iter iter(cmd.args); iter; ++iter) {
1372  const OptionInfo& info = *iter;
1373  StrSizeT len = info.format(tmp, true).size();
1374  if (len > maxlen)
1375  maxlen = len;
1376  if (!info.help.empty())
1377  found_help = true;
1378  }
1379 
1380  if (found_help) {
1381  con_.out << newline_ << "Arguments:" << newline_;
1382  const StrSizeT help_indent = maxlen + SEP.size() + 1;
1383  for (typename ArgList::Iter iter(cmd.args); iter; ++iter) {
1384  const OptionInfo& info = *iter;
1385  con_.out << ' ' << FmtString(info.format(tmp, true), maxlen);
1386  if (info.help.empty())
1387  con_.out << newline_;
1388  else
1389  con_.out << SEP << FmtStringWrap(info.help, maxline_ - help_indent).set_indent(help_indent).set_newline(newline_);
1390  }
1391  }
1392  }
1393  }
1394 
1395  void print_help(const CommandInfo* cur_cmd=NULL) const {
1396  String tmp;
1397 
1398  con_.out << "Usage: " << progname_;
1399  print_help_usage(main_, (subcommands_.size() > 0 ? "global_" : ""));
1400 
1401  if (cur_cmd != NULL) {
1402  con_.out << ' ' << cur_cmd->name;
1403  print_help_usage(*cur_cmd, "command_");
1404  con_.out << newline_;
1405  print_help_args(*cur_cmd);
1406  return;
1407  }
1408 
1409  if (subcommands_.size() > 0)
1410  con_.out << " <command> [args]";
1411  con_.out << newline_;
1412  if (description_.size() > 0)
1413  con_.out << newline_ << FmtStringWrap(description_, maxline_).set_newline(newline_);
1414 
1415  print_help_args(main_, (subcommands_.size() > 0 ? "Global " : ""));
1416 
1417  SubString SEP(" ", 3);
1418  if (subcommands_.size() > 0) {
1419  con_.out << newline_ << "Commands:" << newline_;
1420 
1421  StrSizeT maxlen = 0;
1422  for (List<String>::Iter iter(subcommand_list_); iter; ++iter) {
1423  const StrSizeT len = iter->size();
1424  if (len > maxlen)
1425  maxlen = len;
1426  }
1427 
1428  const StrSizeT help_indent = maxlen + SEP.size() + 1;
1429  for (List<String>::Iter iter(subcommand_list_); iter; ++iter) {
1430  const CommandInfo& info = *(subcommands_.find(*iter));
1431  con_.out << ' ' << FmtString(info.name, maxlen);
1432  if (info.helptext.empty())
1433  con_.out << newline_;
1434  else
1435  con_.out << SEP << FmtStringWrap(info.helptext, maxline_ - help_indent).set_indent(help_indent).set_newline(newline_);
1436  }
1437  }
1438 
1439  if (epilog_.size() > 0)
1440  con_.out << newline_ << epilog_ << newline_;
1441  }
1442 
1443  void print_help_general() const {
1444  const char* help =
1445  "\nOptions\n"
1446  "-------\n\n"
1447  "Types:\n"
1448  " * Flag options are boolean and will give an error if a value is supplied\n"
1449  " * Other options require a value, and in some cases may have multiple values\n"
1450  "Long options start with a double dash, example: --help\n"
1451  " * and may include a value using '=', example: --file=myfile\n"
1452  " * or may give a value with the next argument, example: --file myfile\n"
1453  "Short options are a single character and start with a single dash, example: -h\n"
1454  " * and may be combined in standard Unix/Linux fashion, example '-abc' is the same as: -a -b -c\n"
1455  " * and may include a value using '=', example: -f=myfile\n"
1456  " * or may include a value with an additional argument, example: -f myfile\n"
1457  " * or may include a value without any separator, example '-fmyfile' is the same as: -f=myfile\n"
1458  " * however a separator is required when combining options, example '-abcf=myfile' is the same as: -a -b -c -f=myfile\n"
1459  " * a short option with a value must be last when combined with flag options, to avoid confusion\n"
1460  "\nNote that in most cases options may be mixed in with arguments, but option order often does matter,"
1461  " and repeated options may either accumulate or replace the previous value, depending on the option.\n"
1462  "\nReset Options\n"
1463  "-------------\n\n"
1464  "Any option may be reset (deleted) by prefixing it with '--no-', example '--no-file' deletes option '--file',"
1465  " and the option may then be set again afterwards.\n"
1466  "\nSpecial Arguments\n"
1467  "-----------------\n\n"
1468  "* A double dash argument (--) stops option processesing and all options after this are treated as raw arguments."
1469  " This is useful for specifying options that will be passed to another program via arguments\n"
1470  "* A dash-dot argument (-.) terminates a list of multiple values (rare)\n"
1471  ;
1472  con_.out << "Evo C++ Library CommandLine processor version " << EVO_VERSION_STRING << newline_
1473  << FmtStringWrap(SubString(help), maxline_).set_newline(newline_);
1474  }
1475 };
1476 
1481 
1483 
1484 }
1485 #endif
ListType & add(const Item *data, Size size)
Append new items copied from data pointer (modifier).
Definition: list.h:2019
bool empty() const
Get whether empty.
virtual Option & maxlen(uint len)=0
Set maximum value length, or max digits if required to be numeric.
Size size() const
Get size.
Definition: list.h:759
SubString & triml(Size size)
Trim left (beginning) items.
Definition: substring.h:1307
UInt numu(int base=0) const
Convert to number value (unsigned).
Definition: string.h:4177
CommandLineT CommandLine
Process command-line arguments.
Definition: commandline.h:1480
Holds a Newline value that can be null, which implicitly converts to NL_SYS (system default newline)...
Definition: sys.h:813
Option & addarg(const String &key, const String &help)
Add a positional argument.
Definition: commandline.h:873
Size skipws()
Advance current position for next token by skipping whitespace.
Definition: strtok.h:438
This & set_progname(const String &name)
Set program name to use with usage help.
Definition: commandline.h:728
Evo String container.
Evo Smart Pointers.
bool token(StringT &value, char delim)
Extract next token from string.
Definition: substring.h:383
Evo string tokenizers.
void addflag(const String &names, const String &help)
Add a global option flag.
Definition: commandline.h:811
CommandLineT< ConsoleT > This
This type.
Definition: commandline.h:138
bool tokenr_any(StringT &value, Char &found_delim, const char *delims, Size count)
Extract next token from string in reverse (from end of string) using any of given delimiters...
Definition: substring.h:497
Size findreplace(char ch, const char *str, Size size, Size max=ALL)
Find character and replace with string (modifier).
Definition: string.h:3920
Key iend(Size offset=0) const
Get index from last item using offset.
Definition: list.h:826
Basic character type (char) – see CharT.
Definition: type.h:775
Used to set command options and arguments.
Definition: commandline.h:217
void addflag(const String &names, const String &key, const String &help)
Add a global option flag.
Definition: commandline.h:801
virtual Option & default_value(const String &value)=0
Set default value for option.
bool token_line(StringT &line)
Extract next line from string.
Definition: substring.h:533
ConsoleT & get_con()
Get reference to console object used.
Definition: commandline.h:689
Random access iterator.
Definition: iter.h:904
OutT & show_error()
This writes the beginning (prefix) of an error message and returns a stream for the caller to write t...
Definition: commandline.h:1003
const T * ptr() const
Get current pointer (const).
Definition: type.h:1735
Key findanybut(const char *chars, Size count, Key start=0, Key end=END) const
Find first occurrence of any character not listed with forward search.
Definition: substring.h:873
Option & addopt(const String &names, const String &help)
Add a global option that stores a value.
Definition: commandline.h:850
Evo I/O streams and Console I/O.
Option & addchoices(const T &values)
Add one or more preset choices from string list.
Definition: commandline.h:207
String & clear()
Clear by removing all items.
Definition: string.h:4975
Evo MapList container.
This & set_noexit(bool val=true)
Set no-exit flag to prevent parse() from terminating and instead have it return false.
Definition: commandline.h:749
Option & addopt(const String &names, const String &key, const String &helpname, const String &help)
Add a global option that stores a value.
Definition: commandline.h:825
Size size() const
Get size.
FmtStringWrap & set_indent(int new_indent=0)
Definition: str.h:3017
CommandInfo & addcmd(const String &name, const String &help)
Add a sub-command argument with it&#39;s own options and arguments.
Definition: commandline.h:884
Size remove(Key index, Size size=1)
Remove items (modifier).
Definition: list.h:2189
bool starts(const char *str) const
Check if starts with given terminated string.
Definition: substring.h:658
virtual Option & addchoice(const SubString &value)=0
Add one or more preset choices.
Evo SetList container.
bool parse(TMap &map, int argc, const char *argv[], int offset=0)
Parse and process command-line using current option and argument info.
Definition: commandline.h:916
Size index() const
Get current index before next token.
Definition: strtok.h:30
bool empty() const
Get whether empty.
Definition: list.h:753
uint32 StrSizeT
Default Evo string size type.
Definition: sys.h:734
String container.
Definition: string.h:674
virtual ~Command()
Destructor.
Definition: commandline.h:219
Option & addarg(const String &key, const String &helpname, const String &help)
Add a positional argument.
Definition: commandline.h:862
Used to set additional option/argument information.
Definition: commandline.h:146
Definition: str.h:3003
This & set_maxline(uint maxline)
Set new max line length for usage help output.
Definition: commandline.h:713
SubString & stripl()
Strip left (beginning) whitespace (spaces and tabs).
Definition: substring.h:1090
This & set_epilog(const String &text)
Set epilog text shown in usage help.
Definition: commandline.h:740
SubString & stripr()
Strip right (ending) whitespace (spaces and tabs).
Definition: substring.h:1135
virtual Option & numeric()=0
Require option/argument value to be numeric.
This & addver(const String &version_info)
Add version info and enable option flags to show it.
Definition: commandline.h:761
String & setn(int num, int base=fDEC)
Set as formatted signed number (modifier).
Definition: string.h:1117
ListType & addnew(Size size=1)
Append new items (modifier).
Definition: list.h:1996
const SubString & value() const
Get current token value from last call to next().
Definition: strtok.h:42
Process command-line arguments.
Definition: commandline.h:136
static const uint MAXLINE_DEFAULT
Default maxline value.
Definition: commandline.h:141
virtual Option & multi(bool val=true)=0
Allow multiple values for option/argument.
This & set_newline(const NewlineValue &nl)
Set current newline value used for output.
Definition: commandline.h:704
bool error() const
Check whether an error occurred while parsing arguments.
Definition: commandline.h:900
Option & add(const String &str)
Parse input string and add an global option or argument as described.
Definition: commandline.h:780
bool nextw(char delim)
Find next token using word delimiter.
Definition: strtok.h:278
Evo C++ Library namespace.
Definition: alg.h:11
static const EndT NONE
Special integer value for indicating no item or unknown item.
Definition: type.h:1832
Explicitly format a string.
Definition: str.h:2931
const NewlineValue & get_newline() const
Get current newline value used for output.
Definition: commandline.h:696
void addsep()
Add a global options separator in usage help.
Definition: commandline.h:789
ConsoleT::OutT OutT
Output stream type.
Definition: commandline.h:139
String & addsep(char delim=',')
Append separator/delimiter if needed (modifier).
Definition: string.h:2799
bool next(char delim)
Find next token using delimiter.
Definition: strtok.h:184
Option & addopt(const String &names, const String &helpname, const String &help)
Add a global option that stores a value.
Definition: commandline.h:837
String & add(char ch)
Append character (modifier).
Definition: string.h:2741
OutT & show_warning()
This writes the beginning (prefix) of a warning message and returns a stream for the caller to write ...
Definition: commandline.h:1019
IteratorRa< ThisType >::Const Iter
Iterator (const) - IteratorRa.
Definition: list.h:263
Shared smart pointer to single object.
Definition: ptr.h:399
String forward tokenizer.
Definition: strtok.h:112
Explicitly format a repeated character.
Definition: str.h:2914
FmtStringWrap & set_newline(Newline nl)
Definition: str.h:3022
#define EVO_VERSION_STRING
Evo version string (major.minor.patch).
Definition: evo_config.h:24
virtual Option & required(bool val=true)=0
Make this option/argument required.
bool split(char delim, T1 &left, T2 &right) const
Split at first occurrence of delimiter into left/right substrings.
Definition: substring.h:976
const Value * find(const Key &key) const
Find (lookup) value for given key (const).
Definition: maplist.h:437
Value & add(const Value &item, bool update=false)
Add or update using given item.
Definition: setlist.h:579
String & set()
Set as null and empty.
Definition: string.h:995
SubString & set(const char *data)
Set as reference to terminated string.
Definition: substring.h:353
uint32 SizeT
Default Evo container size type.
Definition: sys.h:729
Reference and access existing string data.
Definition: substring.h:229
virtual ~Option()
Destructor.
Definition: commandline.h:148
CommandLineT(const String &description)
Constructor with help description.
Definition: commandline.h:682
CommandLineT()
Default constructor.
Definition: commandline.h:673
const char * data() const
Get data pointer.