LCOV - code coverage report
Current view: top level - apps - gdalargumentparser.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 225 242 93.0 %
Date: 2024-11-21 22:18:42 Functions: 35 37 94.6 %

          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        5422 : GDALArgumentParser::GDALArgumentParser(const std::string &program_name,
      25        5422 :                                        bool bForBinary)
      26        5422 :     : ArgumentParser(program_name, "", default_arguments::none)
      27             : {
      28        5422 :     set_usage_max_line_width(120);
      29        5422 :     set_usage_break_on_mutex();
      30        5422 :     add_usage_newline();
      31             : 
      32        5422 :     if (bForBinary)
      33             :     {
      34        1063 :         add_argument("-h", "--help")
      35        1063 :             .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        1063 :                 })
      44        1063 :             .help(_("Shows short help message and exits."));
      45             : 
      46        1063 :         add_argument("--long-usage")
      47        1063 :             .flag()
      48             :             .action(
      49           0 :                 [this](const auto & /*unused*/)
      50             :                 {
      51           0 :                     std::cout << *this;
      52           0 :                     std::exit(0);
      53        1063 :                 })
      54        1063 :             .help(_("Shows long help message and exits."));
      55             : 
      56        1063 :         add_argument("--help-general")
      57        1063 :             .flag()
      58        1063 :             .help(_("Report detailed help on general options."));
      59             : 
      60        1063 :         add_argument("--utility_version")
      61        1063 :             .flag()
      62        1063 :             .hidden()
      63             :             .action(
      64          68 :                 [this](const auto &)
      65             :                 {
      66          34 :                     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          34 :                     std::exit(0);
      71        1063 :                 })
      72        1063 :             .help(_("Shows compile-time and run-time GDAL version."));
      73             : 
      74        1063 :         add_usage_newline();
      75             :     }
      76        5422 : }
      77             : 
      78             : /************************************************************************/
      79             : /*                      display_error_and_usage()                       */
      80             : /************************************************************************/
      81             : 
      82           5 : void GDALArgumentParser::display_error_and_usage(const std::exception &err)
      83             : {
      84           5 :     std::cerr << _("Error: ") << err.what() << std::endl;
      85           5 :     std::cerr << usage() << std::endl << std::endl;
      86           5 :     std::cout << _("Note: ") << m_program_name
      87           5 :               << _(" --long-usage for full help.") << std::endl;
      88           5 : }
      89             : 
      90             : /************************************************************************/
      91             : /*                                usage()                               */
      92             : /************************************************************************/
      93             : 
      94          16 : std::string GDALArgumentParser::usage() const
      95             : {
      96          16 :     std::string ret(ArgumentParser::usage());
      97          16 :     if (!m_osExtraUsageHint.empty())
      98             :     {
      99           1 :         ret += '\n';
     100           1 :         ret += '\n';
     101           1 :         ret += m_osExtraUsageHint;
     102             :     }
     103          16 :     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        4436 : Argument &GDALArgumentParser::add_quiet_argument(bool *pVar)
     121             : {
     122             :     auto &arg =
     123        4436 :         this->add_argument("-q", "--quiet")
     124        4436 :             .flag()
     125             :             .help(
     126             :                 _("Quiet mode. No progress message is emitted on the standard "
     127        4436 :                   "output."));
     128        4436 :     if (pVar)
     129        1384 :         arg.store_into(*pVar);
     130             : 
     131        4436 :     return arg;
     132             : }
     133             : 
     134             : /************************************************************************/
     135             : /*                      add_input_format_argument()                     */
     136             : /************************************************************************/
     137             : 
     138        1744 : Argument &GDALArgumentParser::add_input_format_argument(CPLStringList *pvar)
     139             : {
     140        1744 :     return add_argument("-if")
     141        1744 :         .append()
     142        3488 :         .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        1744 :             })
     156             :         .help(
     157        3488 :             _("Format/driver name(s) to be attempted to open the input file."));
     158             : }
     159             : 
     160             : /************************************************************************/
     161             : /*                      add_output_format_argument()                    */
     162             : /************************************************************************/
     163             : 
     164        4784 : Argument &GDALArgumentParser::add_output_format_argument(std::string &var)
     165             : {
     166        4784 :     auto &arg = add_argument("-of")
     167        9568 :                     .metavar("<output_format>")
     168        4784 :                     .store_into(var)
     169        4784 :                     .help(_("Output format."));
     170        4784 :     add_hidden_alias_for(arg, "-f");
     171        4784 :     return arg;
     172             : }
     173             : 
     174             : /************************************************************************/
     175             : /*                     add_creation_options_argument()                  */
     176             : /************************************************************************/
     177             : 
     178        3208 : Argument &GDALArgumentParser::add_creation_options_argument(CPLStringList &var)
     179             : {
     180        3208 :     return add_argument("-co")
     181        6416 :         .metavar("<NAME>=<VALUE>")
     182        3208 :         .append()
     183        3896 :         .action([&var](const std::string &s) { var.AddString(s.c_str()); })
     184        6416 :         .help(_("Creation option(s)."));
     185             : }
     186             : 
     187             : /************************************************************************/
     188             : /*                   add_metadata_item_options_argument()               */
     189             : /************************************************************************/
     190             : 
     191             : Argument &
     192        2975 : GDALArgumentParser::add_metadata_item_options_argument(CPLStringList &var)
     193             : {
     194        2975 :     return add_argument("-mo")
     195        5950 :         .metavar("<NAME>=<VALUE>")
     196        2975 :         .append()
     197        3005 :         .action([&var](const std::string &s) { var.AddString(s.c_str()); })
     198        5950 :         .help(_("Metadata item option(s)."));
     199             : }
     200             : 
     201             : /************************************************************************/
     202             : /*                       add_open_options_argument()                    */
     203             : /************************************************************************/
     204             : 
     205          26 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList &var)
     206             : {
     207          26 :     return add_open_options_argument(&var);
     208             : }
     209             : 
     210             : /************************************************************************/
     211             : /*                       add_open_options_argument()                    */
     212             : /************************************************************************/
     213             : 
     214        4104 : Argument &GDALArgumentParser::add_open_options_argument(CPLStringList *pvar)
     215             : {
     216        4104 :     auto &arg = add_argument("-oo")
     217        8208 :                     .metavar("<NAME>=<VALUE>")
     218        4104 :                     .append()
     219        4104 :                     .help(_("Open option(s) for input dataset."));
     220        4104 :     if (pvar)
     221             :     {
     222          16 :         arg.action([pvar](const std::string &s)
     223         701 :                    { pvar->AddString(s.c_str()); });
     224             :     }
     225             : 
     226        4104 :     return arg;
     227             : }
     228             : 
     229             : /************************************************************************/
     230             : /*                       add_output_type_argument()                     */
     231             : /************************************************************************/
     232             : 
     233        3219 : Argument &GDALArgumentParser::add_output_type_argument(GDALDataType &eDT)
     234             : {
     235        3219 :     return add_argument("-ot")
     236        6438 :         .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
     237             :         .action(
     238         987 :             [&eDT](const std::string &s)
     239             :             {
     240         329 :                 eDT = GDALGetDataTypeByName(s.c_str());
     241         329 :                 if (eDT == GDT_Unknown)
     242             :                 {
     243             :                     throw std::invalid_argument(
     244           0 :                         std::string("Unknown output pixel type: ").append(s));
     245             :                 }
     246        3548 :             })
     247        6438 :         .help(_("Output data type."));
     248             : }
     249             : 
     250             : Argument &
     251         863 : GDALArgumentParser::add_layer_creation_options_argument(CPLStringList &var)
     252             : {
     253         863 :     return add_argument("-lco")
     254        1726 :         .metavar("<NAME>=<VALUE>")
     255         863 :         .append()
     256        1153 :         .action([&var](const std::string &s) { var.AddString(s.c_str()); })
     257        1726 :         .help(_("Layer creation options (format specific)."));
     258             : }
     259             : 
     260             : Argument &
     261         833 : GDALArgumentParser::add_dataset_creation_options_argument(CPLStringList &var)
     262             : {
     263         833 :     return add_argument("-dsco")
     264        1666 :         .metavar("<NAME>=<VALUE>")
     265         833 :         .append()
     266         912 :         .action([&var](const std::string &s) { var.AddString(s.c_str()); })
     267        1666 :         .help(_("Dataset creation options (format specific)."));
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                     parse_args_without_binary_name()                 */
     272             : /************************************************************************/
     273             : 
     274        4799 : void GDALArgumentParser::parse_args_without_binary_name(CSLConstList papszArgs)
     275             : {
     276        9570 :     CPLStringList aosArgs;
     277        4799 :     aosArgs.AddString(m_program_name.c_str());
     278       29077 :     for (CSLConstList papszIter = papszArgs; papszIter && *papszIter;
     279             :          ++papszIter)
     280       24278 :         aosArgs.AddString(*papszIter);
     281        4799 :     parse_args(aosArgs);
     282        4648 : }
     283             : 
     284             : /************************************************************************/
     285             : /*                           find_argument()                            */
     286             : /************************************************************************/
     287             : 
     288             : std::map<std::string, ArgumentParser::argument_it>::iterator
     289       11115 : GDALArgumentParser::find_argument(const std::string &name)
     290             : {
     291       11115 :     auto arg_map_it = m_argument_map.find(name);
     292       11115 :     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        1288 :                          [&name](const auto &oArg)
     298         732 :                          { return EQUAL(name.c_str(), oArg.first.c_str()); });
     299             :     }
     300       11115 :     return arg_map_it;
     301             : }
     302             : 
     303             : /************************************************************************/
     304             : /*                    get_non_positional_arguments()                    */
     305             : /************************************************************************/
     306             : 
     307             : CPLStringList
     308         757 : GDALArgumentParser::get_non_positional_arguments(const CPLStringList &aosArgs)
     309             : {
     310         757 :     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        3028 :     std::vector<std::string> raw_arguments{m_program_name};
     318         757 :     raw_arguments.insert(raw_arguments.end(), aosArgs.List(),
     319        1514 :                          aosArgs.List() + aosArgs.size());
     320        1514 :     auto arguments = preprocess_arguments(raw_arguments);
     321         757 :     auto end = std::end(arguments);
     322         757 :     auto positional_argument_it = std::begin(m_positional_arguments);
     323        2351 :     for (auto it = std::next(std::begin(arguments)); it != end;)
     324             :     {
     325        1595 :         const auto &current_argument = *it;
     326        1595 :         if (Argument::is_positional(current_argument, m_prefix_chars))
     327             :         {
     328         278 :             if (positional_argument_it != std::end(m_positional_arguments))
     329             :             {
     330         278 :                 auto argument = positional_argument_it++;
     331             :                 auto next_it =
     332         278 :                     argument->consume(it, end, "", /* dry_run = */ true);
     333         278 :                 it = next_it;
     334         278 :                 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        1317 :         auto arg_map_it = find_argument(current_argument);
     354        1317 :         if (arg_map_it != m_argument_map.end())
     355             :         {
     356        1317 :             auto argument = arg_map_it->second;
     357             :             auto next_it = argument->consume(
     358        1317 :                 std::next(it), end, arg_map_it->first, /* dry_run = */ true);
     359             :             // Add official argument name (correcting possible case)
     360        1316 :             args.AddString(arg_map_it->first.c_str());
     361        1316 :             ++it;
     362             :             // Add its values
     363        2550 :             for (; it != next_it; ++it)
     364             :             {
     365        1234 :                 args.AddString(it->c_str());
     366             :             }
     367        1316 :             it = next_it;
     368             :         }
     369             :         else
     370             :         {
     371           0 :             throw std::runtime_error("Unknown argument: " + current_argument);
     372             :         }
     373             :     }
     374             : 
     375        1512 :     return args;
     376             : }
     377             : 
     378         962 : Argument &GDALArgumentParser::add_inverted_logic_flag(const std::string &name,
     379             :                                                       bool *store_into,
     380             :                                                       const std::string &help)
     381             : {
     382        1924 :     return add_argument(name)
     383         962 :         .default_value(true)
     384        1924 :         .implicit_value(false)
     385             :         .action(
     386          26 :             [store_into](const auto &)
     387             :             {
     388          13 :                 if (store_into)
     389          13 :                     *store_into = false;
     390         962 :             })
     391        1924 :         .help(help);
     392             : }
     393             : 
     394             : GDALArgumentParser *
     395         603 : GDALArgumentParser::add_subparser(const std::string &description,
     396             :                                   bool bForBinary)
     397             : {
     398        1206 :     auto parser = std::make_unique<GDALArgumentParser>(description, bForBinary);
     399         603 :     ArgumentParser::add_subparser(*parser.get());
     400         603 :     aoSubparsers.emplace_back(std::move(parser));
     401        1206 :     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        5024 : void GDALArgumentParser::parse_args(const CPLStringList &aosArgs)
     451             : {
     452        5187 :     std::vector<std::string> reorderedArgs;
     453        5187 :     std::vector<std::string> positionalArgs;
     454             : 
     455             :     // ArgumentParser::parse_args() expects the first argument to be the
     456             :     // binary name
     457        5024 :     if (!aosArgs.empty())
     458             :     {
     459        5024 :         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        5187 :                                            aosArgs.List() + aosArgs.size()};
     469        5187 :     auto arguments = preprocess_arguments(raw_arguments);
     470        5024 :     auto end = std::end(arguments);
     471        5024 :     auto positional_argument_it = std::begin(m_positional_arguments);
     472       16058 :     for (auto it = std::next(std::begin(arguments)); it != end;)
     473             :     {
     474       11250 :         const auto &current_argument = *it;
     475       11250 :         if (Argument::is_positional(current_argument, m_prefix_chars))
     476             :         {
     477        1452 :             if (positional_argument_it != std::end(m_positional_arguments))
     478             :             {
     479        1322 :                 auto argument = positional_argument_it++;
     480             :                 auto next_it =
     481        1322 :                     argument->consume(it, end, "", /* dry_run = */ true);
     482        2815 :                 for (; it != next_it; ++it)
     483             :                 {
     484        1502 :                     if (!Argument::is_positional(*it, m_prefix_chars))
     485             :                     {
     486           9 :                         next_it = it;
     487           9 :                         break;
     488             :                     }
     489        1493 :                     positionalArgs.push_back(*it);
     490             :                 }
     491        1322 :                 it = next_it;
     492        1322 :                 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        9798 :         auto arg_map_it = find_argument(current_argument);
     534        9798 :         if (arg_map_it != m_argument_map.end())
     535             :         {
     536        9712 :             auto argument = arg_map_it->second;
     537             :             auto next_it = argument->consume(
     538        9712 :                 std::next(it), end, arg_map_it->first, /* dry_run = */ true);
     539             :             // Add official argument name (correcting possible case)
     540        9712 :             reorderedArgs.push_back(arg_map_it->first);
     541        9712 :             ++it;
     542             :             // Add its values
     543       22868 :             for (; it != next_it; ++it)
     544             :             {
     545       13156 :                 reorderedArgs.push_back(*it);
     546             :             }
     547        9712 :             it = next_it;
     548             :         }
     549             :         else
     550             :         {
     551          86 :             throw std::runtime_error("Unknown argument: " + current_argument);
     552             :         }
     553             :     }
     554             : 
     555        4808 :     reorderedArgs.insert(reorderedArgs.end(), positionalArgs.begin(),
     556        9616 :                          positionalArgs.end());
     557             : 
     558        4808 :     ArgumentParser::parse_args(reorderedArgs);
     559             : }

Generated by: LCOV version 1.14