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

Generated by: LCOV version 1.14