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