Visual Servoing Platform  version 3.6.1 under development (2024-05-05)
vpJsonArgumentParser.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  */
30 #include <visp3/io/vpJsonArgumentParser.h>
31 #include <visp3/core/vpException.h>
32 #include <visp3/core/vpConfig.h>
33 #include <fstream>
34 
35 
36 #if defined(VISP_HAVE_NLOHMANN_JSON)
37 
38 using json = nlohmann::json;
39 
40 vpJsonArgumentParser::vpJsonArgumentParser(const std::string &description, const std::string &jsonFileArgumentName,
41  const std::string &nestSeparator) :
42  description(description),
43  jsonFileArgumentName(jsonFileArgumentName),
44  nestSeparator(nestSeparator)
45 {
46  if (jsonFileArgumentName.empty()) {
47  throw vpException(vpException::badValue, "The JSON file argument must not be empty");
48  }
49 
50  if (nestSeparator.empty()) {
51  throw vpException(vpException::badValue, "You must provide a JSON nesting delimiter to be able to parse JSON");
52  }
53 
54  helpers[jsonFileArgumentName] = []() -> std::string {
55  return "Path to the JSON configuration file. Values in this files are loaded, and can be overridden by command line arguments.\nOptional";
56  };
57 }
58 
59 std::string vpJsonArgumentParser::help() const
60 {
61  std::stringstream ss;
62 
63  ss << "Program description: " << description << std::endl;
64  ss << "Arguments: " << std::endl;
65  unsigned spacesBetweenArgAndDescription = 0;
66  for (const auto &helper : helpers) {
67  if (helper.first.size() > spacesBetweenArgAndDescription) {
68  spacesBetweenArgAndDescription = static_cast<unsigned int>(helper.first.size());
69  }
70  }
71  spacesBetweenArgAndDescription += 4;
72 
73  for (const auto &helper : helpers) {
74  std::stringstream argss(helper.second());
75  std::string line;
76  bool first = true;
77  while (getline(argss, line, '\n')) {
78  const unsigned lineSpace = first ? spacesBetweenArgAndDescription - static_cast<unsigned>(helper.first.size()) : spacesBetweenArgAndDescription;
79  const std::string spaceBetweenArgAndDescription(lineSpace, ' ');
80  if (first) {
81  ss << "\t" << helper.first << spaceBetweenArgAndDescription << line << std::endl;
82  }
83  else {
84  ss << "\t" << spaceBetweenArgAndDescription << line << std::endl;
85  }
86  first = false;
87 
88  }
89  ss << std::endl;
90  }
91  ss << "Example JSON configuration file: " << std::endl << std::endl;
92  ss << exampleJson.dump(2) << std::endl;
93  return ss.str();
94 }
95 
96 void vpJsonArgumentParser::parse(int argc, const char *argv[])
97 {
98  json j;
99  const std::vector<std::string> arguments(argv + 1, argv + argc);
100  std::vector<unsigned> ignoredArguments;
101  const auto jsonFileArgumentPos = std::find(arguments.begin(), arguments.end(), jsonFileArgumentName);
102  // Load JSON file if present
103  if (jsonFileArgumentPos != arguments.end()) {
104  ignoredArguments.push_back(static_cast<unsigned>(jsonFileArgumentPos - arguments.begin() + 1));
105  ignoredArguments.push_back(static_cast<unsigned>(jsonFileArgumentPos - arguments.begin() + 2));
106 
107  if (jsonFileArgumentPos == arguments.end() - 1) {
108  throw vpException(vpException::ioError, "No JSON file was provided");
109  }
110  const std::string jsonFileName = *(jsonFileArgumentPos + 1);
111  std::ifstream jsonFile(jsonFileName);
112  if (!jsonFile.good()) {
113  std::stringstream ss;
114  ss << "Could not open JSON file " << jsonFileName << "! Make sure it exists and is readable" << std::endl;
115  throw vpException(vpException::ioError, ss.str());
116  }
117  j = json::parse(jsonFile);
118  jsonFile.close();
119  }
120  // Parse command line arguments
121  for (int i = 1; i < argc; ++i) {
122  const std::string arg = argv[i];
123  if (std::find(ignoredArguments.begin(), ignoredArguments.end(), i) != ignoredArguments.end()) {
124  continue;
125  }
126  if (arg == "-h" || arg == "--help") {
127  std::cout << help() << std::endl;
128  exit(1);
129  }
130 
131  if (parsers.find(arg) != parsers.end()) {
132  if (i < argc - 1) {
133  updaters[arg](j, std::string(argv[i + 1]));
134  ++i;
135  }
136  else {
137  std::stringstream ss;
138  ss << "Argument " << arg << " was passed but no value was provided" << std::endl;
139  throw vpException(vpException::ioError, ss.str());
140  }
141  }
142  else {
143  std::cerr << "Unknown parameter when parsing: " << arg << std::endl;
144  }
145  }
146 
147  // Get the values from json document and store them in the arguments passed by ref in addArgument
148  for (const auto &parser : parsers) {
149  parser.second(j);
150  }
151 }
152 
153 #endif
error that can be emitted by ViSP classes.
Definition: vpException.h:59
@ ioError
I/O error.
Definition: vpException.h:79
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:85
std::string help() const
Generate a help message, containing the description of the arguments, their default value and whether...
void parse(int argc, const char *argv[])
Parse the arguments.
vpJsonArgumentParser(const std::string &description, const std::string &jsonFileArgumentName, const std::string &nestSeparator)
Create a new argument parser, that can take into account both a JSON configuration file and command l...