Coverage Report

Created: 2026-11-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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