/src/WasmEdge/lib/po/argument_parser.cpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 |
2 | | // SPDX-FileCopyrightText: Copyright The WasmEdge Authors |
3 | | |
4 | | #include "po/argument_parser.h" |
5 | | #include "common/defines.h" |
6 | | #include "common/spdlog.h" |
7 | | #include "system/winapi.h" |
8 | | #include <cstdio> |
9 | | |
10 | | namespace WasmEdge { |
11 | | namespace PO { |
12 | | |
13 | | cxx20::expected<bool, Error> ArgumentParser::SubCommandDescriptor::parse( |
14 | | std::FILE *Out, Span<const char *> ProgramNamePrefix, int Argc, |
15 | 1.61k | const char *Argv[], int ArgP, const bool &VersionOpt) noexcept { |
16 | 1.61k | ProgramNames.reserve(ProgramNamePrefix.size() + 1); |
17 | 1.61k | ProgramNames.assign(ProgramNamePrefix.begin(), ProgramNamePrefix.end()); |
18 | 1.61k | if (ArgP < Argc) { |
19 | 1.61k | ProgramNames.push_back(Argv[ArgP]); |
20 | 1.61k | } |
21 | 1.61k | ArgumentDescriptor *CurrentDesc = nullptr; |
22 | 1.61k | bool FirstNonOption = true; |
23 | 1.61k | bool Escaped = false; |
24 | 1.61k | auto PositionalIter = PositionalList.cbegin(); |
25 | 1.60M | for (int ArgI = ArgP + 1; ArgI < Argc; ++ArgI) { |
26 | 1.60M | std::string_view Arg = Argv[ArgI]; |
27 | 1.60M | if (!Escaped && Arg.size() >= 2 && Arg[0] == '-') { |
28 | 316k | if (Arg[1] == '-') { |
29 | 313k | if (Arg.size() == 2) { |
30 | 53 | Escaped = true; |
31 | 313k | } else { |
32 | | // long option |
33 | 313k | if (auto Res = finish_option(CurrentDesc); !Res) { |
34 | 0 | return cxx20::unexpected(Res.error()); |
35 | 0 | } |
36 | 313k | if (auto Res = consume_long_option_with_argument(Arg); !Res) { |
37 | 523 | return cxx20::unexpected(Res.error()); |
38 | 313k | } else { |
39 | 313k | CurrentDesc = *Res; |
40 | 313k | } |
41 | 313k | } |
42 | 313k | } else { |
43 | | // short options |
44 | 3.02k | if (auto Res = finish_option(CurrentDesc); !Res) { |
45 | 0 | return cxx20::unexpected(Res.error()); |
46 | 0 | } |
47 | 3.02k | if (auto Res = consume_short_options(Arg); !Res) { |
48 | 42 | return cxx20::unexpected(Res.error()); |
49 | 2.97k | } else { |
50 | 2.97k | CurrentDesc = *Res; |
51 | 2.97k | } |
52 | 3.02k | } |
53 | 1.28M | } else if (!Escaped && CurrentDesc) { |
54 | 564 | if (auto Res = consume_argument(*CurrentDesc, Arg); !Res) { |
55 | 21 | return cxx20::unexpected(Res.error()); |
56 | 21 | } |
57 | 543 | CurrentDesc = nullptr; |
58 | 1.28M | } else { |
59 | | // no more options |
60 | 1.28M | if (FirstNonOption) { |
61 | 510 | FirstNonOption = false; |
62 | 510 | if (!SubCommandMap.empty()) { |
63 | 0 | if (auto Iter = SubCommandMap.find(Arg); |
64 | 0 | Iter != SubCommandMap.end()) { |
65 | 0 | auto &Child = this[Iter->second]; |
66 | 0 | Child.SC->select(); |
67 | 0 | return Child.parse(Out, ProgramNames, Argc, Argv, ArgI, VersionOpt); |
68 | 0 | } |
69 | 0 | } |
70 | 510 | } |
71 | 1.28M | Escaped = true; |
72 | 1.28M | if (CurrentDesc) { |
73 | 1.28M | if (auto Res = consume_argument(*CurrentDesc, Arg); !Res) { |
74 | 11 | return cxx20::unexpected(Res.error()); |
75 | 1.28M | } else { |
76 | 1.28M | CurrentDesc = *Res; |
77 | 1.28M | } |
78 | 1.28M | } else { |
79 | 749 | if (PositionalIter == PositionalList.cend()) { |
80 | 0 | return cxx20::unexpected<Error>( |
81 | 0 | std::in_place, ErrCode::InvalidArgument, |
82 | 0 | "positional argument exceeds maximum consuming."s); |
83 | 0 | } |
84 | 749 | if (auto Res = |
85 | 749 | consume_argument(ArgumentDescriptors[*PositionalIter], Arg); |
86 | 749 | !Res) { |
87 | 0 | return cxx20::unexpected(Res.error()); |
88 | 749 | } else { |
89 | 749 | CurrentDesc = *Res; |
90 | 749 | } |
91 | 749 | ++PositionalIter; |
92 | 749 | } |
93 | 1.28M | } |
94 | 1.60M | } |
95 | 1.01k | if (auto Res = finish_option(CurrentDesc); !Res) { |
96 | 0 | return cxx20::unexpected(Res.error()); |
97 | 0 | } |
98 | | |
99 | 1.01k | if (VersionOpt) { |
100 | 133 | return true; |
101 | 133 | } |
102 | 881 | if (!HelpOpt->value()) { |
103 | 15.0k | for (const auto &Desc : ArgumentDescriptors) { |
104 | 15.0k | if (Desc.nargs() < Desc.min_nargs()) { |
105 | 342 | help(Out); |
106 | 342 | return false; |
107 | 342 | } |
108 | 15.0k | } |
109 | 793 | } else { |
110 | 88 | help(Out); |
111 | 88 | return true; |
112 | 88 | } |
113 | 451 | return true; |
114 | 881 | } |
115 | | |
116 | | void ArgumentParser::SubCommandDescriptor::usage( |
117 | 430 | std::FILE *Out) const noexcept { |
118 | 430 | fmt::print(Out, "{}USAGE{}\n"sv, YELLOW_COLOR, RESET_COLOR); |
119 | 430 | for (const char *Part : ProgramNames) { |
120 | 430 | fmt::print(Out, "\t{}"sv, Part); |
121 | 430 | } |
122 | 430 | if (!SubCommandList.empty()) { |
123 | 0 | fmt::print(Out, " [SUBCOMMANDS]"sv); |
124 | 0 | } |
125 | 430 | if (!NonpositionalList.empty()) { |
126 | 430 | fmt::print(Out, " [OPTIONS]"sv); |
127 | 430 | } |
128 | 430 | bool First = true; |
129 | 860 | for (const auto &Index : PositionalList) { |
130 | 860 | const auto &Desc = ArgumentDescriptors[Index]; |
131 | 860 | if (Desc.hidden()) { |
132 | 0 | continue; |
133 | 0 | } |
134 | | |
135 | 860 | if (First) { |
136 | 430 | fmt::print(Out, " [--]"sv); |
137 | 430 | First = false; |
138 | 430 | } |
139 | | |
140 | 860 | const bool Optional = (Desc.min_nargs() == 0); |
141 | 860 | fmt::print(Out, " "sv); |
142 | 860 | if (Optional) { |
143 | 430 | fmt::print(Out, "["sv); |
144 | 430 | } |
145 | 860 | switch (ArgumentDescriptors[Index].max_nargs()) { |
146 | 0 | case 0: |
147 | 0 | break; |
148 | 430 | case 1: |
149 | 430 | fmt::print(Out, "{}"sv, Desc.meta()); |
150 | 430 | break; |
151 | 430 | default: |
152 | 430 | fmt::print(Out, "{} ..."sv, Desc.meta()); |
153 | 430 | break; |
154 | 860 | } |
155 | 860 | if (Optional) { |
156 | 430 | fmt::print(Out, "]"sv); |
157 | 430 | } |
158 | 860 | } |
159 | 430 | fmt::print(Out, "\n"sv); |
160 | 430 | } |
161 | | |
162 | 430 | void ArgumentParser::SubCommandDescriptor::help(std::FILE *Out) const noexcept { |
163 | | // For enabling Windows PowerShell color support. |
164 | | #if WASMEDGE_OS_WINDOWS && WINAPI_PARTITION_DESKTOP |
165 | | winapi::HANDLE_ OutputHandler = |
166 | | winapi::GetStdHandle(winapi::STD_OUTPUT_HANDLE_); |
167 | | if (OutputHandler != winapi::INVALID_HANDLE_VALUE_) { |
168 | | winapi::DWORD_ ConsoleMode = 0; |
169 | | if (winapi::GetConsoleMode(OutputHandler, &ConsoleMode)) { |
170 | | ConsoleMode |= winapi::ENABLE_VIRTUAL_TERMINAL_PROCESSING_; |
171 | | winapi::SetConsoleMode(OutputHandler, ConsoleMode); |
172 | | } |
173 | | } |
174 | | #endif |
175 | | |
176 | 430 | usage(Out); |
177 | 430 | const constexpr std::string_view kIndent = "\t"sv; |
178 | | |
179 | 430 | fmt::print(Out, "\n"sv); |
180 | 430 | if (!SubCommandList.empty()) { |
181 | 0 | fmt::print(Out, "{}SUBCOMMANDS{}\n"sv, YELLOW_COLOR, RESET_COLOR); |
182 | 0 | for (const auto Offset : SubCommandList) { |
183 | 0 | fmt::print(Out, "{}{}"sv, kIndent, GREEN_COLOR); |
184 | 0 | bool First = true; |
185 | 0 | for (const auto &Name : this[Offset].SubCommandNames) { |
186 | 0 | if (!First) { |
187 | 0 | fmt::print(Out, "|"sv); |
188 | 0 | } |
189 | 0 | fmt::print(Out, "{}"sv, Name); |
190 | 0 | First = false; |
191 | 0 | } |
192 | 0 | fmt::print(Out, "{}\n"sv, RESET_COLOR); |
193 | 0 | indent_output(Out, kIndent, 2, 80, this[Offset].SC->description()); |
194 | 0 | fmt::print(Out, "\n"sv); |
195 | 0 | } |
196 | 0 | fmt::print(Out, "\n"sv); |
197 | 0 | } |
198 | | |
199 | 430 | fmt::print(Out, "{}OPTIONS{}\n"sv, YELLOW_COLOR, RESET_COLOR); |
200 | 12.4k | for (const auto &Index : NonpositionalList) { |
201 | 12.4k | const auto &Desc = ArgumentDescriptors[Index]; |
202 | 12.4k | if (Desc.hidden()) { |
203 | 0 | continue; |
204 | 0 | } |
205 | | |
206 | 12.4k | fmt::print(Out, "{}{}"sv, kIndent, GREEN_COLOR); |
207 | 12.4k | bool First = true; |
208 | 13.3k | for (const auto &Option : Desc.options()) { |
209 | 13.3k | if (!First) { |
210 | 860 | fmt::print(Out, "|"sv); |
211 | 860 | } |
212 | 13.3k | if (Option.size() == 1) { |
213 | 860 | fmt::print(Out, "-{}"sv, Option); |
214 | 12.4k | } else { |
215 | 12.4k | fmt::print(Out, "--{}"sv, Option); |
216 | 12.4k | } |
217 | 13.3k | First = false; |
218 | 13.3k | } |
219 | 12.4k | fmt::print(Out, "{}\n"sv, RESET_COLOR); |
220 | 12.4k | indent_output(Out, kIndent, 2, 80, Desc.description()); |
221 | 12.4k | fmt::print(Out, "\n"sv); |
222 | 12.4k | } |
223 | 430 | } |
224 | | |
225 | | void ArgumentParser::SubCommandDescriptor::indent_output( |
226 | | std::FILE *Out, const std::string_view kIndent, std::size_t IndentCount, |
227 | 12.4k | std::size_t ScreenWidth, std::string_view Desc) const noexcept { |
228 | 12.4k | const std::size_t Width = ScreenWidth - kIndent.size() * IndentCount; |
229 | 14.6k | while (Desc.size() > Width) { |
230 | | // Break at the last space within the width, or inside a longer word. |
231 | 2.15k | std::size_t LineEnd = Desc.find_last_of(' ', Width); |
232 | 2.15k | std::size_t NextLine = Width; |
233 | 2.15k | if (LineEnd == std::string_view::npos) { |
234 | 0 | LineEnd = Width; |
235 | 2.15k | } else { |
236 | 2.15k | NextLine = Desc.find_first_not_of(' ', LineEnd); |
237 | 2.15k | if (NextLine == std::string_view::npos) { |
238 | 0 | NextLine = Desc.size(); |
239 | 0 | } |
240 | 2.15k | } |
241 | 6.45k | for (std::size_t I = 0; I < IndentCount; ++I) { |
242 | 4.30k | fmt::print(Out, "{}"sv, kIndent); |
243 | 4.30k | } |
244 | 2.15k | fmt::print(Out, "{}\n"sv, Desc.substr(0, LineEnd)); |
245 | 2.15k | Desc = Desc.substr(NextLine); |
246 | 2.15k | } |
247 | 12.4k | if (!Desc.empty()) { |
248 | 37.4k | for (std::size_t I = 0; I < IndentCount; ++I) { |
249 | 24.9k | fmt::print(Out, "{}"sv, kIndent); |
250 | 24.9k | } |
251 | 12.4k | fmt::print(Out, "{}"sv, Desc); |
252 | 12.4k | } |
253 | 12.4k | } |
254 | | |
255 | | cxx20::expected<void, Error> |
256 | | ArgumentParser::SubCommandDescriptor::finish_option( |
257 | 471k | const ArgumentDescriptor *CurrentDesc) noexcept { |
258 | 471k | if (!CurrentDesc || CurrentDesc->nargs() != 0) { |
259 | 469k | return {}; |
260 | 469k | } |
261 | 2.68k | if (CurrentDesc->min_nargs() > 0) { |
262 | 0 | return cxx20::unexpected<Error>( |
263 | 0 | std::in_place, ErrCode::InvalidArgument, |
264 | 0 | fmt::format("option {} requires an argument."sv, |
265 | 0 | CurrentDesc->options().front())); |
266 | 0 | } |
267 | 2.68k | CurrentDesc->default_value(); |
268 | 2.68k | return {}; |
269 | 2.68k | } |
270 | | |
271 | | cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error> |
272 | | ArgumentParser::SubCommandDescriptor::consume_short_options( |
273 | 3.02k | std::string_view Arg) noexcept { |
274 | 3.02k | ArgumentDescriptor *CurrentDesc = nullptr; |
275 | 156k | for (std::size_t I = 1; I < Arg.size(); ++I) { |
276 | 153k | if (auto Res = finish_option(CurrentDesc); !Res) { |
277 | 0 | return cxx20::unexpected(Res.error()); |
278 | 0 | } |
279 | 153k | std::string_view Option = Arg.substr(I, 1); |
280 | 153k | if (auto Res = consume_short_option(Option); !Res) { |
281 | 42 | return cxx20::unexpected(Res.error()); |
282 | 153k | } else { |
283 | 153k | CurrentDesc = *Res; |
284 | 153k | } |
285 | 153k | } |
286 | 2.97k | return CurrentDesc; |
287 | 3.02k | } |
288 | | |
289 | | cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error> |
290 | | ArgumentParser::SubCommandDescriptor::consume_long_option_with_argument( |
291 | 313k | std::string_view Arg) noexcept { |
292 | 313k | if (auto Pos = Arg.find('=', 2); Pos != std::string_view::npos) { |
293 | | // long option with argument |
294 | 303k | std::string_view Option = Arg.substr(2, Pos - 2); |
295 | 303k | std::string_view Argument = Arg.substr(Pos + 1); |
296 | 303k | if (auto Res = consume_long_option(Option); !Res) { |
297 | 57 | return cxx20::unexpected<Error>(Res.error()); |
298 | 303k | } else if (ArgumentDescriptor *CurrentDesc = *Res; !CurrentDesc) { |
299 | 10 | return cxx20::unexpected<Error>( |
300 | 10 | std::in_place, ErrCode::InvalidArgument, |
301 | 10 | fmt::format("option {} doesn't need arguments."sv, Option)); |
302 | 303k | } else { |
303 | 303k | if (auto ConsumeRes = consume_argument(*CurrentDesc, Argument); |
304 | 303k | !ConsumeRes) { |
305 | 267 | return cxx20::unexpected<Error>(ConsumeRes.error()); |
306 | 267 | } |
307 | 303k | return nullptr; |
308 | 303k | } |
309 | 303k | } else { |
310 | | // long option without argument |
311 | 10.1k | std::string_view Option = Arg.substr(2); |
312 | 10.1k | return consume_long_option(Option); |
313 | 10.1k | } |
314 | 313k | } |
315 | | |
316 | | cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error> |
317 | | ArgumentParser::SubCommandDescriptor::consume_short_option( |
318 | 153k | std::string_view Option) noexcept { |
319 | 153k | auto Iter = ArgumentMap.find(Option); |
320 | 153k | if (Iter == ArgumentMap.end()) { |
321 | 42 | return cxx20::unexpected<Error>(std::in_place, ErrCode::InvalidArgument, |
322 | 42 | "unknown option: "s + std::string(Option)); |
323 | 42 | } |
324 | 153k | ArgumentDescriptor &CurrentDesc = ArgumentDescriptors[Iter->second]; |
325 | 153k | if (CurrentDesc.max_nargs() == 0) { |
326 | 153k | CurrentDesc.default_value(); |
327 | 153k | return nullptr; |
328 | 153k | } |
329 | 0 | return &CurrentDesc; |
330 | 153k | } |
331 | | |
332 | | cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error> |
333 | | ArgumentParser::SubCommandDescriptor::consume_long_option( |
334 | 313k | std::string_view Option) noexcept { |
335 | 313k | auto Iter = ArgumentMap.find(Option); |
336 | 313k | if (Iter == ArgumentMap.end()) { |
337 | 246 | return cxx20::unexpected<Error>(std::in_place, ErrCode::InvalidArgument, |
338 | 246 | "unknown option: "s + std::string(Option)); |
339 | 246 | } |
340 | 313k | ArgumentDescriptor &CurrentDesc = ArgumentDescriptors[Iter->second]; |
341 | 313k | if (CurrentDesc.max_nargs() == 0) { |
342 | 1.35k | CurrentDesc.default_value(); |
343 | 1.35k | return nullptr; |
344 | 1.35k | } |
345 | 312k | return &CurrentDesc; |
346 | 313k | } |
347 | | |
348 | | cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error> |
349 | | ArgumentParser::SubCommandDescriptor::consume_argument( |
350 | 1.58M | ArgumentDescriptor &CurrentDesc, std::string_view Argument) noexcept { |
351 | 1.58M | if (auto Res = CurrentDesc.argument(std::string(Argument)); !Res) { |
352 | 299 | return cxx20::unexpected(Res.error()); |
353 | 299 | } |
354 | 1.58M | if (++CurrentDesc.nargs() >= CurrentDesc.max_nargs()) { |
355 | 34.8k | return nullptr; |
356 | 34.8k | } |
357 | 1.55M | return &CurrentDesc; |
358 | 1.58M | } |
359 | | |
360 | | bool ArgumentParser::parse(std::FILE *Out, int Argc, |
361 | 1.61k | const char *Argv[]) noexcept { |
362 | 1.61k | if (auto Res = SubCommandDescriptors.front().parse(Out, {}, Argc, Argv, 0, |
363 | 1.61k | VerOpt.value()); |
364 | 1.61k | !Res) { |
365 | 597 | fmt::print(Out, "{}\n"sv, Res.error().message()); |
366 | 597 | return false; |
367 | 1.01k | } else { |
368 | 1.01k | return *Res || VerOpt.value(); |
369 | 1.01k | } |
370 | 1.61k | } |
371 | | |
372 | | } // namespace PO |
373 | | } // namespace WasmEdge |