1#ifndef BANDIT_OPTIONS_H
2#define BANDIT_OPTIONS_H
3
4#include <algorithm>
5#include <vector>
6#include <iostream>
7
8#include <bandit/external/optionparser.h>
9#include <bandit/filter_chain.h>
10
11namespace bandit {
12 namespace detail {
13 struct options {
14 template<typename ENUM>
15 struct argstr {
16 ENUM id;
17 std::string str;
18 };
19
20 // a vector of argstr that allows to iterate over the strings only
21 template<typename ENUM>
22 struct argstrs : std::vector<argstr<ENUM>> {
23 using std::vector<argstr<ENUM>>::vector;
24
25 struct str_iterator
26 : public std::iterator<std::input_iterator_tag, std::string, int, const std::string*, std::string> {
27 using base_iterator = typename std::vector<argstr<ENUM>>::const_iterator;
28
29 str_iterator() = delete;
30
31 explicit str_iterator(base_iterator it) : it_(it) {}
32
33 str_iterator& operator++() {
34 ++it_;
35 return *this;
36 }
37
38 str_iterator operator++(int) {
39 str_iterator it(*this);
40 ++(*this);
41 return it;
42 }
43
44 bool operator==(const str_iterator& other) const {
45 return it_ == other.it_;
46 }
47
48 bool operator!=(const str_iterator& other) const {
49 return it_ != other.it_;
50 }
51
52 reference operator*() const {
53 return it_->str;
54 }
55
56 ENUM id() {
57 return it_->id;
58 }
59
60 private:
61 base_iterator it_;
62 };
63
64 str_iterator strbegin() const {
65 return str_iterator(this->begin());
66 };
67
68 str_iterator strend() const {
69 return str_iterator(this->end());
70 };
71 };
72
73 enum class formatters {
74 DEFAULT,
75 VS,
76 UNKNOWN
77 };
78
79 enum class reporters {
80 SINGLELINE,
81 XUNIT,
82 INFO,
83 SPEC,
84 DOTS,
85 CRASH,
86 UNKNOWN
87 };
88
89 struct argument : public option::Arg {
90 static const argstrs<reporters> reporter_list() {
91 return {
92 {reporters::CRASH, "crash"},
93 {reporters::DOTS, "dots"},
94 {reporters::SINGLELINE, "singleline"},
95 {reporters::XUNIT, "xunit"},
96 {reporters::INFO, "info"},
97 {reporters::SPEC, "spec"},
98 };
99 }
100
101 static const argstrs<formatters> formatter_list() {
102 return {
103 {formatters::DEFAULT, "default"},
104 {formatters::VS, "vs"},
105 };
106 }
107
108 template<typename ENUM>
109 static std::string comma_separated_list(argstrs<ENUM> list) {
110 std::string csl;
111 auto first = list.strbegin();
112 if (first != list.strend()) {
113 csl += *first;
114 std::for_each(++first, list.strend(), [&](const std::string& elem) {
115 csl += ", " + elem;
116 });
117 }
118 return csl;
119 }
120
121 static std::string name(const option::Option& option) {
122 std::string copy(option.name);
123 return copy.substr(0, option.namelen);
124 }
125
126 static option::ArgStatus Required(const option::Option& option, bool msg) {
127 if (option.arg != nullptr) {
128 return option::ARG_OK;
129 }
130 if (msg) {
131 std::cerr << "Option '" << name(option) << "' requires an argument\n";
132 }
133 return option::ARG_ILLEGAL;
134 }
135
136 template<typename ENUM>
137 static option::ArgStatus OneOf(const option::Option& option, bool msg, const argstrs<ENUM>&& list) {
138 auto status = Required(option, msg);
139 if (status == option::ARG_OK && std::find(list.strbegin(), list.strend(), option.arg) == list.strend()) {
140 if (msg) {
141 std::cerr
142 << "Option argument of '" << name(option) << "' must be one of: "
143 << comma_separated_list(list)
144 << std::endl;
145 }
146 status = option::ARG_ILLEGAL;
147 }
148 return status;
149 }
150
151 static option::ArgStatus Reporter(const option::Option& option, bool msg) {
152 return OneOf(option, msg, reporter_list());
153 }
154
155 static option::ArgStatus Formatter(const option::Option& option, bool msg) {
156 return OneOf(option, msg, formatter_list());
157 }
158 };
159
160 options(int argc, char* argv[]) {
161 argc -= (argc > 0);
162 argv += (argc > 0); // Skip program name (argv[0]) if present
163 option::Stats stats(usage(), argc, argv);
164 options_.resize(stats.options_max);
165 std::vector<option::Option> buffer(stats.buffer_max);
166 option::Parser parse(usage(), argc, argv, options_.data(), buffer.data());
167 parsed_ok_ = !parse.error();
168 has_further_arguments_ = (parse.nonOptionsCount() != 0);
169 has_unknown_options_ = (options_[UNKNOWN] != nullptr);
170
171 for (int i = 0; i < parse.optionsCount(); ++i) {
172 option::Option& opt = buffer[i];
173 switch (opt.index()) {
174 case SKIP:
175 filter_chain_.push_back({opt.arg, true});
176 break;
177 case ONLY:
178 filter_chain_.push_back({opt.arg, false});
179 break;
180 }
181 }
182 }
183
184 bool help() const {
185 return options_[HELP] != nullptr;
186 }
187
188 bool parsed_ok() const {
189 return parsed_ok_;
190 }
191
192 bool has_further_arguments() const {
193 return has_further_arguments_;
194 }
195
196 bool has_unknown_options() const {
197 return has_unknown_options_;
198 }
199
200 void print_usage() const {
201 option::printUsage(std::cout, usage());
202 }
203
204 bool version() const {
205 return options_[VERSION] != nullptr;
206 }
207
208 reporters reporter() const {
209 return get_enumerator_from_string(argument::reporter_list(), options_[REPORTER].arg);
210 }
211
212 bool no_color() const {
213 return options_[NO_COLOR] != nullptr;
214 }
215
216 formatters formatter() const {
217 return get_enumerator_from_string(argument::formatter_list(), options_[FORMATTER].arg);
218 }
219
220 const filter_chain_t& filter_chain() const {
221 return filter_chain_;
222 }
223
224 bool break_on_failure() const {
225 return options_[BREAK_ON_FAILURE] != nullptr;
226 }
227
228 bool dry_run() const {
229 return options_[DRY_RUN] != nullptr;
230 }
231
232 private:
233 template<typename ENUM>
234 ENUM get_enumerator_from_string(const argstrs<ENUM>& list, const char* str) const {
235 if (str != nullptr) {
236 auto it = std::find(list.strbegin(), list.strend(), str);
237 if (it != list.strend()) {
238 return it.id();
239 }
240 }
241 return ENUM::UNKNOWN;
242 }
243
244 enum option_index {
245 UNKNOWN,
246 VERSION,
247 HELP,
248 REPORTER,
249 NO_COLOR,
250 FORMATTER,
251 SKIP,
252 ONLY,
253 BREAK_ON_FAILURE,
254 DRY_RUN,
255 };
256
257 template<typename ENUM>
258 static std::string append_list(std::string desc, argstrs<ENUM> list) {
259 return desc + ": " + argument::comma_separated_list(list);
260 }
261
262 static const option::Descriptor* usage() {
263 static std::string reporter_help = append_list(" --reporter=<reporter>, "
264 "\tSelect reporter", argument::reporter_list());
265 static std::string formatter_help = append_list(" --formatter=<formatter>, "
266 "\tSelect error formatter", argument::formatter_list());
267 static const option::Descriptor usage[] = {
268 {UNKNOWN, 0, "", "", argument::None,
269 "USAGE: <executable> [options]\n\n"
270 "Options:"},
271 {VERSION, 0, "", "version", argument::None,
272 " --version, \tPrint version of bandit"},
273 {HELP, 0, "", "help", argument::None,
274 " --help, \tPrint usage and exit."},
275 {REPORTER, 0, "", "reporter", argument::Reporter, reporter_help.c_str()},
276 {NO_COLOR, 0, "", "no-color", argument::None,
277 " --no-color, \tSuppress colors in output"},
278 {FORMATTER, 0, "", "formatter", argument::Formatter, formatter_help.c_str()},
279 {SKIP, 0, "", "skip", argument::Required,
280 " --skip=<substring>, \tSkip all 'describe' and 'it' containing substring"},
281 {ONLY, 0, "", "only", argument::Required,
282 " --only=<substring>, \tRun only 'describe' and 'it' containing substring"},
283 {BREAK_ON_FAILURE, 0, "", "break-on-failure", argument::None,
284 " --break-on-failure, \tStop test run on first failing test"},
285 {DRY_RUN, 0, "", "dry-run", argument::None,
286 " --dry-run, \tSkip all tests. Use to list available tests"},
287 {0, 0, 0, 0, 0, 0}};
288
289 return usage;
290 }
291
292 private:
293 std::vector<option::Option> options_;
294 filter_chain_t filter_chain_;
295 bool parsed_ok_;
296 bool has_further_arguments_;
297 bool has_unknown_options_;
298 };
299 }
300}
301#endif
302