argagg
argagg.hpp
Go to the documentation of this file.
1 /*
2  * @file
3  * @brief
4  * Defines a very simple command line argument parser.
5  *
6  * @copyright
7  * Copyright (c) 2018 Viet The Nguyen
8  *
9  * @copyright
10  * Permission is hereby granted, free of charge, to any person obtaining a copy
11  * of this software and associated documentation files (the "Software"), to
12  * deal in the Software without restriction, including without limitation the
13  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14  * sell copies of the Software, and to permit persons to whom the Software is
15  * furnished to do so, subject to the following conditions:
16  *
17  * @copyright
18  * The above copyright notice and this permission notice shall be included in
19  * all copies or substantial portions of the Software.
20  *
21  * @copyright
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
28  * IN THE SOFTWARE.
29  */
30 #pragma once
31 #ifndef ARGAGG_ARGAGG_ARGAGG_HPP
32 #define ARGAGG_ARGAGG_ARGAGG_HPP
33 
34 #include <algorithm>
35 #include <array>
36 #include <cctype>
37 #include <cstdlib>
38 #include <cstring>
39 #include <iterator>
40 #include <ostream>
41 #include <sstream>
42 #include <stdexcept>
43 #include <string>
44 #include <unordered_map>
45 #include <utility>
46 #include <vector>
47 
48 
96 namespace argagg {
97 
98 
105 : public std::runtime_error {
107 };
108 
109 
117 : public std::runtime_error {
119 };
120 
121 
129 : public std::runtime_error {
131 };
132 
133 
142 : public std::runtime_error {
144 };
145 
146 
153 : public std::runtime_error {
155 };
156 
157 
164 namespace convert {
165 
171  template <typename T>
172  T arg(const char* arg);
173 
185  template <typename T>
186  struct converter {
187  static T convert(const char* arg);
188  };
189 
248  template <typename T>
250  const char*& s,
251  T& out_arg,
252  const char delim = ',');
253 
254 }
255 
256 
265 
271  const char* arg;
272 
282  template <typename T>
283  T as() const;
284 
294  template <typename T>
295  T as(const T& t) const;
296 
309  template <typename T>
310  operator T () const;
311 
317  bool operator ! () const;
318 
319 };
320 
321 
333 
339 
344  std::size_t count() const;
345 
351 
356  const option_result& operator [] (std::size_t index) const;
357 
367  template <typename T>
368  T as() const;
369 
378  template <typename T>
379  T as(const T& t) const;
380 
393  template <typename T>
394  operator T () const;
395 
401  bool operator ! () const;
402 
403 };
404 
405 
412 
418  const char* program;
419 
426 
432 
437  bool has_option(const std::string& name) const;
438 
446 
453  const option_results& operator [] (const std::string& name) const;
454 
459  std::size_t count() const;
460 
465  const char* operator [] (std::size_t index) const;
466 
471  template <typename T>
472  T as(std::size_t i = 0) const;
473 
478  template <typename T>
479  std::vector<T> all_as() const;
480 
481 };
482 
483 
488 struct definition {
489 
495 
502 
508 
515  unsigned int num_args;
516 
521  bool wants_no_arguments() const;
522 
527  bool requires_arguments() const;
528 
529 };
530 
531 
540  const char* s);
541 
542 
549  const char* s);
550 
551 
557 bool flag_is_short(
558  const char* s);
559 
560 
572 struct parser_map {
573 
580 
587 
592  bool known_short_flag(
593  const char flag) const;
594 
601  const char flag) const;
602 
607  bool known_long_flag(
608  const std::string& flag) const;
609 
616  const std::string& flag) const;
617 
618 };
619 
620 
629  const std::vector<definition>& definitions);
630 
631 
636 struct parser {
637 
644 
654  parser_results parse(int argc, const char** argv) const;
655 
663  parser_results parse(int argc, char** argv) const;
664 
665 };
666 
667 
684 
691 
698 
705  ~fmt_ostream();
706 
707 };
708 
709 
717 
718 
719 } // namespace argagg
720 
721 
727 
728 
729 // ---- end of declarations, header-only implementations follow ----
730 
731 
732 namespace argagg {
733 
734 
735 template <typename T>
737 {
738  if (this->arg) {
739  return convert::arg<T>(this->arg);
740  } else {
741  throw option_lacks_argument_error("option has no argument");
742  }
743 }
744 
745 
746 template <typename T>
747 T option_result::as(const T& t) const
748 {
749  if (this->arg) {
750  try {
751  return convert::arg<T>(this->arg);
752  } catch (...) {
753  return t;
754  }
755  } else {
756  // I actually think this will never happen. To call this method you have
757  // to access a specific option_result for an option. If there's a
758  // specific option_result then the option was found. If the option
759  // requires an argument then it will definitely have an argument
760  // otherwise the parser would have complained.
761  return t;
762  }
763 }
764 
765 
766 template <typename T>
767 option_result::operator T () const
768 {
769  return this->as<T>();
770 }
771 
772 
773 template <> inline
774 option_result::operator bool () const
775 {
776  return this->arg != nullptr;
777 }
778 
779 
780 inline
782 {
783  return !static_cast<bool>(*this);
784 }
785 
786 
787 inline
789 {
790  return this->all.size();
791 }
792 
793 
794 inline
796 {
797  return this->all[index];
798 }
799 
800 
801 inline
803 {
804  return this->all[index];
805 }
806 
807 
808 template <typename T>
810 {
811  if (this->all.size() == 0) {
812  throw std::out_of_range("no option arguments to convert");
813  }
814  return this->all.back().as<T>();
815 }
816 
817 
818 template <typename T>
819 T option_results::as(const T& t) const
820 {
821  if (this->all.size() == 0) {
822  return t;
823  }
824  return this->all.back().as<T>(t);
825 }
826 
827 
828 template <typename T>
829 option_results::operator T () const
830 {
831  return this->as<T>();
832 }
833 
834 
835 template <> inline
836 option_results::operator bool () const
837 {
838  return this->all.size() > 0;
839 }
840 
841 
842 inline
844 {
845  return !static_cast<bool>(*this);
846 }
847 
848 
849 inline
851 {
852  const auto it = this->options.find(name);
853  return ( it != this->options.end()) && it->second.all.size() > 0;
854 }
855 
856 
857 inline
859 try {
860  return this->options.at(name);
861 } catch (const std::out_of_range& e) {
862  std::ostringstream msg;
863  msg << "no option named \"" << name << "\" in parser_results";
864  throw unknown_option(msg.str());
865 }
866 
867 
868 inline
869 const option_results&
871 try {
872  return this->options.at(name);
873 } catch (const std::out_of_range& e) {
874  std::ostringstream msg;
875  msg << "no option named \"" << name << "\" in parser_results";
876  throw unknown_option(msg.str());
877 }
878 
879 
880 inline
882 {
883  return this->pos.size();
884 }
885 
886 
887 inline
889 {
890  return this->pos[index];
891 }
892 
893 
894 template <typename T>
896 {
897  return convert::arg<T>(this->pos[i]);
898 }
899 
900 
901 template <typename T>
903 {
904  std::vector<T> v(this->pos.size());
906  this->pos.begin(), this->pos.end(), v.begin(),
907  [](const char* arg) {
908  return convert::arg<T>(arg);
909  });
910  return v;
911 }
912 
913 
914 inline
916 {
917  return this->num_args == 0;
918 }
919 
920 
921 inline
923 {
924  return this->num_args > 0;
925 }
926 
927 
928 inline
930  const char* s)
931 {
932  auto len = std::strlen(s);
933 
934  // The shortest possible flag has two characters: a hyphen and an
935  // alpha-numeric character.
936  if (len < 2) {
937  return false;
938  }
939 
940  // All flags must start with a hyphen.
941  if (s[0] != '-') {
942  return false;
943  }
944 
945  // Shift the name forward by a character to account for the initial hyphen.
946  // This means if s was originally "-v" then name will be "v".
947  const char* name = s + 1;
948 
949  // Check if we're dealing with a long flag.
950  bool is_long = false;
951  if (s[1] == '-') {
952  is_long = true;
953 
954  // Just -- is not a valid flag.
955  if (len == 2) {
956  return false;
957  }
958 
959  // Shift the name forward to account for the extra hyphen. This means if s
960  // was originally "--output" then name will be "output".
961  name = s + 2;
962  }
963 
964  // The first character of the flag name must be alpha-numeric. This is to
965  // prevent things like "---a" from being valid flags.
966  len = std::strlen(name);
967  if (!std::isalnum(name[0])) {
968  return false;
969  }
970 
971  // At this point in is_valid_flag_definition() we would check if the short
972  // flag has only one character. At command line specification you can group
973  // short flags together or even add an argument to a short flag without a
974  // space delimiter. Thus we don't check if this has only one character
975  // because it might not.
976 
977  // If this is a long flag then we expect all characters *up to* an equal sign
978  // to be alpha-numeric or a hyphen. After the equal sign you are specify the
979  // argument to a long flag which can be basically anything.
980  if (is_long) {
981  bool encountered_equal = false;
982  return std::all_of(name, name + len, [&](const char& c) {
983  if (encountered_equal) {
984  return true;
985  } else {
986  if (c == '=') {
987  encountered_equal = true;
988  return true;
989  }
990  return std::isalnum(c) || c == '-';
991  }
992  });
993  }
994 
995  // At this point we are not dealing with a long flag. We already checked that
996  // the first character is alpha-numeric so we've got the case of a single
997  // short flag covered. This might be a short flag group though and we might
998  // be tempted to check that each character of the short flag group is
999  // alpha-numeric. However, you can specify the argument for a short flag
1000  // without a space delimiter (e.g. "-I/usr/local/include") so you can't tell
1001  // if the rest of a short flag group is part of the argument or not unless
1002  // you know what is a defined flag or not. We leave that kind of processing
1003  // to the parser.
1004  return true;
1005 }
1006 
1007 
1008 inline
1010  const char* s)
1011 {
1012  auto len = std::strlen(s);
1013 
1014  // The shortest possible flag has two characters: a hyphen and an
1015  // alpha-numeric character.
1016  if (len < 2) {
1017  return false;
1018  }
1019 
1020  // All flags must start with a hyphen.
1021  if (s[0] != '-') {
1022  return false;
1023  }
1024 
1025  // Shift the name forward by a character to account for the initial hyphen.
1026  // This means if s was originally "-v" then name will be "v".
1027  const char* name = s + 1;
1028 
1029  // Check if we're dealing with a long flag.
1030  bool is_long = false;
1031  if (s[1] == '-') {
1032  is_long = true;
1033 
1034  // Just -- is not a valid flag.
1035  if (len == 2) {
1036  return false;
1037  }
1038 
1039  // Shift the name forward to account for the extra hyphen. This means if s
1040  // was originally "--output" then name will be "output".
1041  name = s + 2;
1042  }
1043 
1044  // The first character of the flag name must be alpha-numeric. This is to
1045  // prevent things like "---a" from being valid flags.
1046  len = std::strlen(name);
1047  if (!std::isalnum(name[0])) {
1048  return false;
1049  }
1050 
1051  // If this is a short flag then it must only have one character.
1052  if (!is_long && len > 1) {
1053  return false;
1054  }
1055 
1056  // The rest of the characters must be alpha-numeric, but long flags are
1057  // allowed to have hyphens too.
1058  return std::all_of(name + 1, name + len, [&](const char& c) {
1059  return std::isalnum(c) || (c == '-' && is_long);
1060  });
1061 }
1062 
1063 
1064 inline
1066  const char* s)
1067 {
1068  return s[0] == '-' && std::isalnum(s[1]);
1069 }
1070 
1071 
1072 inline
1074  const char flag) const
1075 {
1076  return this->short_map[static_cast<std::size_t>(flag)] != nullptr;
1077 }
1078 
1079 
1080 inline
1082  const char flag) const
1083 {
1084  return this->short_map[static_cast<std::size_t>(flag)];
1085 }
1086 
1087 
1088 inline
1090  const std::string& flag) const
1091 {
1092  const auto existing_long_flag = this->long_map.find(flag);
1093  return existing_long_flag != long_map.end();
1094 }
1095 
1096 
1097 inline
1099  const std::string& flag) const
1100 {
1101  const auto existing_long_flag = this->long_map.find(flag);
1102  if (existing_long_flag == long_map.end()) {
1103  return nullptr;
1104  }
1105  return existing_long_flag->second;
1106 }
1107 
1108 
1109 inline
1111  const std::vector<definition>& definitions)
1112 {
1114  parser_map map {{{nullptr}}, std::move(long_map)};
1115 
1116  for (auto& defn : definitions) {
1117 
1118  if (defn.flags.size() == 0) {
1119  std::ostringstream msg;
1120  msg << "option \"" << defn.name << "\" has no flag definitions";
1121  throw invalid_flag(msg.str());
1122  }
1123 
1124  for (auto& flag : defn.flags) {
1125 
1126  if (!is_valid_flag_definition(flag.data())) {
1127  std::ostringstream msg;
1128  msg << "flag \"" << flag << "\" specified for option \"" << defn.name
1129  << "\" is invalid";
1130  throw invalid_flag(msg.str());
1131  }
1132 
1133  if (flag_is_short(flag.data())) {
1134  const std::size_t short_flag_letter = static_cast<std::size_t>(flag[1]);
1135  const auto existing_short_flag =
1136  map.short_map[short_flag_letter];
1137  bool short_flag_already_exists = (existing_short_flag != nullptr);
1138  if (short_flag_already_exists) {
1139  std::ostringstream msg;
1140  msg << "duplicate short flag \"" << flag
1141  << "\" found, specified by both option \"" << defn.name
1142  << "\" and option \"" << existing_short_flag->name;
1143  throw invalid_flag(msg.str());
1144  }
1145  map.short_map[static_cast<std::size_t>(short_flag_letter)] = &defn;
1146  continue;
1147  }
1148 
1149  // If we're here then this is a valid, long-style flag.
1150  if (map.known_long_flag(flag)) {
1151  const auto existing_long_flag = map.get_definition_for_long_flag(flag);
1152  std::ostringstream msg;
1153  msg << "duplicate long flag \"" << flag
1154  << "\" found, specified by both option \"" << defn.name
1155  << "\" and option \"" << existing_long_flag->name;
1156  throw invalid_flag(msg.str());
1157  }
1158  map.long_map.insert(std::make_pair(flag, &defn));
1159  }
1160  }
1161 
1162  return map;
1163 }
1164 
1165 
1166 inline
1167 parser_results parser::parse(int argc, const char** argv) const
1168 {
1169  // Inspect each definition to see if its valid. You may wonder "why don't
1170  // you do this validation on construction?" I had thought about it but
1171  // realized that since I've made the parser an aggregate type (granted it
1172  // just "aggregates" a single vector) I would need to track any changes to
1173  // the definitions vector and re-run the validity check in order to
1174  // maintain this expected "validity invariant" on the object. That would
1175  // then require hiding the definitions vector as a private entry and then
1176  // turning the parser into a thin interface (by re-exposing setters and
1177  // getters) to the vector methods just so that I can catch when the
1178  // definition has been modified. It seems much simpler to just enforce the
1179  // validity when you actually want to parse because it's at the moment of
1180  // parsing that you know the definitions are complete.
1182 
1183  // Initialize the parser results that we'll be returning. Store the program
1184  // name (assumed to be the first command line argument) and initialize
1185  // everything else as empty.
1188  parser_results results {argv[0], std::move(options), std::move(pos)};
1189 
1190  // Add an empty option result for each definition.
1191  for (const auto& defn : this->definitions) {
1192  option_results opt_results {{}};
1193  results.options.insert(
1194  std::make_pair(defn.name, opt_results));
1195  }
1196 
1197  // Don't start off ignoring flags. We only ignore flags after a -- shows up
1198  // in the command line arguments.
1199  bool ignore_flags = false;
1200 
1201  // Keep track of any options that are expecting arguments.
1202  const char* last_flag_expecting_args = nullptr;
1203  option_result* last_option_expecting_args = nullptr;
1204  unsigned int num_option_args_to_consume = 0;
1205 
1206  // Get pointers to pointers so we can treat the raw pointer array as an
1207  // iterator for standard library algorithms. This isn't used yet but can be
1208  // used to template this function to work on iterators over strings or
1209  // C-strings.
1210  const char** arg_i = argv + 1;
1211  const char** arg_end = argv + argc;
1212 
1213  while (arg_i != arg_end) {
1214  auto arg_i_cstr = *arg_i;
1215  auto arg_i_len = std::strlen(arg_i_cstr);
1216 
1217  // Some behavior to note: if the previous option is expecting an argument
1218  // then the next entry will be treated as a positional argument even if
1219  // it looks like a flag.
1220  bool treat_as_positional_argument = (
1221  ignore_flags
1222  || num_option_args_to_consume > 0
1223  || !cmd_line_arg_is_option_flag(arg_i_cstr)
1224  );
1225  if (treat_as_positional_argument) {
1226 
1227  // If last option is expecting some specific positive number of
1228  // arguments then give this argument to that option, *regardless of
1229  // whether or not the argument looks like a flag or is the special "--"
1230  // argument*.
1231  if (num_option_args_to_consume > 0) {
1232  last_option_expecting_args->arg = arg_i_cstr;
1233  --num_option_args_to_consume;
1234  ++arg_i;
1235  continue;
1236  }
1237 
1238  // Now we check if this is just "--" which is a special argument that
1239  // causes all following arguments to be treated as non-options and is
1240  // itselve discarded.
1241  if (std::strncmp(arg_i_cstr, "--", 2) == 0 && arg_i_len == 2) {
1242  ignore_flags = true;
1243  ++arg_i;
1244  continue;
1245  }
1246 
1247  // If there are no expectations for option arguments then simply use
1248  // this argument as a positional argument.
1249  results.pos.push_back(arg_i_cstr);
1250  ++arg_i;
1251  continue;
1252  }
1253 
1254  // Reset the "expecting argument" state.
1255  last_flag_expecting_args = nullptr;
1256  last_option_expecting_args = nullptr;
1257  num_option_args_to_consume = 0;
1258 
1259  // If we're at this point then we're definitely dealing with something
1260  // that is flag-like and has hyphen as the first character and has a
1261  // length of at least two characters. How we handle this potential flag
1262  // depends on whether or not it is a long-option so we check that first.
1263  bool is_long_flag = (arg_i_cstr[1] == '-');
1264 
1265  if (is_long_flag) {
1266 
1267  // Long flags have a complication: their arguments can be specified
1268  // using an '=' character right inside the argument. That means an
1269  // argument like "--output=foobar.txt" is actually an option with flag
1270  // "--output" and argument "foobar.txt". So we look for the first
1271  // instance of the '=' character and keep it in long_flag_arg. If
1272  // long_flag_arg is nullptr then we didn't find '='. We need the
1273  // flag_len to construct long_flag_str below.
1274  auto long_flag_arg = std::strchr(arg_i_cstr, '=');
1275  std::size_t flag_len = arg_i_len;
1276  if (long_flag_arg != nullptr) {
1277  flag_len = static_cast<std::size_t>(long_flag_arg - arg_i_cstr);
1278  }
1279  std::string long_flag_str(arg_i_cstr, flag_len);
1280 
1281  if (!map.known_long_flag(long_flag_str)) {
1282  std::ostringstream msg;
1283  msg << "found unexpected flag: " << long_flag_str;
1284  throw unexpected_option_error(msg.str());
1285  }
1286 
1287  const auto defn = map.get_definition_for_long_flag(long_flag_str);
1288 
1289  if (long_flag_arg != nullptr && defn->num_args == 0) {
1290  std::ostringstream msg;
1291  msg << "found argument for option not expecting an argument: "
1292  << arg_i_cstr;
1293  throw unexpected_argument_error(msg.str());
1294  }
1295 
1296  // We've got a legitimate, known long flag option so we add an option
1297  // result. This option result initially has an arg of nullptr, but that
1298  // might change in the following block.
1299  auto& opt_results = results.options[defn->name];
1300  option_result opt_result {nullptr};
1301  opt_results.all.push_back(std::move(opt_result));
1302 
1303  if (defn->requires_arguments()) {
1304  bool there_is_an_equal_delimited_arg = (long_flag_arg != nullptr);
1305  if (there_is_an_equal_delimited_arg) {
1306  // long_flag_arg would be "=foo" in the "--output=foo" case so we
1307  // increment by 1 to get rid of the equal sign.
1308  opt_results.all.back().arg = long_flag_arg + 1;
1309  } else {
1310  last_flag_expecting_args = arg_i_cstr;
1311  last_option_expecting_args = &(opt_results.all.back());
1312  num_option_args_to_consume = defn->num_args;
1313  }
1314  }
1315 
1316  ++arg_i;
1317  continue;
1318  }
1319 
1320  // If we've made it here then we're looking at either a short flag or a
1321  // group of short flags. Short flags can be grouped together so long as
1322  // they don't require any arguments unless the option that does is the
1323  // last in the group ("-o x -v" is okay, "-vo x" is okay, "-ov x" is
1324  // not). So starting after the dash we're going to process each character
1325  // as if it were a separate flag. Note "sf_idx" stands for "short flag
1326  // index".
1327  for (std::size_t sf_idx = 1; sf_idx < arg_i_len; ++sf_idx) {
1328  const auto short_flag = arg_i_cstr[sf_idx];
1329 
1330  if (!std::isalnum(short_flag)) {
1331  std::ostringstream msg;
1332  msg << "found non-alphanumeric character '" << arg_i_cstr[sf_idx]
1333  << "' in flag group '" << arg_i_cstr << "'";
1334  throw std::domain_error(msg.str());
1335  }
1336 
1337  if (!map.known_short_flag(short_flag)) {
1338  std::ostringstream msg;
1339  msg << "found unexpected flag '" << arg_i_cstr[sf_idx]
1340  << "' in flag group '" << arg_i_cstr << "'";
1341  throw unexpected_option_error(msg.str());
1342  }
1343 
1344  auto defn = map.get_definition_for_short_flag(short_flag);
1345  auto& opt_results = results.options[defn->name];
1346 
1347  // Create an option result with an empty argument (for now) and add it
1348  // to this option's results.
1349  option_result opt_result {nullptr};
1350  opt_results.all.push_back(std::move(opt_result));
1351 
1352  if (defn->requires_arguments()) {
1353 
1354  // If this short flag's option requires an argument and we're the
1355  // last flag in the short flag group then just put the parser into
1356  // "expecting argument for last option" state and move onto the next
1357  // command line argument.
1358  bool is_last_short_flag_in_group = (sf_idx == arg_i_len - 1);
1359  if (is_last_short_flag_in_group) {
1360  last_flag_expecting_args = arg_i_cstr;
1361  last_option_expecting_args = &(opt_results.all.back());
1362  num_option_args_to_consume = defn->num_args;
1363  break;
1364  }
1365 
1366  // If this short flag's option requires an argument and we're NOT the
1367  // last flag in the short flag group then we automatically consume
1368  // the rest of the short flag group as the argument for this flag.
1369  // This is how we get the POSIX behavior of being able to specify a
1370  // flag's arguments without a white space delimiter (e.g.
1371  // "-I/usr/local/include").
1372  opt_results.all.back().arg = arg_i_cstr + sf_idx + 1;
1373  break;
1374  }
1375  }
1376 
1377  ++arg_i;
1378  continue;
1379  }
1380 
1381  // If we're done with all of the arguments but are still expecting
1382  // arguments for a previous option then we haven't satisfied that option.
1383  // This is an error.
1384  if (num_option_args_to_consume > 0) {
1385  std::ostringstream msg;
1386  msg << "last option \"" << last_flag_expecting_args
1387  << "\" expects an argument but the parser ran out of command line "
1388  << "arguments to parse";
1389  throw option_lacks_argument_error(msg.str());
1390  }
1391 
1392  return results;
1393 }
1394 
1395 
1396 inline
1397 parser_results parser::parse(int argc, char** argv) const
1398 {
1399  return parse(argc, const_cast<const char**>(argv));
1400 }
1401 
1402 
1403 namespace convert {
1404 
1405 
1412  template <typename T> inline
1413  T long_(const char* arg)
1414  {
1415  char* endptr = nullptr;
1416  errno = 0;
1417  T ret = static_cast<T>(std::strtol(arg, &endptr, 0));
1418  if (endptr == arg) {
1419  std::ostringstream msg;
1420  msg << "unable to convert argument to integer: \"" << arg << "\"";
1421  throw std::invalid_argument(msg.str());
1422  }
1423  if (errno == ERANGE) {
1424  throw std::out_of_range("argument numeric value out of range");
1425  }
1426  return ret;
1427  }
1428 
1429 
1436  template <typename T> inline
1437  T long_long_(const char* arg)
1438  {
1439  char* endptr = nullptr;
1440  errno = 0;
1441  T ret = static_cast<T>(std::strtoll(arg, &endptr, 0));
1442  if (endptr == arg) {
1443  std::ostringstream msg;
1444  msg << "unable to convert argument to integer: \"" << arg << "\"";
1445  throw std::invalid_argument(msg.str());
1446  }
1447  if (errno == ERANGE) {
1448  throw std::out_of_range("argument numeric value out of range");
1449  }
1450  return ret;
1451  }
1452 
1453 
1454 #define DEFINE_CONVERSION_FROM_LONG_(TYPE) \
1455  template <> inline \
1456  TYPE arg(const char* arg) \
1457  { \
1458  return long_<TYPE>(arg); \
1459  }
1460 
1462  DEFINE_CONVERSION_FROM_LONG_(unsigned char)
1463  DEFINE_CONVERSION_FROM_LONG_(signed char)
1465  DEFINE_CONVERSION_FROM_LONG_(unsigned short)
1467  DEFINE_CONVERSION_FROM_LONG_(unsigned int)
1469  DEFINE_CONVERSION_FROM_LONG_(unsigned long)
1470 
1471 #undef DEFINE_CONVERSION_FROM_LONG_
1472 
1473 
1474 #define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE) \
1475  template <> inline \
1476  TYPE arg(const char* arg) \
1477  { \
1478  return long_long_<TYPE>(arg); \
1479  }
1480 
1482  DEFINE_CONVERSION_FROM_LONG_LONG_(unsigned long long)
1483 
1484 #undef DEFINE_CONVERSION_FROM_LONG_LONG_
1485 
1486 
1487  template <typename T>
1488  T arg(const char* arg)
1489  {
1490  return converter<T>::convert(arg);
1491  }
1492 
1493 
1494  template <> inline
1495  bool arg(const char* arg)
1496  {
1497  return argagg::convert::arg<int>(arg) != 0;
1498  }
1499 
1500 
1501  template <> inline
1502  float arg(const char* arg)
1503  {
1504  char* endptr = nullptr;
1505  errno = 0;
1506  float ret = std::strtof(arg, &endptr);
1507  if (endptr == arg) {
1508  std::ostringstream msg;
1509  msg << "unable to convert argument to integer: \"" << arg << "\"";
1510  throw std::invalid_argument(msg.str());
1511  }
1512  if (errno == ERANGE) {
1513  throw std::out_of_range("argument numeric value out of range");
1514  }
1515  return ret;
1516  }
1517 
1518 
1519  template <> inline
1520  double arg(const char* arg)
1521  {
1522  char* endptr = nullptr;
1523  errno = 0;
1524  double ret = std::strtod(arg, &endptr);
1525  if (endptr == arg) {
1526  std::ostringstream msg;
1527  msg << "unable to convert argument to integer: \"" << arg << "\"";
1528  throw std::invalid_argument(msg.str());
1529  }
1530  if (errno == ERANGE) {
1531  throw std::out_of_range("argument numeric value out of range");
1532  }
1533  return ret;
1534  }
1535 
1536 
1537  template <> inline
1538  const char* arg(const char* arg)
1539  {
1540  return arg;
1541  }
1542 
1543 
1544  template <> inline
1545  std::string arg(const char* arg)
1546  {
1547  return std::string(arg);
1548  }
1549 
1550 
1551  template <typename T>
1553  const char*& s,
1554  T& out_arg,
1555  const char delim)
1556  {
1557  const char* begin = s;
1558  s = std::strchr(s, delim);
1559  if (s == nullptr) {
1560  std::string arg_str(begin);
1561  out_arg = argagg::convert::arg<T>(arg_str.c_str());
1562  return false;
1563  } else {
1564  std::string arg_str(begin, static_cast<std::size_t>(s - begin));
1565  out_arg = argagg::convert::arg<T>(arg_str.c_str());
1566  s += 1;
1567  return true;
1568  }
1569  }
1570 
1571 
1572 } // namespace convert
1573 
1574 
1575 inline
1577 : std::ostringstream(), output(output)
1578 {
1579 }
1580 
1581 
1582 inline
1584 {
1585  output << fmt_string(this->str());
1586 }
1587 
1588 
1589 inline
1591 {
1592  auto result = text;
1593 
1594  result.erase(
1595  result.begin(),
1596  std::find_if(
1597  result.begin(),
1598  result.end(),
1599  [](int ch) { return !std::isspace(ch); }));
1600 
1601  return result;
1602 }
1603 
1604 
1605 inline
1607 {
1608  auto result = text;
1609 
1610  result.erase(
1611  std::find_if(
1612  result.rbegin(),
1613  result.rend(),
1614  [](int ch) { return !std::isspace(ch); }).base(),
1615  result.end());
1616 
1617  return result;
1618 }
1619 
1620 
1621 inline
1623  const std::string& contents)
1624 {
1625  return indent + rstrip(contents) + "\n";
1626 }
1627 
1628 
1633 inline
1634 std::string wrap_line(const std::string& single_line,
1635  const std::size_t wrap_width)
1636 {
1637  auto indentation_spaces = single_line.find_first_not_of(" ");
1638  if (indentation_spaces == std::string::npos) {
1639  indentation_spaces = 0;
1640  }
1641 
1642  const auto line = lstrip(single_line);
1643  const auto indent = std::string(indentation_spaces, ' ');
1644 
1645  std::string result;
1646 
1647  std::size_t position = 0;
1648  std::size_t line_start = 0;
1649  while (true) {
1650  const auto new_position = line.find_first_of(" ", position);
1651  if (new_position == std::string::npos) {
1652  break;
1653  }
1654 
1655  if (new_position + indentation_spaces > line_start + wrap_width) {
1656  result += construct_line(
1657  indent, line.substr(line_start, position - line_start - 1));
1658 
1659  line_start = position;
1660  }
1661 
1662  position = new_position + 1;
1663  }
1664 
1665  return result + construct_line(indent, line.substr(line_start));
1666 }
1667 
1668 
1669 inline
1671 {
1672  std::stringstream ss(s);
1673  std::string line;
1674 
1675  std::string result;
1676 
1677  // Use default width of `fmt`.
1678  const auto column_width = 75;
1679 
1680  while (std::getline(ss, line, '\n')) {
1681  result += wrap_line(line, column_width);
1682  }
1683 
1684  return result;
1685 }
1686 
1687 
1688 } // namespace argagg
1689 
1690 
1691 inline
1693 {
1694  for (auto& definition : x.definitions) {
1695  os << " ";
1696  for (auto& flag : definition.flags) {
1697  os << flag;
1698  if (flag != definition.flags.back()) {
1699  os << ", ";
1700  }
1701  }
1702  os << "\n " << definition.help << '\n';
1703  }
1704  return os;
1705 }
1706 
1707 
1708 #endif // ARGAGG_ARGAGG_ARGAGG_HPP
#define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE)
Definition: argagg.hpp:1474
option_result & operator[](std::size_t index)
Gets a single option parse result by index.
Definition: argagg.hpp:795
std::string lstrip(const std::string &text)
Definition: argagg.hpp:1590
bool known_short_flag(const char flag) const
Returns true if the provided short flag exists in the map object.
Definition: argagg.hpp:1073
std::size_t count() const
Gets the number of positional arguments.
Definition: argagg.hpp:881
std::string construct_line(const std::string &indent, const std::string &contents)
Definition: argagg.hpp:1622
This exception is thrown when an unknown option is requested by name from an argagg::parser_results t...
Definition: argagg.hpp:152
Represents a single option parse result.
Definition: argagg.hpp:264
bool cmd_line_arg_is_option_flag(const char *s)
Checks whether or not a command line argument should be processed as an option flag. This is very similar to is_valid_flag_definition() but must allow for short flag groups (e.g. "-abc") and equal-assigned long flag arguments (e.g. "--output=foo.txt").
Definition: argagg.hpp:929
const char * arg
Argument parsed for this single option. If no argument was parsed this will be set to nullptr...
Definition: argagg.hpp:271
bool has_option(const std::string &name) const
Used to check if an option was specified at all.
Definition: argagg.hpp:850
T find_first_not_of(T... args)
bool requires_arguments() const
Returns true if this option requires arguments.
Definition: argagg.hpp:922
std::array< const definition *, 256 > short_map
Maps from a short flag (just a character) to a pointer to the original definition that the flag repre...
Definition: argagg.hpp:579
This exception is thrown when an option requires an argument but is not provided one. This can happen if another flag was found after the option or if we simply reach the end of the command line arguments.
Definition: argagg.hpp:128
std::vector< option_result > all
All option parse results for this option.
Definition: argagg.hpp:338
static T convert(const char *arg)
T isalnum(T... args)
bool known_long_flag(const std::string &flag) const
Returns true if the provided long flag exists in the map object.
Definition: argagg.hpp:1089
T getline(T... args)
STL namespace.
option_results & operator[](const std::string &name)
Get the parser results for the given definition. If the definition never showed up then the exception...
Definition: argagg.hpp:858
bool flag_is_short(const char *s)
Tests whether or not a valid flag is short. Assumes the provided cstring is already a valid flag...
Definition: argagg.hpp:1065
T end(T... args)
const definition * get_definition_for_short_flag(const char flag) const
If the short flag exists in the map object then it is returned by this method. If it doesn&#39;t then nul...
Definition: argagg.hpp:1081
bool parse_next_component(const char *&s, T &out_arg, const char delim=',')
A utility function for parsing an argument as a delimited list. To use, initialize a const char* poin...
Definition: argagg.hpp:1552
For simple types the main extension point for adding argument conversions is argagg::convert::arg<T>(...
Definition: argagg.hpp:186
const char * program
Returns the name of the program from the original arguments list. This is always the first argument...
Definition: argagg.hpp:418
std::string fmt_string(const std::string &s)
Processes the provided string using the fmt utility and returns the resulting output as a string...
Definition: argagg.hpp:1670
This exception is thrown when a long option is parsed and is given an argument using the "=" syntax b...
Definition: argagg.hpp:104
STL class.
unsigned int num_args
Number of arguments this option requires. Must be 0 or 1. All other values have undefined behavior...
Definition: argagg.hpp:515
parser_results parse(int argc, const char **argv) const
Parses the provided command line arguments and returns the results as parser_results.
Definition: argagg.hpp:1167
bool wants_no_arguments() const
Returns true if this option does not want any arguments.
Definition: argagg.hpp:915
std::vector< T > all_as() const
Gets all positional arguments converted to the given type.
Definition: argagg.hpp:902
bool operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition: argagg.hpp:843
T arg(const char *arg)
Explicit instantiations of this function are used to convert arguments to types.
Definition: argagg.hpp:1488
~fmt_ostream()
Special destructor that will format the accumulated string using fmt (via the argagg::fmt_string() fu...
Definition: argagg.hpp:1583
A convenience output stream that will accumulate what is streamed to it and then, on destruction...
Definition: argagg.hpp:683
T strlen(T... args)
bool operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition: argagg.hpp:781
Contains two maps which aid in option parsing. The first map, short_map, maps from a short flag (just...
Definition: argagg.hpp:572
fmt_ostream(std::ostream &output)
Construct to output to the provided output stream when this object is destroyed.
Definition: argagg.hpp:1576
T erase(T... args)
const std::string name
Name of the option. Option parser results are keyed by this name.
Definition: argagg.hpp:494
std::ostream & operator<<(std::ostream &os, const argagg::parser &x)
Writes the option help to the given stream.
Definition: argagg.hpp:1692
T as(std::size_t i=0) const
Gets a positional argument converted to the given type.
Definition: argagg.hpp:895
There are only two hard things in Computer Science: cache invalidation and naming things (Phil Karlto...
Definition: argagg.hpp:96
T str(T... args)
std::unordered_map< std::string, const definition * > long_map
Maps from a long flag (an std::string) to a pointer to the original definition that the flag represen...
Definition: argagg.hpp:586
T make_pair(T... args)
std::ostream & output
Reference to the final output stream that the formatted string will be streamed to.
Definition: argagg.hpp:690
T move(T... args)
An option definition which essentially represents what an option is.
Definition: argagg.hpp:488
T find_if(T... args)
T size(T... args)
std::vector< std::string > flags
List of strings to match that correspond to this option. Should be fully specified with hyphens (e...
Definition: argagg.hpp:501
std::string rstrip(const std::string &text)
Definition: argagg.hpp:1606
T long_long_(const char *arg)
Templated function for conversion to T using the std::strtoll() function. This is used for anything l...
Definition: argagg.hpp:1437
std::string help
Help string for this option.
Definition: argagg.hpp:507
STL class.
T begin(T... args)
T strncmp(T... args)
T all_of(T... args)
bool is_valid_flag_definition(const char *s)
Checks whether a flag in an option definition is valid. I suggest reading through the function source...
Definition: argagg.hpp:1009
T c_str(T... args)
std::size_t count() const
Gets the number of times the option shows up.
Definition: argagg.hpp:788
T strtol(T... args)
parser_map validate_definitions(const std::vector< definition > &definitions)
Validates a collection (specifically an std::vector) of definition objects by checking if the contain...
Definition: argagg.hpp:1110
STL class.
This exception is thrown when an option&#39;s flag is invalid. This can be the case if the flag is not pr...
Definition: argagg.hpp:141
#define DEFINE_CONVERSION_FROM_LONG_(TYPE)
Definition: argagg.hpp:1454
std::string arg(const char *arg)
Definition: argagg.hpp:1545
const definition * get_definition_for_long_flag(const std::string &flag) const
If the long flag exists in the map object then it is returned by this method. If it doesn&#39;t then null...
Definition: argagg.hpp:1098
T transform(T... args)
std::vector< definition > definitions
Vector of the option definitions which inform this parser how to parse the command line arguments...
Definition: argagg.hpp:643
std::unordered_map< std::string, option_results > options
Maps from definition name to the structure which contains the parser results for that definition...
Definition: argagg.hpp:425
Represents multiple option parse results for a single option. If treated as a single parse result it ...
Definition: argagg.hpp:332
T as() const
Converts the argument parsed for the LAST option parse result for the parent definition to the provid...
Definition: argagg.hpp:809
std::string wrap_line(const std::string &single_line, const std::size_t wrap_width)
Return a wrapped version of a single line of text.
Definition: argagg.hpp:1634
std::vector< const char * > pos
Vector of positional arguments.
Definition: argagg.hpp:431
This exception is thrown when an option is parsed unexpectedly such as when an argument was expected ...
Definition: argagg.hpp:116
STL class.
T isspace(T... args)
T strtof(T... args)
T long_(const char *arg)
Templated function for conversion to T using the std::strtol() function. This is used for anything lo...
Definition: argagg.hpp:1413
T as() const
Converts the argument parsed for this single option instance into the given type using the type match...
Definition: argagg.hpp:736
T strchr(T... args)
A list of option definitions used to inform how to parse arguments.
Definition: argagg.hpp:636
Represents all results of the parser including options and positional arguments.
Definition: argagg.hpp:411