Line data Source code
1 : /****************************************************************************** 2 : * 3 : * Project: GDAL 4 : * Purpose: gdal subcommand dispatcher 5 : * Author: Even Rouault <even dot rouault at spatialys.com> 6 : * 7 : ****************************************************************************** 8 : * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com> 9 : * 10 : * SPDX-License-Identifier: MIT 11 : ****************************************************************************/ 12 : 13 : #ifndef GDALALG_DISPATCHER_INCLUDED 14 : #define GDALALG_DISPATCHER_INCLUDED 15 : 16 : #include "gdalalgorithm.h" 17 : 18 : #include "gdal_priv.h" 19 : #include "cpl_error.h" 20 : 21 : //! @cond Doxygen_Suppress 22 : 23 : /************************************************************************/ 24 : /* GDALDispatcherAlgorithm */ 25 : /************************************************************************/ 26 : 27 : template <class RasterDispatcher, class VectorDispatcher> 28 : class GDALDispatcherAlgorithm : public GDALAlgorithm 29 : { 30 : public: 31 45 : GDALDispatcherAlgorithm(const std::string &name, 32 : const std::string &description, 33 : const std::string &helpURL) 34 : : GDALAlgorithm(name, description, helpURL), 35 : m_rasterDispatcher(std::make_unique<RasterDispatcher>( 36 0 : /* standalone = */ true, /* openForMixedRasterVector = */ true)), 37 : m_vectorDispatcher( 38 45 : std::make_unique<VectorDispatcher>(/* standalone = */ true)) 39 : { 40 : // A "info" dispatcher command is a shortcut for something like 41 : // "raster info", "vector info". Best to expose the latter. 42 45 : SetDisplayInJSONUsage(false); 43 45 : } 44 : 45 : bool 46 : ParseCommandLineArguments(const std::vector<std::string> &args) override; 47 : 48 : std::string GetUsageForCLI(bool shortUsage, 49 : const UsageOptions &usageOptions) const override; 50 : 51 : private: 52 : std::unique_ptr<RasterDispatcher> m_rasterDispatcher{}; 53 : std::unique_ptr<VectorDispatcher> m_vectorDispatcher{}; 54 : bool m_showUsage = true; 55 : 56 0 : bool RunImpl(GDALProgressFunc, void *) override 57 : { 58 0 : CPLError(CE_Failure, CPLE_AppDefined, 59 : "The Run() method should not be called directly on the \"gdal " 60 : "%s\" program.", 61 0 : GetName().c_str()); 62 0 : return false; 63 : } 64 : }; 65 : 66 : /************************************************************************/ 67 : /* GDALDispatcherAlgorithm::ParseCommandLineArguments() */ 68 : /************************************************************************/ 69 : 70 : template <class RasterDispatcher, class VectorDispatcher> 71 26 : bool GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>:: 72 : ParseCommandLineArguments(const std::vector<std::string> &args) 73 : { 74 26 : if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help")) 75 1 : return GDALAlgorithm::ParseCommandLineArguments(args); 76 : 77 25 : if (IsCalledFromCommandLine()) 78 : { 79 4 : m_rasterDispatcher->SetCalledFromCommandLine(); 80 4 : m_vectorDispatcher->SetCalledFromCommandLine(); 81 : } 82 : 83 : // We first try to process with the raster specific algorithm (that has 84 : // been instantiated in a special way to accept both raster and vector 85 : // input datasets). If the raster specific algorithm can parse successfully 86 : // the arguments *and* the dataset is a raster one, then continue processing 87 : // with it. Otherwise try with the vector specific algorithm. 88 : 89 : bool ok; 90 50 : std::string osLastError; 91 25 : if (args.size() > 1) 92 : { 93 : // Silence errors as it might be rather for the vector algorithm 94 34 : CPLErrorStateBackuper oErrorHandler(CPLQuietErrorHandler); 95 17 : const auto nCounter = CPLGetErrorCounter(); 96 17 : ok = m_rasterDispatcher->ParseCommandLineArguments(args); 97 23 : if (CPLGetErrorCounter() > nCounter && 98 6 : CPLGetLastErrorType() == CE_Failure) 99 6 : osLastError = CPLGetLastErrorMsg(); 100 : } 101 : else 102 : { 103 : // If there's just a single argument, we don't need to silence errors 104 : // as this will trigger a legitimate error message about the subcommand. 105 8 : ok = m_rasterDispatcher->ParseCommandLineArguments(args); 106 : } 107 : 108 25 : if (m_rasterDispatcher->PropagateSpecialActionTo(this)) 109 : { 110 0 : return true; 111 : } 112 : 113 25 : if (ok) 114 : { 115 18 : auto poDS = m_rasterDispatcher->GetInputDatasetRef(); 116 : // cppcheck-suppress knownConditionTrueFalse 117 36 : if (poDS && 118 18 : (poDS->GetRasterCount() > 0 || poDS->GetMetadata("SUBDATASETS"))) 119 : { 120 10 : if (poDS->GetLayerCount() != 0) 121 : { 122 2 : m_showUsage = false; 123 4 : CPLError(CE_Failure, CPLE_AppDefined, 124 : "'%s' has both raster and vector content. " 125 : "Please use 'gdal raster %s' or 'gdal vector %s'.", 126 2 : poDS->GetDescription(), GetName().c_str(), 127 2 : GetName().c_str()); 128 2 : return false; 129 : } 130 : 131 8 : m_selectedSubAlg = m_rasterDispatcher.get(); 132 8 : std::vector<std::string> callPath(m_callPath); 133 8 : callPath.push_back("raster"); 134 8 : m_selectedSubAlg->SetCallPath(callPath); 135 : 136 8 : return true; 137 : } 138 : } 139 7 : else if (args.size() <= 1) 140 : { 141 1 : return false; 142 : } 143 : 144 14 : auto poDSFromRaster = m_rasterDispatcher->GetInputDatasetRef(); 145 : // cppcheck-suppress knownConditionTrueFalse 146 14 : if (poDSFromRaster) 147 : { 148 8 : m_vectorDispatcher->SetInputDataset(poDSFromRaster); 149 : } 150 : 151 28 : std::vector<std::string> argsWithoutInput; 152 14 : bool skipNext = false; 153 28 : std::string osLikelyDatasetName; 154 14 : size_t nCountLikelyDatasetName = 0; 155 50 : for (const auto &arg : args) 156 : { 157 36 : if (arg == "-i" || arg == "--input") 158 : { 159 2 : skipNext = true; 160 : } 161 34 : else if (!skipNext) 162 : { 163 45 : if (!STARTS_WITH(arg.c_str(), "--input=") && 164 13 : !(poDSFromRaster && arg == poDSFromRaster->GetDescription())) 165 : { 166 26 : if (!arg.empty() && arg[0] != '-') 167 : { 168 10 : ++nCountLikelyDatasetName; 169 10 : osLikelyDatasetName = arg; 170 : } 171 26 : argsWithoutInput.push_back(arg); 172 : } 173 : } 174 : else 175 : { 176 2 : skipNext = false; 177 : } 178 : } 179 : 180 : { 181 14 : CPLErrorStateBackuper oErrorHandler(CPLQuietErrorHandler); 182 14 : ok = m_vectorDispatcher->ParseCommandLineArguments(argsWithoutInput); 183 : } 184 14 : if (ok) 185 : { 186 9 : m_selectedSubAlg = m_vectorDispatcher.get(); 187 9 : std::vector<std::string> callPath(m_callPath); 188 9 : callPath.push_back("vector"); 189 9 : m_selectedSubAlg->SetCallPath(callPath); 190 : 191 9 : return true; 192 : } 193 : 194 5 : bool ret = false; 195 5 : bool managedToOpenDS = false; 196 12 : for (const auto &arg : args) 197 : { 198 : VSIStatBufL sStat; 199 12 : if (VSIStatL(arg.c_str(), &sStat) == 0) 200 : { 201 5 : auto poDS = 202 : std::unique_ptr<GDALDataset>(GDALDataset::Open(arg.c_str())); 203 5 : if (poDS) 204 : { 205 4 : managedToOpenDS = true; 206 5 : if (poDS->GetRasterCount() > 0 || 207 1 : poDS->GetMetadata("SUBDATASETS")) 208 : { 209 3 : if (poDS->GetLayerCount() != 0) 210 : { 211 1 : m_showUsage = false; 212 3 : CPLError(CE_Failure, CPLE_AppDefined, 213 : "'%s' has both raster and vector content. " 214 : "Please use 'gdal raster %s' or 'gdal " 215 : "vector %s'.", 216 2 : poDS->GetDescription(), GetName().c_str(), 217 1 : GetName().c_str()); 218 1 : return false; 219 : } 220 2 : m_rasterDispatcher = std::make_unique<RasterDispatcher>(); 221 2 : auto poDSRaw = poDS.get(); 222 2 : m_rasterDispatcher->SetInputDataset(poDS.release()); 223 2 : poDSRaw->Release(); 224 2 : m_selectedSubAlg = m_rasterDispatcher.get(); 225 2 : std::vector<std::string> callPath(m_callPath); 226 2 : callPath.push_back("raster"); 227 2 : m_selectedSubAlg->SetCallPath(callPath); 228 2 : ret = m_selectedSubAlg->ParseCommandLineArguments( 229 : argsWithoutInput); 230 : } 231 1 : else if (poDS->GetLayerCount() != 0) 232 : { 233 1 : m_vectorDispatcher = std::make_unique<VectorDispatcher>(); 234 1 : auto poDSRaw = poDS.get(); 235 1 : m_vectorDispatcher->SetInputDataset(poDS.release()); 236 1 : poDSRaw->Release(); 237 1 : m_selectedSubAlg = m_vectorDispatcher.get(); 238 1 : std::vector<std::string> callPath(m_callPath); 239 1 : callPath.push_back("vector"); 240 1 : m_selectedSubAlg->SetCallPath(callPath); 241 1 : ret = m_selectedSubAlg->ParseCommandLineArguments( 242 : argsWithoutInput); 243 : } 244 : } 245 4 : break; 246 : } 247 : } 248 : 249 5 : if (!ret && !managedToOpenDS && 250 1 : (osLastError.find("not recognized") != std::string::npos || 251 0 : (nCountLikelyDatasetName == 1 && 252 0 : cpl::starts_with(osLastError, osLikelyDatasetName)))) 253 : { 254 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osLastError.c_str()); 255 : } 256 : 257 4 : return ret; 258 : } 259 : 260 : /************************************************************************/ 261 : /* GDALDispatcherAlgorithm::GetUsageForCLI() */ 262 : /************************************************************************/ 263 : 264 : template <class RasterDispatcher, class VectorDispatcher> 265 : std::string 266 4 : GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>::GetUsageForCLI( 267 : bool shortUsage, const UsageOptions &usageOptions) const 268 : { 269 4 : if (m_selectedSubAlg) 270 : { 271 1 : return m_selectedSubAlg->GetUsageForCLI(shortUsage, usageOptions); 272 : } 273 3 : if (m_showUsage) 274 : { 275 2 : return GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions); 276 : } 277 1 : return std::string(); 278 : } 279 : 280 : //! @endcond 281 : 282 : #endif // GDALALG_DISPATCHER_INCLUDED