LCOV - code coverage report
Current view: top level - apps - gdalargumentparser.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 227 250 90.8 %
Date: 2026-02-21 16:21:44 Functions: 34 38 89.5 %

          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 &current_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 &current_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             : }

Generated by: LCOV version 1.14