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 37 : 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 37 : 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 37 : SetDisplayInJSONUsage(false); 43 37 : } 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 22 : bool GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>:: 72 : ParseCommandLineArguments(const std::vector<std::string> &args) 73 : { 74 22 : if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help")) 75 1 : return GDALAlgorithm::ParseCommandLineArguments(args); 76 : 77 : // We first try to process with the raster specific algorithm (that has 78 : // been instantiated in a special way to accept both raster and vector 79 : // input datasets). If the raster specific algorithm can parse successfully 80 : // the arguments *and* the dataset is a raster one, then continue processing 81 : // with it. Otherwise try with the vector specific algorithm. 82 : 83 : bool ok; 84 42 : std::string osLastError; 85 21 : if (args.size() > 1) 86 : { 87 : // Silence errors as it might be rather for the vector algorithm 88 30 : CPLErrorStateBackuper oErrorHandler(CPLQuietErrorHandler); 89 15 : const auto nCounter = CPLGetErrorCounter(); 90 15 : ok = m_rasterDispatcher->ParseCommandLineArguments(args); 91 21 : if (CPLGetErrorCounter() > nCounter && 92 6 : CPLGetLastErrorType() == CE_Failure) 93 6 : osLastError = CPLGetLastErrorMsg(); 94 : } 95 : else 96 : { 97 : // If there's just a single argument, we don't need to silence errors 98 : // as this will trigger a legitimate error message about the subcommand. 99 6 : ok = m_rasterDispatcher->ParseCommandLineArguments(args); 100 : } 101 : 102 21 : if (m_rasterDispatcher->PropagateSpecialActionTo(this)) 103 : { 104 0 : return true; 105 : } 106 : 107 21 : if (ok) 108 : { 109 14 : auto poDS = m_rasterDispatcher->GetInputDatasetRef(); 110 : // cppcheck-suppress knownConditionTrueFalse 111 28 : if (poDS && 112 14 : (poDS->GetRasterCount() > 0 || poDS->GetMetadata("SUBDATASETS"))) 113 : { 114 8 : if (poDS->GetLayerCount() != 0) 115 : { 116 2 : m_showUsage = false; 117 4 : CPLError(CE_Failure, CPLE_AppDefined, 118 : "'%s' has both raster and vector content. " 119 : "Please use 'gdal raster %s' or 'gdal vector %s'.", 120 2 : poDS->GetDescription(), GetName().c_str(), 121 2 : GetName().c_str()); 122 2 : return false; 123 : } 124 : 125 6 : m_selectedSubAlg = m_rasterDispatcher.get(); 126 6 : std::vector<std::string> callPath(m_callPath); 127 6 : callPath.push_back("raster"); 128 6 : m_selectedSubAlg->SetCallPath(callPath); 129 : 130 6 : return true; 131 : } 132 : } 133 7 : else if (args.size() <= 1) 134 : { 135 1 : return false; 136 : } 137 : 138 12 : auto poDSFromRaster = m_rasterDispatcher->GetInputDatasetRef(); 139 : // cppcheck-suppress knownConditionTrueFalse 140 12 : if (poDSFromRaster) 141 : { 142 6 : m_vectorDispatcher->SetInputDataset(poDSFromRaster); 143 : } 144 : 145 24 : std::vector<std::string> argsWithoutInput; 146 12 : bool skipNext = false; 147 24 : std::string osLikelyDatasetName; 148 12 : size_t nCountLikelyDatasetName = 0; 149 44 : for (const auto &arg : args) 150 : { 151 93 : if (arg == "-i" || arg == "--input" || arg == "-f" || arg == "--of" || 152 93 : arg == "--output-format" || arg == "--format") 153 : { 154 2 : skipNext = true; 155 : } 156 30 : else if (!skipNext) 157 : { 158 37 : if (!STARTS_WITH(arg.c_str(), "--input=") && 159 9 : !(poDSFromRaster && arg == poDSFromRaster->GetDescription())) 160 : { 161 24 : if (!arg.empty() && arg[0] != '-') 162 : { 163 9 : ++nCountLikelyDatasetName; 164 9 : osLikelyDatasetName = arg; 165 : } 166 24 : argsWithoutInput.push_back(arg); 167 : } 168 : } 169 : else 170 : { 171 2 : skipNext = false; 172 : } 173 : } 174 : 175 : { 176 12 : CPLErrorStateBackuper oErrorHandler(CPLQuietErrorHandler); 177 12 : ok = m_vectorDispatcher->ParseCommandLineArguments(argsWithoutInput); 178 : } 179 12 : if (ok) 180 : { 181 7 : m_selectedSubAlg = m_vectorDispatcher.get(); 182 7 : std::vector<std::string> callPath(m_callPath); 183 7 : callPath.push_back("vector"); 184 7 : m_selectedSubAlg->SetCallPath(callPath); 185 : 186 7 : return true; 187 : } 188 : 189 5 : bool ret = false; 190 5 : bool managedToOpenDS = false; 191 12 : for (const auto &arg : args) 192 : { 193 : VSIStatBufL sStat; 194 12 : if (VSIStatL(arg.c_str(), &sStat) == 0) 195 : { 196 5 : auto poDS = 197 : std::unique_ptr<GDALDataset>(GDALDataset::Open(arg.c_str())); 198 5 : if (poDS) 199 : { 200 4 : managedToOpenDS = true; 201 5 : if (poDS->GetRasterCount() > 0 || 202 1 : poDS->GetMetadata("SUBDATASETS")) 203 : { 204 3 : if (poDS->GetLayerCount() != 0) 205 : { 206 1 : m_showUsage = false; 207 3 : CPLError(CE_Failure, CPLE_AppDefined, 208 : "'%s' has both raster and vector content. " 209 : "Please use 'gdal raster %s' or 'gdal " 210 : "vector %s'.", 211 2 : poDS->GetDescription(), GetName().c_str(), 212 1 : GetName().c_str()); 213 1 : return false; 214 : } 215 2 : m_rasterDispatcher = std::make_unique<RasterDispatcher>(); 216 2 : auto poDSRaw = poDS.get(); 217 2 : m_rasterDispatcher->SetInputDataset(poDS.release()); 218 2 : poDSRaw->Release(); 219 2 : m_selectedSubAlg = m_rasterDispatcher.get(); 220 2 : std::vector<std::string> callPath(m_callPath); 221 2 : callPath.push_back("raster"); 222 2 : m_selectedSubAlg->SetCallPath(callPath); 223 2 : ret = m_selectedSubAlg->ParseCommandLineArguments( 224 : argsWithoutInput); 225 : } 226 1 : else if (poDS->GetLayerCount() != 0) 227 : { 228 1 : m_vectorDispatcher = std::make_unique<VectorDispatcher>(); 229 1 : auto poDSRaw = poDS.get(); 230 1 : m_vectorDispatcher->SetInputDataset(poDS.release()); 231 1 : poDSRaw->Release(); 232 1 : m_selectedSubAlg = m_vectorDispatcher.get(); 233 1 : std::vector<std::string> callPath(m_callPath); 234 1 : callPath.push_back("vector"); 235 1 : m_selectedSubAlg->SetCallPath(callPath); 236 1 : ret = m_selectedSubAlg->ParseCommandLineArguments( 237 : argsWithoutInput); 238 : } 239 : } 240 4 : break; 241 : } 242 : } 243 : 244 5 : if (!ret && !managedToOpenDS && 245 1 : (osLastError.find("not recognized") != std::string::npos || 246 0 : (nCountLikelyDatasetName == 1 && 247 0 : cpl::starts_with(osLastError, osLikelyDatasetName)))) 248 : { 249 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osLastError.c_str()); 250 : } 251 : 252 4 : return ret; 253 : } 254 : 255 : /************************************************************************/ 256 : /* GDALDispatcherAlgorithm::GetUsageForCLI() */ 257 : /************************************************************************/ 258 : 259 : template <class RasterDispatcher, class VectorDispatcher> 260 : std::string 261 4 : GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>::GetUsageForCLI( 262 : bool shortUsage, const UsageOptions &usageOptions) const 263 : { 264 4 : if (m_selectedSubAlg) 265 : { 266 1 : return m_selectedSubAlg->GetUsageForCLI(shortUsage, usageOptions); 267 : } 268 3 : if (m_showUsage) 269 : { 270 2 : return GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions); 271 : } 272 1 : return std::string(); 273 : } 274 : 275 : //! @endcond 276 : 277 : #endif // GDALALG_DISPATCHER_INCLUDED