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 8369 : GDALArgumentParser::GDALArgumentParser(const std::string &program_name,
25 8369 : bool bForBinary)
26 8369 : : ArgumentParser(program_name, "", default_arguments::none)
27 : {
28 8369 : set_usage_max_line_width(80);
29 8369 : set_usage_break_on_mutex();
30 8369 : add_usage_newline();
31 :
32 8369 : 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 8369 : }
89 :
90 : /************************************************************************/
91 : /* display_error_and_usage() */
92 : /************************************************************************/
93 :
94 3 : void GDALArgumentParser::display_error_and_usage(const std::exception &err)
95 : {
96 3 : std::cerr << _("Error: ") << err.what() << std::endl;
97 3 : std::cerr << usage() << std::endl << std::endl;
98 3 : std::cout << _("Note: ") << m_program_name
99 3 : << _(" --long-usage for full help.") << std::endl;
100 3 : }
101 :
102 : /************************************************************************/
103 : /* usage() */
104 : /************************************************************************/
105 :
106 17 : std::string GDALArgumentParser::usage() const
107 : {
108 17 : std::string ret(ArgumentParser::usage());
109 17 : if (!m_osExtraUsageHint.empty())
110 : {
111 2 : ret += '\n';
112 2 : ret += '\n';
113 2 : ret += m_osExtraUsageHint;
114 : }
115 17 : return ret;
116 : }
117 :
118 : /************************************************************************/
119 : /* add_extra_usage_hint() */
120 : /************************************************************************/
121 :
122 51 : void GDALArgumentParser::add_extra_usage_hint(
123 : const std::string &osExtraUsageHint)
124 : {
125 51 : m_osExtraUsageHint = osExtraUsageHint;
126 51 : }
127 :
128 : /************************************************************************/
129 : /* add_quiet_argument() */
130 : /************************************************************************/
131 :
132 6295 : Argument &GDALArgumentParser::add_quiet_argument(bool *pVar)
133 : {
134 : auto &arg =
135 6295 : this->add_argument("-q", "--quiet")
136 6295 : .flag()
137 : .help(
138 : _("Quiet mode. No progress message is emitted on the standard "
139 6295 : "output."));
140 6295 : if (pVar)
141 4733 : arg.store_into(*pVar);
142 :
143 6295 : return arg;
144 : }
145 :
146 : /************************************************************************/
147 : /* add_input_format_argument() */
148 : /************************************************************************/
149 :
150 2348 : Argument &GDALArgumentParser::add_input_format_argument(CPLStringList *pvar)
151 : {
152 2348 : return add_argument("-if")
153 2348 : .append()
154 4696 : .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 2348 : })
168 : .help(
169 4696 : _("Format/driver name(s) to be attempted to open the input file."));
170 : }
171 :
172 : /************************************************************************/
173 : /* add_output_format_argument() */
174 : /************************************************************************/
175 :
176 7420 : Argument &GDALArgumentParser::add_output_format_argument(std::string &var)
177 : {
178 7420 : auto &arg = add_argument("-of")
179 14840 : .metavar("<output_format>")
180 7420 : .store_into(var)
181 7420 : .help(_("Output format."));
182 7420 : add_hidden_alias_for(arg, "-f");
183 7420 : return arg;
184 : }
185 :
186 : /************************************************************************/
187 : /* add_creation_options_argument() */
188 : /************************************************************************/
189 :
190 5343 : Argument &GDALArgumentParser::add_creation_options_argument(CPLStringList &var)
191 : {
192 5343 : return add_argument("-co")
193 10686 : .metavar("<NAME>=<VALUE>")
194 5343 : .append()
195 6690 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
196 10686 : .help(_("Creation option(s)."));
197 : }
198 :
199 : /************************************************************************/
200 : /* add_metadata_item_options_argument() */
201 : /************************************************************************/
202 :
203 : Argument &
204 4309 : GDALArgumentParser::add_metadata_item_options_argument(CPLStringList &var)
205 : {
206 4309 : return add_argument("-mo")
207 8618 : .metavar("<NAME>=<VALUE>")
208 4309 : .append()
209 4343 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
210 8618 : .help(_("Metadata item option(s)."));
211 : }
212 :
213 : /************************************************************************/
214 : /* add_open_options_argument() */
215 : /************************************************************************/
216 :
217 59 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList &var)
218 : {
219 59 : return add_open_options_argument(&var);
220 : }
221 :
222 : /************************************************************************/
223 : /* add_open_options_argument() */
224 : /************************************************************************/
225 :
226 5857 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList *pvar)
227 : {
228 5857 : auto &arg = add_argument("-oo")
229 11714 : .metavar("<NAME>=<VALUE>")
230 5857 : .append()
231 5857 : .help(_("Open option(s) for input dataset."));
232 5857 : if (pvar)
233 : {
234 14 : arg.action([pvar](const std::string &s)
235 798 : { pvar->AddString(s.c_str()); });
236 : }
237 :
238 5857 : return arg;
239 : }
240 :
241 : /************************************************************************/
242 : /* add_output_type_argument() */
243 : /************************************************************************/
244 :
245 4681 : Argument &GDALArgumentParser::add_output_type_argument(GDALDataType &eDT)
246 : {
247 4681 : return add_argument("-ot")
248 9362 : .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
249 : .action(
250 1611 : [&eDT](const std::string &s)
251 : {
252 537 : eDT = GDALGetDataTypeByName(s.c_str());
253 537 : if (eDT == GDT_Unknown)
254 : {
255 : throw std::invalid_argument(
256 0 : std::string("Unknown output pixel type: ").append(s));
257 : }
258 5218 : })
259 9362 : .help(_("Output data type."));
260 : }
261 :
262 : Argument &
263 1208 : GDALArgumentParser::add_layer_creation_options_argument(CPLStringList &var)
264 : {
265 1208 : return add_argument("-lco")
266 2416 : .metavar("<NAME>=<VALUE>")
267 1208 : .append()
268 1520 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
269 2416 : .help(_("Layer creation options (format specific)."));
270 : }
271 :
272 : Argument &
273 1156 : GDALArgumentParser::add_dataset_creation_options_argument(CPLStringList &var)
274 : {
275 1156 : return add_argument("-dsco")
276 2312 : .metavar("<NAME>=<VALUE>")
277 1156 : .append()
278 1256 : .action([&var](const std::string &s) { var.AddString(s.c_str()); })
279 2312 : .help(_("Dataset creation options (format specific)."));
280 : }
281 :
282 : /************************************************************************/
283 : /* parse_args_without_binary_name() */
284 : /************************************************************************/
285 :
286 7106 : void GDALArgumentParser::parse_args_without_binary_name(CSLConstList papszArgs)
287 : {
288 14212 : CPLStringList aosArgs;
289 7106 : aosArgs.AddString(m_program_name.c_str());
290 44989 : for (CSLConstList papszIter = papszArgs; papszIter && *papszIter;
291 : ++papszIter)
292 37883 : aosArgs.AddString(*papszIter);
293 7106 : parse_args(aosArgs);
294 6815 : }
295 :
296 : /************************************************************************/
297 : /* find_argument() */
298 : /************************************************************************/
299 :
300 : std::map<std::string, ArgumentParser::argument_it>::iterator
301 18120 : GDALArgumentParser::find_argument(const std::string &name)
302 : {
303 18120 : auto arg_map_it = m_argument_map.find(name);
304 18120 : 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 1455 : { return EQUAL(name.c_str(), oArg.first.c_str()); });
311 : }
312 18120 : return arg_map_it;
313 : }
314 :
315 : /************************************************************************/
316 : /* get_non_positional_arguments() */
317 : /************************************************************************/
318 :
319 : CPLStringList
320 1035 : GDALArgumentParser::get_non_positional_arguments(const CPLStringList &aosArgs)
321 : {
322 1035 : 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 4140 : std::vector<std::string> raw_arguments{m_program_name};
330 1035 : raw_arguments.insert(raw_arguments.end(), aosArgs.List(),
331 2070 : aosArgs.List() + aosArgs.size());
332 2070 : auto arguments = preprocess_arguments(raw_arguments);
333 1035 : auto end = std::end(arguments);
334 1035 : auto positional_argument_it = std::begin(m_positional_arguments);
335 3301 : for (auto it = std::next(std::begin(arguments)); it != end;)
336 : {
337 2267 : const auto ¤t_argument = *it;
338 2267 : if (Argument::is_positional(current_argument, m_prefix_chars))
339 : {
340 283 : if (positional_argument_it != std::end(m_positional_arguments))
341 : {
342 283 : auto argument = positional_argument_it++;
343 : auto next_it =
344 283 : argument->consume(it, end, "", /* dry_run = */ true);
345 283 : it = next_it;
346 283 : 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 1984 : auto arg_map_it = find_argument(current_argument);
366 1984 : if (arg_map_it != m_argument_map.end())
367 : {
368 1984 : auto argument = arg_map_it->second;
369 : auto next_it = argument->consume(
370 1984 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
371 : // Add official argument name (correcting possible case)
372 1983 : args.AddString(arg_map_it->first.c_str());
373 1983 : ++it;
374 : // Add its values
375 3437 : for (; it != next_it; ++it)
376 : {
377 1454 : args.AddString(it->c_str());
378 : }
379 1983 : it = next_it;
380 : }
381 : else
382 : {
383 0 : throw std::runtime_error("Unknown argument: " + current_argument);
384 : }
385 : }
386 :
387 2068 : return args;
388 : }
389 :
390 1508 : Argument &GDALArgumentParser::add_inverted_logic_flag(const std::string &name,
391 : bool *store_into,
392 : const std::string &help)
393 : {
394 3016 : return add_argument(name)
395 1508 : .default_value(true)
396 3016 : .implicit_value(false)
397 : .action(
398 42 : [store_into](const auto &)
399 : {
400 21 : if (store_into)
401 21 : *store_into = false;
402 1508 : })
403 3016 : .help(help);
404 : }
405 :
406 : GDALArgumentParser *
407 1414 : GDALArgumentParser::add_subparser(const std::string &description,
408 : bool bForBinary)
409 : {
410 2828 : auto parser = std::make_unique<GDALArgumentParser>(description, bForBinary);
411 1414 : ArgumentParser::add_subparser(*parser.get());
412 1414 : aoSubparsers.emplace_back(std::move(parser));
413 2828 : return aoSubparsers.back().get();
414 : }
415 :
416 294 : GDALArgumentParser *GDALArgumentParser::get_subparser(const std::string &name)
417 : {
418 : auto it = std::find_if(
419 1058 : aoSubparsers.begin(), aoSubparsers.end(), [&name](const auto &parser)
420 823 : { return EQUAL(name.c_str(), parser->m_program_name.c_str()); });
421 294 : return it != aoSubparsers.end() ? it->get() : nullptr;
422 : }
423 :
424 36 : bool GDALArgumentParser::is_used_globally(const std::string &name)
425 : {
426 : try
427 : {
428 36 : return ArgumentParser::is_used(name);
429 : }
430 30 : catch (std::logic_error &)
431 : {
432 : // ignore
433 : }
434 :
435 : // Check if it is used by a subparser
436 : // loop through subparsers
437 72 : for (const auto &subparser : aoSubparsers)
438 : {
439 : // convert subparser name to lower case
440 42 : std::string subparser_name = subparser->m_program_name;
441 : std::transform(subparser_name.begin(), subparser_name.end(),
442 282 : subparser_name.begin(), [](int c) -> char
443 324 : { return static_cast<char>(::tolower(c)); });
444 42 : if (m_subparser_used.find(subparser_name) != m_subparser_used.end())
445 : {
446 30 : if (subparser->is_used_globally(name))
447 : {
448 0 : return true;
449 : }
450 : }
451 : }
452 :
453 30 : return false;
454 : }
455 :
456 : /************************************************************************/
457 : /* parse_args() */
458 : /************************************************************************/
459 :
460 7485 : void GDALArgumentParser::parse_args(const CPLStringList &aosArgs)
461 : {
462 7861 : std::vector<std::string> reorderedArgs;
463 7861 : std::vector<std::string> positionalArgs;
464 :
465 : // ArgumentParser::parse_args() expects the first argument to be the
466 : // binary name
467 7485 : if (!aosArgs.empty())
468 : {
469 7485 : reorderedArgs.push_back(aosArgs[0]);
470 : }
471 :
472 : // Simplified logic borrowed from ArgumentParser::parse_args_internal()
473 : // that make sure that positional arguments are moved after optional ones,
474 : // as this is what ArgumentParser::parse_args() only supports.
475 : // This doesn't support advanced settings, such as sub-parsers or compound
476 : // argument
477 : std::vector<std::string> raw_arguments{aosArgs.List(),
478 7861 : aosArgs.List() + aosArgs.size()};
479 7861 : auto arguments = preprocess_arguments(raw_arguments);
480 7485 : auto end = std::end(arguments);
481 7485 : auto positional_argument_it = std::begin(m_positional_arguments);
482 24742 : for (auto it = std::next(std::begin(arguments)); it != end;)
483 : {
484 17783 : const auto ¤t_argument = *it;
485 17783 : if (Argument::is_positional(current_argument, m_prefix_chars))
486 : {
487 1647 : if (positional_argument_it != std::end(m_positional_arguments))
488 : {
489 1353 : auto argument = positional_argument_it++;
490 : auto next_it =
491 1353 : argument->consume(it, end, "", /* dry_run = */ true);
492 2868 : for (; it != next_it; ++it)
493 : {
494 1524 : if (!Argument::is_positional(*it, m_prefix_chars))
495 : {
496 9 : next_it = it;
497 9 : break;
498 : }
499 1515 : positionalArgs.push_back(*it);
500 : }
501 1353 : it = next_it;
502 1353 : continue;
503 : }
504 : else
505 : {
506 : // Check sub-parsers
507 294 : auto subparser = get_subparser(current_argument);
508 294 : if (subparser)
509 : {
510 :
511 : // build list of remaining args
512 : const auto unprocessed_arguments =
513 876 : CPLStringList(std::vector<std::string>(it, end));
514 :
515 : // invoke subparser
516 292 : m_is_parsed = true;
517 : // convert to lower case
518 377 : std::string current_argument_lower = current_argument;
519 : std::transform(current_argument_lower.begin(),
520 : current_argument_lower.end(),
521 : current_argument_lower.begin(),
522 2448 : [](int c) -> char
523 2740 : { return static_cast<char>(::tolower(c)); });
524 292 : m_subparser_used[current_argument_lower] = true;
525 292 : return subparser->parse_args(unprocessed_arguments);
526 : }
527 :
528 2 : if (m_positional_arguments.empty())
529 : {
530 : throw std::runtime_error(
531 2 : "Zero positional arguments expected");
532 : }
533 : else
534 : {
535 : throw std::runtime_error(
536 : "Maximum number of positional arguments "
537 0 : "exceeded, failed to parse '" +
538 0 : current_argument + "'");
539 : }
540 : }
541 : }
542 :
543 16136 : auto arg_map_it = find_argument(current_argument);
544 16136 : if (arg_map_it != m_argument_map.end())
545 : {
546 15904 : auto argument = arg_map_it->second;
547 : auto next_it = argument->consume(
548 15904 : std::next(it), end, arg_map_it->first, /* dry_run = */ true);
549 : // Add official argument name (correcting possible case)
550 15904 : reorderedArgs.push_back(arg_map_it->first);
551 15904 : ++it;
552 : // Add its values
553 35183 : for (; it != next_it; ++it)
554 : {
555 19279 : reorderedArgs.push_back(*it);
556 : }
557 15904 : it = next_it;
558 : }
559 : else
560 : {
561 232 : throw std::runtime_error("Unknown argument: " + current_argument);
562 : }
563 : }
564 :
565 6959 : reorderedArgs.insert(reorderedArgs.end(), positionalArgs.begin(),
566 13918 : positionalArgs.end());
567 :
568 6959 : ArgumentParser::parse_args(reorderedArgs);
569 : }
|