From c66a0634b24ba2acee69be1a3c040eff927af307 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 11 Mar 2024 18:57:48 +0100 Subject: [PATCH] Add gdalargumentparser.h/cpp --- apps/CMakeLists.txt | 1 + apps/gdalargumentparser.cpp | 222 ++++++++++++++++++++++++++++++++++++ apps/gdalargumentparser.h | 82 +++++++++++++ 3 files changed, 305 insertions(+) create mode 100644 apps/gdalargumentparser.cpp create mode 100644 apps/gdalargumentparser.h diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index b4258090d99a..c0b1a093df2b 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -5,6 +5,7 @@ add_library( appslib OBJECT commonutils.h gdal_utils.h + gdalargumentparser.cpp gdalinfo_lib.cpp gdalbuildvrt_lib.cpp gdal_grid_lib.cpp diff --git a/apps/gdalargumentparser.cpp b/apps/gdalargumentparser.cpp new file mode 100644 index 000000000000..3d5fdf8d0b52 --- /dev/null +++ b/apps/gdalargumentparser.cpp @@ -0,0 +1,222 @@ +/****************************************************************************** + * Project: GDAL Utilities + * Purpose: GDAL argument parser + * Author: Even Rouault + * + * **************************************************************************** + * Copyright (c) 2024, Even Rouault + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "gdal_version_full/gdal_version.h" + +#include "gdal.h" +#include "gdalargumentparser.h" +#include "commonutils.h" + +/************************************************************************/ +/* GDALArgumentParser() */ +/************************************************************************/ + +GDALArgumentParser::GDALArgumentParser(const std::string &program_name, + bool bForBinary) + : ArgumentParser(program_name, "", default_arguments::none) +{ + set_usage_max_line_width(120); + set_usage_break_on_mutex(); + add_usage_newline(); + + if (bForBinary) + { + add_argument("-h", "--help") + .action( + [this, program_name](const auto &) + { + std::cout << usage() << std::endl << std::endl; + std::cout << _("Note: ") << program_name + << _(" --long-usage for full help.") << std::endl; + std::exit(0); + }) + .flag() + .help(_("Shows short help message and exits")); + add_argument("--long-usage") + .action( + [this](const auto & /*unused*/) + { + std::cout << *this; + std::exit(0); + }) + .flag() + .help(_("Shows long help message and exits")); + add_argument("--help-general") + .flag() + .help(_("Report detailed help on general options")); + add_argument("--utility_version") + .action( + [program_name](const auto &) + { + printf("%s was compiled against GDAL %s and " + "is running against GDAL %s\n", + program_name.c_str(), GDAL_RELEASE_NAME, + GDALVersionInfo("RELEASE_NAME")); + std::exit(0); + }) + .flag() + .help(_("Shows compile-time and run-time GDAL version")); + } +} + +/************************************************************************/ +/* display_error_and_usage() */ +/************************************************************************/ + +void GDALArgumentParser::display_error_and_usage(const std::exception &err) +{ + std::cerr << _("Error: ") << err.what() << std::endl; + std::cerr << usage() << std::endl << std::endl; + std::cout << _("Note: ") << m_program_name + << _(" --long-usage for full help.") << std::endl; +} + +/************************************************************************/ +/* add_quiet_argument() */ +/************************************************************************/ + +void GDALArgumentParser::add_quiet_argument(bool *pVar) +{ + auto &arg = + this->add_argument("-q", "--quiet") + .flag() + .help( + _("Quiet mode. No progress message is emitted on the standard " + "output.")); + if (pVar) + arg.store_into(*pVar); +} + +/************************************************************************/ +/* add_output_format_argument() */ +/************************************************************************/ + +void GDALArgumentParser::add_output_format_argument(std::string &var) +{ + auto &arg = add_argument("-of") + .metavar("") + .store_into(var) + .help(_("Output format")); + add_hidden_alias_for(arg, "-f"); +} + +/************************************************************************/ +/* add_creation_options_argument() */ +/************************************************************************/ + +void GDALArgumentParser::add_creation_options_argument(CPLStringList &var) +{ + add_argument("-co") + .metavar("") + .append() + .action([&var](const std::string &s) { var.AddString(s.c_str()); }) + .help("Creation option(s)"); +} + +/************************************************************************/ +/* parse_args_without_binary_name() */ +/************************************************************************/ + +void GDALArgumentParser::parse_args_without_binary_name(CSLConstList papszArgs) +{ + CPLStringList aosArgs; + aosArgs.AddString(m_program_name.c_str()); + for (CSLConstList papszIter = papszArgs; papszIter && *papszIter; + ++papszIter) + aosArgs.AddString(*papszIter); + parse_args(aosArgs); +} + +/************************************************************************/ +/* parse_args() */ +/************************************************************************/ + +void GDALArgumentParser::parse_args(const CPLStringList &aosArgs) +{ + std::vector reorderedArgs; + std::vector positionalArgs; + + // ArgumentParser::parse_args() expects the first argument to be the + // binary name + if (!aosArgs.empty()) + { + reorderedArgs.push_back(aosArgs[0]); + } + + // Simplified logic borrowed from ArgumentParser::parse_args_internal() + // that make sure that positional arguments are moved after optional ones, + // as this is what ArgumentParser::parse_args() only supports. + // This doesn't support advanced settings, such as sub-parsers or compound + // argument + std::vector raw_arguments{aosArgs.List(), + aosArgs.List() + aosArgs.size()}; + auto arguments = preprocess_arguments(raw_arguments); + auto end = std::end(arguments); + auto positional_argument_it = std::begin(m_positional_arguments); + for (auto it = std::next(std::begin(arguments)); it != end;) + { + const auto ¤t_argument = *it; + if (Argument::is_positional(current_argument, m_prefix_chars)) + { + auto argument = positional_argument_it++; + auto next_it = argument->consume(it, end, "", /* dry_run = */ true); + for (; it != next_it; ++it) + { + if ((*it)[0] == '-') + { + next_it = it; + break; + } + positionalArgs.push_back(*it); + } + it = next_it; + continue; + } + + auto arg_map_it = m_argument_map.find(current_argument); + if (arg_map_it != m_argument_map.end()) + { + auto argument = arg_map_it->second; + auto next_it = argument->consume( + std::next(it), end, arg_map_it->first, /* dry_run = */ true); + for (; it != next_it; ++it) + { + reorderedArgs.push_back(*it); + } + it = next_it; + } + else + { + throw std::runtime_error("Unknown argument: " + current_argument); + } + } + + reorderedArgs.insert(reorderedArgs.end(), positionalArgs.begin(), + positionalArgs.end()); + + ArgumentParser::parse_args(reorderedArgs); +} diff --git a/apps/gdalargumentparser.h b/apps/gdalargumentparser.h new file mode 100644 index 000000000000..bcdcead109fc --- /dev/null +++ b/apps/gdalargumentparser.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * Project: GDAL Utilities + * Purpose: GDAL argument parser + * Author: Even Rouault + * + * **************************************************************************** + * Copyright (c) 2024, Even Rouault + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef GDALARGUMENTPARSER_H +#define GDALARGUMENTPARSER_H + +#include "cpl_port.h" +#include "cpl_conv.h" +#include "cpl_string.h" + +// Rename argparse namespace to a GDAL private one +#define argparse gdal_argparse + +// Use our locale-unaware strtod() +#define ARGPARSE_CUSTOM_STRTOD CPLStrtodM + +#include "argparse/argparse.hpp" + +using namespace argparse; + +// Place-holder macro using gettext() convention to indicate (future) translatable strings +#ifndef _ +#define _(x) (x) +#endif + +/** Parse command-line arguments for GDAL utilities. + * + * Add helpers over the standard argparse::ArgumentParser class + * + * @since GDAL 3.9 + */ +class CPL_DLL GDALArgumentParser : public ArgumentParser +{ + public: + //! Constructor + explicit GDALArgumentParser(const std::string &program_name, + bool bForBinary); + + //! Format an exception as an error message and display the program usage + void display_error_and_usage(const std::exception &err); + + //! Add -q/--quiet argument, and store its value in *pVar (if pVar not null) + void add_quiet_argument(bool *pVar); + + //! Add "-of format_name" argument for output format, and store its value into var. + void add_output_format_argument(std::string &var); + + //! Add "-co KEY=VALUE" argument for creation options, and store its value into var. + void add_creation_options_argument(CPLStringList &var); + + //! Parse command line arguments, without the initial program name. + void parse_args_without_binary_name(CSLConstList papszArgs); + + //! Parse command line arguments, with the initial program name. + void parse_args(const CPLStringList &aosArgs); +}; + +#endif /* GDALARGUMENTPARSER_H */