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
T all_of(T... args)
#define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE)
Definition: argagg.hpp:1474
#define DEFINE_CONVERSION_FROM_LONG_(TYPE)
Definition: argagg.hpp:1454
std::ostream & operator<<(std::ostream &os, const argagg::parser &x)
Writes the option help to the given stream.
Definition: argagg.hpp:1692
T begin(T... args)
T c_str(T... args)
T erase(T... args)
T find_first_not_of(T... args)
T find_if(T... args)
T getline(T... args)
T isalnum(T... args)
T make_pair(T... args)
T move(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
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
bool arg(const char *arg)
Definition: argagg.hpp:1495
T arg(const char *arg)
Explicit instantiations of this function are used to convert arguments to types.
Definition: argagg.hpp:1488
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
There are only two hard things in Computer Science: cache invalidation and naming things (Phil Karlto...
Definition: argagg.hpp:96
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
std::string rstrip(const std::string &text)
Definition: argagg.hpp:1606
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
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....
Definition: argagg.hpp:929
std::string construct_line(const std::string &indent, const std::string &contents)
Definition: argagg.hpp:1622
std::string lstrip(const std::string &text)
Definition: argagg.hpp:1590
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
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
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
STL namespace.
T size(T... args)
T str(T... args)
T strchr(T... args)
T strlen(T... args)
T strncmp(T... args)
T strtof(T... args)
T strtol(T... args)
For simple types the main extension point for adding argument conversions is argagg::convert::arg<T>(...
Definition: argagg.hpp:186
static T convert(const char *arg)
An option definition which essentially represents what an option is.
Definition: argagg.hpp:488
bool requires_arguments() const
Returns true if this option requires arguments.
Definition: argagg.hpp:922
bool wants_no_arguments() const
Returns true if this option does not want any arguments.
Definition: argagg.hpp:915
const std::string name
Name of the option. Option parser results are keyed by this name.
Definition: argagg.hpp:494
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 help
Help string for this option.
Definition: argagg.hpp:507
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
A convenience output stream that will accumulate what is streamed to it and then, on destruction,...
Definition: argagg.hpp:683
std::ostream & output
Reference to the final output stream that the formatted string will be streamed to.
Definition: argagg.hpp:690
~fmt_ostream()
Special destructor that will format the accumulated string using fmt (via the argagg::fmt_string() fu...
Definition: argagg.hpp:1583
fmt_ostream(std::ostream &output)
Construct to output to the provided output stream when this object is destroyed.
Definition: argagg.hpp:1576
This exception is thrown when an option's flag is invalid. This can be the case if the flag is not pr...
Definition: argagg.hpp:142
This exception is thrown when an option requires an argument but is not provided one....
Definition: argagg.hpp:129
Represents a single option parse result.
Definition: argagg.hpp:264
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 operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition: argagg.hpp:781
T as() const
Converts the argument parsed for this single option instance into the given type using the type match...
Definition: argagg.hpp:736
Represents multiple option parse results for a single option. If treated as a single parse result it ...
Definition: argagg.hpp:332
std::size_t count() const
Gets the number of times the option shows up.
Definition: argagg.hpp:788
option_result & operator[](std::size_t index)
Gets a single option parse result by index.
Definition: argagg.hpp:795
std::vector< option_result > all
All option parse results for this option.
Definition: argagg.hpp:338
T as() const
Converts the argument parsed for the LAST option parse result for the parent definition to the provid...
Definition: argagg.hpp:809
bool operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition: argagg.hpp:843
Contains two maps which aid in option parsing. The first map, short_map, maps from a short flag (just...
Definition: argagg.hpp:572
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't then null...
Definition: argagg.hpp:1098
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
bool known_short_flag(const char flag) const
Returns true if the provided short flag exists in the map object.
Definition: argagg.hpp:1073
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
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't then nul...
Definition: argagg.hpp:1081
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
Represents all results of the parser including options and positional arguments.
Definition: argagg.hpp:411
std::size_t count() const
Gets the number of positional arguments.
Definition: argagg.hpp:881
std::vector< const char * > pos
Vector of positional arguments.
Definition: argagg.hpp:431
std::vector< T > all_as() const
Gets all positional arguments converted to the given type.
Definition: argagg.hpp:902
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 has_option(const std::string &name) const
Used to check if an option was specified at all.
Definition: argagg.hpp:850
T as(std::size_t i=0) const
Gets a positional argument converted to the given type.
Definition: argagg.hpp:895
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
const char * program
Returns the name of the program from the original arguments list. This is always the first argument.
Definition: argagg.hpp:418
A list of option definitions used to inform how to parse arguments.
Definition: argagg.hpp:636
std::vector< definition > definitions
Vector of the option definitions which inform this parser how to parse the command line arguments.
Definition: argagg.hpp:643
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
This exception is thrown when a long option is parsed and is given an argument using the "=" syntax b...
Definition: argagg.hpp:105
This exception is thrown when an option is parsed unexpectedly such as when an argument was expected ...
Definition: argagg.hpp:117
This exception is thrown when an unknown option is requested by name from an argagg::parser_results t...
Definition: argagg.hpp:153
T transform(T... args)