Coverage Report

Created: 2025-06-13 06:29

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