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