C++ Utilities 5.26.1
Useful C++ classes and routines such as argument parser, IO and conversion utilities
Loading...
Searching...
No Matches
argumentparsertests.cpp
Go to the documentation of this file.
1#include "./outputcheck.h"
2#include "./testutils.h"
3
6
11
13#include "../io/path.h"
14
15#include "../misc/parseerror.h"
16
17#include "resources/config.h"
18
19#include <cppunit/TestFixture.h>
20#include <cppunit/extensions/HelperMacros.h>
21
22#include <cstdlib>
23#include <cstring>
24#include <regex>
25
26#ifdef PLATFORM_WINDOWS
27#include <windows.h>
28#endif
29
30using namespace std;
31using namespace CppUtilities;
32using namespace CppUtilities::Literals;
33
34using namespace CPPUNIT_NS;
35
39class ArgumentParserTests : public TestFixture {
40 CPPUNIT_TEST_SUITE(ArgumentParserTests);
41 CPPUNIT_TEST(testArgument);
42 CPPUNIT_TEST(testParsing);
43 CPPUNIT_TEST(testCallbacks);
44 CPPUNIT_TEST(testSetMainArguments);
45 CPPUNIT_TEST(testValueConversion);
46#ifndef PLATFORM_WINDOWS
47 CPPUNIT_TEST(testBashCompletion);
48 CPPUNIT_TEST(testHelp);
49 CPPUNIT_TEST(testNoColorArgument);
50#endif
51 CPPUNIT_TEST_SUITE_END();
52
53public:
54 void setUp() override;
55 void tearDown() override;
56
57 void testArgument();
58 void testParsing();
59 void testCallbacks();
62#ifndef PLATFORM_WINDOWS
63 void testBashCompletion();
64 void testHelp();
66#endif
67
68private:
69 void callback();
70 [[noreturn]] void failOnExit(int code);
71};
72
74
76{
77#ifndef PLATFORM_WINDOWS
78 setenv("ENABLE_ESCAPE_CODES", "0", 1);
79#endif
81}
82
86
87[[noreturn]] void ArgumentParserTests::failOnExit(int code)
88{
89 CPPUNIT_FAIL(argsToString("Exited unexpectedly with code ", code));
90}
91
96{
97 Argument argument("test", 't', "some description");
98 CPPUNIT_ASSERT_EQUAL(false, argument.isRequired());
99 argument.setConstraints(1, 10);
100 CPPUNIT_ASSERT_EQUAL(true, argument.isRequired());
101 Argument subArg("sub", 's', "sub arg");
102 argument.addSubArgument(&subArg);
103 CPPUNIT_ASSERT_EQUAL(&argument, subArg.parents().at(0));
104 CPPUNIT_ASSERT(!subArg.conflictsWithArgument());
105 CPPUNIT_ASSERT(!argument.firstValue());
106 argument.setEnvironmentVariable("FOO_ENV_VAR");
107#ifndef PLATFORM_WINDOWS // disabled under Windows for same reason as testNoColorArgument()
108 setenv("FOO_ENV_VAR", "foo", 1);
109 CPPUNIT_ASSERT_EQUAL("foo"s, string(argument.firstValue()));
110#endif
111 ArgumentOccurrence occurrence(0, vector<Argument *>(), nullptr);
112 occurrence.values.emplace_back("bar");
113 argument.m_occurrences.emplace_back(std::move(occurrence));
114 CPPUNIT_ASSERT_EQUAL("bar"s, string(argument.firstValue()));
115}
116
121{
122 // setup parser with some test argument definitions
123 auto parser = ArgumentParser();
124 parser.setExitFunction(std::bind(&ArgumentParserTests::failOnExit, this, std::placeholders::_1));
126 auto qtConfigArgs = QT_CONFIG_ARGUMENTS();
127 auto verboseArg = Argument("verbose", 'v', "be verbose");
128 verboseArg.setCombinable(true);
129 auto fileArg = Argument("file", 'f', "specifies the path of the file to be opened");
130 fileArg.setValueNames({ "path" });
131 fileArg.setRequiredValueCount(1);
132 fileArg.setEnvironmentVariable("PATH");
133 auto filesArg = Argument("files", 'f', "specifies the path of the file(s) to be opened");
134 filesArg.setValueNames({ "path 1", "path 2" });
135 filesArg.setRequiredValueCount(Argument::varValueCount);
136 auto outputFileArg = Argument("output-file", 'o', "specifies the path of the output file");
137 outputFileArg.setValueNames({ "path" });
138 outputFileArg.setRequiredValueCount(1);
139 outputFileArg.setRequired(true);
140 outputFileArg.setCombinable(true);
141 auto printFieldNamesArg = Argument("print-field-names", '\0', "prints available field names");
142 auto displayFileInfoArg = Argument("display-file-info", 'i', "displays general file information");
143 auto notAlbumArg = Argument("album", 'a', "should not be confused with album value");
144 displayFileInfoArg.setDenotesOperation(true);
145 displayFileInfoArg.setSubArguments({ &fileArg, &verboseArg, &notAlbumArg });
146 auto fieldsArg = Argument("fields", '\0', "specifies the fields");
147 fieldsArg.setRequiredValueCount(Argument::varValueCount);
148 fieldsArg.setValueNames({ "title", "album", "artist", "trackpos" });
149 fieldsArg.setImplicit(true);
150 auto displayTagInfoArg = Argument("get", 'p', "displays the values of all specified tag fields (displays all fields if none specified)");
151 displayTagInfoArg.setDenotesOperation(true);
152 displayTagInfoArg.setSubArguments({ &fieldsArg, &filesArg, &verboseArg, &notAlbumArg });
153 parser.setMainArguments(
154 { &qtConfigArgs.qtWidgetsGuiArg(), &printFieldNamesArg, &displayTagInfoArg, &displayFileInfoArg, &parser.noColorArg(), &parser.helpArg() });
155
156 // no args present
157 parser.parseArgs(0, nullptr, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
158 CPPUNIT_ASSERT(!parser.executable());
159 CPPUNIT_ASSERT(!parser.specifiedOperation());
160 CPPUNIT_ASSERT_EQUAL(0u, parser.actualArgumentCount());
161
162 // error about uncombinable arguments
163 const char *argv[] = { "tageditor", "get", "album", "title", "diskpos", "-f", "somefile" };
164 // try to parse, this should fail
165 try {
166 parser.parseArgs(7, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
167 CPPUNIT_FAIL("Exception expected.");
168 } catch (const ParseError &e) {
169 CPPUNIT_ASSERT_EQUAL("The argument \"files\" can not be combined with \"fields\"."s, string(e.what()));
170 // test printing btw
171 auto ss = std::stringstream();
172 ss << e;
173 CPPUNIT_ASSERT_EQUAL(
174 "Error: Unable to parse arguments: The argument \"files\" can not be combined with \"fields\".\nSee --help for available commands.\n"sv,
175 std::string_view(ss.str()));
176 }
177 CPPUNIT_ASSERT(parser.isUncombinableMainArgPresent());
178
179 // arguments read correctly after successful parse
180 filesArg.setCombinable(true);
181 parser.resetArgs();
182 parser.parseArgs(7, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
183 // check results
184 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
185 CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
186 CPPUNIT_ASSERT_EQUAL("tageditor"sv, std::string_view(parser.executable()));
187 CPPUNIT_ASSERT(!verboseArg.isPresent());
188 CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
189 CPPUNIT_ASSERT(fieldsArg.isPresent());
190 CPPUNIT_ASSERT_EQUAL("album"sv, std::string_view(fieldsArg.values().at(0)));
191 CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
192 CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
193 CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
194 CPPUNIT_ASSERT_EQUAL(&displayTagInfoArg, parser.specifiedOperation());
195
196 // skip empty args
197 const char *argv2[] = { "tageditor", "", "-p", "album", "title", "diskpos", "", "--files", "somefile" };
198 // reparse the args
199 parser.resetArgs();
200 parser.parseArgs(9, argv2, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
201 // check results again
202 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
203 CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
204 CPPUNIT_ASSERT(!verboseArg.isPresent());
205 CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
206 CPPUNIT_ASSERT(fieldsArg.isPresent());
207 CPPUNIT_ASSERT_EQUAL("album"sv, std::string_view(fieldsArg.values().at(0)));
208 CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
209 CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
210 CPPUNIT_ASSERT_EQUAL(""sv, std::string_view(fieldsArg.values().at(3)));
211 CPPUNIT_ASSERT_EQUAL(4_st, fieldsArg.values().size());
212 CPPUNIT_ASSERT(filesArg.isPresent());
213 CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
214
215 // error about unknown argument: forget get/-p
216 const char *argv3[] = { "tageditor", "album", "title", "diskpos", "--files", "somefile" };
217 try {
218 parser.resetArgs();
219 parser.parseArgs(6, argv3, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
220 CPPUNIT_FAIL("Exception expected.");
221 } catch (const ParseError &e) {
222 CPPUNIT_ASSERT_EQUAL("The specified argument \"album\" is unknown.\nDid you mean get or --help?"sv, std::string_view(e.what()));
223 }
224
225 // error about unknown argument: mistake in final argument
226 const char *argv18[] = { "tageditor", "get", "album", "title", "diskpos", "--verbose", "--fi" };
227 try {
228 parser.resetArgs();
229 parser.parseArgs(7, argv18, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
230 CPPUNIT_FAIL("Exception expected.");
231 } catch (const ParseError &e) {
232 CPPUNIT_ASSERT_EQUAL("The specified argument \"--fi\" is unknown.\nDid you mean --files or --no-color?"sv, std::string_view(e.what()));
233 }
234
235 // warning about unknown argument
236 parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Warn);
237 {
238#ifndef PLATFORM_WINDOWS
239 const OutputCheck outputCheck("Warning: The specified argument \"album\" is unknown and will be ignored.\n"s
240 "Warning: The specified argument \"title\" is unknown and will be ignored.\n"s
241 "Warning: The specified argument \"diskpos\" is unknown and will be ignored.\n"s
242 "Warning: The specified argument \"--files\" is unknown and will be ignored.\n"s
243 "Warning: The specified argument \"somefile\" is unknown and will be ignored.\n"s,
244 cerr);
245#endif
246 parser.resetArgs();
247 EscapeCodes::enabled = false;
248 parser.parseArgs(6, argv3, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
249
250 // none of the arguments should be present now
251 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
252 CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
253 CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
254 CPPUNIT_ASSERT(!fieldsArg.isPresent());
255 CPPUNIT_ASSERT(!filesArg.isPresent());
256 }
257
258 // combined abbreviations like "-vf"
259 const char *argv4[] = { "tageditor", "-i", "-vf", "test" };
260 parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Fail);
261 parser.resetArgs();
262 parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
263 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
264 CPPUNIT_ASSERT(displayFileInfoArg.isPresent());
265 CPPUNIT_ASSERT(verboseArg.isPresent());
266 CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
267 CPPUNIT_ASSERT(!filesArg.isPresent());
268 CPPUNIT_ASSERT(fileArg.isPresent());
269 CPPUNIT_ASSERT_EQUAL("test"sv, std::string_view(fileArg.values().at(0)));
270 CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values().size());
271
272 // constraint checking: no multiple occurrences (not resetting verboseArg on purpose)
273 displayFileInfoArg.reset();
274 fileArg.reset();
275 try {
276 parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
277 CPPUNIT_FAIL("Exception expected.");
278 } catch (const ParseError &e) {
279 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
280 CPPUNIT_ASSERT_EQUAL("The argument \"verbose\" mustn't be specified more than 1 time."sv, std::string_view(e.what()));
281 }
282
283 // constraint checking: no constraint (not resetting verboseArg on purpose)
284 displayFileInfoArg.reset();
285 fileArg.reset();
286 verboseArg.setConstraints(0, Argument::varValueCount);
287 parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
288 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
289
290 // constraint checking: mandatory argument
291 verboseArg.setRequired(true);
292 parser.resetArgs();
293 parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
294 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
295
296 // constraint checking: error about missing mandatory argument
297 const char *argv5[] = { "tageditor", "-i", "-f", "test" };
298 displayFileInfoArg.reset();
299 fileArg.reset();
300 verboseArg.reset();
301 try {
302 parser.parseArgs(4, argv5, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
303 CPPUNIT_FAIL("Exception expected.");
304 } catch (const ParseError &e) {
305 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
306 CPPUNIT_ASSERT_EQUAL("The argument \"verbose\" must be specified at least 1 time."sv, std::string_view(e.what()));
307 }
308 verboseArg.setRequired(false);
309
310 // combined abbreviation with nesting "-pf"
311 const char *argv10[] = { "tageditor", "-pf", "test" };
312 parser.resetArgs();
313 parser.parseArgs(3, argv10, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
314 CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
315 CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
316 CPPUNIT_ASSERT(!fileArg.isPresent());
317 CPPUNIT_ASSERT(filesArg.isPresent());
318 CPPUNIT_ASSERT_EQUAL(1_st, filesArg.values(0).size());
319 CPPUNIT_ASSERT_EQUAL("test"sv, std::string_view(filesArg.values(0).at(0)));
320 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
321
322 // constraint checking: no complains about missing -i
323 const char *argv6[] = { "tageditor", "-g" };
324 parser.resetArgs();
325 parser.parseArgs(2, argv6, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
326 CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
327
328 // constraint checking: dependent arguments (-f requires -i or -p)
329 const char *argv7[] = { "tageditor", "-f", "test" };
330 parser.resetArgs();
331 try {
332 parser.parseArgs(3, argv7, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
333 CPPUNIT_FAIL("Exception expected.");
334 } catch (const ParseError &e) {
335 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
336 CPPUNIT_ASSERT_EQUAL("The specified argument \"-f\" is unknown.\nDid you mean get or --help?"sv, std::string_view(e.what()));
337 }
338
339 // equation sign syntax
340 const char *argv11[] = { "tageditor", "-if=test-v" };
341 parser.resetArgs();
342 parser.parseArgs(2, argv11, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
343 CPPUNIT_ASSERT(!filesArg.isPresent());
344 CPPUNIT_ASSERT(fileArg.isPresent());
345 CPPUNIT_ASSERT(!verboseArg.isPresent());
346 CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
347 CPPUNIT_ASSERT_EQUAL("test-v"s, string(fileArg.values(0).front()));
348 const char *argv15[] = { "tageditor", "-i", "--file=test", "-v" };
349 parser.resetArgs();
350 parser.parseArgs(4, argv15, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
351 CPPUNIT_ASSERT(!filesArg.isPresent());
352 CPPUNIT_ASSERT(fileArg.isPresent());
353 CPPUNIT_ASSERT(verboseArg.isPresent());
354 CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
355 CPPUNIT_ASSERT_EQUAL("test"sv, std::string_view(fileArg.values(0).front()));
356
357 // specifying value directly after abbreviation
358 const char *argv12[] = { "tageditor", "-iftest" };
359 parser.resetArgs();
360 parser.parseArgs(2, argv12, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
361 CPPUNIT_ASSERT(!filesArg.isPresent());
362 CPPUNIT_ASSERT(fileArg.isPresent());
363 CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
364 CPPUNIT_ASSERT_EQUAL("test"sv, std::string_view(fileArg.values().at(0)));
365
366 // specifying top-level argument after abbreviation
367 const char *argv17[] = { "tageditor", "-if=test-v", "--no-color" };
368 parser.resetArgs();
369 parser.parseArgs(3, argv17, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
370 CPPUNIT_ASSERT(!filesArg.isPresent());
371 CPPUNIT_ASSERT(fileArg.isPresent());
372 CPPUNIT_ASSERT(!verboseArg.isPresent());
373 CPPUNIT_ASSERT(parser.noColorArg().isPresent());
374 CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
375 CPPUNIT_ASSERT_EQUAL("test-v"sv, std::string_view(fileArg.values(0).front()));
376
377 // default argument
378 const char *argv8[] = { "tageditor" };
379 parser.resetArgs();
380 parser.parseArgs(1, argv8, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
381 CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
382 CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
383 CPPUNIT_ASSERT(!verboseArg.isPresent());
384 CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
385 CPPUNIT_ASSERT(!filesArg.isPresent());
386 CPPUNIT_ASSERT(!fileArg.isPresent());
387 if (const auto path = std::getenv("PATH")) {
388 CPPUNIT_ASSERT(fileArg.firstValue());
389 CPPUNIT_ASSERT_EQUAL(std::string_view(path), std::string_view(fileArg.firstValue()));
390 } else {
391 CPPUNIT_ASSERT(!fileArg.firstValue());
392 }
393
394 // constraint checking: required value count with sufficient number of provided parameters
395 const char *argv13[] = { "tageditor", "get", "--fields", "album=test", "title", "diskpos", "--files", "somefile" };
396 verboseArg.setRequired(false);
397 parser.resetArgs();
398 parser.parseArgs(8, argv13, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
399 // this should still work without complaints
400 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
401 CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
402 CPPUNIT_ASSERT(!verboseArg.isPresent());
403 CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
404 CPPUNIT_ASSERT(fieldsArg.isPresent());
405 CPPUNIT_ASSERT_EQUAL("album=test"sv, std::string_view(fieldsArg.values().at(0)));
406 CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
407 CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
408 CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
409 CPPUNIT_ASSERT(filesArg.isPresent());
410 CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
411 CPPUNIT_ASSERT(!notAlbumArg.isPresent());
412
413 // constraint checking: required value count with insufficient number of provided parameters
414 const char *argv9[] = { "tageditor", "-p", "album", "title", "diskpos" };
415 fieldsArg.setRequiredValueCount(4);
416 parser.resetArgs();
417 try {
418 parser.parseArgs(5, argv9, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
419 CPPUNIT_FAIL("Exception expected.");
420 } catch (const ParseError &e) {
421 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
422 CPPUNIT_ASSERT_EQUAL(
423 "Not all parameters for argument \"fields\" provided. You have to provide the following parameters: title album artist trackpos"sv,
424 std::string_view(e.what()));
425 }
426
427 // constraint checking: truncated argument not wrongly detected
428 const char *argv16[] = { "tageditor", "--hel", "-p", "album", "title", "diskpos" };
429 fieldsArg.setRequiredValueCount(Argument::varValueCount);
430 parser.resetArgs();
431 try {
432 parser.parseArgs(6, argv16, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
433 CPPUNIT_FAIL("Exception expected.");
434 } catch (const ParseError &e) {
435 CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
436 CPPUNIT_ASSERT_EQUAL("The specified argument \"--hel\" is unknown.\nDid you mean --help or get?"sv, std::string_view(e.what()));
437 }
438
439 // nested operations
440 const char *argv14[] = { "tageditor", "get", "fields", "album=test", "-f", "somefile" };
441 parser.resetArgs();
442 fieldsArg.setDenotesOperation(true);
443 parser.parseArgs(6, argv14, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
444 CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
445 CPPUNIT_ASSERT(fieldsArg.isPresent());
446 CPPUNIT_ASSERT_EQUAL("album=test"sv, std::string_view(fieldsArg.values().at(0)));
447
448 // implicit flag still works when argument doesn't denote operation
449 parser.resetArgs();
450 fieldsArg.setDenotesOperation(false);
451 parser.parseArgs(6, argv14, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
452 CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
453 CPPUNIT_ASSERT(fieldsArg.isPresent());
454 CPPUNIT_ASSERT_EQUAL("fields"sv, std::string_view(fieldsArg.values().at(0)));
455 CPPUNIT_ASSERT_EQUAL("album=test"sv, std::string_view(fieldsArg.values().at(1)));
456
457 // greedy-flag
458 const char *argv19[] = { "tageditor", "get", "--fields", "foo", "bar", "--help" };
459 parser.resetArgs();
460 try {
461 parser.parseArgs(6, argv19, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
462 CPPUNIT_FAIL("Exception expected.");
463 } catch (const ParseError &e) {
464 CPPUNIT_ASSERT_EQUAL_MESSAGE("--help assumed to be an argument without greedy-flag (leading to error)",
465 "The argument \"help\" can not be combined with \"get\"."sv, std::string_view(e.what()));
466 }
467 parser.resetArgs();
468 fieldsArg.setFlags(Argument::Flags::Greedy, true);
469 parser.parseArgs(6, argv19, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
470 CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
471 CPPUNIT_ASSERT(fieldsArg.isPresent());
472 CPPUNIT_ASSERT_MESSAGE("--help not considered an argument with greedy-flag", !parser.helpArg().isPresent());
473 CPPUNIT_ASSERT_EQUAL("foo"sv, std::string_view(fieldsArg.values().at(0)));
474 CPPUNIT_ASSERT_EQUAL("bar"sv, std::string_view(fieldsArg.values().at(1)));
475 CPPUNIT_ASSERT_EQUAL("--help"sv, std::string_view(fieldsArg.values().at(2)));
476 CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
477}
478
483{
484 ArgumentParser parser;
485 parser.setExitFunction(std::bind(&ArgumentParserTests::failOnExit, this, std::placeholders::_1));
486 Argument callbackArg("with-callback", 't', "callback test");
487 callbackArg.setRequiredValueCount(2);
488 callbackArg.setCallback([](const ArgumentOccurrence &occurrence) {
489 CPPUNIT_ASSERT_EQUAL(0_st, occurrence.index);
490 CPPUNIT_ASSERT(occurrence.path.empty());
491 CPPUNIT_ASSERT_EQUAL(2_st, occurrence.values.size());
492 CPPUNIT_ASSERT(!strcmp(occurrence.values[0], "val1"));
493 CPPUNIT_ASSERT(!strcmp(occurrence.values[1], "val2"));
494 throw 42;
495 });
496 Argument noCallbackArg("no-callback", 'l', "callback test");
497 noCallbackArg.setRequiredValueCount(2);
498 parser.setMainArguments({ &callbackArg, &noCallbackArg });
499
500 // test whether callback is invoked when argument with callback is specified
501 const char *argv[] = { "test", "-t", "val1", "val2" };
502 try {
503 parser.parseArgs(4, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
504 } catch (int i) {
505 CPPUNIT_ASSERT_EQUAL(i, 42);
506 }
507
508 // test whether callback is not invoked when argument with callback is not specified
509 callbackArg.reset();
510 const char *argv2[] = { "test", "-l", "val1", "val2" };
511 parser.parseArgs(4, argv2, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
512}
513
514#ifndef PLATFORM_WINDOWS
518static bool exitCalled = false;
519
527{
528 ArgumentParser parser;
529 parser.setExitFunction(std::bind(&ArgumentParserTests::failOnExit, this, std::placeholders::_1));
530 Argument verboseArg("verbose", 'v', "be verbose");
531 verboseArg.setCombinable(true);
532 Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
534 filesArg.setCombinable(true);
535 Argument nestedSubArg("nested-sub", '\0', "nested sub arg");
536 Argument subArg("sub", '\0', "sub arg");
537 subArg.setSubArguments({ &nestedSubArg });
538 Argument displayFileInfoArg("display-file-info", 'i', "displays general file information");
539 displayFileInfoArg.setDenotesOperation(true);
540 displayFileInfoArg.setSubArguments({ &filesArg, &verboseArg, &subArg });
541 Argument fieldsArg("fields", '\0', "specifies the fields");
543 fieldsArg.setPreDefinedCompletionValues("title album artist trackpos");
544 fieldsArg.setImplicit(true);
545 Argument valuesArg("values", '\0', "specifies the fields");
547 valuesArg.setPreDefinedCompletionValues("title album artist trackpos");
548 valuesArg.setImplicit(false);
549 valuesArg.setValueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues | ValueCompletionBehavior::AppendEquationSign);
550 Argument selectorsArg("selectors", '\0', "has some more pre-defined values");
552 selectorsArg.setPreDefinedCompletionValues("tag=id3v1 tag=id3v2 tag=matroska target=file target=track");
553 selectorsArg.setImplicit(false);
554 selectorsArg.setValueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues);
555 selectorsArg.setCallback(
556 [&selectorsArg](const ArgumentOccurrence &) { selectorsArg.setPreDefinedCompletionValues("tag=matroska tag=mp4 tag=vorbis"); });
557 Argument getArg("get", 'g', "gets tag values");
558 getArg.setSubArguments({ &fieldsArg, &filesArg });
559 Argument setArg("set", 's', "sets tag values");
560 setArg.setSubArguments({ &valuesArg, &filesArg, &selectorsArg });
561
562 parser.setMainArguments({ &displayFileInfoArg, &getArg, &setArg, &parser.noColorArg(), &parser.helpArg() });
563
564 // fail due to operation flags not set
565 const char *const argv1[] = { "se" };
566 ArgumentReader reader(parser, argv1, argv1 + 1, true);
567 {
568 const OutputCheck c("COMPREPLY=()\n");
569 CPPUNIT_ASSERT(reader.read());
570 parser.printBashCompletion(1, argv1, 0, reader);
571 }
572
573 // correct operation arg flags
574 getArg.setDenotesOperation(true);
575 setArg.setDenotesOperation(true);
576 {
577 const OutputCheck c("COMPREPLY=('set' )\n");
578 reader.reset(argv1, argv1 + 1).read();
579 parser.printBashCompletion(1, argv1, 0, reader);
580 }
581
582 // argument at current cursor position already specified -> the completion should just return the argument
583 const char *const argv2[] = { "set" };
584 parser.resetArgs();
585 {
586 const OutputCheck c("COMPREPLY=('set' )\n");
587 reader.reset(argv2, argv2 + 1).read();
588 parser.printBashCompletion(1, argv2, 0, reader);
589 }
590
591 // advance the cursor position -> the completion should propose the next argument
592 parser.resetArgs();
593 {
594 const OutputCheck c("COMPREPLY=('--files' '--no-color' '--selectors' '--values' )\n");
595 reader.reset(argv2, argv2 + 1).read();
596 parser.printBashCompletion(1, argv2, 1, reader);
597 }
598
599 // nested operations should be proposed as operations
600 parser.resetArgs();
601 filesArg.setDenotesOperation(true);
602 {
603 const OutputCheck c("COMPREPLY=('files' '--no-color' '--selectors' '--values' )\n");
604 reader.reset(argv2, argv2 + 1).read();
605 parser.printBashCompletion(1, argv2, 1, reader);
606 }
607
608 // specifying no args should propose all main arguments
609 parser.resetArgs();
610 filesArg.setDenotesOperation(false);
611 {
612 const OutputCheck c("COMPREPLY=('display-file-info' 'get' 'set' '--help' '--no-color' )\n");
613 reader.reset(nullptr, nullptr).read();
614 parser.printBashCompletion(0, nullptr, 0, reader);
615 }
616
617 // pre-defined values
618 const char *const argv3[] = { "get", "--fields" };
619 parser.resetArgs();
620 {
621 const OutputCheck c("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--files' '--no-color' )\n");
622 reader.reset(argv3, argv3 + 2).read();
623 parser.printBashCompletion(2, argv3, 2, reader);
624 }
625
626 // pre-defined values with equation sign, one letter already present
627 const char *const argv4[] = { "set", "--values", "a" };
628 parser.resetArgs();
629 {
630 const OutputCheck c("COMPREPLY=('album=' 'artist=' ); compopt -o nospace\n");
631 reader.reset(argv4, argv4 + 3).read();
632 parser.printBashCompletion(3, argv4, 2, reader);
633 }
634
635 // pre-defined values containing equation sign, equation sign already present
636 const char *const argv12[] = { "set", "--selectors", "tag=id3" };
637 parser.resetArgs();
638 {
639 const OutputCheck c("COMPREPLY=('tag=id3v1' 'tag=id3v2' )\n");
640 reader.reset(argv12, argv12 + 3).read();
641 parser.printBashCompletion(3, argv12, 2, reader);
642 }
643
644 // recombining pre-defined values containing equation sign, equation sign already present
645 const char *const argv13[] = { "set", "--selectors", "tag", "=", "id3" };
646 parser.resetArgs();
647 {
648 const OutputCheck c("COMPREPLY=('id3v1' 'id3v2' )\n");
649 reader.reset(argv13, argv13 + 5).read();
650 parser.printBashCompletion(5, argv13, 4, reader);
651 }
652 parser.resetArgs();
653 {
654 const OutputCheck c("COMPREPLY=('id3v1' 'id3v2' 'matroska' )\n");
655 reader.reset(argv13, argv13 + 5).read();
656 parser.printBashCompletion(5, argv13, 3, reader);
657 }
658
659 // computing pre-defined values just in time using callback
660 selectorsArg.setValueCompletionBehavior(selectorsArg.valueCompletionBehaviour() | ValueCompletionBehavior::InvokeCallback);
661 parser.resetArgs();
662 {
663 const OutputCheck c("COMPREPLY=('matroska' 'mp4' 'vorbis' )\n");
664 reader.reset(argv13, argv13 + 5).read();
665 parser.printBashCompletion(5, argv13, 3, reader);
666 }
667
668 // pre-defined values for implicit argument
669 parser.resetArgs();
670 {
671 const OutputCheck c("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--fields' '--files' '--no-color' )\n");
672 reader.reset(argv3, argv3 + 1).read();
673 parser.printBashCompletion(1, argv3, 2, reader);
674 }
675
676 // file names
677 string iniFilePath = CppUtilities::testFilePath("test.ini");
678 iniFilePath.resize(iniFilePath.size() - 4);
679 string mkvFilePath = CppUtilities::testFilePath("test 'with quote'.mkv");
680 mkvFilePath.resize(mkvFilePath.size() - 17);
681 parser.resetArgs();
682 const char *const argv5[] = { "get", "--files", iniFilePath.c_str() };
683 {
684#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
685 // order for file names is not specified
686 const OutputCheck c("COMPREPLY=('" % mkvFilePath % " '\"'\"'with quote'\"'\"'.mkv' '" % iniFilePath + ".ini' ); compopt -o filenames\n",
687 "COMPREPLY=('" % iniFilePath % ".ini' '" % mkvFilePath + " '\"'\"'with quote'\"'\"'.mkv' ); compopt -o filenames\n");
688#else
689 const OutputCheck c("COMPREPLY=()\n");
690#endif
691 reader.reset(argv5, argv5 + 3).read();
692 parser.printBashCompletion(3, argv5, 2, reader);
693 }
694
695 // directory names
696 string directoryPath = CppUtilities::testFilePath("subdir/foo/bar");
697 directoryPath.resize(directoryPath.size() - 4);
698 filesArg.setValueCompletionBehavior(ValueCompletionBehavior::Directories);
699 parser.resetArgs();
700 const char *const argv14[] = { "get", "--files", directoryPath.c_str() };
701 {
702#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
703 const OutputCheck c("COMPREPLY=('" % directoryPath + "' ); compopt -o filenames\n");
704#else
705 const OutputCheck c("COMPREPLY=()\n");
706#endif
707 reader.reset(argv14, argv14 + 3).read();
708 parser.printBashCompletion(3, argv14, 2, reader);
709 }
710
711 // sub arguments
712 const char *const argv6[] = { "set", "--" };
713 parser.resetArgs();
714 {
715 const OutputCheck c("COMPREPLY=('--files' '--no-color' '--selectors' '--values' )\n");
716 reader.reset(argv6, argv6 + 2).read();
717 parser.printBashCompletion(2, argv6, 1, reader);
718 }
719
720 // nested sub arguments
721 const char *const argv7[] = { "-i", "--sub", "--" };
722 parser.resetArgs();
723 {
724 const OutputCheck c("COMPREPLY=('--files' '--nested-sub' '--no-color' '--verbose' )\n");
725 reader.reset(argv7, argv7 + 3).read();
726 parser.printBashCompletion(3, argv7, 2, reader);
727 }
728
729 // started pre-defined values with equation sign, one letter already present, last value matches
730 const char *const argv8[] = { "set", "--values", "t" };
731 parser.resetArgs();
732 {
733 const OutputCheck c("COMPREPLY=('title=' 'trackpos=' ); compopt -o nospace\n");
734 reader.reset(argv8, argv8 + 3).read();
735 parser.printBashCompletion(3, argv8, 2, reader);
736 }
737
738 // combined abbreviations
739 const char *const argv9[] = { "-gf" };
740 parser.resetArgs();
741 {
742 const OutputCheck c("COMPREPLY=('-gf' )\n");
743 reader.reset(argv9, argv9 + 1).read();
744 parser.printBashCompletion(1, argv9, 0, reader);
745 }
746 parser.resetArgs();
747 {
748 const OutputCheck c([](const string &actualOutput) { CPPUNIT_ASSERT_EQUAL(0_st, actualOutput.find("COMPREPLY=('--fields' ")); });
749 reader.reset(argv9, argv9 + 1).read();
750 parser.printBashCompletion(1, argv9, 1, reader);
751 }
752
753 // override exit function to prevent readArgs() from terminating the test run
754 parser.setExitFunction([](int) { exitCalled = true; });
755
756 // call completion via readArgs() with current word index
757 const char *const argv10[] = { "/some/path/tageditor", "--bash-completion-for", "0" };
758 parser.resetArgs();
759 {
760 const OutputCheck c("COMPREPLY=('display-file-info' 'get' 'set' '--help' '--no-color' )\n");
761 parser.readArgs(3, argv10);
762 }
763 CPPUNIT_ASSERT(!strcmp("/some/path/tageditor", parser.executable()));
764 CPPUNIT_ASSERT(exitCalled);
765
766 // call completion via readArgs() without current word index
767 const char *const argv11[] = { "/some/path/tageditor", "--bash-completion-for", "ge" };
768 parser.resetArgs();
769 {
770 const OutputCheck c("COMPREPLY=('get' )\n");
771 parser.readArgs(3, argv11);
772 }
773}
774#endif
775
776#ifndef PLATFORM_WINDOWS
782{
783 // indentation
784 Indentation indent;
785 indent = indent + 3;
786 CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(4 + 3), indent.level);
787
788 // setup parser
789 ArgumentParser parser;
790 parser.setExitFunction(std::bind(&ArgumentParserTests::failOnExit, this, std::placeholders::_1));
791 OperationArgument verboseArg("verbose", 'v', "be verbose", "actually not an operation");
792 verboseArg.setCombinable(true);
793 ConfigValueArgument nestedSubArg("nested-sub", '\0', "nested sub arg", { "value1", "value2" });
795 Argument subArg("foo", 'f', "dummy");
796 subArg.setName("sub");
797 subArg.setAbbreviation('\0');
798 subArg.setDescription("sub arg");
799 subArg.setExample("sub arg example");
800 subArg.setRequired(true);
801 subArg.addSubArgument(&nestedSubArg);
802 Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
803 filesArg.setCombinable(true);
804 filesArg.addSubArgument(&subArg);
805 filesArg.setSubArguments({ &subArg }); // test re-assignment btw
806 Argument envArg("env", '\0', "env");
807 envArg.setEnvironmentVariable("FILES");
808 envArg.setRequiredValueCount(2);
809 envArg.appendValueName("file");
810 Argument deprecatedArg("deprecated");
811 deprecatedArg.markAsDeprecated(&filesArg);
812 parser.helpArg().setRequired(true);
813 parser.setMainArguments({ &verboseArg, &filesArg, &envArg, &deprecatedArg, &parser.noColorArg(), &parser.helpArg() });
814 applicationInfo.dependencyVersions = { "somelib", "some other lib" };
815
816 // parse args and assert output
817 const char *const argv[] = { "app", "-h" };
818 {
819 const OutputCheck c("\033[1m" APP_NAME ", version " APP_VERSION "\n"
820 "\n"
821 "\033[0m" APP_DESCRIPTION "\n"
822 "\n"
823 "Available operations:\n"
824 "\033[1mverbose, -v\033[0m\n"
825 " be verbose\n"
826 " example: actually not an operation\n"
827 "\n"
828 "Available top-level options:\n"
829 "\033[1m--files, -f\033[0m\n"
830 " specifies the path of the file(s) to be opened\n"
831 " \033[1m--sub\033[0m\n"
832 " sub arg\n"
833 " particularities: mandatory if parent argument is present\n"
834 " \033[1m--nested-sub\033[0m [value1] [value2] ...\n"
835 " nested sub arg\n"
836 " example: sub arg example\n"
837 "\n"
838 "\033[1m--env\033[0m [file] [value 2]\n"
839 " env\n"
840 " default environment variable: FILES\n"
841 "\n"
842 "\033[1m--no-color\033[0m\n"
843 " disables formatted/colorized output\n"
844 " default environment variable: ENABLE_ESCAPE_CODES\n"
845 "\n"
846 "Linked against: somelib, some other lib\n"
847 "\n"
848 "Project website: " APP_URL "\n");
850 parser.parseArgs(2, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
851 }
852
853 verboseArg.setDenotesOperation(false);
854 parser.setMainArguments({ &verboseArg, &filesArg, &envArg, &parser.helpArg() });
855 {
856 const OutputCheck c(APP_NAME ", version " APP_VERSION "\n"
857 "\n" APP_DESCRIPTION "\n"
858 "\n"
859 "Available arguments:\n"
860 "--verbose, -v\n"
861 " be verbose\n"
862 " example: actually not an operation\n"
863 "\n"
864 "--files, -f\n"
865 " specifies the path of the file(s) to be opened\n"
866 " --sub\n"
867 " sub arg\n"
868 " particularities: mandatory if parent argument is present\n"
869 " --nested-sub [value1] [value2] ...\n"
870 " nested sub arg\n"
871 " example: sub arg example\n"
872 "\n"
873 "--env [file] [value 2]\n"
874 " env\n"
875 " default environment variable: FILES\n"
876 "\n"
877 "Linked against: somelib, some other lib\n"
878 "\n"
879 "Project website: " APP_URL "\n");
880 EscapeCodes::enabled = false;
881 parser.resetArgs();
882 parser.parseArgs(2, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
883 }
884}
885#endif
886
891{
892 ArgumentParser parser;
893 parser.setExitFunction(std::bind(&ArgumentParserTests::failOnExit, this, std::placeholders::_1));
894 HelpArgument &helpArg(parser.helpArg());
895 Argument subArg("sub-arg", 's', "mandatory sub arg");
896 subArg.setRequired(true);
897 helpArg.addSubArgument(&subArg);
898 parser.addMainArgument(&helpArg);
899 parser.setMainArguments({});
900 CPPUNIT_ASSERT_MESSAGE("clear main args", parser.mainArguments().empty());
901 parser.setMainArguments({ &helpArg });
902 CPPUNIT_ASSERT_MESSAGE("no default due to required sub arg", !parser.defaultArgument());
903 subArg.setConstraints(0, 20);
904 parser.setMainArguments({ &helpArg });
905 CPPUNIT_ASSERT_MESSAGE("default if no required sub arg", &helpArg == parser.defaultArgument());
906}
907
908#ifndef PLATFORM_WINDOWS
917{
918 // assume escape codes are enabled by default
920
921 {
922 unsetenv("ENABLE_ESCAPE_CODES");
923 NoColorArgument noColorArg;
924 noColorArg.apply();
925 CPPUNIT_ASSERT_MESSAGE("default used if not present", EscapeCodes::enabled);
926 noColorArg.m_occurrences.emplace_back(0);
927 noColorArg.apply();
928 CPPUNIT_ASSERT_MESSAGE("default negated if present", !EscapeCodes::enabled);
929 }
930 {
931 setenv("ENABLE_ESCAPE_CODES", " 0 ", 1);
932 const NoColorArgument noColorArg;
933 CPPUNIT_ASSERT(!EscapeCodes::enabled);
934 }
935 {
936 setenv("ENABLE_ESCAPE_CODES", " 1 ", 1);
937 const NoColorArgument noColorArg;
938 CPPUNIT_ASSERT(EscapeCodes::enabled);
939 }
940}
941#endif
942
943template <typename ValueTuple> void checkConvertedValues(const std::string &message, const ValueTuple &values)
944{
945 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, "foo"s, get<0>(values));
946 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, 42u, get<1>(values));
947 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, 7.5, get<2>(values));
948 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, -42, get<3>(values));
949}
950
955{
956 // convert values directly from ArgumentOccurrence
957 ArgumentOccurrence occurrence(0);
958 occurrence.values = { "foo", "42", "7.5", "-42" };
959 checkConvertedValues("values from ArgumentOccurrence::convertValues", occurrence.convertValues<string, unsigned int, double, int>());
960 static_assert(std::is_same<std::tuple<>, decltype(occurrence.convertValues<>())>::value, "specifying no types yields empty tuple");
961
962 // convert values via Argument's API
963 Argument arg("test", '\0');
964 arg.m_occurrences = { occurrence, occurrence };
965 checkConvertedValues("values from Argument::convertValues", arg.valuesAs<string, unsigned int, double, int>());
966 checkConvertedValues("values from Argument::convertValues(1)", arg.valuesAs<string, unsigned int, double, int>(1));
967 const auto allValues = arg.allValuesAs<string, unsigned int, double, int>();
968 CPPUNIT_ASSERT_EQUAL(2_st, allValues.size());
969 for (const auto &values : allValues) {
970 checkConvertedValues("values from Argument::convertAllValues", values);
971 }
972 static_assert(std::is_same<std::tuple<>, decltype(arg.valuesAs<>())>::value, "specifying no types yields empty tuple");
973
974 // error handling
975 try {
976 occurrence.convertValues<string, unsigned int, double, int, int>();
977 CPPUNIT_FAIL("Expected exception");
978 } catch (const ParseError &failure) {
979 CPPUNIT_ASSERT_EQUAL("Expected 5 top-level values to be present but only 4 have been specified."s, string(failure.what()));
980 }
981 try {
982 occurrence.convertValues<int>();
983 CPPUNIT_FAIL("Expected exception");
984 } catch (const ParseError &failure) {
985 TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of top-level value",
986 "Conversion of top-level value \"foo\" to type \".*\" failed: The character \"f\" is no valid digit."s, std::regex::extended,
987 std::string(failure.what()));
988 }
989 occurrence.path = { &arg };
990 try {
991 occurrence.convertValues<string, unsigned int, double, int, int>();
992 CPPUNIT_FAIL("Expected exception");
993 } catch (const ParseError &failure) {
994 CPPUNIT_ASSERT_EQUAL("Expected 5 values for argument --test to be present but only 4 have been specified."s, string(failure.what()));
995 }
996 try {
997 occurrence.convertValues<int>();
998 CPPUNIT_FAIL("Expected exception");
999 } catch (const ParseError &failure) {
1000 TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of argument value",
1001 "Conversion of value \"foo\" \\(for argument --test\\) to type \".*\" failed: The character \"f\" is no valid digit."s,
1002 std::regex::extended, std::string(failure.what()));
1003 }
1004}
#define SET_APPLICATION_INFO
Sets application meta data (including SET_DEPENDENCY_INFO) used by ArgumentParser::printHelp().
CPPUNIT_TEST_SUITE_REGISTRATION(ArgumentParserTests)
void checkConvertedValues(const std::string &message, const ValueTuple &values)
The ArgumentParserTests class tests the ArgumentParser and Argument classes.
void testNoColorArgument()
Tests whether NocolorArgument toggles escape codes correctly.
void testCallbacks()
Tests whether callbacks are called correctly.
void testValueConversion()
Tests value conversion provided by Argument and ArgumentOccurrence.
void testArgument()
Tests the behaviour of the argument class.
void testBashCompletion()
Tests bash completion.
void testHelp()
Tests –help output.
void testSetMainArguments()
Tests some corner cases in setMainArguments() which are not already checked in the other tests.
void testParsing()
Tests parsing command line arguments.
The ArgumentParser class provides a means for handling command line arguments.
const HelpArgument & helpArg() const
Returns the --help argument.
const char * executable() const
Returns the name of the current executable.
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const ArgumentVector & mainArguments() const
Returns the main arguments.
Argument * defaultArgument() const
Returns the default argument.
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
void setExitFunction(std::function< void(int)> exitFunction)
Specifies a function quit the application.
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
void parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior=ParseArgumentBehavior::CheckConstraints|ParseArgumentBehavior::InvokeCallbacks|ParseArgumentBehavior::ExitOnFailure)
Parses the specified command line arguments.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
const NoColorArgument & noColorArg() const
Returns the --no-color argument.
The ArgumentReader class internally encapsulates the process of reading command line arguments.
ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
bool read()
Reads the commands line arguments specified when constructing the object.
The Argument class is a wrapper for command line argument information.
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
static constexpr std::size_t varValueCount
Denotes a variable number of values.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
void setValueCompletionBehavior(ValueCompletionBehavior valueCompletionBehaviour)
Sets the items to be considered when generating completion for the values.
ValueCompletionBehavior valueCompletionBehaviour() const
Returns the items to be considered when generating completion for the values.
void setConstraints(std::size_t minOccurrences, std::size_t maxOccurrences)
Sets the allowed number of occurrences.
void setImplicit(bool implicit)
Sets whether the argument is an implicit argument.
void setDenotesOperation(bool denotesOperation)
Sets whether the argument denotes the operation.
void markAsDeprecated(const Argument *deprecatedBy=nullptr)
Marks the argument as deprecated.
void setAbbreviation(char abbreviation)
Sets the abbreviation of the argument.
void setDescription(const char *description)
Sets the description of the argument.
void appendValueName(const char *valueName)
Appends a value name.
std::vector< std::tuple< TargetType... > > allValuesAs() const
Converts the present values for all occurrence to the specified target types.
void reset()
Resets occurrences (indices, values and paths).
void setExample(const char *example)
Sets the a usage example for the argument.
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
const ArgumentVector & parents() const
Returns the parents of this argument.
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this argument.
void setCombinable(bool combinable)
Sets whether this argument can be combined.
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
bool isRequired() const
Returns an indication whether the argument is mandatory.
void setRequired(bool required)
Sets whether this argument is mandatory or not.
void setPreDefinedCompletionValues(const char *preDefinedCompletionValues)
Assigns the values to be used when generating completion for the values.
void setEnvironmentVariable(const char *environmentVariable)
Sets the environment variable queried when firstValue() is called.
std::tuple< TargetType... > valuesAs(std::size_t occurrence=0) const
Converts the present values for the specified occurrence to the specified target types.
void setRequiredValueCount(std::size_t requiredValueCount)
Sets the number of values which are required to be given for this argument.
void setName(const char *name)
Sets the name of the argument.
The ConfigValueArgument class is an Argument where setCombinable() is true by default.
The HelpArgument class prints help information for an argument parser when present (–help,...
The Indentation class allows printing indentation conveniently, eg.
The NoColorArgument class allows to specify whether use of escape codes or similar technique to provi...
void apply() const
Sets EscapeCodes::enabled according to the presence of the first instantiation of NoColorArgument.
The OperationArgument class is an Argument where denotesOperation() is true by default.
The StandardOutputCheck class asserts whether the (standard) output written in the enclosing code blo...
Definition outputcheck.h:20
The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
Definition parseerror.h:11
#define QT_CONFIG_ARGUMENTS
CPP_UTILITIES_EXPORT bool enabled
Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes.
Contains literals to ease asserting with CPPUNIT_ASSERT_EQUAL.
Definition testutils.h:368
Contains all utilities provides by the c++utilities library.
CPP_UTILITIES_EXPORT std::string testFilePath(const std::string &relativeTestFilePath)
Convenience function to invoke TestApplication::testFilePath().
Definition testutils.h:161
StringType argsToString(Args &&...args)
CPP_UTILITIES_EXPORT ApplicationInfo applicationInfo
Stores global application info used by ArgumentParser::printHelp() and AboutDialog.
STL namespace.
std::vector< const char * > dependencyVersions
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
std::size_t index
The index of the occurrence.
std::vector< const char * > values
The parameter values which have been specified after the occurrence of the argument.
std::tuple< RemainingTargetTypes... > convertValues() const
Converts the present values to the specified target types.
std::vector< Argument * > path
The "path" of the occurrence (the parent elements which have been specified before).
#define TESTUTILS_ASSERT_LIKE_FLAGS(message, expectedRegex, regexFlags, actualString)
Asserts whether the specified string matches the specified regex.
Definition testutils.h:328
constexpr int i