C++ Utilities: tests/argumentparsertests.cpp Source File

C++ Utilities 5.33.0 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
4#include "../conversion/stringbuilder.h"
5#include "../conversion/stringconversion.h"
6
7#include "../application/argumentparser.h"
8#include "../application/argumentparserprivate.h"
9#include "../application/commandlineutils.h"
10#include "../application/fakeqtconfigarguments.h"
11
12#include "../io/ansiescapecodes.h"
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
39 class 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();
60 void testSetMainArguments();
61 void testValueConversion();
62#ifndef PLATFORM_WINDOWS
63 void testBashCompletion();
64 void testHelp();
65 void testNoColorArgument();
66#endif
67
68private:
69 void callback();
70 [[noreturn]] void failOnExit(int code);
71};
72
73CPPUNIT_TEST_SUITE_REGISTRATION(ArgumentParserTests);
74
75 void ArgumentParserTests::setUp()
76{
77#ifndef PLATFORM_WINDOWS
78 setenv("ENABLE_ESCAPE_CODES", "0", 1);
79#endif
80 EscapeCodes::enabled = false;
81}
82
83 void ArgumentParserTests::tearDown()
84{
85}
86
87[[noreturn]] void ArgumentParserTests::failOnExit(int code)
88{
89 CPPUNIT_FAIL(argsToString("Exited unexpectedly with code ", code));
90}
91
95 void ArgumentParserTests::testArgument()
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
120 void ArgumentParserTests::testParsing()
121{
122 // setup parser with some test argument definitions
123 auto parser = ArgumentParser();
124 parser.setExitFunction(std::bind(&ArgumentParserTests::failOnExit, this, std::placeholders::_1));
125 SET_APPLICATION_INFO;
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
482 void ArgumentParserTests::testCallbacks()
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
526 void ArgumentParserTests::testBashCompletion()
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");
533 filesArg.setRequiredValueCount(Argument::varValueCount);
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");
542 fieldsArg.setRequiredValueCount(Argument::varValueCount);
543 fieldsArg.setPreDefinedCompletionValues("title album artist trackpos");
544 fieldsArg.setImplicit(true);
545 Argument valuesArg("values", '\0', "specifies the fields");
546 valuesArg.setRequiredValueCount(Argument::varValueCount);
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");
551 selectorsArg.setRequiredValueCount(Argument::varValueCount);
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
781 void ArgumentParserTests::testHelp()
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" });
794 nestedSubArg.setRequiredValueCount(Argument::varValueCount);
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");
849 EscapeCodes::enabled = true;
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
890 void ArgumentParserTests::testSetMainArguments()
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
916 void ArgumentParserTests::testNoColorArgument()
917{
918 // assume escape codes are enabled by default
919 EscapeCodes::enabled = true;
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
943 template <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
954 void ArgumentParserTests::testValueConversion()
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}
ansiescapecodes.h
argumentparser.h
SET_APPLICATION_INFO
#define SET_APPLICATION_INFO
Sets application meta data (including SET_DEPENDENCY_INFO) used by ArgumentParser::printHelp().
Definition argumentparser.h:53
argumentparserprivate.h
CPPUNIT_TEST_SUITE_REGISTRATION
CPPUNIT_TEST_SUITE_REGISTRATION(ArgumentParserTests)
checkConvertedValues
void checkConvertedValues(const std::string &message, const ValueTuple &values)
Definition argumentparsertests.cpp:943
ArgumentParserTests
The ArgumentParserTests class tests the ArgumentParser and Argument classes.
Definition argumentparsertests.cpp:39
ArgumentParserTests::testNoColorArgument
void testNoColorArgument()
Tests whether NocolorArgument toggles escape codes correctly.
Definition argumentparsertests.cpp:916
ArgumentParserTests::testCallbacks
void testCallbacks()
Tests whether callbacks are called correctly.
Definition argumentparsertests.cpp:482
ArgumentParserTests::testValueConversion
void testValueConversion()
Tests value conversion provided by Argument and ArgumentOccurrence.
Definition argumentparsertests.cpp:954
ArgumentParserTests::testArgument
void testArgument()
Tests the behaviour of the argument class.
Definition argumentparsertests.cpp:95
ArgumentParserTests::testBashCompletion
void testBashCompletion()
Tests bash completion.
Definition argumentparsertests.cpp:526
ArgumentParserTests::testHelp
void testHelp()
Tests –help output.
Definition argumentparsertests.cpp:781
ArgumentParserTests::testSetMainArguments
void testSetMainArguments()
Tests some corner cases in setMainArguments() which are not already checked in the other tests.
Definition argumentparsertests.cpp:890
ArgumentParserTests::testParsing
void testParsing()
Tests parsing command line arguments.
Definition argumentparsertests.cpp:120
ArgumentParserTests::setUp
void setUp() override
Definition argumentparsertests.cpp:75
ArgumentParserTests::tearDown
void tearDown() override
Definition argumentparsertests.cpp:83
CppUtilities::ArgumentParser
The ArgumentParser class provides a means for handling command line arguments.
Definition argumentparser.h:457
CppUtilities::ArgumentParser::helpArg
const HelpArgument & helpArg() const
Returns the --help argument.
Definition argumentparser.h:1187
CppUtilities::ArgumentParser::executable
const char * executable() const
Returns the name of the current executable.
Definition argumentparser.h:1113
CppUtilities::ArgumentParser::readArgs
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
Definition argumentparser.cpp:972
CppUtilities::ArgumentParser::mainArguments
const ArgumentVector & mainArguments() const
Returns the main arguments.
Definition argumentparser.h:1097
CppUtilities::ArgumentParser::defaultArgument
Argument * defaultArgument() const
Returns the default argument.
Definition argumentparser.h:1142
CppUtilities::ArgumentParser::setMainArguments
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
Definition argumentparser.cpp:790
CppUtilities::ArgumentParser::setExitFunction
void setExitFunction(std::function< void(int)> exitFunction)
Specifies a function quit the application.
Definition argumentparser.h:1179
CppUtilities::ArgumentParser::addMainArgument
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
Definition argumentparser.cpp:821
CppUtilities::ArgumentParser::parseArgs
void parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior=ParseArgumentBehavior::CheckConstraints|ParseArgumentBehavior::InvokeCallbacks|ParseArgumentBehavior::ExitOnFailure)
Parses the specified command line arguments.
Definition argumentparser.cpp:936
CppUtilities::ArgumentParser::resetArgs
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
Definition argumentparser.cpp:1034
CppUtilities::ArgumentParser::noColorArg
const NoColorArgument & noColorArg() const
Returns the --no-color argument.
Definition argumentparser.h:1203
CppUtilities::ArgumentReader
The ArgumentReader class internally encapsulates the process of reading command line arguments.
Definition argumentparserprivate.h:9
CppUtilities::ArgumentReader::reset
ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
Definition argumentparser.cpp:141
CppUtilities::ArgumentReader::read
bool read()
Reads the commands line arguments specified when constructing the object.
Definition argumentparser.cpp:155
CppUtilities::Argument
The Argument class is a wrapper for command line argument information.
Definition argumentparser.h:263
CppUtilities::Argument::addSubArgument
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
Definition argumentparser.cpp:666
CppUtilities::Argument::varValueCount
static constexpr std::size_t varValueCount
Denotes a variable number of values.
Definition argumentparser.h:368
CppUtilities::Argument::firstValue
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
Definition argumentparser.cpp:495
CppUtilities::Argument::Flags::Greedy
@ Greedy
Definition argumentparser.h:279
CppUtilities::Argument::setValueCompletionBehavior
void setValueCompletionBehavior(ValueCompletionBehavior valueCompletionBehaviour)
Sets the items to be considered when generating completion for the values.
Definition argumentparser.h:1024
CppUtilities::Argument::valueCompletionBehaviour
ValueCompletionBehavior valueCompletionBehaviour() const
Returns the items to be considered when generating completion for the values.
Definition argumentparser.h:1013
CppUtilities::Argument::setConstraints
void setConstraints(std::size_t minOccurrences, std::size_t maxOccurrences)
Sets the allowed number of occurrences.
Definition argumentparser.h:817
CppUtilities::Argument::setImplicit
void setImplicit(bool implicit)
Sets whether the argument is an implicit argument.
Definition argumentparser.h:739
CppUtilities::Argument::setDenotesOperation
void setDenotesOperation(bool denotesOperation)
Sets whether the argument denotes the operation.
Definition argumentparser.h:934
CppUtilities::Argument::markAsDeprecated
void markAsDeprecated(const Argument *deprecatedBy=nullptr)
Marks the argument as deprecated.
Definition argumentparser.h:806
CppUtilities::Argument::setAbbreviation
void setAbbreviation(char abbreviation)
Sets the abbreviation of the argument.
Definition argumentparser.h:564
CppUtilities::Argument::setDescription
void setDescription(const char *description)
Sets the description of the argument.
Definition argumentparser.h:604
CppUtilities::Argument::appendValueName
void appendValueName(const char *valueName)
Appends a value name.
Definition argumentparser.h:712
CppUtilities::Argument::allValuesAs
std::vector< std::tuple< TargetType... > > allValuesAs() const
Converts the present values for all occurrences to the specified target types.
Definition argumentparser.h:421
CppUtilities::Argument::reset
void reset()
Resets occurrences (indices, values and paths).
Definition argumentparser.h:1050
CppUtilities::Argument::setExample
void setExample(const char *example)
Sets the a usage example for the argument.
Definition argumentparser.h:624
CppUtilities::Argument::conflictsWithArgument
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
Definition argumentparser.cpp:702
CppUtilities::Argument::parents
const ArgumentVector & parents() const
Returns the parents of this argument.
Definition argumentparser.h:991
CppUtilities::Argument::setSubArguments
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this argument.
Definition argumentparser.cpp:617
CppUtilities::Argument::setCombinable
void setCombinable(bool combinable)
Sets whether this argument can be combined.
Definition argumentparser.h:910
CppUtilities::Argument::setCallback
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
Definition argumentparser.h:954
CppUtilities::Argument::isRequired
bool isRequired() const
Returns an indication whether the argument is mandatory.
Definition argumentparser.h:840
CppUtilities::Argument::setRequired
void setRequired(bool required)
Sets whether this argument is mandatory or not.
Definition argumentparser.h:852
CppUtilities::Argument::setPreDefinedCompletionValues
void setPreDefinedCompletionValues(const char *preDefinedCompletionValues)
Assigns the values to be used when generating completion for the values.
Definition argumentparser.h:1040
CppUtilities::Argument::setEnvironmentVariable
void setEnvironmentVariable(const char *environmentVariable)
Sets the environment variable queried when firstValue() is called.
Definition argumentparser.h:584
CppUtilities::Argument::valuesAs
std::tuple< TargetType... > valuesAs(std::size_t occurrence=0) const
Converts the present values for the specified occurrence to the specified target types.
Definition argumentparser.h:411
CppUtilities::Argument::setRequiredValueCount
void setRequiredValueCount(std::size_t requiredValueCount)
Sets the number of values which are required to be given for this argument.
Definition argumentparser.h:670
CppUtilities::Argument::setName
void setName(const char *name)
Sets the name of the argument.
Definition argumentparser.h:534
CppUtilities::ConfigValueArgument
The ConfigValueArgument class is an Argument where setCombinable() is true by default.
Definition argumentparser.h:441
CppUtilities::HelpArgument
The HelpArgument class prints help information for an argument parser when present (–help,...
Definition argumentparser.h:431
CppUtilities::Indentation
The Indentation class allows printing indentation conveniently, eg.
Definition commandlineutils.h:71
CppUtilities::Indentation::level
unsigned char level
Definition commandlineutils.h:84
CppUtilities::NoColorArgument
The NoColorArgument class allows to specify whether use of escape codes or similar technique to provi...
Definition argumentparser.h:447
CppUtilities::NoColorArgument::apply
void apply() const
Sets EscapeCodes::enabled according to the presence of the first instantiation of NoColorArgument.
Definition argumentparser.cpp:1818
CppUtilities::OperationArgument
The OperationArgument class is an Argument where denotesOperation() is true by default.
Definition argumentparser.h:436
CppUtilities::OutputCheck
The StandardOutputCheck class asserts whether the (standard) output written in the enclosing code blo...
Definition outputcheck.h:20
CppUtilities::ParseError
The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
Definition parseerror.h:11
commandlineutils.h
fakeqtconfigarguments.h
QT_CONFIG_ARGUMENTS
#define QT_CONFIG_ARGUMENTS
Definition fakeqtconfigarguments.h:49
CppUtilities::EscapeCodes::enabled
CPP_UTILITIES_EXPORT bool enabled
Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes.
Definition ansiescapecodes.cpp:22
CppUtilities::Literals
Contains literals to ease asserting with CPPUNIT_ASSERT_EQUAL.
Definition testutils.h:366
CppUtilities
Contains all utilities provided by the c++utilities library.
Definition argumentparser.h:18
CppUtilities::testFilePath
CPP_UTILITIES_EXPORT std::string testFilePath(const std::string &relativeTestFilePath)
Convenience function to invoke TestApplication::testFilePath().
Definition testutils.h:161
CppUtilities::UnknownArgumentBehavior::Warn
@ Warn
Definition argumentparser.h:78
CppUtilities::UnknownArgumentBehavior::Fail
@ Fail
Definition argumentparser.h:79
CppUtilities::ParseArgumentBehavior::CheckConstraints
@ CheckConstraints
Definition argumentparser.h:90
CppUtilities::ParseArgumentBehavior::InvokeCallbacks
@ InvokeCallbacks
Definition argumentparser.h:91
CppUtilities::argsToString
StringType argsToString(Args &&...args)
Definition stringbuilder.h:260
CppUtilities::ValueCompletionBehavior::AppendEquationSign
@ AppendEquationSign
Definition argumentparser.h:123
CppUtilities::ValueCompletionBehavior::PreDefinedValues
@ PreDefinedValues
Definition argumentparser.h:119
CppUtilities::ValueCompletionBehavior::InvokeCallback
@ InvokeCallback
Definition argumentparser.h:124
CppUtilities::ValueCompletionBehavior::Directories
@ Directories
Definition argumentparser.h:121
CppUtilities::applicationInfo
CPP_UTILITIES_EXPORT ApplicationInfo applicationInfo
Stores global application info used by ArgumentParser::printHelp() and AboutDialog.
Definition argumentparser.cpp:436
std
STL namespace.
outputcheck.h
parseerror.h
path.h
stringbuilder.h
stringconversion.h
CppUtilities::ArgumentOccurrence
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
Definition argumentparser.h:194
CppUtilities::ArgumentOccurrence::index
std::size_t index
The index of the occurrence.
Definition argumentparser.h:201
CppUtilities::ArgumentOccurrence::values
std::vector< const char * > values
The parameter values which have been specified after the occurrence of the argument.
Definition argumentparser.h:206
CppUtilities::ArgumentOccurrence::convertValues
std::tuple< RemainingTargetTypes... > convertValues() const
Converts the present values to the specified target types.
Definition argumentparser.h:225
CppUtilities::ArgumentOccurrence::path
std::vector< Argument * > path
The "path" of the occurrence (the parent elements which have been specified before).
Definition argumentparser.h:212
testutils.h
TESTUTILS_ASSERT_LIKE_FLAGS
#define TESTUTILS_ASSERT_LIKE_FLAGS(message, expectedRegex, regexFlags, actualString)
Asserts whether the specified string matches the specified regex.
Definition testutils.h:328
i
constexpr int i
Definition traitstests.cpp:106