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) 2017 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#ifdef __unix__
35#include <stdio.h>
36#include <unistd.h>
37#endif // #ifdef __unix__
38
39#include <algorithm>
40#include <array>
41#include <cstdlib>
42#include <cstring>
43#include <cctype>
44#include <iterator>
45#include <ostream>
46#include <sstream>
47#include <stdexcept>
48#include <string>
49#include <unordered_map>
50#include <utility>
51#include <vector>
52
53
101 namespace argagg {
102
103
109 struct unexpected_argument_error
110: public std::runtime_error {
111 using std::runtime_error::runtime_error;
112};
113
114
121 struct unexpected_option_error
122: public std::runtime_error {
123 using std::runtime_error::runtime_error;
124};
125
126
133 struct option_lacks_argument_error
134: public std::runtime_error {
135 using std::runtime_error::runtime_error;
136};
137
138
146 struct invalid_flag
147: public std::runtime_error {
148 using std::runtime_error::runtime_error;
149};
150
151
158 namespace convert {
159
165 template <typename T>
166 T arg(const char* arg);
167
168}
169
170
178 struct option_result {
179
185 const char* arg;
186
196 template <typename T>
197 T as() const;
198
208 template <typename T>
209 T as(const T& t) const;
210
223 template <typename T>
224 operator T () const;
225
226};
227
228
239 struct option_results {
240
245 std::vector<option_result> all;
246
251 std::size_t count() const;
252
257 option_result& operator [] (std::size_t index);
258
263 const option_result& operator [] (std::size_t index) const;
264
274 template <typename T>
275 T as() const;
276
285 template <typename T>
286 T as(const T& t) const;
287
300 template <typename T>
301 operator T () const;
302
303};
304
305
311 struct parser_results {
312
318 const char* program;
319
325 std::unordered_map<std::string, option_results> options;
326
331 std::vector<const char*> pos;
332
337 bool has_option(const std::string& name) const;
338
345 option_results& operator [] (const std::string& name);
346
353 const option_results& operator [] (const std::string& name) const;
354
359 std::size_t count() const;
360
365 const char* operator [] (std::size_t index) const;
366
371 template <typename T>
372 T as(std::size_t i = 0) const;
373
378 template <typename T>
379 std::vector<T> all_as() const;
380
381};
382
383
388 struct definition {
389
394 const std::string name;
395
401 std::vector<std::string> flags;
402
407 std::string help;
408
415 unsigned int num_args;
416
421 bool wants_no_arguments() const;
422
427 bool requires_arguments() const;
428
429};
430
431
439 bool cmd_line_arg_is_option_flag(
440 const char* s);
441
442
448 bool is_valid_flag_definition(
449 const char* s);
450
451
457 bool flag_is_short(
458 const char* s);
459
460
472 struct parser_map {
473
479 std::array<const definition*, 256> short_map;
480
486 std::unordered_map<std::string, const definition*> long_map;
487
492 bool known_short_flag(
493 const char flag) const;
494
500 const definition* get_definition_for_short_flag(
501 const char flag) const;
502
507 bool known_long_flag(
508 const std::string& flag) const;
509
515 const definition* get_definition_for_long_flag(
516 const std::string& flag) const;
517
518};
519
520
528 parser_map validate_definitions(
529 const std::vector<definition>& definitions);
530
531
536 struct parser {
537
543 std::vector<definition> definitions;
544
554 parser_results parse(int argc, const char** argv) const;
555
563 parser_results parse(int argc, char** argv) const;
564
565};
566
567
591 struct fmt_ostream : public std::ostringstream {
592
598 std::ostream& output;
599
605 fmt_ostream(std::ostream& output);
606
613 ~fmt_ostream();
614
615};
616
617
633 std::string fmt_string(const std::string& s);
634
635
636} // namespace argagg
637
638
643 std::ostream& operator << (std::ostream& os, const argagg::parser& x);
644
645
646// ---- end of declarations, header-only implementations follow ----
647
648
649 namespace argagg {
650
651
652 template <typename T>
653T option_result::as() const
654{
655 if (this->arg) {
656 return convert::arg<T>(this->arg);
657 } else {
658 throw option_lacks_argument_error("option has no argument");
659 }
660}
661
662
663 template <typename T>
664T option_result::as(const T& t) const
665{
666 if (this->arg) {
667 try {
668 return convert::arg<T>(this->arg);
669 } catch (...) {
670 return t;
671 }
672 } else {
673 // I actually think this will never happen. To call this method you have
674 // to access a specific option_result for an option. If there's a
675 // specific option_result then the option was found. If the option
676 // requires an argument then it will definitely have an argument
677 // otherwise the parser would have complained.
678 return t;
679 }
680}
681
682
683 template <typename T>
684option_result::operator T () const
685{
686 return this->as<T>();
687}
688
689
690 template <> inline
691option_result::operator bool () const
692{
693 return this->arg != nullptr;
694}
695
696
697 inline
698 std::size_t option_results::count() const
699{
700 return this->all.size();
701}
702
703
704 inline
705 option_result& option_results::operator [] (std::size_t index)
706{
707 return this->all[index];
708}
709
710
711 inline
712 const option_result& option_results::operator [] (std::size_t index) const
713{
714 return this->all[index];
715}
716
717
718 template <typename T>
719T option_results::as() const
720{
721 if (this->all.size() == 0) {
722 throw std::out_of_range("no option arguments to convert");
723 }
724 return this->all.back().as<T>();
725}
726
727
728 template <typename T>
729T option_results::as(const T& t) const
730{
731 if (this->all.size() == 0) {
732 return t;
733 }
734 return this->all.back().as<T>(t);
735}
736
737
738 template <typename T>
739option_results::operator T () const
740{
741 return this->as<T>();
742}
743
744
745 template <> inline
746option_results::operator bool () const
747{
748 return this->all.size() > 0;
749}
750
751
752 inline
753 bool parser_results::has_option(const std::string& name) const
754{
755 const auto it = this->options.find(name);
756 return ( it != this->options.end()) && it->second.all.size() > 0;
757}
758
759
760 inline
761 option_results& parser_results::operator [] (const std::string& name)
762{
763 return this->options.at(name);
764}
765
766
767 inline
768 const option_results&
769 parser_results::operator [] (const std::string& name) const
770{
771 return this->options.at(name);
772}
773
774
775 inline
776 std::size_t parser_results::count() const
777{
778 return this->pos.size();
779}
780
781
782 inline
783 const char* parser_results::operator [] (std::size_t index) const
784{
785 return this->pos[index];
786}
787
788
789 template <typename T>
790T parser_results::as(std::size_t i) const
791{
792 return convert::arg<T>(this->pos[i]);
793}
794
795
796 template <typename T>
797 std::vector<T> parser_results::all_as() const
798{
799 std::vector<T> v(this->pos.size());
800 std::transform(
801 this->pos.begin(), this->pos.end(), v.begin(),
802 [](const char* arg) {
803 return convert::arg<T>(arg);
804 });
805 return v;
806}
807
808
809 inline
810 bool definition::wants_no_arguments() const
811{
812 return this->num_args == 0;
813}
814
815
816 inline
817 bool definition::requires_arguments() const
818{
819 return this->num_args > 0;
820}
821
822
823 inline
824 bool cmd_line_arg_is_option_flag(
825 const char* s)
826{
827 auto len = std::strlen(s);
828
829 // The shortest possible flag has two characters: a hyphen and an
830 // alpha-numeric character.
831 if (len < 2) {
832 return false;
833 }
834
835 // All flags must start with a hyphen.
836 if (s[0] != '-') {
837 return false;
838 }
839
840 // Shift the name forward by a character to account for the initial hyphen.
841 // This means if s was originally "-v" then name will be "v".
842 const char* name = s + 1;
843
844 // Check if we're dealing with a long flag.
845 bool is_long = false;
846 if (s[1] == '-') {
847 is_long = true;
848
849 // Just -- is not a valid flag.
850 if (len == 2) {
851 return false;
852 }
853
854 // Shift the name forward to account for the extra hyphen. This means if s
855 // was originally "--output" then name will be "output".
856 name = s + 2;
857 }
858
859 // The first character of the flag name must be alpha-numeric. This is to
860 // prevent things like "---a" from being valid flags.
861 len = std::strlen(name);
862 if (!std::isalnum(name[0])) {
863 return false;
864 }
865
866 // At this point in is_valid_flag_definition() we would check if the short
867 // flag has only one character. At command line specification you can group
868 // short flags together or even add an argument to a short flag without a
869 // space delimiter. Thus we don't check if this has only one character
870 // because it might not.
871
872 // If this is a long flag then we expect all characters *up to* an equal sign
873 // to be alpha-numeric or a hyphen. After the equal sign you are specify the
874 // argument to a long flag which can be basically anything.
875 if (is_long) {
876 bool encountered_equal = false;
877 return std::all_of(name, name + len, [&](const char& c) {
878 if (encountered_equal) {
879 return true;
880 } else {
881 if (c == '=') {
882 encountered_equal = true;
883 return true;
884 }
885 return std::isalnum(c) || c == '-';
886 }
887 });
888 }
889
890 // At this point we are not dealing with a long flag. We already checked that
891 // the first character is alpha-numeric so we've got the case of a single
892 // short flag covered. This might be a short flag group though and we might
893 // be tempted to check that each character of the short flag group is
894 // alpha-numeric. However, you can specify the argument for a short flag
895 // without a space delimiter (e.g. "-I/usr/local/include") so you can't tell
896 // if the rest of a short flag group is part of the argument or not unless
897 // you know what is a defined flag or not. We leave that kind of processing
898 // to the parser.
899 return true;
900}
901
902
903 inline
904 bool is_valid_flag_definition(
905 const char* s)
906{
907 auto len = std::strlen(s);
908
909 // The shortest possible flag has two characters: a hyphen and an
910 // alpha-numeric character.
911 if (len < 2) {
912 return false;
913 }
914
915 // All flags must start with a hyphen.
916 if (s[0] != '-') {
917 return false;
918 }
919
920 // Shift the name forward by a character to account for the initial hyphen.
921 // This means if s was originally "-v" then name will be "v".
922 const char* name = s + 1;
923
924 // Check if we're dealing with a long flag.
925 bool is_long = false;
926 if (s[1] == '-') {
927 is_long = true;
928
929 // Just -- is not a valid flag.
930 if (len == 2) {
931 return false;
932 }
933
934 // Shift the name forward to account for the extra hyphen. This means if s
935 // was originally "--output" then name will be "output".
936 name = s + 2;
937 }
938
939 // The first character of the flag name must be alpha-numeric. This is to
940 // prevent things like "---a" from being valid flags.
941 len = std::strlen(name);
942 if (!std::isalnum(name[0])) {
943 return false;
944 }
945
946 // If this is a short flag then it must only have one character.
947 if (!is_long && len > 1) {
948 return false;
949 }
950
951 // The rest of the characters must be alpha-numeric, but long flags are
952 // allowed to have hyphens too.
953 return std::all_of(name + 1, name + len, [&](const char& c) {
954 return std::isalnum(c) || (c == '-' && is_long);
955 });
956}
957
958
959 inline
960 bool flag_is_short(
961 const char* s)
962{
963 return s[0] == '-' && std::isalnum(s[1]);
964}
965
966
967 inline
968 bool parser_map::known_short_flag(
969 const char flag) const
970{
971 return this->short_map[flag] != nullptr;
972}
973
974
975 inline
976 const definition* parser_map::get_definition_for_short_flag(
977 const char flag) const
978{
979 return this->short_map[flag];
980}
981
982
983 inline
984 bool parser_map::known_long_flag(
985 const std::string& flag) const
986{
987 const auto existing_long_flag = this->long_map.find(flag);
988 return existing_long_flag != long_map.end();
989}
990
991
992 inline
993 const definition* parser_map::get_definition_for_long_flag(
994 const std::string& flag) const
995{
996 const auto existing_long_flag = this->long_map.find(flag);
997 if (existing_long_flag == long_map.end()) {
998 return nullptr;
999 }
1000 return existing_long_flag->second;
1001}
1002
1003
1004 inline
1005 parser_map validate_definitions(
1006 const std::vector<definition>& definitions)
1007{
1008 std::unordered_map<std::string, const definition*> long_map;
1009 parser_map map {{{nullptr}}, std::move(long_map)};
1010
1011 for (auto& defn : definitions) {
1012
1013 if (defn.flags.size() == 0) {
1014 std::ostringstream msg;
1015 msg << "option \"" << defn.name << "\" has no flag definitions";
1016 throw invalid_flag(msg.str());
1017 }
1018
1019 for (auto& flag : defn.flags) {
1020
1021 if (!is_valid_flag_definition(flag.data())) {
1022 std::ostringstream msg;
1023 msg << "flag \"" << flag << "\" specified for option \"" << defn.name
1024 << "\" is invalid";
1025 throw invalid_flag(msg.str());
1026 }
1027
1028 if (flag_is_short(flag.data())) {
1029 const int short_flag_letter = flag[1];
1030 const auto existing_short_flag = map.short_map[short_flag_letter];
1031 bool short_flag_already_exists = (existing_short_flag != nullptr);
1032 if (short_flag_already_exists) {
1033 std::ostringstream msg;
1034 msg << "duplicate short flag \"" << flag
1035 << "\" found, specified by both option \"" << defn.name
1036 << "\" and option \"" << existing_short_flag->name;
1037 throw invalid_flag(msg.str());
1038 }
1039 map.short_map[short_flag_letter] = &defn;
1040 continue;
1041 }
1042
1043 // If we're here then this is a valid, long-style flag.
1044 if (map.known_long_flag(flag)) {
1045 const auto existing_long_flag = map.get_definition_for_long_flag(flag);
1046 std::ostringstream msg;
1047 msg << "duplicate long flag \"" << flag
1048 << "\" found, specified by both option \"" << defn.name
1049 << "\" and option \"" << existing_long_flag->name;
1050 throw invalid_flag(msg.str());
1051 }
1052 map.long_map.insert(std::make_pair(flag, &defn));
1053 }
1054 }
1055
1056 return map;
1057}
1058
1059
1060 inline
1061 parser_results parser::parse(int argc, const char** argv) const
1062{
1063 // Inspect each definition to see if its valid. You may wonder "why don't
1064 // you do this validation on construction?" I had thought about it but
1065 // realized that since I've made the parser an aggregate type (granted it
1066 // just "aggregates" a single vector) I would need to track any changes to
1067 // the definitions vector and re-run the validity check in order to
1068 // maintain this expected "validity invariant" on the object. That would
1069 // then require hiding the definitions vector as a private entry and then
1070 // turning the parser into a thin interface (by re-exposing setters and
1071 // getters) to the vector methods just so that I can catch when the
1072 // definition has been modified. It seems much simpler to just enforce the
1073 // validity when you actually want to parser because it's at the moment of
1074 // parsing that you know the definitions are complete.
1075 parser_map map = validate_definitions(this->definitions);
1076
1077 // Initialize the parser results that we'll be returning. Store the program
1078 // name (assumed to be the first command line argument) and initialize
1079 // everything else as empty.
1080 std::unordered_map<std::string, option_results> options {};
1081 std::vector<const char*> pos;
1082 parser_results results {argv[0], std::move(options), std::move(pos)};
1083
1084 // Add an empty option result for each definition.
1085 for (const auto& defn : this->definitions) {
1086 option_results opt_results {{}};
1087 results.options.insert(
1088 std::make_pair(defn.name, opt_results));
1089 }
1090
1091 // Don't start off ignoring flags. We only ignore flags after a -- shows up
1092 // in the command line arguments.
1093 bool ignore_flags = false;
1094
1095 // Keep track of any options that are expecting arguments.
1096 const char* last_flag_expecting_args = nullptr;
1097 option_result* last_option_expecting_args = nullptr;
1098 unsigned int num_option_args_to_consume = 0;
1099
1100 // Get pointers to pointers so we can treat the raw pointer array as an
1101 // iterator for standard library algorithms. This isn't used yet but can be
1102 // used to template this function to work on iterators over strings or
1103 // C-strings.
1104 const char** arg_i = argv + 1;
1105 const char** arg_end = argv + argc;
1106
1107 while (arg_i != arg_end) {
1108 auto arg_i_cstr = *arg_i;
1109 auto arg_i_len = std::strlen(arg_i_cstr);
1110
1111 // Some behavior to note: if the previous option is expecting an argument
1112 // then the next entry will be treated as a positional argument even if
1113 // it looks like a flag.
1114 bool treat_as_positional_argument = (
1115 ignore_flags
1116 || num_option_args_to_consume > 0
1117 || !cmd_line_arg_is_option_flag(arg_i_cstr)
1118 );
1119 if (treat_as_positional_argument) {
1120
1121 // If last option is expecting some specific positive number of
1122 // arguments then give this argument to that option, *regardless of
1123 // whether or not the argument looks like a flag or is the special "--"
1124 // argument*.
1125 if (num_option_args_to_consume > 0) {
1126 last_option_expecting_args->arg = arg_i_cstr;
1127 --num_option_args_to_consume;
1128 ++arg_i;
1129 continue;
1130 }
1131
1132 // Now we check if this is just "--" which is a special argument that
1133 // causes all following arguments to be treated as non-options and is
1134 // itselve discarded.
1135 if (std::strncmp(arg_i_cstr, "--", 2) == 0 && arg_i_len == 2) {
1136 ignore_flags = true;
1137 ++arg_i;
1138 continue;
1139 }
1140
1141 // If there are no expectations for option arguments then simply use
1142 // this argument as a positional argument.
1143 results.pos.push_back(arg_i_cstr);
1144 ++arg_i;
1145 continue;
1146 }
1147
1148 // Reset the "expecting argument" state.
1149 last_flag_expecting_args = nullptr;
1150 last_option_expecting_args = nullptr;
1151 num_option_args_to_consume = 0;
1152
1153 // If we're at this point then we're definitely dealing with something
1154 // that is flag-like and has hyphen as the first character and has a
1155 // length of at least two characters. How we handle this potential flag
1156 // depends on whether or not it is a long-option so we check that first.
1157 bool is_long_flag = (arg_i_cstr[1] == '-');
1158
1159 if (is_long_flag) {
1160
1161 // Long flags have a complication: their arguments can be specified
1162 // using an '=' character right inside the argument. That means an
1163 // argument like "--output=foobar.txt" is actually an option with flag
1164 // "--output" and argument "foobar.txt". So we look for the first
1165 // instance of the '=' character and keep it in long_flag_arg. If
1166 // long_flag_arg is nullptr then we didn't find '='. We need the
1167 // flag_len to construct long_flag_str below.
1168 auto long_flag_arg = std::strchr(arg_i_cstr, '=');
1169 std::size_t flag_len = arg_i_len;
1170 if (long_flag_arg != nullptr) {
1171 flag_len = long_flag_arg - arg_i_cstr;
1172 }
1173 std::string long_flag_str(arg_i_cstr, flag_len);
1174
1175 if (!map.known_long_flag(long_flag_str)) {
1176 std::ostringstream msg;
1177 msg << "found unexpected flag: " << long_flag_str;
1178 throw unexpected_option_error(msg.str());
1179 }
1180
1181 const auto defn = map.get_definition_for_long_flag(long_flag_str);
1182
1183 if (long_flag_arg != nullptr && defn->num_args == 0) {
1184 std::ostringstream msg;
1185 msg << "found argument for option not expecting an argument: "
1186 << arg_i_cstr;
1187 throw unexpected_argument_error(msg.str());
1188 }
1189
1190 // We've got a legitimate, known long flag option so we add an option
1191 // result. This option result initially has an arg of nullptr, but that
1192 // might change in the following block.
1193 auto& opt_results = results.options[defn->name];
1194 option_result opt_result {nullptr};
1195 opt_results.all.push_back(std::move(opt_result));
1196
1197 if (defn->requires_arguments()) {
1198 bool there_is_an_equal_delimited_arg = (long_flag_arg != nullptr);
1199 if (there_is_an_equal_delimited_arg) {
1200 // long_flag_arg would be "=foo" in the "--output=foo" case so we
1201 // increment by 1 to get rid of the equal sign.
1202 opt_results.all.back().arg = long_flag_arg + 1;
1203 } else {
1204 last_flag_expecting_args = arg_i_cstr;
1205 last_option_expecting_args = &(opt_results.all.back());
1206 num_option_args_to_consume = defn->num_args;
1207 }
1208 }
1209
1210 ++arg_i;
1211 continue;
1212 }
1213
1214 // If we've made it here then we're looking at either a short flag or a
1215 // group of short flags. Short flags can be grouped together so long as
1216 // they don't require any arguments unless the option that does is the
1217 // last in the group ("-o x -v" is okay, "-vo x" is okay, "-ov x" is
1218 // not). So starting after the dash we're going to process each character
1219 // as if it were a separate flag. Note "sf_idx" stands for "short flag
1220 // index".
1221 for (std::size_t sf_idx = 1; sf_idx < arg_i_len; ++sf_idx) {
1222 const auto short_flag = arg_i_cstr[sf_idx];
1223
1224 if (!std::isalnum(short_flag)) {
1225 std::ostringstream msg;
1226 msg << "found non-alphanumeric character '" << arg_i_cstr[sf_idx]
1227 << "' in flag group '" << arg_i_cstr << "'";
1228 throw std::domain_error(msg.str());
1229 }
1230
1231 if (!map.known_short_flag(short_flag)) {
1232 std::ostringstream msg;
1233 msg << "found unexpected flag '" << arg_i_cstr[sf_idx]
1234 << "' in flag group '" << arg_i_cstr << "'";
1235 throw unexpected_option_error(msg.str());
1236 }
1237
1238 auto defn = map.get_definition_for_short_flag(short_flag);
1239 auto& opt_results = results.options[defn->name];
1240
1241 // Create an option result with an empty argument (for now) and add it
1242 // to this option's results.
1243 option_result opt_result {nullptr};
1244 opt_results.all.push_back(std::move(opt_result));
1245
1246 if (defn->requires_arguments()) {
1247
1248 // If this short flag's option requires an argument and we're the
1249 // last flag in the short flag group then just put the parser into
1250 // "expecting argument for last option" state and move onto the next
1251 // command line argument.
1252 bool is_last_short_flag_in_group = (sf_idx == arg_i_len - 1);
1253 if (is_last_short_flag_in_group) {
1254 last_flag_expecting_args = arg_i_cstr;
1255 last_option_expecting_args = &(opt_results.all.back());
1256 num_option_args_to_consume = defn->num_args;
1257 break;
1258 }
1259
1260 // If this short flag's option requires an argument and we're NOT the
1261 // last flag in the short flag group then we automatically consume
1262 // the rest of the short flag group as the argument for this flag.
1263 // This is how we get the POSIX behavior of being able to specify a
1264 // flag's arguments without a white space delimiter (e.g.
1265 // "-I/usr/local/include").
1266 opt_results.all.back().arg = arg_i_cstr + sf_idx + 1;
1267 break;
1268 }
1269 }
1270
1271 ++arg_i;
1272 continue;
1273 }
1274
1275 // If we're done with all of the arguments but are still expecting
1276 // arguments for a previous option then we haven't satisfied that option.
1277 // This is an error.
1278 if (num_option_args_to_consume > 0) {
1279 std::ostringstream msg;
1280 msg << "last option \"" << last_flag_expecting_args
1281 << "\" expects an argument but the parser ran out of command line "
1282 << "arguments to parse";
1283 throw option_lacks_argument_error(msg.str());
1284 }
1285
1286 return results;
1287}
1288
1289
1290 inline
1291 parser_results parser::parse(int argc, char** argv) const
1292{
1293 return parse(argc, const_cast<const char**>(argv));
1294}
1295
1296
1297 namespace convert {
1298
1299
1306 template <typename T> inline
1307 T long_(const char* arg)
1308 {
1309 char* endptr = nullptr;
1310 errno = 0;
1311 T ret = static_cast<T>(std::strtol(arg, &endptr, 0));
1312 if (endptr == arg) {
1313 std::ostringstream msg;
1314 msg << "unable to convert argument to integer: \"" << arg << "\"";
1315 throw std::invalid_argument(msg.str());
1316 }
1317 if (errno == ERANGE) {
1318 throw std::out_of_range("argument numeric value out of range");
1319 }
1320 return ret;
1321 }
1322
1323
1330 template <typename T> inline
1331 T long_long_(const char* arg)
1332 {
1333 char* endptr = nullptr;
1334 errno = 0;
1335 T ret = static_cast<T>(std::strtoll(arg, &endptr, 0));
1336 if (endptr == arg) {
1337 std::ostringstream msg;
1338 msg << "unable to convert argument to integer: \"" << arg << "\"";
1339 throw std::invalid_argument(msg.str());
1340 }
1341 if (errno == ERANGE) {
1342 throw std::out_of_range("argument numeric value out of range");
1343 }
1344 return ret;
1345 }
1346
1347
1348#define DEFINE_CONVERSION_FROM_LONG_(TYPE) \
1349 template <> inline \
1350 TYPE arg(const char* arg) \
1351 { \
1352 return long_<TYPE>(arg); \
1353 }
1354
1355 DEFINE_CONVERSION_FROM_LONG_(char)
1356 DEFINE_CONVERSION_FROM_LONG_(unsigned char)
1357 DEFINE_CONVERSION_FROM_LONG_(signed char)
1358 DEFINE_CONVERSION_FROM_LONG_(short)
1359 DEFINE_CONVERSION_FROM_LONG_(unsigned short)
1360 DEFINE_CONVERSION_FROM_LONG_(int)
1361 DEFINE_CONVERSION_FROM_LONG_(unsigned int)
1362 DEFINE_CONVERSION_FROM_LONG_(long)
1363 DEFINE_CONVERSION_FROM_LONG_(unsigned long)
1364
1365#undef DEFINE_CONVERSION_FROM_LONG_
1366
1367
1368#define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE) \
1369 template <> inline \
1370 TYPE arg(const char* arg) \
1371 { \
1372 return long_long_<TYPE>(arg); \
1373 }
1374
1375 DEFINE_CONVERSION_FROM_LONG_LONG_(long long)
1376 DEFINE_CONVERSION_FROM_LONG_LONG_(unsigned long long)
1377
1378#undef DEFINE_CONVERSION_FROM_LONG_LONG_
1379
1380
1381 template <> inline
1382 bool arg(const char* arg)
1383 {
1384 return argagg::convert::arg<int>(arg) != 0;
1385 }
1386
1387
1388 template <> inline
1389 float arg(const char* arg)
1390 {
1391 char* endptr = nullptr;
1392 errno = 0;
1393 float ret = std::strtof(arg, &endptr);
1394 if (endptr == arg) {
1395 std::ostringstream msg;
1396 msg << "unable to convert argument to integer: \"" << arg << "\"";
1397 throw std::invalid_argument(msg.str());
1398 }
1399 if (errno == ERANGE) {
1400 throw std::out_of_range("argument numeric value out of range");
1401 }
1402 return ret;
1403 }
1404
1405
1406 template <> inline
1407 double arg(const char* arg)
1408 {
1409 char* endptr = nullptr;
1410 errno = 0;
1411 double ret = std::strtod(arg, &endptr);
1412 if (endptr == arg) {
1413 std::ostringstream msg;
1414 msg << "unable to convert argument to integer: \"" << arg << "\"";
1415 throw std::invalid_argument(msg.str());
1416 }
1417 if (errno == ERANGE) {
1418 throw std::out_of_range("argument numeric value out of range");
1419 }
1420 return ret;
1421 }
1422
1423
1424 template <> inline
1425 const char* arg(const char* arg)
1426 {
1427 return arg;
1428 }
1429
1430
1431 template <> inline
1432 std::string arg(const char* arg)
1433 {
1434 return std::string(arg);
1435 }
1436
1437}
1438
1439
1440 inline
1441 fmt_ostream::fmt_ostream(std::ostream& output)
1442: std::ostringstream(), output(output)
1443{
1444}
1445
1446
1447 inline
1448 fmt_ostream::~fmt_ostream()
1449{
1450 output << fmt_string(this->str());
1451}
1452
1453
1454#ifdef __unix__
1455
1456
1457 inline
1458 std::string fmt_string(const std::string& s)
1459{
1460 constexpr int read_end = 0;
1461 constexpr int write_end = 1;
1462
1463 // TODO (vnguyen): This function overall needs to handle possible error
1464 // returns from the various syscalls.
1465
1466 int read_pipe[2];
1467 int write_pipe[2];
1468 if (pipe(read_pipe) == -1) {
1469 return s;
1470 }
1471 if (pipe(write_pipe) == -1) {
1472 return s;
1473 }
1474
1475 auto parent_pid = fork();
1476 bool is_fmt_proc = (parent_pid == 0);
1477 if (is_fmt_proc) {
1478 dup2(write_pipe[read_end], STDIN_FILENO);
1479 dup2(read_pipe[write_end], STDOUT_FILENO);
1480 close(write_pipe[read_end]);
1481 close(write_pipe[write_end]);
1482 close(read_pipe[read_end]);
1483 close(read_pipe[write_end]);
1484 const char* argv[] = {"fmt", NULL};
1485 execvp(const_cast<char*>(argv[0]), const_cast<char**>(argv));
1486 }
1487
1488 close(write_pipe[read_end]);
1489 close(read_pipe[write_end]);
1490 auto fmt_write_fd = write_pipe[write_end];
1491 auto write_result = write(fmt_write_fd, s.c_str(), s.length());
1492 if (write_result != static_cast<ssize_t>(s.length())) {
1493 return s;
1494 }
1495 close(fmt_write_fd);
1496
1497 auto fmt_read_fd = read_pipe[read_end];
1498 std::ostringstream os;
1499 char buf[64];
1500 while (true) {
1501 auto read_count = read(
1502 fmt_read_fd, reinterpret_cast<void*>(buf), sizeof(buf));
1503 if (read_count <= 0) {
1504 break;
1505 }
1506 os.write(buf, static_cast<std::streamsize>(read_count));
1507 }
1508 close(fmt_read_fd);
1509
1510 return os.str();
1511}
1512
1513
1514#else // #ifdef __unix__
1515
1516
1517 inline
1518 std::string fmt_string(const std::string& s)
1519{
1520 return s;
1521}
1522
1523
1524#endif // #ifdef __unix__
1525
1526
1527} // namespace argagg
1528
1529
1530 inline
1531 std::ostream& operator << (std::ostream& os, const argagg::parser& x)
1532{
1533 for (auto& definition : x.definitions) {
1534 os << " ";
1535 for (auto& flag : definition.flags) {
1536 os << flag;
1537 if (flag != definition.flags.back()) {
1538 os << ", ";
1539 }
1540 }
1541 os << std::endl;
1542 os << " " << definition.help << std::endl;
1543 }
1544 return os;
1545}
1546
1547
1548#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:1368
DEFINE_CONVERSION_FROM_LONG_
#define DEFINE_CONVERSION_FROM_LONG_(TYPE)
Definition: argagg.hpp:1348
operator<<
std::ostream & operator<<(std::ostream &os, const argagg::parser &x)
Writes the option help to the given stream.
Definition: argagg.hpp:1531
std::array
std::ostream
std::ostringstream
std::string
std::vector::begin
T begin(T... args)
std::string::c_str
T c_str(T... args)
std::domain_error
std::endl
T endl(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::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:1307
argagg::convert::arg
std::string arg(const char *arg)
Definition: argagg.hpp:1432
argagg::convert::arg
T arg(const char *arg)
Explicit instantiations of this function are used to convert arguments to types.
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:1331
argagg
There are only two hard things in Computer Science: cache invalidation and naming things (Phil Karlto...
Definition: argagg.hpp:101
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:1005
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:960
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:824
argagg::fmt_string
std::string fmt_string(const std::string &s)
Processes the provided string using the fmt util and returns the resulting output as a string....
Definition: argagg.hpp:1458
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:904
std
STL namespace.
std::out_of_range
std::runtime_error
std::vector::size
T size(T... args)
std::size_t
std::ostringstream::str
T str(T... args)
std::strchr
T strchr(T... args)
std::streamsize
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::definition
An option definition which essentially represents what an option is.
Definition: argagg.hpp:388
argagg::definition::requires_arguments
bool requires_arguments() const
Returns true if this option requires arguments.
Definition: argagg.hpp:817
argagg::definition::wants_no_arguments
bool wants_no_arguments() const
Returns true if this option does not want any arguments.
Definition: argagg.hpp:810
argagg::definition::name
const std::string name
Name of the option. Option parser results are keyed by this name.
Definition: argagg.hpp:394
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:401
argagg::definition::help
std::string help
Help string for this option.
Definition: argagg.hpp:407
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:415
argagg::fmt_ostream
A convenience output stream that will accumulate what is streamed to it and then, on destruction,...
Definition: argagg.hpp:591
argagg::fmt_ostream::output
std::ostream & output
Reference to the final output stream that the formatted string will be streamed to.
Definition: argagg.hpp:598
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:1448
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:1441
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:147
argagg::option_lacks_argument_error
This exception is thrown when an option requires an argument but is not provided one....
Definition: argagg.hpp:134
argagg::option_result
Represents a single option parse result.
Definition: argagg.hpp:178
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:185
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:653
argagg::option_results
Represents multiple option parse results for a single option. If treated as a single parse result it ...
Definition: argagg.hpp:239
argagg::option_results::count
std::size_t count() const
Gets the number of times the option shows up.
Definition: argagg.hpp:698
argagg::option_results::operator[]
option_result & operator[](std::size_t index)
Gets a single option parse result by index.
Definition: argagg.hpp:705
argagg::option_results::all
std::vector< option_result > all
All option parse results for this option.
Definition: argagg.hpp:245
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:719
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:472
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:993
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:486
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:968
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:984
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:976
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:479
argagg::parser_results
Represents all results of the parser including options and positional arguments.
Definition: argagg.hpp:311
argagg::parser_results::count
std::size_t count() const
Gets the number of positional arguments.
Definition: argagg.hpp:776
argagg::parser_results::pos
std::vector< const char * > pos
Vector of positional arguments.
Definition: argagg.hpp:331
argagg::parser_results::all_as
std::vector< T > all_as() const
Gets all positional arguments converted to the given type.
Definition: argagg.hpp:797
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:761
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:753
argagg::parser_results::as
T as(std::size_t i=0) const
Gets a positional argument converted to the given type.
Definition: argagg.hpp:790
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:325
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:318
argagg::parser
A list of option definitions used to inform how to parse arguments.
Definition: argagg.hpp:536
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:543
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:1061
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:110
argagg::unexpected_option_error
This exception is thrown when an option is parsed unexpectedly such as when an argument was expected ...
Definition: argagg.hpp:122
std::transform
T transform(T... args)
std::unordered_map
std::vector
std::ostringstream::write
T write(T... args)