LCOV - code coverage report
Current view: top level - apps - gdalargumentparser.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 222 242 91.7 %
Date: 2025-01-18 12:42:00 Functions: 34 37 91.9 %

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

Generated by: LCOV version 1.14