Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: CLI front-end
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 : #include "gdalalgorithm.h"
14 : #include "commonutils.h"
15 : #include "cpl_error.h"
16 :
17 : #include "gdal.h"
18 :
19 : #include <cassert>
20 : #include <utility>
21 :
22 : // #define DEBUG_COMPLETION
23 :
24 : /************************************************************************/
25 : /* EmitCompletion() */
26 : /************************************************************************/
27 :
28 : /** Return on stdout a space-separated list of choices for bash completion */
29 163 : static void EmitCompletion(std::unique_ptr<GDALAlgorithm> rootAlg,
30 : const std::vector<std::string> &argsIn,
31 : bool lastWordIsComplete)
32 : {
33 : #ifdef DEBUG_COMPLETION
34 : for (size_t i = 0; i < argsIn.size(); ++i)
35 : fprintf(stderr, "arg[%d]='%s'\n", static_cast<int>(i),
36 : argsIn[i].c_str());
37 : #endif
38 :
39 163 : std::vector<std::string> args = argsIn;
40 :
41 163 : std::string ret;
42 484 : if (!args.empty() &&
43 161 : (args.back() == "--config" ||
44 318 : STARTS_WITH(args.back().c_str(), "--config=") ||
45 313 : (args.size() >= 2 && args[args.size() - 2] == "--config")))
46 : {
47 5 : if (args.back() == "--config=" || args.back().back() != '=')
48 : {
49 6 : CPLStringList aosConfigOptions(CPLGetKnownConfigOptions());
50 3318 : for (const char *pszOpt : cpl::Iterate(aosConfigOptions))
51 : {
52 3315 : ret += pszOpt;
53 3315 : ret += "=\n";
54 : }
55 3 : printf("%s", ret.c_str());
56 : }
57 5 : return;
58 : }
59 :
60 15463 : for (const auto &choice : rootAlg->GetAutoComplete(
61 30768 : args, lastWordIsComplete, /*showAllOptions = */ true))
62 : {
63 15305 : ret += choice;
64 15305 : ret += '\n';
65 : }
66 :
67 : #ifdef DEBUG_COMPLETION
68 : fprintf(stderr, "ret = '%s'\n", ret.c_str());
69 : #endif
70 158 : if (!ret.empty())
71 151 : printf("%s", ret.c_str());
72 : }
73 :
74 : /************************************************************************/
75 : /* main() */
76 : /************************************************************************/
77 :
78 331 : MAIN_START(argc, argv)
79 : {
80 331 : const bool bIsCompletion = argc >= 3 && strcmp(argv[1], "completion") == 0;
81 :
82 331 : if (bIsCompletion)
83 : {
84 326 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
85 163 : EarlySetConfigOptions(argc, argv);
86 : }
87 : else
88 : {
89 168 : EarlySetConfigOptions(argc, argv);
90 778 : for (int i = 1; i < argc; ++i)
91 : {
92 : // Used by gdal raster tile --parallel-method=spawn to pass
93 : // config options in a stealth way
94 660 : if (strcmp(argv[i], "--config-options-in-stdin") == 0)
95 : {
96 100 : std::string line;
97 50 : constexpr int LINE_SIZE = 10 * 1024;
98 50 : line.resize(LINE_SIZE);
99 172 : while (fgets(line.data(), LINE_SIZE, stdin))
100 : {
101 294 : if (strcmp(line.c_str(), "--config\n") == 0 &&
102 122 : fgets(line.data(), LINE_SIZE, stdin))
103 : {
104 244 : std::string osLine(line.c_str());
105 122 : if (!osLine.empty() && osLine.back() == '\n')
106 : {
107 122 : osLine.pop_back();
108 122 : char *pszUnescaped = CPLUnescapeString(
109 : osLine.c_str(), nullptr, CPLES_URL);
110 122 : char *pszKey = nullptr;
111 : const char *pszValue =
112 122 : CPLParseNameValue(pszUnescaped, &pszKey);
113 122 : if (pszKey && pszValue)
114 : {
115 122 : CPLSetConfigOption(pszKey, pszValue);
116 : }
117 122 : CPLFree(pszKey);
118 122 : CPLFree(pszUnescaped);
119 : }
120 : }
121 50 : else if (strcmp(line.c_str(), "END_CONFIG\n") == 0)
122 : {
123 50 : break;
124 : }
125 : }
126 :
127 50 : break;
128 : }
129 : }
130 : }
131 :
132 331 : auto alg = GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate(
133 993 : GDALGlobalAlgorithmRegistry::ROOT_ALG_NAME);
134 331 : assert(alg);
135 :
136 : // Register GDAL drivers
137 331 : GDALAllRegister();
138 :
139 331 : alg->SetCalledFromCommandLine();
140 :
141 331 : if (bIsCompletion)
142 : {
143 163 : const bool bLastWordIsComplete =
144 163 : EQUAL(argv[argc - 1], "last_word_is_complete=true");
145 163 : if (STARTS_WITH(argv[argc - 1], "last_word_is_complete="))
146 10 : --argc;
147 153 : else if (argc >= 2 && STARTS_WITH(argv[argc - 2], "prev=") &&
148 4 : STARTS_WITH(argv[argc - 1], "cur="))
149 : {
150 4 : const char *pszPrevVal = argv[argc - 2] + strlen("prev=");
151 4 : const char *pszCurVal = argv[argc - 1] + strlen("cur=");
152 8 : std::string osCurVal;
153 4 : const bool bIsPrevValEqual = (strcmp(pszPrevVal, "=") == 0);
154 4 : if (bIsPrevValEqual)
155 : {
156 1 : osCurVal = std::string("=").append(pszCurVal);
157 1 : pszCurVal = osCurVal.c_str();
158 : }
159 4 : int iMatch = 0;
160 26 : for (int i = 3; i < argc - 1; ++i)
161 : {
162 38 : if (bIsPrevValEqual ? (strstr(argv[i], pszCurVal) != nullptr)
163 16 : : (strcmp(argv[i], pszCurVal) == 0))
164 : {
165 4 : if (iMatch == 0)
166 4 : iMatch = i;
167 : else
168 0 : iMatch = -1;
169 : }
170 : }
171 4 : if (iMatch > 0)
172 4 : argc = iMatch + 1;
173 : else
174 0 : argc -= 2;
175 : }
176 :
177 : // Process lines like "gdal completion gdal raster last_word_is_complete=true|false"
178 163 : EmitCompletion(std::move(alg),
179 326 : std::vector<std::string>(argv + 3, argv + argc),
180 : bLastWordIsComplete);
181 163 : return 0;
182 : }
183 :
184 : // Prevent GDALGeneralCmdLineProcessor() to process --format XXX, unless
185 : // "gdal" is invoked only with it. Cf #12411
186 336 : std::vector<std::pair<char **, char *>> apOrigFormat;
187 168 : constexpr const char *pszFormatReplaced = "--format-XXXX";
188 168 : if (!(argc == 3 && strcmp(argv[1], "--format") == 0))
189 : {
190 2127 : for (int i = 1; i < argc; ++i)
191 : {
192 1960 : if (strcmp(argv[i], "--format") == 0)
193 : {
194 4 : apOrigFormat.emplace_back(argv + i, argv[i]);
195 4 : argv[i] = const_cast<char *>(pszFormatReplaced);
196 : }
197 : }
198 : }
199 :
200 : // Process generic cmomand options
201 168 : argc = GDALGeneralCmdLineProcessor(
202 : argc, &argv, GDAL_OF_RASTER | GDAL_OF_VECTOR | GDAL_OF_MULTIDIM_RASTER);
203 172 : for (auto &pair : apOrigFormat)
204 : {
205 4 : *(pair.first) = pair.second;
206 : }
207 :
208 168 : if (argc < 1)
209 8 : return (-argc);
210 :
211 320 : std::vector<std::string> args;
212 1911 : for (int i = 1; i < argc; ++i)
213 3498 : args.push_back(strcmp(argv[i], pszFormatReplaced) == 0 ? "--format"
214 1747 : : argv[i]);
215 160 : CSLDestroy(argv);
216 :
217 160 : if (!alg->ParseCommandLineArguments(args))
218 : {
219 30 : if (strstr(CPLGetLastErrorMsg(), "Do you mean") == nullptr &&
220 27 : strstr(CPLGetLastErrorMsg(), "Should be one among") == nullptr &&
221 27 : strstr(CPLGetLastErrorMsg(), "Potential values for argument") ==
222 57 : nullptr &&
223 24 : strstr(CPLGetLastErrorMsg(),
224 : "Single potential value for argument") == nullptr)
225 : {
226 22 : fprintf(stderr, "%s", alg->GetUsageForCLI(true).c_str());
227 : }
228 30 : return 1;
229 : }
230 :
231 : {
232 : const auto stdoutArg =
233 130 : alg->GetActualAlgorithm().GetArg(GDAL_ARG_NAME_STDOUT);
234 130 : if (stdoutArg && stdoutArg->GetType() == GAAT_BOOLEAN)
235 52 : stdoutArg->Set(true);
236 : }
237 :
238 : GDALProgressFunc pfnProgress =
239 130 : alg->IsProgressBarRequested() ? GDALTermProgress : nullptr;
240 130 : void *pProgressData = nullptr;
241 :
242 130 : int ret = (alg->Run(pfnProgress, pProgressData) && alg->Finalize()) ? 0 : 1;
243 :
244 : const auto outputArg =
245 130 : alg->GetActualAlgorithm().GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
246 187 : if (outputArg && outputArg->GetType() == GAAT_STRING &&
247 57 : outputArg->IsOutput())
248 : {
249 57 : printf("%s", outputArg->Get<std::string>().c_str());
250 57 : fflush(stdout);
251 : }
252 :
253 130 : const auto retCodeArg = alg->GetActualAlgorithm().GetArg("return-code");
254 130 : if (retCodeArg && retCodeArg->GetType() == GAAT_INTEGER &&
255 0 : retCodeArg->IsOutput())
256 : {
257 0 : ret = retCodeArg->Get<int>();
258 : }
259 :
260 130 : alg.reset();
261 :
262 130 : GDALDestroy();
263 :
264 130 : return ret;
265 : }
266 :
267 0 : MAIN_END
|