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