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 5877 : GDALArgumentParser::GDALArgumentParser(const std::string &program_name,
25 5877 : bool bForBinary)
26 5877 : : ArgumentParser(program_name, "", default_arguments::none)
27 : {
28 5877 : set_usage_max_line_width(80);
29 5877 : set_usage_break_on_mutex();
30 5877 : add_usage_newline();
31 :
32 5877 : 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 5877 : }
89 :
90 : /************************************************************************/
91 : /* display_error_and_usage() */
92 : /************************************************************************/
93 :
94 7 : void GDALArgumentParser::display_error_and_usage(const std::exception &err)
95 : {
96 7 : std::cerr << _("Error: ") << err.what() << std::endl;
97 7 : std::cerr << usage() << std::endl << std::endl;
98 7 : std::cout << _("Note: ") << m_program_name
99 7 : << _(" --long-usage for full help.") << std::endl;
100 7 : }
101 :
102 : /************************************************************************/
103 : /* usage() */
104 : /************************************************************************/
105 :
106 21 : std::string GDALArgumentParser::usage() const
107 : {
108 21 : std::string ret(ArgumentParser::usage());
109 21 : if (!m_osExtraUsageHint.empty())
110 : {
111 2 : ret += '\n';
112 2 : ret += '\n';
113 2 : ret += m_osExtraUsageHint;
114 : }
115 21 : return ret;
116 : }
117 :
118 : /************************************************************************/
119 : /* add_extra_usage_hint() */
120 : /************************************************************************/
121 :
122 44 : void GDALArgumentParser::add_extra_usage_hint(
123 : const std::string &osExtraUsageHint)
124 : {
125 44 : m_osExtraUsageHint = osExtraUsageHint;
126 44 : }
127 :
128 : /************************************************************************/
129 : /* add_quiet_argument() */
130 : /************************************************************************/
131 :
132 4870 : Argument &GDALArgumentParser::add_quiet_argument(bool *pVar)
133 : {
134 : auto &arg =
135 4870 : this->add_argument("-q", "--quiet")
136 4870 : .flag()
137 : .help(
138 : _("Quiet mode. No progress message is emitted on the standard "
139 4870 : "output."));
140 4870 : if (pVar)
141 1460 : arg.store_into(*pVar);
142 :
143 4870 : return arg;
144 : }
145 :
146 : /************************************************************************/
147 : /* add_input_format_argument() */
148 : /************************************************************************/
149 :
150 1909 : Argument &GDALArgumentParser::add_input_format_argument(CPLStringList *pvar)
151 : {
152 1909 : return add_argument("-if")
153 1909 : .append()
154 3818 : .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 1909 : })
168 : .help(
169 3818 : _("Format/driver name(s) to be attempted to open the input file."));
170 : }
171 :
172 : /************************************************************************/
173 : /* add_output_format_argument() */
174 : /************************************************************************/
175 :
176 5162 : Argument &GDALArgumentParser::add_output_format_argument(std::string &var)
177 : {
178 5162 : auto &arg = add_argument("-of")
179 10324 : .metavar("<output_format>")
180 5162 : .store_into(var)
181 5162 : .help(_("Output format."));
182 5162 : add_hidden_alias_for(arg, "-f");
183 5162 : return arg;
184 : }
185 :
186 : /************************************************************************/
187 : /* add_creation_options_argument() */
188 : /************************************************************************/
189 :
190 3473 : Argument &GDALArgumentParser::add_creation_options_argument(CPLStringList &var)
191 : {
192 3473 : return add_argument("-co")
193 6946 : .metavar("<NAME>=<VALUE>")
194 3473 : .append()
195 4170 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
196 6946 : .help(_("Creation option(s)."));
197 : }
198 :
199 : /************************************************************************/
200 : /* add_metadata_item_options_argument() */
201 : /************************************************************************/
202 :
203 : Argument &
204 3259 : GDALArgumentParser::add_metadata_item_options_argument(CPLStringList &var)
205 : {
206 3259 : return add_argument("-mo")
207 6518 : .metavar("<NAME>=<VALUE>")
208 3259 : .append()
209 3292 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
210 6518 : .help(_("Metadata item option(s)."));
211 : }
212 :
213 : /************************************************************************/
214 : /* add_open_options_argument() */
215 : /************************************************************************/
216 :
217 55 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList &var)
218 : {
219 55 : return add_open_options_argument(&var);
220 : }
221 :
222 : /************************************************************************/
223 : /* add_open_options_argument() */
224 : /************************************************************************/
225 :
226 4520 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList *pvar)
227 : {
228 4520 : auto &arg = add_argument("-oo")
229 9040 : .metavar("<NAME>=<VALUE>")
230 4520 : .append()
231 4520 : .help(_("Open option(s) for input dataset."));
232 4520 : if (pvar)
233 : {
234 16 : arg.action([pvar](const std::string &s)
235 743 : { pvar->AddString(s.c_str()); });
236 : }
237 :
238 4520 : return arg;
239 : }
240 :
241 : /************************************************************************/
242 : /* add_output_type_argument() */
243 : /************************************************************************/
244 :
245 3486 : Argument &GDALArgumentParser::add_output_type_argument(GDALDataType &eDT)
246 : {
247 3486 : return add_argument("-ot")
248 6972 : .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
249 : .action(
250 1041 : [&eDT](const std::string &s)
251 : {
252 347 : eDT = GDALGetDataTypeByName(s.c_str());
253 347 : if (eDT == GDT_Unknown)
254 : {
255 : throw std::invalid_argument(
256 0 : std::string("Unknown output pixel type: ").append(s));
257 : }
258 3833 : })
259 6972 : .help(_("Output data type."));
260 : }
261 :
262 : Argument &
263 972 : GDALArgumentParser::add_layer_creation_options_argument(CPLStringList &var)
264 : {
265 972 : return add_argument("-lco")
266 1944 : .metavar("<NAME>=<VALUE>")
267 972 : .append()
268 1264 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
269 1944 : .help(_("Layer creation options (format specific)."));
270 : }
271 :
272 : Argument &
273 943 : GDALArgumentParser::add_dataset_creation_options_argument(CPLStringList &var)
274 : {
275 943 : return add_argument("-dsco")
276 1886 : .metavar("<NAME>=<VALUE>")
277 943 : .append()
278 1030 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
279 1886 : .help(_("Dataset creation options (format specific)."));
280 : }
281 :
282 : /************************************************************************/
283 : /* parse_args_without_binary_name() */
284 : /************************************************************************/
285 :
286 5267 : void GDALArgumentParser::parse_args_without_binary_name(CSLConstList papszArgs)
287 : {
288 10534 : CPLStringList aosArgs;
289 5267 : aosArgs.AddString(m_program_name.c_str());
290 31563 : for (CSLConstList papszIter = papszArgs; papszIter && *papszIter;
291 : ++papszIter)
292 26296 : aosArgs.AddString(*papszIter);
293 5267 : parse_args(aosArgs);
294 5141 : }
295 :
296 : /************************************************************************/
297 : /* find_argument() */
298 : /************************************************************************/
299 :
300 : std::map<std::string, ArgumentParser::argument_it>::iterator
301 12462 : GDALArgumentParser::find_argument(const std::string &name)
302 : {
303 12462 : auto arg_map_it = m_argument_map.find(name);
304 12462 : 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 1374 : [&name](const auto &oArg)
310 777 : { return EQUAL(name.c_str(), oArg.first.c_str()); });
311 : }
312 12462 : return arg_map_it;
313 : }
314 :
315 : /************************************************************************/
316 : /* get_non_positional_arguments() */
317 : /************************************************************************/
318 :
319 : CPLStringList
320 857 : GDALArgumentParser::get_non_positional_arguments(const CPLStringList &aosArgs)
321 : {
322 857 : 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 3428 : std::vector<std::string> raw_arguments{m_program_name};
330 857 : raw_arguments.insert(raw_arguments.end(), aosArgs.List(),
331 1714 : aosArgs.List() + aosArgs.size());
332 1714 : auto arguments = preprocess_arguments(raw_arguments);
333 857 : auto end = std::end(arguments);
334 857 : auto positional_argument_it = std::begin(m_positional_arguments);
335 2686 : for (auto it = std::next(std::begin(arguments)); it != end;)
336 : {
337 1830 : const auto ¤t_argument = *it;
338 1830 : 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 1548 : auto arg_map_it = find_argument(current_argument);
366 1548 : if (arg_map_it != m_argument_map.end())
367 : {
368 1548 : auto argument = arg_map_it->second;
369 : auto next_it = argument->consume(
370 1548 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
371 : // Add official argument name (correcting possible case)
372 1547 : args.AddString(arg_map_it->first.c_str());
373 1547 : ++it;
374 : // Add its values
375 2841 : for (; it != next_it; ++it)
376 : {
377 1294 : args.AddString(it->c_str());
378 : }
379 1547 : it = next_it;
380 : }
381 : else
382 : {
383 0 : throw std::runtime_error("Unknown argument: " + current_argument);
384 : }
385 : }
386 :
387 1712 : return args;
388 : }
389 :
390 1038 : Argument &GDALArgumentParser::add_inverted_logic_flag(const std::string &name,
391 : bool *store_into,
392 : const std::string &help)
393 : {
394 2076 : return add_argument(name)
395 1038 : .default_value(true)
396 2076 : .implicit_value(false)
397 : .action(
398 38 : [store_into](const auto &)
399 : {
400 19 : if (store_into)
401 19 : *store_into = false;
402 1038 : })
403 2076 : .help(help);
404 : }
405 :
406 : GDALArgumentParser *
407 595 : GDALArgumentParser::add_subparser(const std::string &description,
408 : bool bForBinary)
409 : {
410 1190 : auto parser = std::make_unique<GDALArgumentParser>(description, bForBinary);
411 595 : ArgumentParser::add_subparser(*parser.get());
412 595 : aoSubparsers.emplace_back(std::move(parser));
413 1190 : return aoSubparsers.back().get();
414 : }
415 :
416 130 : GDALArgumentParser *GDALArgumentParser::get_subparser(const std::string &name)
417 : {
418 : auto it = std::find_if(
419 : aoSubparsers.begin(), aoSubparsers.end(),
420 524 : [&name](const auto &parser)
421 392 : { return EQUAL(name.c_str(), parser->m_program_name.c_str()); });
422 130 : return it != aoSubparsers.end() ? it->get() : nullptr;
423 : }
424 :
425 24 : bool GDALArgumentParser::is_used_globally(const std::string &name)
426 : {
427 : try
428 : {
429 24 : return ArgumentParser::is_used(name);
430 : }
431 20 : catch (std::logic_error &)
432 : {
433 : // ignore
434 : }
435 :
436 : // Check if it is used by a subparser
437 : // loop through subparsers
438 48 : for (const auto &subparser : aoSubparsers)
439 : {
440 : // convert subparser name to lower case
441 28 : std::string subparser_name = subparser->m_program_name;
442 : std::transform(subparser_name.begin(), subparser_name.end(),
443 : subparser_name.begin(),
444 188 : [](int c) -> char
445 216 : { return static_cast<char>(::tolower(c)); });
446 28 : if (m_subparser_used.find(subparser_name) != m_subparser_used.end())
447 : {
448 20 : if (subparser->is_used_globally(name))
449 : {
450 0 : return true;
451 : }
452 : }
453 : }
454 :
455 20 : return false;
456 : }
457 :
458 : /************************************************************************/
459 : /* parse_args() */
460 : /************************************************************************/
461 :
462 5484 : void GDALArgumentParser::parse_args(const CPLStringList &aosArgs)
463 : {
464 5652 : std::vector<std::string> reorderedArgs;
465 5652 : std::vector<std::string> positionalArgs;
466 :
467 : // ArgumentParser::parse_args() expects the first argument to be the
468 : // binary name
469 5484 : if (!aosArgs.empty())
470 : {
471 5484 : 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 5652 : aosArgs.List() + aosArgs.size()};
481 5652 : auto arguments = preprocess_arguments(raw_arguments);
482 5484 : auto end = std::end(arguments);
483 5484 : auto positional_argument_it = std::begin(m_positional_arguments);
484 17634 : for (auto it = std::next(std::begin(arguments)); it != end;)
485 : {
486 12368 : const auto ¤t_argument = *it;
487 12368 : if (Argument::is_positional(current_argument, m_prefix_chars))
488 : {
489 1454 : if (positional_argument_it != std::end(m_positional_arguments))
490 : {
491 1324 : auto argument = positional_argument_it++;
492 : auto next_it =
493 1324 : argument->consume(it, end, "", /* dry_run = */ true);
494 2811 : for (; it != next_it; ++it)
495 : {
496 1496 : if (!Argument::is_positional(*it, m_prefix_chars))
497 : {
498 9 : next_it = it;
499 9 : break;
500 : }
501 1487 : positionalArgs.push_back(*it);
502 : }
503 1324 : it = next_it;
504 1324 : continue;
505 : }
506 : else
507 : {
508 : // Check sub-parsers
509 130 : auto subparser = get_subparser(current_argument);
510 130 : if (subparser)
511 : {
512 :
513 : // build list of remaining args
514 : const auto unprocessed_arguments =
515 384 : CPLStringList(std::vector<std::string>(it, end));
516 :
517 : // invoke subparser
518 128 : m_is_parsed = true;
519 : // convert to lower case
520 166 : 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 1074 : [](int c) -> char
525 1202 : { return static_cast<char>(::tolower(c)); });
526 128 : m_subparser_used[current_argument_lower] = true;
527 128 : 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 10914 : auto arg_map_it = find_argument(current_argument);
546 10914 : if (arg_map_it != m_argument_map.end())
547 : {
548 10826 : auto argument = arg_map_it->second;
549 : auto next_it = argument->consume(
550 10826 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
551 : // Add official argument name (correcting possible case)
552 10826 : reorderedArgs.push_back(arg_map_it->first);
553 10826 : ++it;
554 : // Add its values
555 24841 : for (; it != next_it; ++it)
556 : {
557 14015 : reorderedArgs.push_back(*it);
558 : }
559 10826 : it = next_it;
560 : }
561 : else
562 : {
563 88 : throw std::runtime_error("Unknown argument: " + current_argument);
564 : }
565 : }
566 :
567 5266 : reorderedArgs.insert(reorderedArgs.end(), positionalArgs.begin(),
568 10532 : positionalArgs.end());
569 :
570 5266 : ArgumentParser::parse_args(reorderedArgs);
571 : }
|