argagg: /tmp/pkg/src/argagg-0.4.7/include/argagg/argagg.hpp Source File

argagg

Loading...
Searching...
No Matches
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
104 struct unexpected_argument_error
105: public std::runtime_error {
106 using std::runtime_error::runtime_error;
107};
108
109
116 struct unexpected_option_error
117: public std::runtime_error {
118 using std::runtime_error::runtime_error;
119};
120
121
128 struct option_lacks_argument_error
129: public std::runtime_error {
130 using std::runtime_error::runtime_error;
131};
132
133
141 struct invalid_flag
142: public std::runtime_error {
143 using std::runtime_error::runtime_error;
144};
145
146
152 struct unknown_option
153: public std::runtime_error {
154 using std::runtime_error::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>
249 bool parse_next_component(
250 const char*& s,
251 T& out_arg,
252 const char delim = ',');
253
254}
255
256
264 struct option_result {
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
332 struct option_results {
333
338 std::vector<option_result> all;
339
344 std::size_t count() const;
345
350 option_result& operator [] (std::size_t index);
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
411 struct parser_results {
412
418 const char* program;
419
425 std::unordered_map<std::string, option_results> options;
426
431 std::vector<const char*> pos;
432
437 bool has_option(const std::string& name) const;
438
445 option_results& operator [] (const std::string& name);
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
494 const std::string name;
495
501 std::vector<std::string> flags;
502
507 std::string help;
508
515 unsigned int num_args;
516
521 bool wants_no_arguments() const;
522
527 bool requires_arguments() const;
528
529};
530
531
539bool cmd_line_arg_is_option_flag(
540 const char* s);
541
542
548bool is_valid_flag_definition(
549 const char* s);
550
551
557bool flag_is_short(
558 const char* s);
559
560
572 struct parser_map {
573
579 std::array<const definition*, 256> short_map;
580
586 std::unordered_map<std::string, const definition*> long_map;
587
592 bool known_short_flag(
593 const char flag) const;
594
600 const definition* get_definition_for_short_flag(
601 const char flag) const;
602
607 bool known_long_flag(
608 const std::string& flag) const;
609
615 const definition* get_definition_for_long_flag(
616 const std::string& flag) const;
617
618};
619
620
628 parser_map validate_definitions(
629 const std::vector<definition>& definitions);
630
631
636 struct parser {
637
643 std::vector<definition> definitions;
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
683 struct fmt_ostream : public std::ostringstream {
684
690 std::ostream& output;
691
697 fmt_ostream(std::ostream& output);
698
705 ~fmt_ostream();
706
707};
708
709
716 std::string fmt_string(const std::string& s);
717
718
719} // namespace argagg
720
721
726 std::ostream& operator << (std::ostream& os, const argagg::parser& x);
727
728
729// ---- end of declarations, header-only implementations follow ----
730
731
732namespace argagg {
733
734
735template <typename T>
736T option_result::as() const
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
746template <typename T>
747T 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
766template <typename T>
767option_result::operator T () const
768{
769 return this->as<T>();
770}
771
772
773template <> inline
774option_result::operator bool () const
775{
776 return this->arg != nullptr;
777}
778
779
780inline
781 bool option_result::operator ! () const
782{
783 return !static_cast<bool>(*this);
784}
785
786
787inline
788 std::size_t option_results::count() const
789{
790 return this->all.size();
791}
792
793
794inline
795 option_result& option_results::operator [] (std::size_t index)
796{
797 return this->all[index];
798}
799
800
801inline
802 const option_result& option_results::operator [] (std::size_t index) const
803{
804 return this->all[index];
805}
806
807
808template <typename T>
809T option_results::as() const
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
818template <typename T>
819T 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
828template <typename T>
829option_results::operator T () const
830{
831 return this->as<T>();
832}
833
834
835template <> inline
836option_results::operator bool () const
837{
838 return this->all.size() > 0;
839}
840
841
842inline
843 bool option_results::operator ! () const
844{
845 return !static_cast<bool>(*this);
846}
847
848
849inline
850 bool parser_results::has_option(const std::string& name) const
851{
852 const auto it = this->options.find(name);
853 return ( it != this->options.end()) && it->second.all.size() > 0;
854}
855
856
857inline
858 option_results& parser_results::operator [] (const std::string& name)
859try {
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
868inline
869const option_results&
870 parser_results::operator [] (const std::string& name) const
871try {
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
880inline
881 std::size_t parser_results::count() const
882{
883 return this->pos.size();
884}
885
886
887inline
888 const char* parser_results::operator [] (std::size_t index) const
889{
890 return this->pos[index];
891}
892
893
894template <typename T>
895T parser_results::as(std::size_t i) const
896{
897 return convert::arg<T>(this->pos[i]);
898}
899
900
901template <typename T>
902 std::vector<T> parser_results::all_as() const
903{
904 std::vector<T> v(this->pos.size());
905 std::transform(
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
914inline
915 bool definition::wants_no_arguments() const
916{
917 return this->num_args == 0;
918}
919
920
921inline
922 bool definition::requires_arguments() const
923{
924 return this->num_args > 0;
925}
926
927
928inline
929 bool cmd_line_arg_is_option_flag(
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
1008inline
1009 bool is_valid_flag_definition(
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
1064inline
1065 bool flag_is_short(
1066 const char* s)
1067{
1068 return s[0] == '-' && std::isalnum(s[1]);
1069}
1070
1071
1072inline
1073 bool parser_map::known_short_flag(
1074 const char flag) const
1075{
1076 return this->short_map[static_cast<std::size_t>(flag)] != nullptr;
1077}
1078
1079
1080inline
1081 const definition* parser_map::get_definition_for_short_flag(
1082 const char flag) const
1083{
1084 return this->short_map[static_cast<std::size_t>(flag)];
1085}
1086
1087
1088inline
1089 bool parser_map::known_long_flag(
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
1097inline
1098 const definition* parser_map::get_definition_for_long_flag(
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
1109inline
1110 parser_map validate_definitions(
1111 const std::vector<definition>& definitions)
1112{
1113 std::unordered_map<std::string, const definition*> long_map;
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
1166inline
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.
1181 parser_map map = validate_definitions(this->definitions);
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.
1186 std::unordered_map<std::string, option_results> options {};
1187 std::vector<const char*> pos;
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
1396inline
1397 parser_results parser::parse(int argc, char** argv) const
1398{
1399 return parse(argc, const_cast<const char**>(argv));
1400}
1401
1402
1403namespace 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
1461 DEFINE_CONVERSION_FROM_LONG_(char)
1462 DEFINE_CONVERSION_FROM_LONG_(unsigned char)
1463 DEFINE_CONVERSION_FROM_LONG_(signed char)
1464 DEFINE_CONVERSION_FROM_LONG_(short)
1465 DEFINE_CONVERSION_FROM_LONG_(unsigned short)
1466 DEFINE_CONVERSION_FROM_LONG_(int)
1467 DEFINE_CONVERSION_FROM_LONG_(unsigned int)
1468 DEFINE_CONVERSION_FROM_LONG_(long)
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
1481 DEFINE_CONVERSION_FROM_LONG_LONG_(long long)
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>
1552 bool parse_next_component(
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
1575inline
1576fmt_ostream::fmt_ostream(std::ostream& output)
1577: std::ostringstream(), output(output)
1578{
1579}
1580
1581
1582inline
1583fmt_ostream::~fmt_ostream()
1584{
1585 output << fmt_string(this->str());
1586}
1587
1588
1589inline
1590 std::string lstrip(const std::string& text)
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
1605inline
1606 std::string rstrip(const std::string& text)
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
1621inline
1622 std::string construct_line(const std::string& indent,
1623 const std::string& contents)
1624{
1625 return indent + rstrip(contents) + "\n";
1626}
1627
1628
1633inline
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
1669inline
1670 std::string fmt_string(const std::string& s)
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
1691inline
1692 std::ostream& operator << (std::ostream& os, const argagg::parser& x)
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
std::all_of
T all_of(T... args)
DEFINE_CONVERSION_FROM_LONG_LONG_
#define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE)
Definition argagg.hpp:1474
DEFINE_CONVERSION_FROM_LONG_
#define DEFINE_CONVERSION_FROM_LONG_(TYPE)
Definition argagg.hpp:1454
operator<<
std::ostream & operator<<(std::ostream &os, const argagg::parser &x)
Writes the option help to the given stream.
Definition argagg.hpp:1692
std::array
std::ostream
std::ostringstream
std::string
std::stringstream
std::vector::begin
T begin(T... args)
std::string::c_str
T c_str(T... args)
std::domain_error
std::string::erase
T erase(T... args)
std::string::find_first_not_of
T find_first_not_of(T... args)
std::find_if
T find_if(T... args)
std::getline
T getline(T... args)
std::invalid_argument
std::isalnum
T isalnum(T... args)
std::make_pair
T make_pair(T... args)
std::move
T move(T... args)
argagg::convert
The set of template instantiations that convert C-strings to other types for the option_result::as(),...
Definition argagg.hpp:164
argagg::convert::long_
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
argagg::convert::parse_next_component
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
argagg::convert::arg
T arg(const char *arg)
Explicit instantiations of this function are used to convert arguments to types.
Definition argagg.hpp:1488
argagg::convert::long_long_
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
argagg
There are only two hard things in Computer Science: cache invalidation and naming things (Phil Karlto...
Definition argagg.hpp:96
argagg::validate_definitions
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
argagg::rstrip
std::string rstrip(const std::string &text)
Definition argagg.hpp:1606
argagg::flag_is_short
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
argagg::cmd_line_arg_is_option_flag
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
argagg::construct_line
std::string construct_line(const std::string &indent, const std::string &contents)
Definition argagg.hpp:1622
argagg::lstrip
std::string lstrip(const std::string &text)
Definition argagg.hpp:1590
argagg::fmt_string
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
argagg::wrap_line
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
argagg::is_valid_flag_definition
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
std
STL namespace.
std::out_of_range
std::vector::push_back
T push_back(T... args)
std::runtime_error
std::size_t
std::ostringstream::str
T str(T... args)
std::strchr
T strchr(T... args)
std::strlen
T strlen(T... args)
std::strncmp
T strncmp(T... args)
std::strtof
T strtof(T... args)
std::strtol
T strtol(T... args)
argagg::convert::converter
For simple types the main extension point for adding argument conversions is argagg::convert::arg<T>(...
Definition argagg.hpp:186
argagg::convert::converter::convert
static T convert(const char *arg)
argagg::definition
An option definition which essentially represents what an option is.
Definition argagg.hpp:488
argagg::definition::requires_arguments
bool requires_arguments() const
Returns true if this option requires arguments.
Definition argagg.hpp:922
argagg::definition::wants_no_arguments
bool wants_no_arguments() const
Returns true if this option does not want any arguments.
Definition argagg.hpp:915
argagg::definition::name
const std::string name
Name of the option. Option parser results are keyed by this name.
Definition argagg.hpp:494
argagg::definition::flags
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
argagg::definition::help
std::string help
Help string for this option.
Definition argagg.hpp:507
argagg::definition::num_args
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
argagg::fmt_ostream::output
std::ostream & output
Reference to the final output stream that the formatted string will be streamed to.
Definition argagg.hpp:690
argagg::fmt_ostream::~fmt_ostream
~fmt_ostream()
Special destructor that will format the accumulated string using fmt (via the argagg::fmt_string() fu...
Definition argagg.hpp:1583
argagg::fmt_ostream::fmt_ostream
fmt_ostream(std::ostream &output)
Construct to output to the provided output stream when this object is destroyed.
Definition argagg.hpp:1576
argagg::invalid_flag
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
argagg::option_lacks_argument_error
This exception is thrown when an option requires an argument but is not provided one....
Definition argagg.hpp:129
argagg::option_result
Represents a single option parse result.
Definition argagg.hpp:264
argagg::option_result::arg
const char * arg
Argument parsed for this single option. If no argument was parsed this will be set to nullptr.
Definition argagg.hpp:271
argagg::option_result::operator!
bool operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition argagg.hpp:781
argagg::option_result::as
T as() const
Converts the argument parsed for this single option instance into the given type using the type match...
Definition argagg.hpp:736
argagg::option_results
Represents multiple option parse results for a single option. If treated as a single parse result it ...
Definition argagg.hpp:332
argagg::option_results::count
std::size_t count() const
Gets the number of times the option shows up.
Definition argagg.hpp:788
argagg::option_results::operator[]
option_result & operator[](std::size_t index)
Gets a single option parse result by index.
Definition argagg.hpp:795
argagg::option_results::all
std::vector< option_result > all
All option parse results for this option.
Definition argagg.hpp:338
argagg::option_results::as
T as() const
Converts the argument parsed for the LAST option parse result for the parent definition to the provid...
Definition argagg.hpp:809
argagg::option_results::operator!
bool operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition argagg.hpp:843
argagg::parser_map
Contains two maps which aid in option parsing. The first map, short_map, maps from a short flag (just...
Definition argagg.hpp:572
argagg::parser_map::get_definition_for_long_flag
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
argagg::parser_map::long_map
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
argagg::parser_map::known_short_flag
bool known_short_flag(const char flag) const
Returns true if the provided short flag exists in the map object.
Definition argagg.hpp:1073
argagg::parser_map::known_long_flag
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
argagg::parser_map::get_definition_for_short_flag
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
argagg::parser_map::short_map
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
argagg::parser_results
Represents all results of the parser including options and positional arguments.
Definition argagg.hpp:411
argagg::parser_results::count
std::size_t count() const
Gets the number of positional arguments.
Definition argagg.hpp:881
argagg::parser_results::pos
std::vector< const char * > pos
Vector of positional arguments.
Definition argagg.hpp:431
argagg::parser_results::all_as
std::vector< T > all_as() const
Gets all positional arguments converted to the given type.
Definition argagg.hpp:902
argagg::parser_results::operator[]
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
argagg::parser_results::has_option
bool has_option(const std::string &name) const
Used to check if an option was specified at all.
Definition argagg.hpp:850
argagg::parser_results::as
T as(std::size_t i=0) const
Gets a positional argument converted to the given type.
Definition argagg.hpp:895
argagg::parser_results::options
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
argagg::parser_results::program
const char * program
Returns the name of the program from the original arguments list. This is always the first argument.
Definition argagg.hpp:418
argagg::parser
A list of option definitions used to inform how to parse arguments.
Definition argagg.hpp:636
argagg::parser::definitions
std::vector< definition > definitions
Vector of the option definitions which inform this parser how to parse the command line arguments.
Definition argagg.hpp:643
argagg::parser::parse
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
argagg::unexpected_argument_error
This exception is thrown when a long option is parsed and is given an argument using the "=" syntax b...
Definition argagg.hpp:105
argagg::unexpected_option_error
This exception is thrown when an option is parsed unexpectedly such as when an argument was expected ...
Definition argagg.hpp:117
argagg::unknown_option
This exception is thrown when an unknown option is requested by name from an argagg::parser_results t...
Definition argagg.hpp:153
std::transform
T transform(T... args)
std::unordered_map
std::vector