1 #include "argparse.h"
2
3 #include <iostream>
4
5 using std::pair;
6 using std::string;
7
8 /**
9 * @brief try to parse the positional arguments and as many flags
10 * as possible. When the last positional argument has been read
11 * then flags are still read until the first unknown token.
12 * If unknown tokens at the end of input should be regarded as
13 * an error, use parsingAllFails()
14 * @param input
15 * @param output
16 * @return return whether there has been an error (true = error, false = no error)
17 */
parsingFails(Input & input,Output & output)18 bool ArgParse::parsingFails(Input& input, Output& output)
19 {
20 size_t mandatoryArguments = 0;
21 size_t optionalArguments = 0;
22 for (const auto& arg : arguments_) {
23 if (arg.optional_) {
24 optionalArguments++;
25 } else {
26 mandatoryArguments++;
27 }
28 }
29 optionalArguments += flags_.size();
30 if (input.size() < mandatoryArguments) {
31 output << input.command() << ": Expected ";
32 if (optionalArguments) {
33 output << "between " << mandatoryArguments
34 << " and " << (optionalArguments + mandatoryArguments)
35 << " arguments";
36 } else if (mandatoryArguments == 1) {
37 output << "one argument";
38 } else {
39 output << mandatoryArguments << " arguments";
40 }
41 output << ", but got only " << input.size() << " arguments.";
42 errorCode_ = HERBST_NEED_MORE_ARGS;
43 return true;
44 }
45 // the number of optional arguments that are provided by 'input':
46 size_t optionalArgumentsRemaining = input.size() - mandatoryArguments;
47 for (const auto& arg : arguments_) {
48 if (arg.optional_ && optionalArgumentsRemaining == 0) {
49 continue;
50 }
51 string valueString;
52 input >> valueString;
53 // try to parse this token as a flag
54 while (optionalArgumentsRemaining && tryParseFlag(valueString)) {
55 optionalArgumentsRemaining--;
56 // if this token was the last optional argument
57 if (optionalArgumentsRemaining == 0 && arg.optional_) {
58 // then skip this 'arg'
59 continue;
60 }
61 // otherwise, there is room for another optional
62 // argument or 'arg' is mandatory. So get another
63 // token from the input, and parse it to the
64 // current 'arg'
65 input >> valueString;
66 }
67 // in any case, we have this here:
68 // assert (!arg.optional_ || optionalArgumentsRemaining > 0);
69 if (arg.optional_) {
70 optionalArgumentsRemaining--;
71 }
72
73 try {
74 arg.tryParse_(valueString);
75 } catch (std::exception& e) {
76 output << input.command() << ": Cannot parse argument \""
77 << valueString << "\": " << e.what() << "\n";
78 errorCode_ = HERBST_INVALID_ARGUMENT;
79 return true;
80 }
81 }
82 // try to parse more flags after the positional arguments
83 while (!input.empty()) {
84 // only consume tokens if they are recognized flags
85 if (tryParseFlag(input.front())) {
86 input.shift();
87 } else {
88 break;
89 }
90 }
91
92 // if all arguments were parsed, then we report that there were
93 // no errors. It's ok if there are remaining elements in 'input' that
94 // have not been parsed.
95 return false;
96 }
97
98 /**
99 * @brief run parsingFails() and assert that there are no unknown
100 * tokens left in the input.
101 * @param input
102 * @param output
103 * @return whether there is an unparsable flag or unexpected argument at the end
104 */
parsingAllFails(Input & input,Output & output)105 bool ArgParse::parsingAllFails(Input& input, Output& output)
106 {
107 return parsingFails(input, output) || unparsedTokens(input, output);
108 }
109
unparsedTokens(Input & input,Output & output)110 bool ArgParse::unparsedTokens(Input& input, Output& output)
111 {
112 string extraToken;
113 if (input >> extraToken) {
114 output << input.command()
115 << ": Unknown argument or flag \""
116 << extraToken << "\" given.\n";
117 errorCode_ = HERBST_INVALID_ARGUMENT;
118 return true;
119 }
120 return false;
121 }
122
123 /**
124 * @brief Accept boolean flags (e.g. --all --horizontal ...) at
125 * any position between the (mandatory or optional) positional arguments
126 * @param a list of flags
127 * @return
128 */
flags(std::initializer_list<Flag> flagTable)129 ArgParse& ArgParse::flags(std::initializer_list<Flag> flagTable)
130 {
131 for (auto& it : flagTable) {
132 // the usual array-style assignment does not work
133 // because Flag has no parameterless constructor.
134 // hence, we explicitly call 'insert':
135 flags_.insert(pair<string, Flag>(it.name_, it));
136 }
137 return *this;
138 }
139
140 /**
141 * @brief try parse a flag
142 * @param argument token from a Input object
143 * @return whether the token was a flag
144 */
tryParseFlag(string inputToken)145 bool ArgParse::tryParseFlag(string inputToken)
146 {
147 auto flag = flags_.find(inputToken);
148 if (flag == flags_.end()) {
149 // stop parsing flags on the first non-flag
150 return false;
151 }
152 flag->second.callback_();
153 return true;
154 }
155