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