master
h 582 lines 13.5 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 CommandLine.h
8
9 Abstract:
10
11 This file contains the command line parsing logic.
12
13 --*/
14
15 #pragma once
16
17 #include <string>
18 #include <functional>
19 #include <type_traits>
20 #include "Localization.h"
21
22 namespace wsl::shared {
23
24 #ifdef WIN32
25
26 #define THROW_USER_ERROR(Message) THROW_HR_WITH_USER_ERROR(E_INVALIDARG, (Message))
27 using TChar = wchar_t;
28
29 #else
30
31 using TChar = char;
32
33 #endif
34
35 using TString = std::basic_string<TChar>;
36
37 using TMatchMethod = std::function<bool(const TChar*, int)>;
38 using TParseMethod = std::function<int(const TChar*)>;
39
40 struct Argument
41 {
42 TMatchMethod Matches;
43 TParseMethod Consume;
44 bool Positional;
45 };
46
47 template <auto Flag, typename T = std::remove_reference_t<decltype(Flag)>>
48 struct SetFlag
49 {
50 T& value;
51
52 void operator()() const
53 {
54 WI_SetFlag(value, Flag);
55 }
56 };
57
58 template <auto Flag, typename T = std::remove_reference_t<decltype(Flag)>>
59 struct ClearFlag
60 {
61 T& value;
62
63 void operator()() const
64 {
65 WI_ClearFlag(value, Flag);
66 }
67 };
68
69 template <typename T>
70 struct is_optional : std::false_type
71 {
72 };
73
74 template <typename T>
75 struct is_optional<std::optional<T>> : std::true_type
76 {
77 };
78
79 template <typename T>
80 struct Integer
81 {
82 T& value;
83
84 int operator()(const TChar* input) const
85 {
86 if (input == nullptr)
87 {
88 return -1;
89 }
90
91 if constexpr (is_optional<T>::value)
92 {
93 value.emplace();
94 return Integer<typename T::value_type>{value.value()}(input);
95 }
96 else
97 {
98
99 #ifdef WIN32
100
101 const auto utf8value = wsl::shared::string::WideToMultiByte(input);
102 auto result = std::from_chars(utf8value.c_str(), utf8value.c_str() + utf8value.size(), value);
103
104 #else
105 std::from_chars_result result{};
106 if constexpr (std::is_enum_v<T>)
107 {
108 result = std::from_chars(input, input + strlen(input), reinterpret_cast<std::underlying_type_t<T>&>(value));
109 }
110 else
111 {
112 result = std::from_chars(input, input + strlen(input), value);
113 }
114
115 #endif
116
117 if (result.ec == std::errc::invalid_argument)
118 {
119 THROW_USER_ERROR(wsl::shared::Localization::MessageInvalidInteger(input));
120 }
121 }
122
123 return 1;
124 }
125 };
126
127 #ifdef WIN32
128 template <typename T>
129 struct UnquotedPath
130 {
131 T& value;
132
133 int operator()(const TChar* input) const
134 {
135 if (input == nullptr)
136 {
137 return -1;
138 }
139
140 value = wsl::windows::common::filesystem::UnquotePath(input);
141
142 return 1;
143 }
144 };
145
146 template <typename T>
147 struct AbsolutePath
148 {
149 T& value;
150
151 int operator()(const TChar* input) const
152 {
153 if (input == nullptr)
154 {
155 return -1;
156 }
157
158 if (PathIsRelative(input))
159 {
160 std::error_code error;
161 value = std::filesystem::absolute(input, error);
162 if (error)
163 {
164 THROW_WIN32(error.value());
165 }
166 }
167 else
168 {
169 value = input;
170 }
171
172 return 1;
173 }
174 };
175
176 template <typename THandle = wil::unique_handle>
177 struct Handle
178 {
179 THandle& output;
180
181 int operator()(const TChar* input) const
182 {
183 if (input == nullptr)
184 {
185 return -1;
186 }
187
188 if constexpr (std::is_same_v<THandle, wil::unique_socket>)
189 {
190 output.reset(reinterpret_cast<SOCKET>(ULongToHandle(wcstoul(input, nullptr, 0))));
191 }
192 else
193 {
194 output.reset(ULongToHandle(wcstoul(input, nullptr, 0)));
195 }
196
197 return 1;
198 }
199 };
200
201 struct Utf8String
202 {
203 std::string& Value;
204
205 int operator()(const TChar* Input) const
206 {
207 if (Input == nullptr)
208 {
209 return -1;
210 }
211
212 Value = wsl::shared::string::WideToMultiByte(Input);
213
214 return 1;
215 }
216 };
217
218 #else
219
220 struct UniqueFd
221 {
222 wil::unique_fd& output;
223
224 int operator()(const TChar* input)
225 {
226 if (input == nullptr)
227 {
228 return -1;
229 }
230
231 int fd = -1;
232 auto result = std::from_chars(input, input + strlen(input), fd);
233 if (result.ec == std::errc::invalid_argument || fd < 0)
234 {
235 THROW_USER_ERROR(wsl::shared::Localization::MessageInvalidInteger(input));
236 }
237
238 output.reset(fd);
239
240 return 1;
241 }
242 };
243
244 #endif
245
246 template <typename T>
247 struct ParsedBool
248 {
249 T& value;
250
251 int operator()(const TChar* input) const
252 {
253 if (input == nullptr)
254 {
255 return -1;
256 }
257
258 auto result = wsl::shared::string::ParseBool(input);
259 if (!result.has_value())
260 {
261 THROW_USER_ERROR(wsl::shared::Localization::MessageInvalidBoolean(input));
262 }
263
264 value = result.value();
265 return 1;
266 }
267 };
268
269 template <typename T>
270 struct SizeString
271 {
272 T& value;
273
274 int operator()(const TChar* input) const
275 {
276 if (input == nullptr)
277 {
278 return -1;
279 }
280
281 auto parsed = wsl::shared::string::ParseMemorySize(input);
282 if (!parsed.has_value())
283 {
284 THROW_USER_ERROR(wsl::shared::Localization::MessageInvalidSize(input));
285 }
286
287 value = parsed;
288 return 1;
289 }
290 };
291
292 struct NoOp
293 {
294 void operator()(const TChar*) const
295 {
296 }
297 };
298
299 template <typename T, T SetValue>
300 struct UniqueSetValue
301 {
302 std::optional<T>& value;
303 std::function<TString()> errorMessage;
304
305 void operator()()
306 {
307 if (value.has_value())
308 {
309 THROW_USER_ERROR(errorMessage());
310 }
311
312 value = SetValue;
313 }
314 };
315
316 class ArgumentParser
317 {
318 public:
319 #ifdef WIN32
320
321 ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool ignoreUnknownArgs = false) :
322 m_parseIndex(StartIndex), m_name(Name), m_ignoreUnknownArgs(ignoreUnknownArgs)
323 {
324 m_argv.reset(CommandLineToArgvW(std::wstring(CommandLine).c_str(), &m_argc));
325 THROW_LAST_ERROR_IF(!m_argv);
326 }
327
328 #else
329
330 ArgumentParser(int argc, const char* const* argv, bool ignoreUnknownArgs = false) :
331 m_argc(argc), m_argv(argv), m_parseIndex(1), m_ignoreUnknownArgs(ignoreUnknownArgs)
332 {
333 }
334
335 #endif
336
337 template <typename T>
338 void AddArgument(T&& Output, const TChar* LongName, TChar ShortName = '\0')
339 {
340 auto match = [LongName, ShortName](const TChar* Name, int Pos) {
341 if (Name == nullptr)
342 {
343 return false;
344 }
345 else if (LongName != nullptr && wsl::shared::string::IsEqual(Name, LongName))
346 {
347 return true;
348 }
349 else
350 {
351 return ShortName != '\0' && Name[0] == '-' && Name[1] == ShortName && Name[2] == '\0';
352 }
353 };
354
355 m_arguments.emplace_back(std::move(match), BuildParseMethod(std::forward<T>(Output)), false);
356 }
357
358 template <typename T>
359 void AddPositionalArgument(T&& Output, int Position)
360 {
361 WI_ASSERT(Position >= 0);
362
363 auto match = [Position](const TChar*, int Index) { return Position == Index; };
364
365 m_arguments.emplace_back(std::move(match), BuildParseMethod(std::forward<T>(Output)), true);
366 }
367
368 void Parse()
369 {
370 int argumentPosition = 0;
371 bool stopParameters = false;
372 for (; m_parseIndex < m_argc; m_parseIndex++)
373 {
374 if (!stopParameters && wsl::shared::string::IsEqual(m_argv[m_parseIndex], TEXT("--")))
375 {
376 stopParameters = true;
377 continue;
378 }
379
380 bool foundMatch = false;
381 int offset = 0;
382
383 // Special case for short argument with multiple values like -abc
384 if (!stopParameters && m_argv[m_parseIndex][0] == '-' && m_argv[m_parseIndex][1] != '-' &&
385 m_argv[m_parseIndex][1] != '\0' && m_argv[m_parseIndex][2] != '\0')
386 {
387 for (const auto* arg = &m_argv[m_parseIndex][1]; *arg != '\0'; arg++)
388 {
389 foundMatch = false;
390 for (const auto& e : m_arguments)
391 {
392 const TChar fullArgument[] = {'-', *arg, '\0'};
393 if (!e.Positional && e.Matches(fullArgument, -1))
394 {
395 e.Consume(nullptr);
396 foundMatch = true;
397 break;
398 }
399 }
400
401 if (!foundMatch)
402 {
403 break;
404 }
405 }
406 }
407
408 if (!foundMatch)
409 {
410 for (const auto& e : m_arguments)
411 {
412 if (e.Matches(
413 stopParameters ? nullptr : m_argv[m_parseIndex],
414 m_argv[m_parseIndex][0] == '-' && m_argv[m_parseIndex][1] != '\0' && !stopParameters ? -1 : argumentPosition))
415 {
416 const TChar* value = nullptr;
417 if (e.Positional)
418 {
419 value = m_argv[m_parseIndex]; // Positional arguments directly receive argv[i]
420 }
421 else if (m_parseIndex + 1 < m_argc)
422 {
423 value = m_argv[m_parseIndex + 1];
424 }
425
426 offset = e.Consume(value);
427 if (offset < 0)
428 {
429 WI_ASSERT(value == nullptr);
430 THROW_USER_ERROR(
431 wsl::shared::Localization::MessageMissingArgument(m_argv[m_parseIndex], m_name ? m_name : m_argv[0]));
432 }
433
434 if (e.Positional) // Positional arguments can't consume extra arguments.
435 {
436 offset = 0;
437 }
438
439 m_parseIndex += offset;
440 foundMatch = true;
441
442 break;
443 }
444 }
445 }
446
447 if (!foundMatch)
448 {
449 if (m_ignoreUnknownArgs)
450 {
451 break;
452 }
453
454 THROW_USER_ERROR(wsl::shared::Localization::MessageInvalidCommandLine(m_argv[m_parseIndex], m_name ? m_name : m_argv[0]));
455 }
456
457 if (m_parseIndex < m_argc && m_argv[m_parseIndex - offset][0] != '-')
458 {
459 argumentPosition++;
460 }
461 }
462 }
463
464 size_t ParseIndex() const noexcept
465 {
466 return m_parseIndex;
467 }
468
469 size_t Argc() const noexcept
470 {
471 return m_argc;
472 }
473
474 const auto* Argv(size_t Index) const noexcept
475 {
476 WI_ASSERT(Index < static_cast<size_t>(m_argc));
477 return m_argv[Index];
478 }
479
480 private:
481 template <typename T>
482 static std::function<int(const TChar*)> BuildParseMethod(T&& Output)
483 {
484 if constexpr (std::is_rvalue_reference_v<T&&>)
485 {
486 return [Output = std::move(Output)](const TChar* Value) mutable { return ParseArgumentImpl<T>(Output, Value); };
487 }
488 else
489 {
490 return [&Output](const TChar* Value) mutable { return ParseArgumentImpl<T>(Output, Value); };
491 }
492 }
493
494 template <typename T>
495 static int ParseArgumentImpl(T& Output, const TChar* Value)
496 {
497 if constexpr (std::is_invocable_v<T, const TChar*>) // Callable with const TChar*.
498 {
499 if constexpr (std::is_same_v<std::invoke_result_t<T, const TChar*>, void>)
500 {
501 Output(Value);
502 return 0;
503 }
504 else
505 {
506 return Output(Value);
507 }
508 }
509 else if constexpr (std::is_invocable_v<T>) // Callable without argument.
510 {
511 Output();
512 return 0;
513 }
514 else // Simple type.
515 {
516 return ParseSimpleArgument<std::remove_cvref_t<T>>(Output, Value);
517 }
518 }
519
520 template <typename T>
521 static int ParseSimpleArgument(T& Output, const TChar* Value)
522 {
523 // If this is a flag, just set the flag and exit
524 if constexpr (std::is_same_v<T, bool>)
525 {
526 Output = true;
527 return 0;
528 }
529 else
530 {
531 // Otherwise, we need an actual value
532 if (Value == nullptr)
533 {
534 return -1;
535 }
536
537 if constexpr (std::is_same_v<T, GUID> || std::is_same_v<T, std::optional<GUID>>)
538 {
539 auto guid = wsl::shared::string::ToGuid(Value);
540 if (!guid.has_value())
541 {
542 THROW_USER_ERROR(wsl::shared::Localization::MessageInvalidGuid(Value));
543 }
544
545 Output = guid.value();
546 }
547 else
548 {
549
550 // If this assert is hit, an unsupported type was passed.
551 static_assert(
552 std::is_same_v<T, TString> || std::is_same_v<T, std::optional<TString>> ||
553 std::is_same_v<T, std::filesystem::path> || std::is_same_v<T, const TChar*>);
554
555 Output = Value;
556 }
557 return 1;
558 }
559 }
560
561 std::vector<Argument> m_arguments;
562 int m_argc{};
563
564 #ifdef WIN32
565
566 wil::unique_hlocal_ptr<LPWSTR[]> m_argv;
567
568 #else
569
570 const char* const* m_argv{};
571
572 #endif
573
574 int m_parseIndex{};
575 const TChar* m_name{};
576 bool m_ignoreUnknownArgs{false};
577 };
578 } // namespace wsl::shared
579
580 #ifdef WIN32
581 #undef THROW_USER_ERROR
582 #endif