Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalargumentparser.cpp
Line
Count
Source
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
9.02k
    : ArgumentParser(program_name, "", default_arguments::none)
27
9.02k
{
28
9.02k
    set_usage_max_line_width(80);
29
9.02k
    set_usage_break_on_mutex();
30
9.02k
    add_usage_newline();
31
32
9.02k
    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
9.02k
}
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
9.02k
{
134
9.02k
    auto &arg =
135
9.02k
        this->add_argument("-q", "--quiet")
136
9.02k
            .flag()
137
9.02k
            .help(
138
9.02k
                _("Quiet mode. No progress message is emitted on the standard "
139
9.02k
                  "output."));
140
9.02k
    if (pVar)
141
9.02k
        arg.store_into(*pVar);
142
143
9.02k
    return arg;
144
9.02k
}
145
146
/************************************************************************/
147
/*                     add_input_format_argument()                      */
148
/************************************************************************/
149
150
Argument &GDALArgumentParser::add_input_format_argument(CPLStringList *pvar)
151
7.70k
{
152
7.70k
    return add_argument("-if")
153
7.70k
        .append()
154
7.70k
        .metavar("<format>")
155
7.70k
        .action(
156
7.70k
            [pvar](const std::string &s)
157
7.70k
            {
158
4
                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
4
            })
168
7.70k
        .help(
169
7.70k
            _("Format/driver name(s) to be attempted to open the input file."));
170
7.70k
}
171
172
/************************************************************************/
173
/*                     add_output_format_argument()                     */
174
/************************************************************************/
175
176
Argument &GDALArgumentParser::add_output_format_argument(std::string &var)
177
9.02k
{
178
9.02k
    auto &arg = add_argument("-of")
179
9.02k
                    .metavar("<output_format>")
180
9.02k
                    .store_into(var)
181
9.02k
                    .help(_("Output format."));
182
9.02k
    add_hidden_alias_for(arg, "-f");
183
9.02k
    return arg;
184
9.02k
}
185
186
/************************************************************************/
187
/*                   add_creation_options_argument()                    */
188
/************************************************************************/
189
190
Argument &GDALArgumentParser::add_creation_options_argument(CPLStringList &var)
191
1.32k
{
192
1.32k
    return add_argument("-co")
193
1.32k
        .metavar("<NAME>=<VALUE>")
194
1.32k
        .append()
195
33.5k
        .action([&var](const std::string &s) { var.AddString(s.c_str()); })
196
1.32k
        .help(_("Creation option(s)."));
197
1.32k
}
198
199
/************************************************************************/
200
/*                 add_metadata_item_options_argument()                 */
201
/************************************************************************/
202
203
Argument &
204
GDALArgumentParser::add_metadata_item_options_argument(CPLStringList &var)
205
9.02k
{
206
9.02k
    return add_argument("-mo")
207
9.02k
        .metavar("<NAME>=<VALUE>")
208
9.02k
        .append()
209
47.9k
        .action([&var](const std::string &s) { var.AddString(s.c_str()); })
210
9.02k
        .help(_("Metadata item option(s)."));
211
9.02k
}
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
9.02k
{
228
9.02k
    auto &arg = add_argument("-oo")
229
9.02k
                    .metavar("<NAME>=<VALUE>")
230
9.02k
                    .append()
231
9.02k
                    .help(_("Open option(s) for input dataset."));
232
9.02k
    if (pvar)
233
0
    {
234
0
        arg.action([pvar](const std::string &s)
235
0
                   { pvar->AddString(s.c_str()); });
236
0
    }
237
238
9.02k
    return arg;
239
9.02k
}
240
241
/************************************************************************/
242
/*                      add_output_type_argument()                      */
243
/************************************************************************/
244
245
Argument &GDALArgumentParser::add_output_type_argument(GDALDataType &eDT)
246
1.32k
{
247
1.32k
    return add_argument("-ot")
248
1.32k
        .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
249
1.32k
        .action(
250
1.32k
            [&eDT](const std::string &s)
251
1.32k
            {
252
57
                eDT = GDALGetDataTypeByName(s.c_str());
253
57
                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
57
            })
259
1.32k
        .help(_("Output data type."));
260
1.32k
}
261
262
Argument &
263
GDALArgumentParser::add_layer_creation_options_argument(CPLStringList &var)
264
7.70k
{
265
7.70k
    return add_argument("-lco")
266
7.70k
        .metavar("<NAME>=<VALUE>")
267
7.70k
        .append()
268
7.70k
        .action([&var](const std::string &s) { var.AddString(s.c_str()); })
269
7.70k
        .help(_("Layer creation options (format specific)."));
270
7.70k
}
271
272
Argument &
273
GDALArgumentParser::add_dataset_creation_options_argument(CPLStringList &var)
274
7.70k
{
275
7.70k
    return add_argument("-dsco")
276
7.70k
        .metavar("<NAME>=<VALUE>")
277
7.70k
        .append()
278
7.70k
        .action([&var](const std::string &s) { var.AddString(s.c_str()); })
279
7.70k
        .help(_("Dataset creation options (format specific)."));
280
7.70k
}
281
282
/************************************************************************/
283
/*                   parse_args_without_binary_name()                   */
284
/************************************************************************/
285
286
void GDALArgumentParser::parse_args_without_binary_name(CSLConstList papszArgs)
287
8.98k
{
288
8.98k
    CPLStringList aosArgs;
289
8.98k
    aosArgs.AddString(m_program_name.c_str());
290
511k
    for (CSLConstList papszIter = papszArgs; papszIter && *papszIter;
291
502k
         ++papszIter)
292
502k
        aosArgs.AddString(*papszIter);
293
8.98k
    parse_args(aosArgs);
294
8.98k
}
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
211k
{
303
211k
    auto arg_map_it = m_argument_map.find(name);
304
211k
    if (arg_map_it == m_argument_map.end())
305
834
    {
306
        // Attempt case insensitive lookup
307
834
        arg_map_it =
308
834
            std::find_if(m_argument_map.begin(), m_argument_map.end(),
309
834
                         [&name](const auto &oArg)
310
51.9k
                         { return EQUAL(name.c_str(), oArg.first.c_str()); });
311
834
    }
312
211k
    return arg_map_it;
313
211k
}
314
315
/************************************************************************/
316
/*                    get_non_positional_arguments()                    */
317
/************************************************************************/
318
319
CPLStringList
320
GDALArgumentParser::get_non_positional_arguments(const CPLStringList &aosArgs)
321
7.70k
{
322
7.70k
    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
7.70k
    std::vector<std::string> raw_arguments{m_program_name};
330
7.70k
    raw_arguments.insert(raw_arguments.end(), aosArgs.List(),
331
7.70k
                         aosArgs.List() + aosArgs.size());
332
7.70k
    auto arguments = preprocess_arguments(raw_arguments);
333
7.70k
    auto end = std::end(arguments);
334
7.70k
    auto positional_argument_it = std::begin(m_positional_arguments);
335
18.7k
    for (auto it = std::next(std::begin(arguments)); it != end;)
336
11.0k
    {
337
11.0k
        const auto &current_argument = *it;
338
11.0k
        if (Argument::is_positional(current_argument, m_prefix_chars))
339
1.33k
        {
340
1.33k
            if (positional_argument_it != std::end(m_positional_arguments))
341
1.33k
            {
342
1.33k
                auto argument = positional_argument_it++;
343
1.33k
                auto next_it =
344
1.33k
                    argument->consume(it, end, "", /* dry_run = */ true);
345
1.33k
                it = next_it;
346
1.33k
                continue;
347
1.33k
            }
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
1.33k
        }
364
365
9.76k
        auto arg_map_it = find_argument(current_argument);
366
9.76k
        if (arg_map_it != m_argument_map.end())
367
9.73k
        {
368
9.73k
            auto argument = arg_map_it->second;
369
9.73k
            auto next_it = argument->consume(
370
9.73k
                std::next(it), end, arg_map_it->first, /* dry_run = */ true);
371
            // Add official argument name (correcting possible case)
372
9.73k
            args.AddString(arg_map_it->first.c_str());
373
9.73k
            ++it;
374
            // Add its values
375
16.5k
            for (; it != next_it; ++it)
376
6.79k
            {
377
6.79k
                args.AddString(it->c_str());
378
6.79k
            }
379
9.73k
            it = next_it;
380
9.73k
        }
381
34
        else
382
34
        {
383
34
            throw std::runtime_error("Unknown argument: " + current_argument);
384
34
        }
385
9.76k
    }
386
387
7.66k
    return args;
388
7.70k
}
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
11
{
418
11
    auto it = std::find_if(
419
11
        aoSubparsers.begin(), aoSubparsers.end(), [&name](const auto &parser)
420
11
        { return EQUAL(name.c_str(), parser->m_program_name.c_str()); });
421
11
    return it != aoSubparsers.end() ? it->get() : nullptr;
422
11
}
423
424
bool GDALArgumentParser::is_used_globally(const std::string &name)
425
0
{
426
0
    try
427
0
    {
428
0
        return ArgumentParser::is_used(name);
429
0
    }
430
0
    catch (std::logic_error &)
431
0
    {
432
        // ignore
433
0
    }
434
435
    // Check if it is used by a subparser
436
    // loop through subparsers
437
0
    for (const auto &subparser : aoSubparsers)
438
0
    {
439
        // convert subparser name to lower case
440
0
        std::string subparser_name = subparser->m_program_name;
441
0
        std::transform(subparser_name.begin(), subparser_name.end(),
442
0
                       subparser_name.begin(), [](int c) -> char
443
0
                       { return static_cast<char>(::tolower(c)); });
444
0
        if (m_subparser_used.find(subparser_name) != m_subparser_used.end())
445
0
        {
446
0
            if (subparser->is_used_globally(name))
447
0
            {
448
0
                return true;
449
0
            }
450
0
        }
451
0
    }
452
453
0
    return false;
454
0
}
455
456
/************************************************************************/
457
/*                             parse_args()                             */
458
/************************************************************************/
459
460
void GDALArgumentParser::parse_args(const CPLStringList &aosArgs)
461
8.98k
{
462
8.98k
    std::vector<std::string> reorderedArgs;
463
8.98k
    std::vector<std::string> positionalArgs;
464
465
    // ArgumentParser::parse_args() expects the first argument to be the
466
    // binary name
467
8.98k
    if (!aosArgs.empty())
468
8.98k
    {
469
8.98k
        reorderedArgs.push_back(aosArgs[0]);
470
8.98k
    }
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
8.98k
    std::vector<std::string> raw_arguments{aosArgs.List(),
478
8.98k
                                           aosArgs.List() + aosArgs.size()};
479
8.98k
    auto arguments = preprocess_arguments(raw_arguments);
480
8.98k
    auto end = std::end(arguments);
481
8.98k
    auto positional_argument_it = std::begin(m_positional_arguments);
482
212k
    for (auto it = std::next(std::begin(arguments)); it != end;)
483
203k
    {
484
203k
        const auto &current_argument = *it;
485
203k
        if (Argument::is_positional(current_argument, m_prefix_chars))
486
1.34k
        {
487
1.34k
            if (positional_argument_it != std::end(m_positional_arguments))
488
1.33k
            {
489
1.33k
                auto argument = positional_argument_it++;
490
1.33k
                auto next_it =
491
1.33k
                    argument->consume(it, end, "", /* dry_run = */ true);
492
6.65k
                for (; it != next_it; ++it)
493
5.66k
                {
494
5.66k
                    if (!Argument::is_positional(*it, m_prefix_chars))
495
339
                    {
496
339
                        next_it = it;
497
339
                        break;
498
339
                    }
499
5.32k
                    positionalArgs.push_back(*it);
500
5.32k
                }
501
1.33k
                it = next_it;
502
1.33k
                continue;
503
1.33k
            }
504
11
            else
505
11
            {
506
                // Check sub-parsers
507
11
                auto subparser = get_subparser(current_argument);
508
11
                if (subparser)
509
0
                {
510
511
                    // build list of remaining args
512
0
                    const auto unprocessed_arguments =
513
0
                        CPLStringList(std::vector<std::string>(it, end));
514
515
                    // invoke subparser
516
0
                    m_is_parsed = true;
517
                    // convert to lower case
518
0
                    std::string current_argument_lower = current_argument;
519
0
                    std::transform(current_argument_lower.begin(),
520
0
                                   current_argument_lower.end(),
521
0
                                   current_argument_lower.begin(),
522
0
                                   [](int c) -> char
523
0
                                   { return static_cast<char>(::tolower(c)); });
524
0
                    m_subparser_used[current_argument_lower] = true;
525
0
                    return subparser->parse_args(unprocessed_arguments);
526
0
                }
527
528
11
                if (m_positional_arguments.empty())
529
5
                {
530
5
                    throw std::runtime_error(
531
5
                        "Zero positional arguments expected");
532
5
                }
533
6
                else
534
6
                {
535
6
                    throw std::runtime_error(
536
6
                        "Maximum number of positional arguments "
537
6
                        "exceeded, failed to parse '" +
538
6
                        current_argument + "'");
539
6
                }
540
11
            }
541
1.34k
        }
542
543
202k
        auto arg_map_it = find_argument(current_argument);
544
202k
        if (arg_map_it != m_argument_map.end())
545
202k
        {
546
202k
            auto argument = arg_map_it->second;
547
202k
            auto next_it = argument->consume(
548
202k
                std::next(it), end, arg_map_it->first, /* dry_run = */ true);
549
            // Add official argument name (correcting possible case)
550
202k
            reorderedArgs.push_back(arg_map_it->first);
551
202k
            ++it;
552
            // Add its values
553
402k
            for (; it != next_it; ++it)
554
199k
            {
555
199k
                reorderedArgs.push_back(*it);
556
199k
            }
557
202k
            it = next_it;
558
202k
        }
559
37
        else
560
37
        {
561
37
            throw std::runtime_error("Unknown argument: " + current_argument);
562
37
        }
563
202k
    }
564
565
8.94k
    reorderedArgs.insert(reorderedArgs.end(), positionalArgs.begin(),
566
8.94k
                         positionalArgs.end());
567
568
8.94k
    ArgumentParser::parse_args(reorderedArgs);
569
8.94k
}