C++ Utilities  5.10.5
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 
29 using namespace std;
30 using namespace CppUtilities;
31 using namespace CppUtilities::Literals;
32 
33 using namespace CPPUNIT_NS;
34 
38 class 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 
52 public:
53  void setUp() override;
54  void tearDown() override;
55 
56  void testArgument();
57  void testParsing();
58  void testCallbacks();
59  void testSetMainArguments();
60  void testValueConversion();
61 #ifndef PLATFORM_WINDOWS
62  void testBashCompletion();
63  void testHelp();
64  void testNoColorArgument();
65 #endif
66 
67 private:
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
79  EscapeCodes::enabled = false;
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
496 static 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");
827  EscapeCodes::enabled = true;
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
897  EscapeCodes::enabled = true;
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 
921 template <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.
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