Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpJsonArgumentParser.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2025 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
31#include <visp3/core/vpConfig.h>
32#include <visp3/io/vpJsonArgumentParser.h>
33#include <visp3/core/vpException.h>
34#include <fstream>
35
36#if defined(VISP_HAVE_NLOHMANN_JSON)
38
39using json = nlohmann::json;
40
41vpJsonArgumentParser::vpJsonArgumentParser(const std::string &description, const std::string &jsonFileArgumentName,
42 const std::string &nestSeparator) :
43 m_description(description),
44 m_jsonFileArgumentName(jsonFileArgumentName),
45 m_nestSeparator(nestSeparator)
46{
47 if (m_jsonFileArgumentName.empty()) {
48 throw vpException(vpException::badValue, "The JSON file argument must not be empty");
49 }
50
51 if (m_nestSeparator.empty()) {
52 throw vpException(vpException::badValue, "You must provide a JSON nesting delimiter to be able to parse JSON");
53 }
54
55 m_helpers[m_jsonFileArgumentName] = []() -> std::string {
56 return "Path to the JSON configuration file. Values in this files are loaded, and can be overridden by command line arguments.\nOptional";
57 };
58}
59
60std::string vpJsonArgumentParser::help() const
61{
62 std::stringstream ss;
63
64 ss << "Program description: " << m_description << std::endl;
65 ss << "Arguments: " << std::endl;
66 unsigned spacesBetweenArgAndDescription = 0;
67 for (const auto &helper : m_helpers) {
68 if (helper.first.size() > spacesBetweenArgAndDescription) {
69 spacesBetweenArgAndDescription = static_cast<unsigned int>(helper.first.size());
70 }
71 }
72 spacesBetweenArgAndDescription += 4;
73
74 for (const auto &helper : m_helpers) {
75 std::stringstream argss(helper.second());
76 std::string line;
77 bool first = true;
78 while (getline(argss, line, '\n')) {
79 const unsigned lineSpace = first ? spacesBetweenArgAndDescription - static_cast<unsigned>(helper.first.size()) : spacesBetweenArgAndDescription;
80 const std::string spaceBetweenArgAndDescription(lineSpace, ' ');
81 if (first) {
82 ss << "\t" << helper.first << spaceBetweenArgAndDescription << line << std::endl;
83 }
84 else {
85 ss << "\t" << spaceBetweenArgAndDescription << line << std::endl;
86 }
87 first = false;
88
89 }
90 ss << std::endl;
91 }
92 ss << "Example JSON configuration file: " << std::endl << std::endl;
93 ss << m_exampleJson.dump(2) << std::endl;
94 return ss.str();
95}
96
97vpJsonArgumentParser &vpJsonArgumentParser::addFlag(const std::string &name, bool &parameter, const std::string &help)
98{
99 m_argumentType[name] = FLAG;
100 const auto getter = [name, this](nlohmann::json &j, bool create) -> nlohmann::json *{
101 size_t pos = 0;
102 nlohmann::json *f = &j;
103 std::string token;
104 std::string name_copy = name;
105
106 while ((pos = name_copy.find(m_nestSeparator)) != std::string::npos) {
107 token = name_copy.substr(0, pos);
108
109 name_copy.erase(0, pos + m_nestSeparator.length());
110 if (create && !f->contains(token)) {
111 (*f)[token] = {};
112 }
113 else if (!f->contains(token)) {
114 return nullptr;
115 }
116 f = &(f->at(token));
117 }
118 if (create && !f->contains(name_copy)) {
119 (*f)[name_copy] = {};
120 }
121 else if (!f->contains(name_copy)) {
122 return nullptr;
123 }
124 f = &(f->at(name_copy));
125 return f;
126 };
127
128 m_parsers[name] = [&parameter, getter, name](nlohmann::json &j) {
129 const nlohmann::json *field = getter(j, false);
130 const bool fieldHasNoValue = ((field == nullptr) || (field != nullptr && field->is_null()));
131 if (!fieldHasNoValue && (field->type() == json::value_t::boolean && (*field) == true)) {
132 parameter = !parameter;
133 }
134 };
135
136 m_updaters[name] = [getter](nlohmann::json &j, const std::string &) {
137 nlohmann::json *field = getter(j, true);
138 *field = true;
139 };
140
141 m_helpers[name] = [help, parameter]() -> std::string {
142 std::stringstream ss;
143 nlohmann::json repr = parameter;
144 ss << help << std::endl << "Default: " << repr;
145 return ss.str();
146 };
147
148 nlohmann::json *exampleField = getter(m_exampleJson, true);
149 *exampleField = parameter;
150
151 return *this;
152}
153
154void vpJsonArgumentParser::parse(int argc, const char *argv[])
155{
156 json j;
157 const std::vector<std::string> arguments(argv + 1, argv + argc);
158 std::vector<unsigned> ignoredArguments;
159 const auto jsonFileArgumentPos = std::find(arguments.begin(), arguments.end(), m_jsonFileArgumentName);
160 // Load JSON file if present
161 if (jsonFileArgumentPos != arguments.end()) {
162 ignoredArguments.push_back(static_cast<unsigned>(jsonFileArgumentPos - arguments.begin() + 1));
163 ignoredArguments.push_back(static_cast<unsigned>(jsonFileArgumentPos - arguments.begin() + 2));
164
165 if (jsonFileArgumentPos == arguments.end() - 1) {
166 throw vpException(vpException::ioError, "No JSON file was provided");
167 }
168 const std::string jsonFileName = *(jsonFileArgumentPos + 1);
169 std::ifstream jsonFile(jsonFileName);
170 if (!jsonFile.good()) {
171 std::stringstream ss;
172 ss << "Could not open JSON file " << jsonFileName << "! Make sure it exists and is readable" << std::endl;
173 throw vpException(vpException::ioError, ss.str());
174 }
175 j = json::parse(jsonFile);
176 jsonFile.close();
177 }
178 // Parse command line arguments
179 for (int i = 1; i < argc; ++i) {
180 const std::string arg = argv[i];
181 bool stop_for_loop = false;
182 if (std::find(ignoredArguments.begin(), ignoredArguments.end(), i) != ignoredArguments.end()) {
183 stop_for_loop = true;
184 }
185 if (!stop_for_loop) {
186 if (arg == "-h" || arg == "--help") {
187 std::cout << help() << std::endl;
188 exit(1);
189 }
190
191 if (m_parsers.find(arg) != m_parsers.end()) {
192 if (m_argumentType[arg] == WITH_FIELD) {
193 if (i < argc - 1) {
194 m_updaters[arg](j, std::string(argv[i + 1]));
195 ++i;
196 }
197 else {
198 std::stringstream ss;
199 ss << "Argument " << arg << " was passed but no value was provided" << std::endl;
200 throw vpException(vpException::ioError, ss.str());
201 }
202 }
203 else if (m_argumentType[arg] == FLAG) {
204 m_updaters[arg](j, std::string());
205 }
206 }
207 else {
208 std::cerr << "Unknown parameter when parsing: " << arg << std::endl;
209 }
210 }
211 }
212
213 // Get the values from json document and store them in the arguments passed by ref in addArgument
214 for (const auto &parser : m_parsers) {
215 parser.second(j);
216 }
217}
218
219END_VISP_NAMESPACE
220
221#endif
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ ioError
I/O error.
Definition vpException.h:67
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...
vpJsonArgumentParser & addFlag(const std::string &name, bool &parameter, const std::string &help="No description")
Add an argument that acts as a flag when specified on the command line. When this flag is specified,...