40 #include <visp3/core/vpIoTools.h>
41 #include <visp3/io/vpJsonArgumentParser.h>
43 #if defined(VISP_HAVE_NLOHMANN_JSON) && defined(VISP_HAVE_CATCH2)
44 #include VISP_NLOHMANN_JSON(json.hpp)
45 using json = nlohmann::json;
47 #include <catch_amalgamated.hpp>
49 #ifdef ENABLE_VISP_NAMESPACE
53 std::pair<int, std::vector<char *>> convertToArgcAndArgv(
const std::vector<std::string> &args)
55 std::vector<char *> argvs;
56 argvs.reserve(args.size());
57 int argc =
static_cast<int>(args.size());
58 for (
unsigned i = 0; i < args.size(); ++i) {
59 argvs.push_back(
const_cast<char *
>(args[i].c_str()));
61 return std::make_pair(argc, argvs);
64 json loadJson(
const std::string &path)
66 std::ifstream json_file(path);
67 if (!json_file.good()) {
70 json j = json::parse(json_file);
75 void saveJson(
const json &j,
const std::string &path)
77 std::ofstream json_file(path);
78 if (!json_file.good()) {
81 json_file << j.dump();
85 SCENARIO(
"Parsing arguments from JSON file",
"[json]")
91 const std::string jsonPath = tmp_dir +
"/" +
"arguments.json";
93 const auto modifyJson = [&jsonPath](std::function<void(json &)> modify) ->
void {
94 json j = loadJson(jsonPath);
96 saveJson(j, jsonPath);
99 GIVEN(
"Some specific arguments")
101 const std::string s =
"hello";
102 WHEN(
"Converting a string to a json rep")
104 const json js = convertCommandLineArgument<std::string>(s);
105 const json truejs = s;
106 THEN(
"Conversion is correct")
108 REQUIRE(js == truejs);
113 GIVEN(
"Some JSON parameters saved in a file, and some C++ variables")
121 {
"flagDefaultTrue",
true},
125 saveJson(j, jsonPath);
133 bool flagInitialValue = flag;
135 bool invertedFlag =
true;
136 WHEN(
"Declaring a parser with all parameters required")
139 parser.addArgument(
"a", a,
true)
140 .addArgument(
"b", b,
true)
141 .addArgument(
"c", c,
true)
142 .addArgument(
"d", d,
true)
143 .addArgument(
"e/a", ea,
true)
144 .addFlag(
"flag", flag)
145 .addFlag(
"flagDefaultTrue", invertedFlag);
147 THEN(
"Calling the parser without any argument fails")
150 const char *argv[] = {
154 REQUIRE_THROWS(parser.parse(argc, argv));
157 THEN(
"Calling the parser with only the JSON file works")
160 const char *argv[] = {
165 REQUIRE_NOTHROW(parser.parse(argc, argv));
166 REQUIRE(a == j[
"a"]);
167 REQUIRE(b == j[
"b"]);
168 REQUIRE(c == j[
"c"]);
169 REQUIRE(d == j[
"d"]);
170 REQUIRE(ea == j[
"e"][
"a"]);
171 REQUIRE(flag != flagInitialValue);
172 REQUIRE(invertedFlag !=
true);
174 THEN(
"Calling the parser by specifying the json argument but leaving the file path empty throws an error")
177 const char *argv[] = {
181 REQUIRE_THROWS(parser.parse(argc, argv));
183 THEN(
"Calling the parser with only the json file but deleting a random field throws an error")
186 for (
const auto &jsonElem : j.items()) {
187 if (jsonElem.key().rfind(
"flag", 0) != 0) {
188 modifyJson([&jsonElem](json &j) { j.erase(jsonElem.key()); });
189 const char *argv[] = {
194 REQUIRE_THROWS(parser.parse(argc, argv));
198 THEN(
"Calling the parser with only the json file but setting a random field to null throws an error")
201 for (
const auto &jsonElem : j.items()) {
202 if (jsonElem.key().rfind(
"flag", 0) != 0) {
203 modifyJson([&jsonElem](json &j) { j[jsonElem.key()] =
nullptr; });
204 const char *argv[] = {
209 REQUIRE_THROWS(parser.parse(argc, argv));
213 THEN(
"Calling the parser with an invalid json file path throws an error")
216 const char *argv[] = {
219 "some_invalid_json/file/path.json"
221 REQUIRE_THROWS(parser.parse(argc, argv));
223 THEN(
"Calling the parser with only the command line arguments works")
225 const int newa = a + 1, newea = ea + 6;
226 const double newb = b + 2.0;
227 const std::string newc = c +
"hello";
228 const bool newd = !d;
230 const std::string newdstr(newd ?
"true" :
"false");
231 std::vector<std::string> args = {
233 "a", std::to_string(newa),
234 "b", std::to_string(newb),
237 "e/a", std::to_string(newea),
241 std::vector<char *> argv;
242 std::tie(argc, argv) = convertToArgcAndArgv(args);
243 REQUIRE_NOTHROW(parser.parse(argc, (
const char **)(&argv[0])));
248 REQUIRE(ea == newea);
249 REQUIRE(flag != flagInitialValue);
252 THEN(
"Calling the parser with JSON and command line argument works")
254 const int newa = a + 1;
255 const double newb = b + 2.0;
256 std::vector<std::string> args = {
258 "--config", jsonPath,
259 "a", std::to_string(newa),
260 "b", std::to_string(newb),
264 std::vector<char *> argv;
265 std::tie(argc, argv) = convertToArgcAndArgv(args);
266 REQUIRE_NOTHROW(parser.parse(argc, (
const char **)(&argv[0])));
269 REQUIRE(c == j[
"c"]);
270 REQUIRE(d == j[
"d"]);
271 REQUIRE(ea == j[
"e"][
"a"]);
272 REQUIRE(invertedFlag ==
false);
274 THEN(
"Calling the parser with a missing argument value throws an error")
277 std::vector<std::string> args = {
279 "--config", jsonPath,
283 std::vector<char *> argv;
284 std::tie(argc, argv) = convertToArgcAndArgv(args);
285 REQUIRE_THROWS(parser.parse(argc, (
const char **)(&argv[0])));
289 THEN(
"Declaring a parser with an undefined nesting delimiter fails")
293 THEN(
"Declaring a parser with an invalid JSON file argument fails")
298 WHEN(
"Instantiating a parser with some optional fields")
302 parser.addArgument(
"b", b,
false);
304 THEN(
"Calling the parser without any argument works and does not modify the default value")
308 const char *argv[] = {
312 REQUIRE_NOTHROW(parser.parse(argc, argv));
317 WHEN(
"Instantiating a parser with nested parameters")
321 parser.addArgument(
"b", b,
false);
323 THEN(
"Calling the parser without any argument works and does not modify the default value")
327 const char *argv[] = {
331 REQUIRE_NOTHROW(parser.parse(argc, argv));
338 WHEN(
"Instantiating a parser with some documentation")
340 const std::string programString =
"ProgramString";
341 const std::string firstArg =
"FirstArgName", firstArgDescription =
"FirstArgDescription";
342 const std::string secondArg =
"secondArgName", secondArgDescription =
"secondArgDescription";
344 std::string a =
"DefaultFirstArg", b =
"DefaultSecondArg";
345 parser.addArgument(firstArg, a,
true, firstArgDescription);
346 parser.addArgument(secondArg, b,
false, secondArgDescription);
347 WHEN(
"Getting the help string")
349 const std::string help = parser.help();
350 THEN(
"Output should contain the basic program description")
352 REQUIRE(help.find(programString) < help.size());
354 THEN(
"Output should contain the json argument")
356 REQUIRE(help.find(
"--config") < help.size());
358 THEN(
"Output should contain the arguments, their description and their default value")
360 const std::vector<std::string> requireds = { firstArg, secondArg, firstArgDescription, secondArgDescription, a, b };
361 for (
const auto &required : requireds) {
362 REQUIRE(help.find(required) < help.size());
371 int main(
int argc,
char *argv[])
373 Catch::Session session;
374 session.applyCommandLine(argc, argv);
376 int numFailed = session.run();
error that can be emitted by ViSP classes.
Command line argument parsing with support for JSON files. If a JSON file is supplied,...