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 : * SPDX-License-Identifier: MIT
10 : ****************************************************************************/
11 :
12 : #include "gdal_version_full/gdal_version.h"
13 :
14 : #include "gdal.h"
15 : #include "gdalargumentparser.h"
16 : #include "commonutils.h"
17 :
18 : #include <algorithm>
19 :
20 : /************************************************************************/
21 : /* GDALArgumentParser() */
22 : /************************************************************************/
23 :
24 7086 : GDALArgumentParser::GDALArgumentParser(const std::string &program_name,
25 7086 : bool bForBinary)
26 7086 : : ArgumentParser(program_name, "", default_arguments::none)
27 : {
28 7086 : set_usage_max_line_width(80);
29 7086 : set_usage_break_on_mutex();
30 7086 : add_usage_newline();
31 :
32 7086 : if (bForBinary)
33 : {
34 1011 : add_argument("-h", "--help")
35 1011 : .flag()
36 : .action(
37 0 : [this](const auto &)
38 : {
39 0 : std::cout << usage() << std::endl << std::endl;
40 0 : std::cout << _("Note: ") << m_parser_path
41 0 : << _(" --long-usage for full help.") << std::endl;
42 0 : std::exit(0);
43 1011 : })
44 1011 : .help(_("Shows short help message and exits."));
45 :
46 : // Used by program-output directives in .rst files
47 1011 : add_argument("--help-doc")
48 1011 : .flag()
49 1011 : .hidden()
50 : .action(
51 0 : [this](const auto &)
52 : {
53 0 : std::cout << usage() << std::endl;
54 0 : std::exit(0);
55 1011 : })
56 1011 : .help(_("Display help message for use by documentation."));
57 :
58 1011 : add_argument("--long-usage")
59 1011 : .flag()
60 : .action(
61 0 : [this](const auto & /*unused*/)
62 : {
63 0 : std::cout << *this;
64 0 : std::exit(0);
65 1011 : })
66 1011 : .help(_("Shows long help message and exits."));
67 :
68 1011 : add_argument("--help-general")
69 1011 : .flag()
70 1011 : .help(_("Report detailed help on general options."));
71 :
72 1011 : add_argument("--utility_version")
73 1011 : .flag()
74 1011 : .hidden()
75 : .action(
76 0 : [this](const auto &)
77 : {
78 0 : printf("%s was compiled against GDAL %s and "
79 : "is running against GDAL %s\n",
80 : m_program_name.c_str(), GDAL_RELEASE_NAME,
81 : GDALVersionInfo("RELEASE_NAME"));
82 0 : std::exit(0);
83 1011 : })
84 1011 : .help(_("Shows compile-time and run-time GDAL version."));
85 :
86 1011 : add_usage_newline();
87 : }
88 7086 : }
89 :
90 : /************************************************************************/
91 : /* display_error_and_usage() */
92 : /************************************************************************/
93 :
94 5 : void GDALArgumentParser::display_error_and_usage(const std::exception &err)
95 : {
96 5 : std::cerr << _("Error: ") << err.what() << std::endl;
97 5 : std::cerr << usage() << std::endl << std::endl;
98 5 : std::cout << _("Note: ") << m_program_name
99 5 : << _(" --long-usage for full help.") << std::endl;
100 5 : }
101 :
102 : /************************************************************************/
103 : /* usage() */
104 : /************************************************************************/
105 :
106 19 : std::string GDALArgumentParser::usage() const
107 : {
108 19 : std::string ret(ArgumentParser::usage());
109 19 : if (!m_osExtraUsageHint.empty())
110 : {
111 2 : ret += '\n';
112 2 : ret += '\n';
113 2 : ret += m_osExtraUsageHint;
114 : }
115 19 : return ret;
116 : }
117 :
118 : /************************************************************************/
119 : /* add_extra_usage_hint() */
120 : /************************************************************************/
121 :
122 45 : void GDALArgumentParser::add_extra_usage_hint(
123 : const std::string &osExtraUsageHint)
124 : {
125 45 : m_osExtraUsageHint = osExtraUsageHint;
126 45 : }
127 :
128 : /************************************************************************/
129 : /* add_quiet_argument() */
130 : /************************************************************************/
131 :
132 5348 : Argument &GDALArgumentParser::add_quiet_argument(bool *pVar)
133 : {
134 : auto &arg =
135 5348 : this->add_argument("-q", "--quiet")
136 5348 : .flag()
137 : .help(
138 : _("Quiet mode. No progress message is emitted on the standard "
139 5348 : "output."));
140 5348 : if (pVar)
141 4042 : arg.store_into(*pVar);
142 :
143 5348 : return arg;
144 : }
145 :
146 : /************************************************************************/
147 : /* add_input_format_argument() */
148 : /************************************************************************/
149 :
150 1983 : Argument &GDALArgumentParser::add_input_format_argument(CPLStringList *pvar)
151 : {
152 1983 : return add_argument("-if")
153 1983 : .append()
154 3966 : .metavar("<format>")
155 : .action(
156 24 : [pvar](const std::string &s)
157 : {
158 12 : if (pvar)
159 : {
160 12 : if (GDALGetDriverByName(s.c_str()) == nullptr)
161 : {
162 4 : CPLError(CE_Warning, CPLE_AppDefined,
163 : "%s is not a recognized driver", s.c_str());
164 : }
165 12 : pvar->AddString(s.c_str());
166 : }
167 1983 : })
168 : .help(
169 3966 : _("Format/driver name(s) to be attempted to open the input file."));
170 : }
171 :
172 : /************************************************************************/
173 : /* add_output_format_argument() */
174 : /************************************************************************/
175 :
176 6270 : Argument &GDALArgumentParser::add_output_format_argument(std::string &var)
177 : {
178 6270 : auto &arg = add_argument("-of")
179 12540 : .metavar("<output_format>")
180 6270 : .store_into(var)
181 6270 : .help(_("Output format."));
182 6270 : add_hidden_alias_for(arg, "-f");
183 6270 : return arg;
184 : }
185 :
186 : /************************************************************************/
187 : /* add_creation_options_argument() */
188 : /************************************************************************/
189 :
190 4501 : Argument &GDALArgumentParser::add_creation_options_argument(CPLStringList &var)
191 : {
192 4501 : return add_argument("-co")
193 9002 : .metavar("<NAME>=<VALUE>")
194 4501 : .append()
195 5262 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
196 9002 : .help(_("Creation option(s)."));
197 : }
198 :
199 : /************************************************************************/
200 : /* add_metadata_item_options_argument() */
201 : /************************************************************************/
202 :
203 : Argument &
204 3600 : GDALArgumentParser::add_metadata_item_options_argument(CPLStringList &var)
205 : {
206 3600 : return add_argument("-mo")
207 7200 : .metavar("<NAME>=<VALUE>")
208 3600 : .append()
209 3633 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
210 7200 : .help(_("Metadata item option(s)."));
211 : }
212 :
213 : /************************************************************************/
214 : /* add_open_options_argument() */
215 : /************************************************************************/
216 :
217 56 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList &var)
218 : {
219 56 : return add_open_options_argument(&var);
220 : }
221 :
222 : /************************************************************************/
223 : /* add_open_options_argument() */
224 : /************************************************************************/
225 :
226 4904 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList *pvar)
227 : {
228 4904 : auto &arg = add_argument("-oo")
229 9808 : .metavar("<NAME>=<VALUE>")
230 4904 : .append()
231 4904 : .help(_("Open option(s) for input dataset."));
232 4904 : if (pvar)
233 : {
234 16 : arg.action([pvar](const std::string &s)
235 736 : { pvar->AddString(s.c_str()); });
236 : }
237 :
238 4904 : return arg;
239 : }
240 :
241 : /************************************************************************/
242 : /* add_output_type_argument() */
243 : /************************************************************************/
244 :
245 3913 : Argument &GDALArgumentParser::add_output_type_argument(GDALDataType &eDT)
246 : {
247 3913 : return add_argument("-ot")
248 7826 : .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
249 : .action(
250 1311 : [&eDT](const std::string &s)
251 : {
252 437 : eDT = GDALGetDataTypeByName(s.c_str());
253 437 : if (eDT == GDT_Unknown)
254 : {
255 : throw std::invalid_argument(
256 0 : std::string("Unknown output pixel type: ").append(s));
257 : }
258 4350 : })
259 7826 : .help(_("Output data type."));
260 : }
261 :
262 : Argument &
263 1044 : GDALArgumentParser::add_layer_creation_options_argument(CPLStringList &var)
264 : {
265 1044 : return add_argument("-lco")
266 2088 : .metavar("<NAME>=<VALUE>")
267 1044 : .append()
268 1340 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
269 2088 : .help(_("Layer creation options (format specific)."));
270 : }
271 :
272 : Argument &
273 1002 : GDALArgumentParser::add_dataset_creation_options_argument(CPLStringList &var)
274 : {
275 1002 : return add_argument("-dsco")
276 2004 : .metavar("<NAME>=<VALUE>")
277 1002 : .append()
278 1092 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
279 2004 : .help(_("Dataset creation options (format specific)."));
280 : }
281 :
282 : /************************************************************************/
283 : /* parse_args_without_binary_name() */
284 : /************************************************************************/
285 :
286 6008 : void GDALArgumentParser::parse_args_without_binary_name(CSLConstList papszArgs)
287 : {
288 12016 : CPLStringList aosArgs;
289 6008 : aosArgs.AddString(m_program_name.c_str());
290 37215 : for (CSLConstList papszIter = papszArgs; papszIter && *papszIter;
291 : ++papszIter)
292 31207 : aosArgs.AddString(*papszIter);
293 6008 : parse_args(aosArgs);
294 5751 : }
295 :
296 : /************************************************************************/
297 : /* find_argument() */
298 : /************************************************************************/
299 :
300 : std::map<std::string, ArgumentParser::argument_it>::iterator
301 14721 : GDALArgumentParser::find_argument(const std::string &name)
302 : {
303 14721 : auto arg_map_it = m_argument_map.find(name);
304 14721 : if (arg_map_it == m_argument_map.end())
305 : {
306 : // Attempt case insensitive lookup
307 : arg_map_it =
308 : std::find_if(m_argument_map.begin(), m_argument_map.end(),
309 2442 : [&name](const auto &oArg)
310 1422 : { return EQUAL(name.c_str(), oArg.first.c_str()); });
311 : }
312 14721 : return arg_map_it;
313 : }
314 :
315 : /************************************************************************/
316 : /* get_non_positional_arguments() */
317 : /************************************************************************/
318 :
319 : CPLStringList
320 885 : GDALArgumentParser::get_non_positional_arguments(const CPLStringList &aosArgs)
321 : {
322 885 : CPLStringList args;
323 :
324 : // Simplified logic borrowed from ArgumentParser::parse_args_internal()
325 : // that make sure that positional arguments are moved after optional ones,
326 : // as this is what ArgumentParser::parse_args() only supports.
327 : // This doesn't support advanced settings, such as sub-parsers or compound
328 : // argument
329 3540 : std::vector<std::string> raw_arguments{m_program_name};
330 885 : raw_arguments.insert(raw_arguments.end(), aosArgs.List(),
331 1770 : aosArgs.List() + aosArgs.size());
332 1770 : auto arguments = preprocess_arguments(raw_arguments);
333 885 : auto end = std::end(arguments);
334 885 : auto positional_argument_it = std::begin(m_positional_arguments);
335 2797 : for (auto it = std::next(std::begin(arguments)); it != end;)
336 : {
337 1913 : const auto ¤t_argument = *it;
338 1913 : if (Argument::is_positional(current_argument, m_prefix_chars))
339 : {
340 282 : if (positional_argument_it != std::end(m_positional_arguments))
341 : {
342 282 : auto argument = positional_argument_it++;
343 : auto next_it =
344 282 : argument->consume(it, end, "", /* dry_run = */ true);
345 282 : it = next_it;
346 282 : continue;
347 : }
348 : else
349 : {
350 0 : if (m_positional_arguments.empty())
351 : {
352 : throw std::runtime_error(
353 0 : "Zero positional arguments expected");
354 : }
355 : else
356 : {
357 : throw std::runtime_error(
358 : "Maximum number of positional arguments "
359 0 : "exceeded, failed to parse '" +
360 0 : current_argument + "'");
361 : }
362 : }
363 : }
364 :
365 1631 : auto arg_map_it = find_argument(current_argument);
366 1631 : if (arg_map_it != m_argument_map.end())
367 : {
368 1631 : auto argument = arg_map_it->second;
369 : auto next_it = argument->consume(
370 1631 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
371 : // Add official argument name (correcting possible case)
372 1630 : args.AddString(arg_map_it->first.c_str());
373 1630 : ++it;
374 : // Add its values
375 2957 : for (; it != next_it; ++it)
376 : {
377 1327 : args.AddString(it->c_str());
378 : }
379 1630 : it = next_it;
380 : }
381 : else
382 : {
383 0 : throw std::runtime_error("Unknown argument: " + current_argument);
384 : }
385 : }
386 :
387 1768 : return args;
388 : }
389 :
390 1262 : Argument &GDALArgumentParser::add_inverted_logic_flag(const std::string &name,
391 : bool *store_into,
392 : const std::string &help)
393 : {
394 2524 : return add_argument(name)
395 1262 : .default_value(true)
396 2524 : .implicit_value(false)
397 : .action(
398 42 : [store_into](const auto &)
399 : {
400 21 : if (store_into)
401 21 : *store_into = false;
402 1262 : })
403 2524 : .help(help);
404 : }
405 :
406 : GDALArgumentParser *
407 1183 : GDALArgumentParser::add_subparser(const std::string &description,
408 : bool bForBinary)
409 : {
410 2366 : auto parser = std::make_unique<GDALArgumentParser>(description, bForBinary);
411 1183 : ArgumentParser::add_subparser(*parser.get());
412 1183 : aoSubparsers.emplace_back(std::move(parser));
413 2366 : return aoSubparsers.back().get();
414 : }
415 :
416 261 : GDALArgumentParser *GDALArgumentParser::get_subparser(const std::string &name)
417 : {
418 : auto it = std::find_if(
419 : aoSubparsers.begin(), aoSubparsers.end(),
420 992 : [&name](const auto &parser)
421 757 : { return EQUAL(name.c_str(), parser->m_program_name.c_str()); });
422 261 : return it != aoSubparsers.end() ? it->get() : nullptr;
423 : }
424 :
425 36 : bool GDALArgumentParser::is_used_globally(const std::string &name)
426 : {
427 : try
428 : {
429 36 : return ArgumentParser::is_used(name);
430 : }
431 30 : catch (std::logic_error &)
432 : {
433 : // ignore
434 : }
435 :
436 : // Check if it is used by a subparser
437 : // loop through subparsers
438 72 : for (const auto &subparser : aoSubparsers)
439 : {
440 : // convert subparser name to lower case
441 42 : std::string subparser_name = subparser->m_program_name;
442 : std::transform(subparser_name.begin(), subparser_name.end(),
443 : subparser_name.begin(),
444 282 : [](int c) -> char
445 324 : { return static_cast<char>(::tolower(c)); });
446 42 : if (m_subparser_used.find(subparser_name) != m_subparser_used.end())
447 : {
448 30 : if (subparser->is_used_globally(name))
449 : {
450 0 : return true;
451 : }
452 : }
453 : }
454 :
455 30 : return false;
456 : }
457 :
458 : /************************************************************************/
459 : /* parse_args() */
460 : /************************************************************************/
461 :
462 6367 : void GDALArgumentParser::parse_args(const CPLStringList &aosArgs)
463 : {
464 6711 : std::vector<std::string> reorderedArgs;
465 6711 : std::vector<std::string> positionalArgs;
466 :
467 : // ArgumentParser::parse_args() expects the first argument to be the
468 : // binary name
469 6367 : if (!aosArgs.empty())
470 : {
471 6367 : reorderedArgs.push_back(aosArgs[0]);
472 : }
473 :
474 : // Simplified logic borrowed from ArgumentParser::parse_args_internal()
475 : // that make sure that positional arguments are moved after optional ones,
476 : // as this is what ArgumentParser::parse_args() only supports.
477 : // This doesn't support advanced settings, such as sub-parsers or compound
478 : // argument
479 : std::vector<std::string> raw_arguments{aosArgs.List(),
480 6711 : aosArgs.List() + aosArgs.size()};
481 6711 : auto arguments = preprocess_arguments(raw_arguments);
482 6367 : auto end = std::end(arguments);
483 6367 : auto positional_argument_it = std::begin(m_positional_arguments);
484 20595 : for (auto it = std::next(std::begin(arguments)); it != end;)
485 : {
486 14688 : const auto ¤t_argument = *it;
487 14688 : if (Argument::is_positional(current_argument, m_prefix_chars))
488 : {
489 1598 : if (positional_argument_it != std::end(m_positional_arguments))
490 : {
491 1337 : auto argument = positional_argument_it++;
492 : auto next_it =
493 1337 : argument->consume(it, end, "", /* dry_run = */ true);
494 2836 : for (; it != next_it; ++it)
495 : {
496 1508 : if (!Argument::is_positional(*it, m_prefix_chars))
497 : {
498 9 : next_it = it;
499 9 : break;
500 : }
501 1499 : positionalArgs.push_back(*it);
502 : }
503 1337 : it = next_it;
504 1337 : continue;
505 : }
506 : else
507 : {
508 : // Check sub-parsers
509 261 : auto subparser = get_subparser(current_argument);
510 261 : if (subparser)
511 : {
512 :
513 : // build list of remaining args
514 : const auto unprocessed_arguments =
515 777 : CPLStringList(std::vector<std::string>(it, end));
516 :
517 : // invoke subparser
518 259 : m_is_parsed = true;
519 : // convert to lower case
520 344 : std::string current_argument_lower = current_argument;
521 : std::transform(current_argument_lower.begin(),
522 : current_argument_lower.end(),
523 : current_argument_lower.begin(),
524 2151 : [](int c) -> char
525 2410 : { return static_cast<char>(::tolower(c)); });
526 259 : m_subparser_used[current_argument_lower] = true;
527 259 : return subparser->parse_args(unprocessed_arguments);
528 : }
529 :
530 2 : if (m_positional_arguments.empty())
531 : {
532 : throw std::runtime_error(
533 2 : "Zero positional arguments expected");
534 : }
535 : else
536 : {
537 : throw std::runtime_error(
538 : "Maximum number of positional arguments "
539 0 : "exceeded, failed to parse '" +
540 0 : current_argument + "'");
541 : }
542 : }
543 : }
544 :
545 13090 : auto arg_map_it = find_argument(current_argument);
546 13090 : if (arg_map_it != m_argument_map.end())
547 : {
548 12891 : auto argument = arg_map_it->second;
549 : auto next_it = argument->consume(
550 12891 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
551 : // Add official argument name (correcting possible case)
552 12891 : reorderedArgs.push_back(arg_map_it->first);
553 12891 : ++it;
554 : // Add its values
555 28962 : for (; it != next_it; ++it)
556 : {
557 16071 : reorderedArgs.push_back(*it);
558 : }
559 12891 : it = next_it;
560 : }
561 : else
562 : {
563 199 : throw std::runtime_error("Unknown argument: " + current_argument);
564 : }
565 : }
566 :
567 5907 : reorderedArgs.insert(reorderedArgs.end(), positionalArgs.begin(),
568 11814 : positionalArgs.end());
569 :
570 5907 : ArgumentParser::parse_args(reorderedArgs);
571 : }
|