Line data Source code
1 : /******************************************************************************
2 : * Project: GDAL Utilities
3 : * Purpose: GDAL argument parser
4 : * Author: Even Rouault <even.rouault at spatialys.com>
5 : *
6 : * ****************************************************************************
7 : * Copyright (c) 2024, Even Rouault <even.rouault at spatialys.com>
8 : *
9 : * Permission is hereby granted, free of charge, to any person obtaining a
10 : * copy of this software and associated documentation files (the "Software"),
11 : * to deal in the Software without restriction, including without limitation
12 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 : * and/or sell copies of the Software, and to permit persons to whom the
14 : * Software is furnished to do so, subject to the following conditions:
15 : *
16 : * The above copyright notice and this permission notice shall be included
17 : * in all copies or substantial portions of the Software.
18 : *
19 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 : * DEALINGS IN THE SOFTWARE.
26 : ****************************************************************************/
27 :
28 : #include "gdal_version_full/gdal_version.h"
29 :
30 : #include "gdal.h"
31 : #include "gdalargumentparser.h"
32 : #include "commonutils.h"
33 :
34 : #include <algorithm>
35 :
36 : /************************************************************************/
37 : /* GDALArgumentParser() */
38 : /************************************************************************/
39 :
40 3488 : GDALArgumentParser::GDALArgumentParser(const std::string &program_name,
41 3488 : bool bForBinary)
42 3488 : : ArgumentParser(program_name, "", default_arguments::none)
43 : {
44 3488 : set_usage_max_line_width(120);
45 3488 : set_usage_break_on_mutex();
46 3488 : add_usage_newline();
47 :
48 3488 : if (bForBinary)
49 : {
50 675 : add_argument("-h", "--help")
51 675 : .flag()
52 : .action(
53 0 : [this, program_name](const auto &)
54 : {
55 0 : std::cout << usage() << std::endl << std::endl;
56 0 : std::cout << _("Note: ") << program_name
57 0 : << _(" --long-usage for full help.") << std::endl;
58 0 : std::exit(0);
59 675 : })
60 675 : .help(_("Shows short help message and exits."));
61 :
62 675 : add_argument("--long-usage")
63 675 : .flag()
64 : .action(
65 0 : [this](const auto & /*unused*/)
66 : {
67 0 : std::cout << *this;
68 0 : std::exit(0);
69 675 : })
70 675 : .help(_("Shows long help message and exits."));
71 :
72 675 : add_argument("--help-general")
73 675 : .flag()
74 675 : .help(_("Report detailed help on general options."));
75 :
76 675 : add_argument("--utility_version")
77 675 : .flag()
78 675 : .hidden()
79 : .action(
80 25 : [program_name](const auto &)
81 : {
82 25 : printf("%s was compiled against GDAL %s and "
83 : "is running against GDAL %s\n",
84 : program_name.c_str(), GDAL_RELEASE_NAME,
85 : GDALVersionInfo("RELEASE_NAME"));
86 25 : std::exit(0);
87 675 : })
88 675 : .help(_("Shows compile-time and run-time GDAL version."));
89 :
90 675 : add_usage_newline();
91 : }
92 3488 : }
93 :
94 : /************************************************************************/
95 : /* display_error_and_usage() */
96 : /************************************************************************/
97 :
98 4 : void GDALArgumentParser::display_error_and_usage(const std::exception &err)
99 : {
100 4 : std::cerr << _("Error: ") << err.what() << std::endl;
101 4 : std::cerr << usage() << std::endl << std::endl;
102 4 : std::cout << _("Note: ") << m_program_name
103 4 : << _(" --long-usage for full help.") << std::endl;
104 4 : }
105 :
106 : /************************************************************************/
107 : /* add_quiet_argument() */
108 : /************************************************************************/
109 :
110 3246 : Argument &GDALArgumentParser::add_quiet_argument(bool *pVar)
111 : {
112 : auto &arg =
113 3246 : this->add_argument("-q", "--quiet")
114 3246 : .flag()
115 : .help(
116 : _("Quiet mode. No progress message is emitted on the standard "
117 3246 : "output."));
118 3246 : if (pVar)
119 1099 : arg.store_into(*pVar);
120 :
121 3246 : return arg;
122 : }
123 :
124 : /************************************************************************/
125 : /* add_input_format_argument() */
126 : /************************************************************************/
127 :
128 1628 : Argument &GDALArgumentParser::add_input_format_argument(CPLStringList *pvar)
129 : {
130 1628 : return add_argument("-if")
131 1628 : .append()
132 3256 : .metavar("<format>")
133 : .action(
134 16 : [pvar](const std::string &s)
135 : {
136 8 : if (pvar)
137 : {
138 8 : if (GDALGetDriverByName(s.c_str()) == nullptr)
139 : {
140 2 : CPLError(CE_Warning, CPLE_AppDefined,
141 : "%s is not a recognized driver", s.c_str());
142 : }
143 8 : pvar->AddString(s.c_str());
144 : }
145 1628 : })
146 : .help(
147 3256 : _("Format/driver name(s) to be attempted to open the input file."));
148 : }
149 :
150 : /************************************************************************/
151 : /* add_output_format_argument() */
152 : /************************************************************************/
153 :
154 3102 : Argument &GDALArgumentParser::add_output_format_argument(std::string &var)
155 : {
156 3102 : auto &arg = add_argument("-of")
157 6204 : .metavar("<output_format>")
158 3102 : .store_into(var)
159 3102 : .help(_("Output format."));
160 3102 : add_hidden_alias_for(arg, "-f");
161 3102 : return arg;
162 : }
163 :
164 : /************************************************************************/
165 : /* add_creation_options_argument() */
166 : /************************************************************************/
167 :
168 1534 : Argument &GDALArgumentParser::add_creation_options_argument(CPLStringList &var)
169 : {
170 1534 : return add_argument("-co")
171 3068 : .metavar("<NAME>=<VALUE>")
172 1534 : .append()
173 2175 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
174 3068 : .help(_("Creation option(s)."));
175 : }
176 :
177 : /************************************************************************/
178 : /* add_metadata_item_options_argument() */
179 : /************************************************************************/
180 :
181 : Argument &
182 2053 : GDALArgumentParser::add_metadata_item_options_argument(CPLStringList &var)
183 : {
184 2053 : return add_argument("-mo")
185 4106 : .metavar("<NAME>=<VALUE>")
186 2053 : .append()
187 2080 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
188 4106 : .help(_("Metadata item option(s)."));
189 : }
190 :
191 : /************************************************************************/
192 : /* add_open_options_argument() */
193 : /************************************************************************/
194 :
195 0 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList &var)
196 : {
197 0 : return add_open_options_argument(&var);
198 : }
199 :
200 : /************************************************************************/
201 : /* add_open_options_argument() */
202 : /************************************************************************/
203 :
204 3100 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList *pvar)
205 : {
206 3100 : auto &arg = add_argument("-oo")
207 6200 : .metavar("<NAME>=<VALUE>")
208 3100 : .append()
209 3100 : .help(_("Open option(s) for input dataset."));
210 3100 : if (pvar)
211 : {
212 14 : arg.action([pvar](const std::string &s)
213 626 : { pvar->AddString(s.c_str()); });
214 : }
215 :
216 3100 : return arg;
217 : }
218 :
219 : /************************************************************************/
220 : /* add_output_type_argument() */
221 : /************************************************************************/
222 :
223 2286 : Argument &GDALArgumentParser::add_output_type_argument(GDALDataType &eDT)
224 : {
225 2286 : return add_argument("-ot")
226 4572 : .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
227 : .action(
228 948 : [&eDT](const std::string &s)
229 : {
230 316 : eDT = GDALGetDataTypeByName(s.c_str());
231 316 : if (eDT == GDT_Unknown)
232 : {
233 : throw std::invalid_argument(
234 0 : std::string("Unknown output pixel type: ").append(s));
235 : }
236 2602 : })
237 4572 : .help(_("Output data type."));
238 : }
239 :
240 : Argument &
241 736 : GDALArgumentParser::add_layer_creation_options_argument(CPLStringList &var)
242 : {
243 736 : return add_argument("-lco")
244 1472 : .metavar("<NAME>=<VALUE>")
245 736 : .append()
246 1024 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
247 1472 : .help(_("Layer creation options (format specific)."));
248 : }
249 :
250 : /************************************************************************/
251 : /* parse_args_without_binary_name() */
252 : /************************************************************************/
253 :
254 3406 : void GDALArgumentParser::parse_args_without_binary_name(CSLConstList papszArgs)
255 : {
256 6793 : CPLStringList aosArgs;
257 3406 : aosArgs.AddString(m_program_name.c_str());
258 20984 : for (CSLConstList papszIter = papszArgs; papszIter && *papszIter;
259 : ++papszIter)
260 17578 : aosArgs.AddString(*papszIter);
261 3406 : parse_args(aosArgs);
262 3361 : }
263 :
264 : /************************************************************************/
265 : /* find_argument() */
266 : /************************************************************************/
267 :
268 : std::map<std::string, ArgumentParser::argument_it>::iterator
269 8991 : GDALArgumentParser::find_argument(const std::string &name)
270 : {
271 8991 : auto arg_map_it = m_argument_map.find(name);
272 8991 : if (arg_map_it == m_argument_map.end())
273 : {
274 : // Attempt case insensitive lookup
275 : arg_map_it =
276 : std::find_if(m_argument_map.begin(), m_argument_map.end(),
277 280 : [&name](const auto &oArg)
278 142 : { return EQUAL(name.c_str(), oArg.first.c_str()); });
279 : }
280 8991 : return arg_map_it;
281 : }
282 :
283 : /************************************************************************/
284 : /* get_non_positional_arguments() */
285 : /************************************************************************/
286 :
287 : CPLStringList
288 698 : GDALArgumentParser::get_non_positional_arguments(const CPLStringList &aosArgs)
289 : {
290 698 : CPLStringList args;
291 :
292 : // Simplified logic borrowed from ArgumentParser::parse_args_internal()
293 : // that make sure that positional arguments are moved after optional ones,
294 : // as this is what ArgumentParser::parse_args() only supports.
295 : // This doesn't support advanced settings, such as sub-parsers or compound
296 : // argument
297 2792 : std::vector<std::string> raw_arguments{m_program_name};
298 698 : raw_arguments.insert(raw_arguments.end(), aosArgs.List(),
299 1396 : aosArgs.List() + aosArgs.size());
300 1396 : auto arguments = preprocess_arguments(raw_arguments);
301 698 : auto end = std::end(arguments);
302 698 : auto positional_argument_it = std::begin(m_positional_arguments);
303 2198 : for (auto it = std::next(std::begin(arguments)); it != end;)
304 : {
305 1501 : const auto ¤t_argument = *it;
306 1501 : if (Argument::is_positional(current_argument, m_prefix_chars))
307 : {
308 270 : if (positional_argument_it != std::end(m_positional_arguments))
309 : {
310 270 : auto argument = positional_argument_it++;
311 : auto next_it =
312 270 : argument->consume(it, end, "", /* dry_run = */ true);
313 270 : it = next_it;
314 270 : continue;
315 : }
316 : else
317 : {
318 0 : if (m_positional_arguments.empty())
319 : {
320 : throw std::runtime_error(
321 0 : "Zero positional arguments expected");
322 : }
323 : else
324 : {
325 : throw std::runtime_error(
326 : "Maximum number of positional arguments "
327 0 : "exceeded, failed to parse '" +
328 0 : current_argument + "'");
329 : }
330 : }
331 : }
332 :
333 1231 : auto arg_map_it = find_argument(current_argument);
334 1231 : if (arg_map_it != m_argument_map.end())
335 : {
336 1231 : auto argument = arg_map_it->second;
337 : auto next_it = argument->consume(
338 1231 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
339 : // Add official argument name (correcting possible case)
340 1230 : args.AddString(arg_map_it->first.c_str());
341 1230 : ++it;
342 : // Add its values
343 2389 : for (; it != next_it; ++it)
344 : {
345 1159 : args.AddString(it->c_str());
346 : }
347 1230 : it = next_it;
348 : }
349 : else
350 : {
351 0 : throw std::runtime_error("Unknown argument: " + current_argument);
352 : }
353 : }
354 :
355 1394 : return args;
356 : }
357 :
358 530 : Argument &GDALArgumentParser::add_inverted_logic_flag(const std::string &name,
359 : bool *store_into,
360 : const std::string &help)
361 : {
362 1060 : return add_argument(name)
363 530 : .default_value(true)
364 1060 : .implicit_value(false)
365 : .action(
366 22 : [store_into](const auto &)
367 : {
368 11 : if (store_into)
369 11 : *store_into = false;
370 530 : })
371 1060 : .help(help);
372 : }
373 :
374 : /************************************************************************/
375 : /* parse_args() */
376 : /************************************************************************/
377 :
378 3483 : void GDALArgumentParser::parse_args(const CPLStringList &aosArgs)
379 : {
380 6941 : std::vector<std::string> reorderedArgs;
381 6941 : std::vector<std::string> positionalArgs;
382 :
383 : // ArgumentParser::parse_args() expects the first argument to be the
384 : // binary name
385 3483 : if (!aosArgs.empty())
386 : {
387 3483 : reorderedArgs.push_back(aosArgs[0]);
388 : }
389 :
390 : // Simplified logic borrowed from ArgumentParser::parse_args_internal()
391 : // that make sure that positional arguments are moved after optional ones,
392 : // as this is what ArgumentParser::parse_args() only supports.
393 : // This doesn't support advanced settings, such as sub-parsers or compound
394 : // argument
395 : std::vector<std::string> raw_arguments{aosArgs.List(),
396 6941 : aosArgs.List() + aosArgs.size()};
397 6941 : auto arguments = preprocess_arguments(raw_arguments);
398 3483 : auto end = std::end(arguments);
399 3483 : auto positional_argument_it = std::begin(m_positional_arguments);
400 12346 : for (auto it = std::next(std::begin(arguments)); it != end;)
401 : {
402 8864 : const auto ¤t_argument = *it;
403 8864 : if (Argument::is_positional(current_argument, m_prefix_chars))
404 : {
405 1104 : if (positional_argument_it != std::end(m_positional_arguments))
406 : {
407 1103 : auto argument = positional_argument_it++;
408 : auto next_it =
409 1103 : argument->consume(it, end, "", /* dry_run = */ true);
410 2359 : for (; it != next_it; ++it)
411 : {
412 1265 : if (!Argument::is_positional(*it, m_prefix_chars))
413 : {
414 9 : next_it = it;
415 9 : break;
416 : }
417 1256 : positionalArgs.push_back(*it);
418 : }
419 1103 : it = next_it;
420 1103 : continue;
421 : }
422 : else
423 : {
424 1 : if (m_positional_arguments.empty())
425 : {
426 : throw std::runtime_error(
427 1 : "Zero positional arguments expected");
428 : }
429 : else
430 : {
431 : throw std::runtime_error(
432 : "Maximum number of positional arguments "
433 0 : "exceeded, failed to parse '" +
434 0 : current_argument + "'");
435 : }
436 : }
437 : }
438 :
439 7760 : auto arg_map_it = find_argument(current_argument);
440 7760 : if (arg_map_it != m_argument_map.end())
441 : {
442 7760 : auto argument = arg_map_it->second;
443 : auto next_it = argument->consume(
444 7760 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
445 : // Add official argument name (correcting possible case)
446 7760 : reorderedArgs.push_back(arg_map_it->first);
447 7760 : ++it;
448 : // Add its values
449 16706 : for (; it != next_it; ++it)
450 : {
451 8946 : reorderedArgs.push_back(*it);
452 : }
453 7760 : it = next_it;
454 : }
455 : else
456 : {
457 0 : throw std::runtime_error("Unknown argument: " + current_argument);
458 : }
459 : }
460 :
461 3482 : reorderedArgs.insert(reorderedArgs.end(), positionalArgs.begin(),
462 6964 : positionalArgs.end());
463 :
464 3482 : ArgumentParser::parse_args(reorderedArgs);
465 3428 : }
|