GDAL
gdalalg_dispatcher.h
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
22
23/************************************************************************/
24/* GDALDispatcherAlgorithm */
25/************************************************************************/
26
27template <class RasterDispatcher, class VectorDispatcher>
28class GDALDispatcherAlgorithm : public GDALAlgorithm
29{
30 public:
31 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 /* standalone = */ true, /* openForMixedRasterVector = */ true)),
37 m_vectorDispatcher(
38 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.
43 }
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 bool RunImpl(GDALProgressFunc, void *) override
57 {
58 CPLError(CE_Failure, CPLE_AppDefined,
59 "The Run() method should not be called directly on the \"gdal "
60 "%s\" program.",
61 GetName().c_str());
62 return false;
63 }
64};
65
66/************************************************************************/
67/* GDALDispatcherAlgorithm::ParseCommandLineArguments() */
68/************************************************************************/
69
70template <class RasterDispatcher, class VectorDispatcher>
71bool GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>::
72 ParseCommandLineArguments(const std::vector<std::string> &args)
73{
74 if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help"))
76
77 if (IsCalledFromCommandLine())
78 {
79 m_rasterDispatcher->SetCalledFromCommandLine();
80 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 std::string osLastError;
91 if (args.size() > 1)
92 {
93 // Silence errors as it might be rather for the vector algorithm
94 CPLErrorStateBackuper oErrorHandler(CPLQuietErrorHandler);
95 const auto nCounter = CPLGetErrorCounter();
96 ok = m_rasterDispatcher->ParseCommandLineArguments(args);
97 if (CPLGetErrorCounter() > nCounter &&
98 CPLGetLastErrorType() == CE_Failure)
99 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 ok = m_rasterDispatcher->ParseCommandLineArguments(args);
106 }
107
108 if (m_rasterDispatcher->PropagateSpecialActionTo(this))
109 {
110 return true;
111 }
112
113 if (ok)
114 {
115 auto poDS = m_rasterDispatcher->GetInputDatasetRef();
116 // cppcheck-suppress knownConditionTrueFalse
117 if (poDS &&
118 (poDS->GetRasterCount() > 0 || poDS->GetMetadata("SUBDATASETS")))
119 {
120 if (poDS->GetLayerCount() != 0)
121 {
122 m_showUsage = false;
123 CPLError(CE_Failure, CPLE_AppDefined,
124 "'%s' has both raster and vector content. "
125 "Please use 'gdal raster %s' or 'gdal vector %s'.",
126 poDS->GetDescription(), GetName().c_str(),
127 GetName().c_str());
128 return false;
129 }
130
131 m_selectedSubAlg = m_rasterDispatcher.get();
132 std::vector<std::string> callPath(m_callPath);
133 callPath.push_back("raster");
134 m_selectedSubAlg->SetCallPath(callPath);
135
136 return true;
137 }
138 }
139 else if (args.size() <= 1)
140 {
141 return false;
142 }
143
144 auto poDSFromRaster = m_rasterDispatcher->GetInputDatasetRef();
145 // cppcheck-suppress knownConditionTrueFalse
146 if (poDSFromRaster)
147 {
148 m_vectorDispatcher->SetInputDataset(poDSFromRaster);
149 }
150
151 std::vector<std::string> argsWithoutInput;
152 bool skipNext = false;
153 std::string osLikelyDatasetName;
154 size_t nCountLikelyDatasetName = 0;
155 for (const auto &arg : args)
156 {
157 if (arg == "-i" || arg == "--input")
158 {
159 skipNext = true;
160 }
161 else if (!skipNext)
162 {
163 if (!STARTS_WITH(arg.c_str(), "--input=") &&
164 !(poDSFromRaster && arg == poDSFromRaster->GetDescription()))
165 {
166 if (!arg.empty() && arg[0] != '-')
167 {
168 ++nCountLikelyDatasetName;
169 osLikelyDatasetName = arg;
170 }
171 argsWithoutInput.push_back(arg);
172 }
173 }
174 else
175 {
176 skipNext = false;
177 }
178 }
179
180 {
181 CPLErrorStateBackuper oErrorHandler(CPLQuietErrorHandler);
182 ok = m_vectorDispatcher->ParseCommandLineArguments(argsWithoutInput);
183 }
184 if (ok)
185 {
186 m_selectedSubAlg = m_vectorDispatcher.get();
187 std::vector<std::string> callPath(m_callPath);
188 callPath.push_back("vector");
189 m_selectedSubAlg->SetCallPath(callPath);
190
191 return true;
192 }
193
194 bool ret = false;
195 bool managedToOpenDS = false;
196 for (const auto &arg : args)
197 {
198 VSIStatBufL sStat;
199 if (VSIStatL(arg.c_str(), &sStat) == 0)
200 {
201 auto poDS =
202 std::unique_ptr<GDALDataset>(GDALDataset::Open(arg.c_str()));
203 if (poDS)
204 {
205 managedToOpenDS = true;
206 if (poDS->GetRasterCount() > 0 ||
207 poDS->GetMetadata("SUBDATASETS"))
208 {
209 if (poDS->GetLayerCount() != 0)
210 {
211 m_showUsage = false;
212 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 poDS->GetDescription(), GetName().c_str(),
217 GetName().c_str());
218 return false;
219 }
220 m_rasterDispatcher = std::make_unique<RasterDispatcher>();
221 auto poDSRaw = poDS.get();
222 m_rasterDispatcher->SetInputDataset(poDS.release());
223 poDSRaw->Release();
224 m_selectedSubAlg = m_rasterDispatcher.get();
225 std::vector<std::string> callPath(m_callPath);
226 callPath.push_back("raster");
227 m_selectedSubAlg->SetCallPath(callPath);
228 ret = m_selectedSubAlg->ParseCommandLineArguments(
229 argsWithoutInput);
230 }
231 else if (poDS->GetLayerCount() != 0)
232 {
233 m_vectorDispatcher = std::make_unique<VectorDispatcher>();
234 auto poDSRaw = poDS.get();
235 m_vectorDispatcher->SetInputDataset(poDS.release());
236 poDSRaw->Release();
237 m_selectedSubAlg = m_vectorDispatcher.get();
238 std::vector<std::string> callPath(m_callPath);
239 callPath.push_back("vector");
240 m_selectedSubAlg->SetCallPath(callPath);
241 ret = m_selectedSubAlg->ParseCommandLineArguments(
242 argsWithoutInput);
243 }
244 }
245 break;
246 }
247 }
248
249 if (!ret && !managedToOpenDS &&
250 (osLastError.find("not recognized") != std::string::npos ||
251 (nCountLikelyDatasetName == 1 &&
252 cpl::starts_with(osLastError, osLikelyDatasetName))))
253 {
254 CPLError(CE_Failure, CPLE_AppDefined, "%s", osLastError.c_str());
255 }
256
257 return ret;
258}
259
260/************************************************************************/
261/* GDALDispatcherAlgorithm::GetUsageForCLI() */
262/************************************************************************/
263
264template <class RasterDispatcher, class VectorDispatcher>
265std::string
266GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>::GetUsageForCLI(
267 bool shortUsage, const UsageOptions &usageOptions) const
268{
269 if (m_selectedSubAlg)
270 {
271 return m_selectedSubAlg->GetUsageForCLI(shortUsage, usageOptions);
272 }
273 if (m_showUsage)
274 {
275 return GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions);
276 }
277 return std::string();
278}
279
281
282#endif // GDALALG_DISPATCHER_INCLUDED
Class that saves the error state on construction, and restores it on destruction.
Definition cpl_error.h:410
GDAL algorithm.
Definition gdalalgorithm_cpp.h:2261
virtual std::string GetUsageForCLI(bool shortUsage, const UsageOptions &usageOptions=UsageOptions()) const
Return the usage as a string appropriate for command-line interface --help output.
Definition gdalalgorithm.cpp:5909
virtual bool ParseCommandLineArguments(const std::vector< std::string > &args)
Parse a command line argument, which does not include the algorithm name, to set the value of corresp...
Definition gdalalgorithm.cpp:2003
const std::string & GetName() const
Get the algorithm name.
Definition gdalalgorithm_cpp.h:2268
void SetDisplayInJSONUsage(bool b)
Set whether this algorithm should be reported in JSON usage.
Definition gdalalgorithm_cpp.h:2970
static GDALDataset * Open(const char *pszFilename, unsigned int nOpenFlags=0, const char *const *papszAllowedDrivers=nullptr, const char *const *papszOpenOptions=nullptr, const char *const *papszSiblingFiles=nullptr)
Definition gdal_dataset.h:554
CPL error handling services.
GUInt32 CPLGetErrorCounter(void)
Get the error counter.
Definition cpl_error.cpp:981
#define CPLE_AppDefined
Application defined error.
Definition cpl_error.h:85
const char * CPLGetLastErrorMsg(void)
Get the last error message.
Definition cpl_error.cpp:959
CPLErr CPLGetLastErrorType(void)
Fetch the last error type.
Definition cpl_error.cpp:935
#define STARTS_WITH(a, b)
Returns whether a starts with b.
Definition cpl_port.h:550
#define VSIStatBufL
Type for VSIStatL().
Definition cpl_vsi.h:195
int VSIStatL(const char *, VSIStatBufL *)
Get filesystem object info.
Definition cpl_vsil.cpp:1547
This file is legacy since GDAL 3.12, but will be kept at least in the whole GDAL 3....