Coverage Report

Created: 2025-07-23 06:30

/src/WasmEdge/lib/po/argument_parser.cpp
Line
Count
Source (jump to first uncovered line)
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: 2019-2024 Second State INC
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.96k
    const char *Argv[], int ArgP, const bool &VersionOpt) noexcept {
16
1.96k
  ProgramNames.reserve(ProgramNamePrefix.size() + 1);
17
1.96k
  ProgramNames.assign(ProgramNamePrefix.begin(), ProgramNamePrefix.end());
18
1.96k
  if (ArgP < Argc) {
19
1.96k
    ProgramNames.push_back(Argv[ArgP]);
20
1.96k
  }
21
1.96k
  ArgumentDescriptor *CurrentDesc = nullptr;
22
1.96k
  bool FirstNonOption = true;
23
1.96k
  bool Escaped = false;
24
1.96k
  auto PositionalIter = PositionalList.cbegin();
25
3.00M
  for (int ArgI = ArgP + 1; ArgI < Argc; ++ArgI) {
26
3.00M
    std::string_view Arg = Argv[ArgI];
27
3.00M
    if (!Escaped && Arg.size() >= 2 && Arg[0] == '-') {
28
360k
      if (Arg[1] == '-') {
29
357k
        if (Arg.size() == 2) {
30
101
          Escaped = true;
31
357k
        } else {
32
          // long option
33
357k
          if (CurrentDesc && CurrentDesc->nargs() == 0) {
34
2.27k
            CurrentDesc->default_value();
35
2.27k
          }
36
357k
          if (auto Res = consume_long_option_with_argument(Arg); !Res) {
37
348
            return cxx20::unexpected(Res.error());
38
357k
          } else {
39
357k
            CurrentDesc = *Res;
40
357k
          }
41
357k
        }
42
357k
      } else {
43
        // short options
44
2.55k
        if (CurrentDesc && CurrentDesc->nargs() == 0) {
45
379
          CurrentDesc->default_value();
46
379
        }
47
2.55k
        if (auto Res = consume_short_options(Arg); !Res) {
48
33
          return cxx20::unexpected(Res.error());
49
2.51k
        } else {
50
2.51k
          CurrentDesc = *Res;
51
2.51k
        }
52
2.55k
      }
53
2.64M
    } else if (!Escaped && CurrentDesc) {
54
2.38k
      consume_argument(*CurrentDesc, Arg);
55
2.38k
      CurrentDesc = nullptr;
56
2.63M
    } else {
57
      // no more options
58
2.63M
      if (FirstNonOption) {
59
776
        FirstNonOption = false;
60
776
        if (!SubCommandMap.empty()) {
61
0
          if (auto Iter = SubCommandMap.find(Arg);
62
0
              Iter != SubCommandMap.end()) {
63
0
            auto &Child = this[Iter->second];
64
0
            Child.SC->select();
65
0
            return Child.parse(Out, ProgramNames, Argc, Argv, ArgI, VersionOpt);
66
0
          }
67
0
        }
68
776
      }
69
2.63M
      Escaped = true;
70
2.63M
      if (CurrentDesc) {
71
2.63M
        if (auto Res = consume_argument(*CurrentDesc, Arg); !Res) {
72
2
          return cxx20::unexpected(Res.error());
73
2.63M
        } else {
74
2.63M
          CurrentDesc = *Res;
75
2.63M
        }
76
2.63M
      } else {
77
1.14k
        if (PositionalIter == PositionalList.cend()) {
78
0
          return cxx20::unexpected<Error>(
79
0
              std::in_place, ErrCode::InvalidArgument,
80
0
              "positional argument exceeds maximum consuming."s);
81
0
        }
82
1.14k
        if (auto Res =
83
1.14k
                consume_argument(ArgumentDescriptors[*PositionalIter], Arg);
84
1.14k
            !Res) {
85
0
          return cxx20::unexpected(Res.error());
86
1.14k
        } else {
87
1.14k
          CurrentDesc = *Res;
88
1.14k
        }
89
1.14k
        ++PositionalIter;
90
1.14k
      }
91
2.63M
    }
92
3.00M
  }
93
1.58k
  if (CurrentDesc && CurrentDesc->nargs() == 0) {
94
29
    CurrentDesc->default_value();
95
29
  }
96
97
1.58k
  if (VersionOpt) {
98
314
    return true;
99
314
  }
100
1.26k
  if (!HelpOpt->value()) {
101
19.0k
    for (const auto &Desc : ArgumentDescriptors) {
102
19.0k
      if (Desc.nargs() < Desc.min_nargs()) {
103
566
        help(Out);
104
566
        return false;
105
566
      }
106
19.0k
    }
107
1.20k
  } else {
108
61
    help(Out);
109
61
    return true;
110
61
  }
111
642
  return true;
112
1.26k
}
113
114
void ArgumentParser::SubCommandDescriptor::usage(
115
627
    std::FILE *Out) const noexcept {
116
627
  fmt::print(Out, "{}USAGE{}\n"sv, YELLOW_COLOR, RESET_COLOR);
117
627
  for (const char *Part : ProgramNames) {
118
627
    fmt::print(Out, "\t{}"sv, Part);
119
627
  }
120
627
  if (!SubCommandList.empty()) {
121
0
    fmt::print(Out, " [SUBCOMMANDS]"sv);
122
0
  }
123
627
  if (!NonpositionalList.empty()) {
124
627
    fmt::print(Out, " [OPTIONS]"sv);
125
627
  }
126
627
  bool First = true;
127
1.25k
  for (const auto &Index : PositionalList) {
128
1.25k
    const auto &Desc = ArgumentDescriptors[Index];
129
1.25k
    if (Desc.hidden()) {
130
0
      continue;
131
0
    }
132
133
1.25k
    if (First) {
134
627
      fmt::print(Out, " [--]"sv);
135
627
      First = false;
136
627
    }
137
138
1.25k
    const bool Optional = (Desc.min_nargs() == 0);
139
1.25k
    fmt::print(Out, " "sv);
140
1.25k
    if (Optional) {
141
627
      fmt::print(Out, "["sv);
142
627
    }
143
1.25k
    switch (ArgumentDescriptors[Index].max_nargs()) {
144
0
    case 0:
145
0
      break;
146
627
    case 1:
147
627
      fmt::print(Out, "{}"sv, Desc.meta());
148
627
      break;
149
627
    default:
150
627
      fmt::print(Out, "{} ..."sv, Desc.meta());
151
627
      break;
152
1.25k
    }
153
1.25k
    if (Optional) {
154
627
      fmt::print(Out, "]"sv);
155
627
    }
156
1.25k
  }
157
627
  fmt::print(Out, "\n"sv);
158
627
}
159
160
627
void ArgumentParser::SubCommandDescriptor::help(std::FILE *Out) const noexcept {
161
// For enabling Windows PowerShell color support.
162
#if WASMEDGE_OS_WINDOWS && WINAPI_PARTITION_DESKTOP
163
  winapi::HANDLE_ OutputHandler =
164
      winapi::GetStdHandle(winapi::STD_OUTPUT_HANDLE_);
165
  if (OutputHandler != winapi::INVALID_HANDLE_VALUE_) {
166
    winapi::DWORD_ ConsoleMode = 0;
167
    if (winapi::GetConsoleMode(OutputHandler, &ConsoleMode)) {
168
      ConsoleMode |= winapi::ENABLE_VIRTUAL_TERMINAL_PROCESSING_;
169
      winapi::SetConsoleMode(OutputHandler, ConsoleMode);
170
    }
171
  }
172
#endif
173
174
627
  usage(Out);
175
627
  const constexpr std::string_view kIndent = "\t"sv;
176
177
627
  fmt::print(Out, "\n"sv);
178
627
  if (!SubCommandList.empty()) {
179
0
    fmt::print(Out, "{}SUBCOMMANDS{}\n"sv, YELLOW_COLOR, RESET_COLOR);
180
0
    for (const auto Offset : SubCommandList) {
181
0
      fmt::print(Out, "{}{}"sv, kIndent, GREEN_COLOR);
182
0
      bool First = true;
183
0
      for (const auto &Name : this[Offset].SubCommandNames) {
184
0
        if (!First) {
185
0
          fmt::print(Out, "|"sv);
186
0
        }
187
0
        fmt::print(Out, "{}"sv, Name);
188
0
        First = false;
189
0
      }
190
0
      fmt::print(Out, "{}\n"sv, RESET_COLOR);
191
0
      indent_output(Out, kIndent, 2, 80, this[Offset].SC->description());
192
0
      fmt::print(Out, "\n"sv);
193
0
    }
194
0
    fmt::print(Out, "\n"sv);
195
0
  }
196
197
627
  fmt::print(Out, "{}OPTIONS{}\n"sv, YELLOW_COLOR, RESET_COLOR);
198
15.6k
  for (const auto &Index : NonpositionalList) {
199
15.6k
    const auto &Desc = ArgumentDescriptors[Index];
200
15.6k
    if (Desc.hidden()) {
201
0
      continue;
202
0
    }
203
204
15.6k
    fmt::print(Out, "{}{}\n"sv, kIndent, GREEN_COLOR);
205
15.6k
    bool First = true;
206
16.9k
    for (const auto &Option : Desc.options()) {
207
16.9k
      if (!First) {
208
1.25k
        fmt::print(Out, "|"sv);
209
1.25k
      }
210
16.9k
      if (Option.size() == 1) {
211
1.25k
        fmt::print(Out, "-{}"sv, Option);
212
15.6k
      } else {
213
15.6k
        fmt::print(Out, "--{}"sv, Option);
214
15.6k
      }
215
16.9k
      First = false;
216
16.9k
    }
217
15.6k
    fmt::print(Out, "{}\n"sv, RESET_COLOR);
218
15.6k
    indent_output(Out, kIndent, 2, 80, Desc.description());
219
15.6k
    fmt::print(Out, "\n"sv);
220
15.6k
  }
221
627
}
222
223
void ArgumentParser::SubCommandDescriptor::indent_output(
224
    std::FILE *Out, const std::string_view kIndent, std::size_t IndentCount,
225
15.6k
    std::size_t ScreenWidth, std::string_view Desc) const noexcept {
226
15.6k
  const std::size_t Width = ScreenWidth - kIndent.size() * IndentCount;
227
19.4k
  while (Desc.size() > Width) {
228
3.76k
    const std::size_t SpacePos = Desc.find_last_of(' ', Width);
229
3.76k
    if (SpacePos != std::string_view::npos) {
230
11.2k
      for (std::size_t I = 0; I < IndentCount; ++I) {
231
7.52k
        fmt::print(Out, "{}"sv, kIndent);
232
7.52k
      }
233
3.76k
      fmt::print(Out, "{}\n"sv, Desc.substr(0, SpacePos));
234
3.76k
      const std::size_t WordPos = Desc.find_first_not_of(' ', SpacePos);
235
3.76k
      if (WordPos != std::string_view::npos) {
236
3.76k
        Desc = Desc.substr(WordPos);
237
3.76k
      } else {
238
0
        Desc = {};
239
0
      }
240
3.76k
    }
241
3.76k
  }
242
15.6k
  if (!Desc.empty()) {
243
47.0k
    for (std::size_t I = 0; I < IndentCount; ++I) {
244
31.3k
      fmt::print(Out, "{}"sv, kIndent);
245
31.3k
    }
246
15.6k
    fmt::print(Out, "{}"sv, Desc);
247
15.6k
  }
248
15.6k
}
249
250
cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error>
251
ArgumentParser::SubCommandDescriptor::consume_short_options(
252
2.55k
    std::string_view Arg) noexcept {
253
2.55k
  ArgumentDescriptor *CurrentDesc = nullptr;
254
1.34M
  for (std::size_t I = 1; I < Arg.size(); ++I) {
255
1.34M
    if (CurrentDesc && CurrentDesc->nargs() == 0) {
256
0
      CurrentDesc->default_value();
257
0
    }
258
1.34M
    std::string_view Option = Arg.substr(I, 1);
259
1.34M
    if (auto Res = consume_short_option(Option); !Res) {
260
33
      return cxx20::unexpected(Res.error());
261
1.34M
    } else {
262
1.34M
      CurrentDesc = *Res;
263
1.34M
    }
264
1.34M
  }
265
2.51k
  return CurrentDesc;
266
2.55k
}
267
268
cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error>
269
ArgumentParser::SubCommandDescriptor::consume_long_option_with_argument(
270
357k
    std::string_view Arg) noexcept {
271
357k
  if (auto Pos = Arg.find('=', 2); Pos != std::string_view::npos) {
272
    // long option with argument
273
343k
    std::string_view Option = Arg.substr(2, Pos - 2);
274
343k
    std::string_view Argument = Arg.substr(Pos + 1);
275
343k
    if (auto Res = consume_long_option(Option); !Res) {
276
55
      return cxx20::unexpected<Error>(Res.error());
277
343k
    } else if (ArgumentDescriptor *CurrentDesc = *Res; !CurrentDesc) {
278
15
      return cxx20::unexpected<Error>(std::in_place, ErrCode::InvalidArgument,
279
15
                                      "option "s + std::string(Option) +
280
15
                                          "doesn't need arguments."s);
281
343k
    } else {
282
343k
      consume_argument(*CurrentDesc, Argument);
283
343k
      return nullptr;
284
343k
    }
285
343k
  } else {
286
    // long option without argument
287
14.2k
    std::string_view Option = Arg.substr(2);
288
14.2k
    return consume_long_option(Option);
289
14.2k
  }
290
357k
}
291
292
cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error>
293
ArgumentParser::SubCommandDescriptor::consume_short_option(
294
1.34M
    std::string_view Option) noexcept {
295
1.34M
  auto Iter = ArgumentMap.find(Option);
296
1.34M
  if (Iter == ArgumentMap.end()) {
297
33
    return cxx20::unexpected<Error>(std::in_place, ErrCode::InvalidArgument,
298
33
                                    "unknown option: "s + std::string(Option));
299
33
  }
300
1.34M
  ArgumentDescriptor &CurrentDesc = ArgumentDescriptors[Iter->second];
301
1.34M
  if (CurrentDesc.max_nargs() == 0) {
302
1.34M
    CurrentDesc.default_value();
303
1.34M
    return nullptr;
304
1.34M
  }
305
0
  return &CurrentDesc;
306
1.34M
}
307
308
cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error>
309
ArgumentParser::SubCommandDescriptor::consume_long_option(
310
357k
    std::string_view Option) noexcept {
311
357k
  auto Iter = ArgumentMap.find(Option);
312
357k
  if (Iter == ArgumentMap.end()) {
313
333
    return cxx20::unexpected<Error>(std::in_place, ErrCode::InvalidArgument,
314
333
                                    "unknown option: "s + std::string(Option));
315
333
  }
316
357k
  ArgumentDescriptor &CurrentDesc = ArgumentDescriptors[Iter->second];
317
357k
  if (CurrentDesc.max_nargs() == 0) {
318
2.30k
    CurrentDesc.default_value();
319
2.30k
    return nullptr;
320
2.30k
  }
321
355k
  return &CurrentDesc;
322
357k
}
323
324
cxx20::expected<ArgumentParser::ArgumentDescriptor *, Error>
325
ArgumentParser::SubCommandDescriptor::consume_argument(
326
2.98M
    ArgumentDescriptor &CurrentDesc, std::string_view Argument) noexcept {
327
2.98M
  if (auto Res = CurrentDesc.argument(std::string(Argument)); !Res) {
328
2.07k
    return cxx20::unexpected(Res.error());
329
2.07k
  }
330
2.98M
  if (++CurrentDesc.nargs() >= CurrentDesc.max_nargs()) {
331
66.8k
    return nullptr;
332
66.8k
  }
333
2.91M
  return &CurrentDesc;
334
2.98M
}
335
336
bool ArgumentParser::parse(std::FILE *Out, int Argc,
337
1.96k
                           const char *Argv[]) noexcept {
338
1.96k
  if (auto Res = SubCommandDescriptors.front().parse(Out, {}, Argc, Argv, 0,
339
1.96k
                                                     VerOpt.value());
340
1.96k
      !Res) {
341
383
    fmt::print(Out, "{}\n"sv, Res.error().message());
342
383
    return false;
343
1.58k
  } else {
344
1.58k
    return *Res || VerOpt.value();
345
1.58k
  }
346
1.96k
}
347
348
} // namespace PO
349
} // namespace WasmEdge