CLI: Cache converted CLI arguments with on-demand revalidation, and unify typed value accessors (#41189)
David Bennett committed
Aug 7, 2026 at 18:19 UTC
3e27a6a50177ce4e32e72f7b957aef5db5768062
39 files changed
+1435
-579
localization/strings/en-US/Resources.resw
+3
@@ -3135,6 +3135,9 @@ On first run, creates the file with all settings commented out at their defaults
3135
<data name="WSLCCLI_SessionStoragePositionalArgDescription" xml:space="preserve">
3136
<value>Session storage path</value>
3137
</data>
3138
+ <data name="WSLCCLI_StoragePathArgDescription" xml:space="preserve">
3139
+ <value>Path to the session storage directory</value>
3140
+ </data>
3141
<data name="WSLCCLI_SignalArgDescription" xml:space="preserve">
3142
<value>Signal to send</value>
3143
</data>
src/windows/common/EnumVariantMap.h
+4
-2
@@ -54,12 +54,14 @@ enum class EnumBasedVariantMapAction
54
Add,
55
Contains,
56
Get,
57
+ GetMutable,
58
GetAll,
59
Count,
60
Remove,
61
};
62
62
-// A callback function that can be used for logging map actions.
63
+// A callback function that can take any action in response to map operations, such as logging
64
+// accesses or maintaining state derived from the map contents.
65
template <typename Enum>
66
using EnumBasedVariantMapActionCallback = void (*)(const void* map, Enum value, EnumBasedVariantMapAction action);
67
@@ -172,7 +174,7 @@ struct EnumBasedVariantMap
174
{
175
if constexpr (Callback)
176
{
175
- Callback(this, E, EnumBasedVariantMapAction::Get);
177
+ Callback(this, E, EnumBasedVariantMapAction::GetMutable);
178
}
179
auto itr = m_data.find(E);
180
THROW_HR_IF_MSG(E_NOT_SET, itr == m_data.end(), "Get(%d): key not found", static_cast<int>(E));
src/windows/wslc/arguments/ArgMap.h
new
+352
@@ -0,0 +1,352 @@
1
+/*++
2
+
3
+Copyright (c) Microsoft. All rights reserved.
4
+
5
+Module Name:
6
+
7
+ ArgMap.h
8
+
9
+Abstract:
10
+
11
+ Declaration of ArgMap, the container for parsed command-line arguments and their validated
12
+ (converted) value cache. Split out from ArgumentTypes.h, which holds only the argument enums
13
+ and type mappings ArgMap is built on.
14
+
15
+--*/
16
+#pragma once
17
+#include "ArgumentTypes.h"
18
+#include "EnumVariantMap.h"
19
+#include <any>
20
+#include <map>
21
+#include <set>
22
+#include <type_traits>
23
+#include <vector>
24
+#include <utility>
25
+
26
+namespace wsl::windows::wslc::argument {
27
+
28
+struct ArgMap;
29
+
30
+namespace details {
31
+ struct RawArgMapAccess;
32
+
33
+ template <ArgType E, bool IsFlag = std::is_same_v<typename ArgDataMapping<E>::value_t, bool>>
34
+ struct ArgValueTraits;
35
+
36
+ template <ArgType E>
37
+ struct ArgValueTraits<E, true>
38
+ {
39
+ using value_t = typename ArgDataMapping<E>::value_t;
40
+ static constexpr bool Converted = false;
41
+ };
42
+
43
+ template <ArgType E>
44
+ struct ArgValueTraits<E, false>
45
+ {
46
+ using converted_t = typename ArgConvertedTypeMapping<E>::value_t;
47
+ using value_t = std::conditional_t<std::is_same_v<converted_t, NoConversion>, typename ArgDataMapping<E>::value_t, converted_t>;
48
+ static constexpr bool Converted = !std::is_same_v<converted_t, NoConversion>;
49
+ };
50
+} // namespace details
51
+
52
+// Validates one argument on demand against its current raw values. Defined in ArgumentValidation.cpp
53
+// so this header stays decoupled from the converter/domain headers.
54
+void EnsureArgumentValidated(ArgMap& map, ArgType type);
55
+
56
+// Map-action callback (defined after ArgMap, as it calls a member): operations that can mutate raw
57
+// values update that ArgType's validation state.
58
+inline void ArgMapInvalidateValidatedCache(const void* map, ArgType type, EnumBasedVariantMapAction action);
59
+
60
+// This is the main ArgType map used for storing parsed arguments.
61
+struct ArgMap : private wsl::windows::wslc::EnumBasedVariantMap<ArgType, wsl::windows::wslc::argument::details::ArgDataMapping, &ArgMapInvalidateValidatedCache>
62
+{
63
+private:
64
+ using Base = wsl::windows::wslc::EnumBasedVariantMap<ArgType, wsl::windows::wslc::argument::details::ArgDataMapping, &ArgMapInvalidateValidatedCache>;
65
+
66
+ friend struct details::RawArgMapAccess;
67
+
68
+ // Raw reads are implementation details used by validation and the typed accessors below.
69
+ // Callers consume arguments through GetValue/GetAllValues so reads validate and freeze them.
70
+ using Base::Get;
71
+ using Base::GetAll;
72
+
73
+public:
74
+ ArgMap() = default;
75
+ ArgMap(const ArgMap&) = default;
76
+ ArgMap(ArgMap&&) = default;
77
+ ArgMap& operator=(const ArgMap&) = delete;
78
+ ArgMap& operator=(ArgMap&&) = delete;
79
+
80
+ using Base::Add;
81
+ using Base::Contains;
82
+ using Base::Count;
83
+ using Base::GetCount;
84
+ using Base::GetKeys;
85
+ using Base::IsMatchingType;
86
+ using Base::Remove;
87
+
88
+ template <ArgType E>
89
+ using value_t = typename details::ArgValueTraits<E>::value_t;
90
+
91
+ // Validated-value cache. Argument validation converts raw strings into typed values and caches
92
+ // them here so execution reuses them without re-parsing. The store is type-erased (std::any keyed
93
+ // by ArgType) to keep this base header free of the domain headers that define the converted types;
94
+ // access is by a compile-time ArgType whose value type is derived from the argument's ConvertedType
95
+ // (ArgumentConvertedTypes.h), so a wrong-type access is a compile error. A multimap preserves order
96
+ // and multiplicity for arguments that allow multiple values.
97
+ template <ArgType E>
98
+ void AddValidated(typename details::ArgConvertedTypeMapping<E>::value_t value)
99
+ {
100
+ using value_t = typename details::ArgConvertedTypeMapping<E>::value_t;
101
+ static_assert(
102
+ !std::is_same_v<value_t, details::NoConversion>,
103
+ "This argument has no converted type (NoConversion); it cannot be cached. "
104
+ "Declare its ConvertedType in ArgumentDefinitions.h to enable caching.");
105
+
106
+ ThrowIfImmutable(E, "add converted validation data");
107
+ m_validated.emplace(E, std::any{std::move(value)});
108
+ }
109
+
110
+ bool ContainsValidated(ArgType type) const
111
+ {
112
+ return m_validated.find(type) != m_validated.end();
113
+ }
114
+
115
+ size_t CountValidated(ArgType type) const
116
+ {
117
+ return m_validated.count(type);
118
+ }
119
+
120
+ // Drops `type`'s memoized validation state (converted cache and validated record) so it never
121
+ // outlives the raw data.
122
+ void InvalidateValidated(ArgType type)
123
+ {
124
+ ThrowIfImmutable(type, "invalidate cached validation data");
125
+ ClearValidated(type);
126
+ }
127
+
128
+ // Records `type` as validated for its current raw values so reads skip re-validation.
129
+ void MarkValidated(ArgType type)
130
+ {
131
+ ThrowIfImmutable(type, "mark the argument as validated");
132
+ m_validatedTypes.insert(type);
133
+ }
134
+
135
+ void HandleMapMutation(ArgType type, EnumBasedVariantMapAction action)
136
+ {
137
+ WI_ASSERT(action == EnumBasedVariantMapAction::Add || action == EnumBasedVariantMapAction::GetMutable || action == EnumBasedVariantMapAction::Remove);
138
+
139
+ const char* operation = nullptr;
140
+ switch (action)
141
+ {
142
+ case EnumBasedVariantMapAction::Add:
143
+ operation = "add a raw argument value";
144
+ break;
145
+
146
+ case EnumBasedVariantMapAction::GetMutable:
147
+ operation = "get mutable access to a raw argument value";
148
+ break;
149
+
150
+ case EnumBasedVariantMapAction::Remove:
151
+ operation = "remove the raw argument values";
152
+ break;
153
+
154
+ default:
155
+ WI_ASSERT(false);
156
+ return;
157
+ }
158
+
159
+ ThrowIfImmutable(type, operation);
160
+ ClearValidated(type);
161
+ }
162
+
163
+ // Reads an argument in one call: the cached converted value if the argument declares a
164
+ // ConvertedType, otherwise the raw parsed value. An absent argument resolves to defaultValue,
165
+ // which defaults to the value type's default constructor. The first resolved default is retained
166
+ // so later reads return the same effective value. Caller-provided defaults are already typed and
167
+ // do not populate the raw map or change Contains(). A successful read makes the argument immutable.
168
+ template <ArgType E>
169
+ const value_t<E>& GetValue(value_t<E> defaultValue = {})
170
+ {
171
+ if (const auto* resolvedDefault = GetResolvedDefault<E>())
172
+ {
173
+ return *resolvedDefault;
174
+ }
175
+
176
+ if (!Contains(E))
177
+ {
178
+ auto [itr, inserted] = m_resolvedDefaults.emplace(E, std::any{std::move(defaultValue)});
179
+ WI_ASSERT(inserted);
180
+ MarkImmutable(E);
181
+
182
+ const auto* value = std::any_cast<value_t<E>>(&itr->second);
183
+ WI_ASSERT_MSG(value != nullptr, "resolved default holds the wrong type for this argument");
184
+ return *value;
185
+ }
186
+
187
+ if constexpr (!details::ArgValueTraits<E>::Converted)
188
+ {
189
+ // Validate-only arguments have no converted cache but can still fail validation, so run
190
+ // it on demand before returning the raw value (covers values added post-validation).
191
+ EnsureValidated(E);
192
+ const auto& value = std::as_const(*this).template Get<E>();
193
+ MarkImmutable(E);
194
+ return value;
195
+ }
196
+ else
197
+ {
198
+ const auto& value = GetValidated<E>();
199
+ MarkImmutable(E);
200
+ return value;
201
+ }
202
+ }
203
+
204
+ // Like GetValue, but returns every value for an argument that may appear multiple times (ArgMap
205
+ // is a multimap), in insertion order. An absent argument returns an empty vector.
206
+ template <ArgType E>
207
+ auto GetAllValues()
208
+ {
209
+ static_assert(details::ArgDataMapping<E>::c_kind != Kind::Flag, "GetAllValues is not valid for Kind::Flag arguments.");
210
+
211
+ if constexpr (!details::ArgValueTraits<E>::Converted)
212
+ {
213
+ // See GetValue: ensure validate-only arguments are checked on demand too.
214
+ EnsureValidated(E);
215
+ auto values = GetAll<E>();
216
+ MarkImmutable(E);
217
+ return values;
218
+ }
219
+ else
220
+ {
221
+ auto values = GetAllValidated<E>();
222
+ MarkImmutable(E);
223
+ return values;
224
+ }
225
+ }
226
+
227
+private:
228
+ // Validates `type` against its current raw values unless already recorded as validated. The
229
+ // record is set by a completed validation and cleared by the map-action callback on any raw
230
+ // Add/Remove, so an argument added or overwritten after the up-front pass is validated on
231
+ // demand, and its errors reported, exactly like a command-line value.
232
+ void EnsureValidated(ArgType type)
233
+ {
234
+ if (m_validatedTypes.count(type) != 0)
235
+ {
236
+ return;
237
+ }
238
+
239
+ EnsureArgumentValidated(*this, type);
240
+ }
241
+
242
+ // Branch helper for GetValue's converted path. Private so callers go through GetValue.
243
+ template <ArgType E>
244
+ const typename details::ArgConvertedTypeMapping<E>::value_t& GetValidated()
245
+ {
246
+ using value_t = typename details::ArgConvertedTypeMapping<E>::value_t;
247
+ static_assert(
248
+ !std::is_same_v<value_t, details::NoConversion>,
249
+ "This argument has no converted type (NoConversion); it cannot be read from the cache. "
250
+ "Declare its ConvertedType in ArgumentDefinitions.h to enable caching.");
251
+
252
+ // Validate on demand if `E` is not recorded as validated (added or overwritten after the
253
+ // up-front pass), so the value read here is converted and its errors reported as usual.
254
+ EnsureValidated(E);
255
+
256
+ auto itr = m_validated.find(E);
257
+ THROW_HR_IF_MSG(E_NOT_SET, itr == m_validated.end(), "GetValidated(%d): argument not validated", static_cast<int>(E));
258
+
259
+ // any_cast cannot fail: entries under key E are only ever written by AddValidated<E>, which
260
+ // stores exactly value_t. A null result is an internal invariant violation, not a runtime case.
261
+ const value_t* value = std::any_cast<value_t>(&itr->second);
262
+ WI_ASSERT_MSG(value != nullptr, "validated cache holds the wrong type for this argument");
263
+
264
+ return *value;
265
+ }
266
+
267
+ // Branch helper for GetAllValues's converted path. Private so callers go through GetAllValues.
268
+ template <ArgType E>
269
+ std::vector<typename details::ArgConvertedTypeMapping<E>::value_t> GetAllValidated()
270
+ {
271
+ using value_t = typename details::ArgConvertedTypeMapping<E>::value_t;
272
+ static_assert(
273
+ !std::is_same_v<value_t, details::NoConversion>,
274
+ "This argument has no converted type (NoConversion); it cannot be read from the cache. "
275
+ "Declare its ConvertedType in ArgumentDefinitions.h to enable caching.");
276
+
277
+ // See GetValidated: validate on demand if `E`'s validated record was cleared post-validation.
278
+ EnsureValidated(E);
279
+
280
+ std::vector<value_t> results;
281
+ auto range = m_validated.equal_range(E);
282
+ for (auto it = range.first; it != range.second; ++it)
283
+ {
284
+ // See GetValidated: any_cast cannot fail for a correctly populated cache.
285
+ const value_t* value = std::any_cast<value_t>(&it->second);
286
+ WI_ASSERT_MSG(value != nullptr, "validated cache holds the wrong type for this argument");
287
+ results.push_back(*value);
288
+ }
289
+
290
+ return results;
291
+ }
292
+
293
+ template <ArgType E>
294
+ const value_t<E>* GetResolvedDefault() const
295
+ {
296
+ const auto itr = m_resolvedDefaults.find(E);
297
+ if (itr == m_resolvedDefaults.end())
298
+ {
299
+ return nullptr;
300
+ }
301
+
302
+ const auto* value = std::any_cast<value_t<E>>(&itr->second);
303
+ WI_ASSERT_MSG(value != nullptr, "resolved default holds the wrong type for this argument");
304
+ return value;
305
+ }
306
+
307
+ void MarkImmutable(ArgType type)
308
+ {
309
+ m_immutableTypes.insert(type);
310
+ }
311
+
312
+ void ClearValidated(ArgType type)
313
+ {
314
+ m_validated.erase(type);
315
+ m_validatedTypes.erase(type);
316
+ }
317
+
318
+ void ThrowIfImmutable(ArgType type, const char* operation) const
319
+ {
320
+ THROW_HR_IF_MSG(
321
+ E_ILLEGAL_METHOD_CALL,
322
+ m_immutableTypes.count(type) != 0,
323
+ "ArgMap argument %d is immutable because its effective value was already read by GetValue/GetAllValues; attempted to "
324
+ "%hs",
325
+ static_cast<int>(type),
326
+ operation);
327
+ }
328
+
329
+ std::multimap<ArgType, std::any> m_validated;
330
+ std::map<ArgType, std::any> m_resolvedDefaults;
331
+
332
+ // ArgTypes validated against their current raw values. Distinct from m_validated (only converted
333
+ // arguments populate that), so validate-only arguments are covered too. Cleared per type by
334
+ // InvalidateValidated on a raw Add/Remove.
335
+ std::set<ArgType> m_validatedTypes;
336
+
337
+ // A successful GetValue/GetAllValues makes that ArgType's raw and validated data immutable.
338
+ std::set<ArgType> m_immutableTypes;
339
+};
340
+
341
+// Only operations that can mutate raw values affect validation state; const reads are ignored.
342
+// Recovering the non-const ArgMap from the callback's type-erased pointer is valid because these
343
+// actions originate from non-const base operations. The base subobject is at offset 0 of ArgMap.
344
+inline void ArgMapInvalidateValidatedCache(const void* map, ArgType type, EnumBasedVariantMapAction action)
345
+{
346
+ if (action == EnumBasedVariantMapAction::Add || action == EnumBasedVariantMapAction::GetMutable || action == EnumBasedVariantMapAction::Remove)
347
+ {
348
+ const_cast<ArgMap*>(static_cast<const ArgMap*>(map))->HandleMapMutation(type, action);
349
+ }
350
+}
351
+
352
+} // namespace wsl::windows::wslc::argument
src/windows/wslc/arguments/Argument.cpp
+1
-1
@@ -40,7 +40,7 @@ Argument Argument::Create(ArgType type, std::optional<bool> required, std::optio
40
{
41
switch (type)
42
{
43
-#define WSLC_ARG_CREATE_CASE(EnumName, Name, Alias, ArgumentKind, Desc) \
43
+#define WSLC_ARG_CREATE_CASE(EnumName, Name, Alias, ArgumentKind, ConvertedType, Desc) \
44
case ArgType::EnumName: \
45
return Argument{ \
46
type, \
src/windows/wslc/arguments/Argument.h
+4
-3
@@ -12,7 +12,7 @@ Abstract:
12
13
--*/
14
#pragma once
15
-#include "ArgumentTypes.h"
15
+#include "ArgMap.h"
16
17
#include <string>
18
@@ -105,8 +105,9 @@ struct Argument
105
return m_limit == argument::Limit::Unlimited;
106
}
107
108
- // Validates this argument's value in the provided args
109
- void Validate(const ArgMap& execArgs) const;
108
+ // Validates this argument's current values, caching the converted result (converted arguments
109
+ // only) on `execArgs` so reads reuse it without re-parsing until the raw values change.
110
+ void Validate(ArgMap& execArgs) const;
111
112
private:
113
ArgType m_argType;
src/windows/wslc/arguments/ArgumentConvertedTypes.h
new
+60
@@ -0,0 +1,60 @@
1
+/*++
2
+
3
+Copyright (c) Microsoft. All rights reserved.
4
+
5
+Module Name:
6
+
7
+ ArgumentConvertedTypes.h
8
+
9
+Abstract:
10
+
11
+ Declaration of the converted-value type aliases and ArgConvertedTypeMapping specializations that
12
+ back ArgMap's typed value cache. Types only; the validation:: converter functions that produce
13
+ these values live in ArgumentValidation.h.
14
+
15
+--*/
16
+#pragma once
17
+
18
+#include "ArgumentTypes.h"
19
+#include "ContainerModel.h"
20
+#include "InspectModel.h"
21
+
22
+#include <cstdint>
23
+#include <string>
24
+#include <tuple>
25
+#include <utility>
26
+#include <wslc.h>
27
+
28
+namespace wsl::windows::wslc::services {
29
+struct BuildOutput;
30
+struct BuildSecret;
31
+} // namespace wsl::windows::wslc::services
32
+
33
+namespace wsl::windows::wslc::argument::details {
34
+
35
+// Local aliases so the ConvertedType tokens in the WSLC_ARGUMENTS X-macro (ArgumentDefinitions.h)
36
+// resolve here regardless of include order. Aggregate converted types must be aliased because their
37
+// commas would otherwise break X-macro argument parsing if written inline in the table.
38
+using FormatType = wsl::windows::wslc::models::FormatType;
39
+using InspectType = wsl::windows::wslc::models::InspectType;
40
+using JsonIndent = int;
41
+using WSLCSignal = ::WSLCSignal;
42
+using UlimitValue = std::tuple<std::string, int64_t, int64_t>;
43
+using KeyValuePair = std::pair<std::string, std::string>;
44
+using BuildOutput = wsl::windows::wslc::services::BuildOutput;
45
+using BuildSecret = wsl::windows::wslc::services::BuildSecret;
46
+
47
+// Generate the ArgType -> converted type mapping from the X-macro. Every ArgType gets a
48
+// specialization; arguments that are not converted map to NoConversion (their raw string is used
49
+// directly at execution, and the validated cache accessors reject NoConversion at compile time).
50
+#define WSLC_ARG_CONVERTED_MAPPING(EnumName, Name, Alias, Kind, ConvertedType, Desc) \
51
+ template <> \
52
+ struct ArgConvertedTypeMapping<ArgType::EnumName> \
53
+ { \
54
+ using value_t = ConvertedType; \
55
+ };
56
+
57
+WSLC_ARGUMENTS(WSLC_ARG_CONVERTED_MAPPING)
58
+#undef WSLC_ARG_CONVERTED_MAPPING
59
+
60
+} // namespace wsl::windows::wslc::argument::details
src/windows/wslc/arguments/ArgumentDefinitions.h
+111
-107
@@ -30,113 +30,117 @@ Abstract:
30
// if you wish to add validation for the new argument or have it use existing validation.
31
32
// X-Macro for defining all arguments in one place
33
-// Format: ARGUMENT(EnumName, Name, Alias, Kind, Desc)
33
+// Format: ARGUMENT(EnumName, Name, Alias, Kind, ConvertedType, Desc)
34
+// ConvertedType is the type the argument's string value is converted to during validation and cached for
35
+// execution (see ArgumentConvertedTypes.h). Use NoConversion for arguments that are not converted to a typed value.
36
// clang-format off
37
#define WSLC_ARGUMENTS(_) \
36
-_(All, "all", L"a", Kind::Flag, Localization::WSLCCLI_AllArgDescription()) \
37
-_(Archive, "archive", L"a", Kind::Flag, Localization::WSLCCLI_ArchiveArgDescription()) \
38
-_(Attach, "attach", L"a", Kind::Flag, Localization::WSLCCLI_AttachArgDescription()) \
39
-_(BuildArg, "build-arg", NO_ALIAS, Kind::Value, Localization::WSLCCLI_BuildArgDescription()) \
40
-_(BuildPull, "pull", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_BuildPullArgDescription()) \
41
-_(BuildTarget, "target", NO_ALIAS, Kind::Value, Localization::WSLCCLI_BuildTargetArgDescription()) \
42
-_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CIDFileArgDescription()) \
43
-_(Command, "command", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_CommandArgDescription()) \
44
-_(ContainerId, "container-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ContainerIdArgDescription()) \
45
-_(Cpus, "cpus", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CpusArgDescription()) \
46
-_(Force, "force", L"f", Kind::Flag, Localization::WSLCCLI_ForceArgDescription()) \
47
-_(Detach, "detach", L"d", Kind::Flag, Localization::WSLCCLI_DetachArgDescription()) \
48
-_(DNS, "dns", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSArgDescription()) \
49
-/*_(DNSDomain, "dns-domain", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSDomainArgDescription())*/ \
50
-_(DNSOption, "dns-option", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSOptionArgDescription()) \
51
-_(DNSSearch, "dns-search", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSSearchArgDescription()) \
52
-_(Domainname, "domainname", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DomainnameArgDescription()) \
53
-_(Driver, "driver", L"d", Kind::Value, Localization::WSLCCLI_DriverOptionDescription()) \
54
-_(DriverOpt, "driver-opt", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DriverOptArgDescription()) \
55
-_(Entrypoint, "entrypoint", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EntrypointArgDescription()) \
56
-_(Env, "env", L"e", Kind::Value, Localization::WSLCCLI_EnvArgDescription()) \
57
-_(EnvFile, "env-file", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EnvFileArgDescription()) \
58
-_(File, "file", L"f", Kind::Value, Localization::WSLCCLI_FileArgDescription()) \
59
-_(Filter, "filter", L"f", Kind::Value, Localization::WSLCCLI_FilterArgDescription()) \
60
-_(Follow, "follow", L"f", Kind::Flag, Localization::WSLCCLI_FollowArgDescription()) \
61
-_(Timestamps, "timestamps", L"t", Kind::Flag, Localization::WSLCCLI_TimestampsArgDescription()) \
62
-_(Since, "since", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SinceArgDescription()) \
63
-_(Until, "until", NO_ALIAS, Kind::Value, Localization::WSLCCLI_UntilArgDescription()) \
64
-_(Format, "format", NO_ALIAS, Kind::Value, Localization::WSLCCLI_FormatArgDescription()) \
65
-_(ForwardArgs, "arguments", NO_ALIAS, Kind::Forward, Localization::WSLCCLI_ForwardArgsDescription()) \
66
-_(Gateway, "gateway", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkGatewayArgDescription()) \
67
-_(Gpus, "gpus", NO_ALIAS, Kind::Value, Localization::WSLCCLI_GpusArgDescription()) \
68
-/*_(GroupId, "groupid", NO_ALIAS, Kind::Value, Localization::WSLCCLI_GroupIdArgDescription())*/ \
69
-_(HealthCmd, "health-cmd", NO_ALIAS, Kind::Value, Localization::WSLCCLI_HealthCmdArgDescription()) \
70
-_(HealthInterval, "health-interval", NO_ALIAS, Kind::Value, Localization::WSLCCLI_HealthIntervalArgDescription()) \
71
-_(HealthRetries, "health-retries", NO_ALIAS, Kind::Value, Localization::WSLCCLI_HealthRetriesArgDescription()) \
72
-_(HealthStartPeriod, "health-start-period", NO_ALIAS, Kind::Value, Localization::WSLCCLI_HealthStartPeriodArgDescription()) \
73
-_(HealthTimeout, "health-timeout", NO_ALIAS, Kind::Value, Localization::WSLCCLI_HealthTimeoutArgDescription()) \
74
-_(Help, "help", WSLC_CLI_HELP_ARG, Kind::Flag, Localization::WSLCCLI_HelpArgDescription()) \
75
-_(Hostname, "hostname", L"h", Kind::Value, Localization::WSLCCLI_HostnameArgDescription()) \
76
-_(ImageForce, "force", L"f", Kind::Flag, Localization::WSLCCLI_ImageForceArgDescription()) \
77
-_(ImageId, "image", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ImageIdArgDescription()) \
78
-_(ImportFile, "file", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ImportFileArgDescription()) \
79
-_(IidFile, "iidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_IidFileArgDescription()) \
80
-_(Input, "input", L"i", Kind::Value, Localization::WSLCCLI_InputArgDescription()) \
81
-_(InspectFormat, "format", NO_ALIAS, Kind::Value, Localization::WSLCCLI_InspectFormatArgDescription()) \
82
-_(Interactive, "interactive", L"i", Kind::Flag, Localization::WSLCCLI_InteractiveArgDescription()) \
83
-_(Internal, "internal", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NetworkInternalArgDescription()) \
84
-_(IpAddress, "ip", NO_ALIAS, Kind::Value, Localization::WSLCCLI_IpAddressArgDescription()) \
85
-_(IpRange, "ip-range", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkIpRangeArgDescription()) \
86
-_(Label, "label", L"l", Kind::Value, Localization::WSLCCLI_LabelArgDescription()) \
87
-_(Last, "last", L"n", Kind::Value, Localization::WSLCCLI_LastArgDescription()) \
88
-_(Latest, "latest", L"l", Kind::Flag, Localization::WSLCCLI_LatestArgDescription()) \
89
-_(Link, "link", NO_ALIAS, Kind::Value, Localization::WSLCCLI_LinkArgDescription()) \
90
-_(LinkLocalIp, "link-local-ip", NO_ALIAS, Kind::Value, Localization::WSLCCLI_LinkLocalIpArgDescription()) \
91
-_(Memory, "memory", L"m", Kind::Value, Localization::WSLCCLI_MemoryArgDescription()) \
92
-_(Name, "name", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NameArgDescription()) \
93
-_(Network, "network", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkArgDescription()) \
94
-_(NetworkAlias, "network-alias", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkAliasArgDescription()) \
95
-_(NetworkName, "network-name", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_NetworkNameArgDescription()) \
96
-/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/ \
97
-_(NoCache, "no-cache", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoCacheArgDescription()) \
98
-_(NoColor, "no-color", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoColorArgDescription()) \
99
-_(NoHealthcheck, "no-healthcheck", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoHealthcheckArgDescription()) \
100
-_(NoPrune, "no-prune", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoPruneArgDescription()) \
101
-_(NoTrunc, "no-trunc", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoTruncArgDescription()) \
102
-_(ObjectId, "object-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ObjectIdArgDescription()) \
103
-_(Options, "opt", L"o", Kind::Value, Localization::WSLCCLI_OptionsArgDescription()) \
104
-_(Output, "output", L"o", Kind::Value, Localization::WSLCCLI_OutputArgDescription()) \
105
-_(Password, "password", L"p", Kind::Value, Localization::WSLCCLI_LoginPasswordArgDescription()) \
106
-_(PasswordStdin, "password-stdin", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_LoginPasswordStdinArgDescription()) \
107
-_(Path, "path", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_PathArgDescription()) \
108
-/*_(Progress, "progress", NO_ALIAS, Kind::Value, Localization::WSLCCLI_ProgressArgDescription())*/ \
109
-_(Publish, "publish", L"p", Kind::Value, Localization::WSLCCLI_PublishArgDescription()) \
110
-_(PublishAll, "publish-all", L"P", Kind::Flag, Localization::WSLCCLI_PublishAllArgDescription()) \
111
-/*_(Pull, "pull", NO_ALIAS, Kind::Value, Localization::WSLCCLI_PullArgDescription())*/ \
112
-_(Quiet, "quiet", L"q", Kind::Flag, Localization::WSLCCLI_QuietArgDescription()) \
113
-_(Remove, "rm", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_RemoveArgDescription()) \
114
-/*_(Scheme, "scheme", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SchemeArgDescription())*/ \
115
-_(Secret, "secret", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SecretArgDescription()) \
116
-_(Server, "server", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_LoginServerArgDescription()) \
117
-_(Session, "session", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SessionIdArgDescription()) \
118
-_(ShmSize, "shm-size", NO_ALIAS, Kind::Value, Localization::WSLCCLI_ShmSizeArgDescription()) \
119
-_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L"Path to the session storage directory") \
120
-_(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription()) \
121
-_(Source, "source", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SourceArgDescription()) \
122
-_(StopSignal, "stop-signal", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopSignalArgDescription()) \
123
-_(StopTimeout, "stop-timeout", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopTimeoutArgDescription()) \
124
-_(Subnet, "subnet", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkSubnetArgDescription()) \
125
-_(Tail, "tail", L"n", Kind::Value, Localization::WSLCCLI_TailArgDescription()) \
126
-_(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \
127
-_(Target, "target", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_TargetArgDescription()) \
128
-_(Time, "time", L"t", Kind::Value, Localization::WSLCCLI_TimeArgDescription()) \
129
-_(TMPFS, "tmpfs", NO_ALIAS, Kind::Value, Localization::WSLCCLI_TMPFSArgDescription()) \
130
-_(TTY, "tty", L"t", Kind::Flag, Localization::WSLCCLI_TTYArgDescription()) \
131
-_(Type, "type", L"t", Kind::Value, Localization::WSLCCLI_TypeArgDescription()) \
132
-_(Ulimit, "ulimit", NO_ALIAS, Kind::Value, Localization::WSLCCLI_UlimitArgDescription()) \
133
-_(User, "user", L"u", Kind::Value, Localization::WSLCCLI_UserArgDescription()) \
134
-_(Username, "username", L"u", Kind::Value, Localization::WSLCCLI_LoginUsernameArgDescription()) \
135
-_(Verbose, "verbose", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_VerboseArgDescription()) \
136
-_(Version, "version", L"v", Kind::Flag, Localization::WSLCCLI_VersionArgDescription()) \
137
-/*_(Virtual, "virtualization", NO_ALIAS, Kind::Value, Localization::WSLCCLI_VirtualArgDescription())*/ \
138
-_(Volume, "volume", L"v", Kind::Value, Localization::WSLCCLI_VolumeArgDescription()) \
139
-_(VolumeName, "volume-name", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_VolumeNameArgDescription()) \
140
-_(Volumes, "volumes", L"v", Kind::Flag, Localization::WSLCCLI_RemoveVolumesArgDescription()) \
141
-_(WorkDir, "workdir", L"w", Kind::Value, Localization::WSLCCLI_WorkingDirArgDescription()) \
38
+_(All, "all", L"a", Kind::Flag, NoConversion, Localization::WSLCCLI_AllArgDescription()) \
39
+_(Archive, "archive", L"a", Kind::Flag, NoConversion, Localization::WSLCCLI_ArchiveArgDescription()) \
40
+_(Attach, "attach", L"a", Kind::Flag, NoConversion, Localization::WSLCCLI_AttachArgDescription()) \
41
+_(BuildArg, "build-arg", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_BuildArgDescription()) \
42
+_(BuildLabel, "label", L"l", Kind::Value, NoConversion, Localization::WSLCCLI_LabelArgDescription()) \
43
+_(BuildOutput, "output", L"o", Kind::Value, BuildOutput, Localization::WSLCCLI_OutputArgDescription()) \
44
+_(BuildPull, "pull", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_BuildPullArgDescription()) \
45
+_(BuildTarget, "target", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_BuildTargetArgDescription()) \
46
+_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_CIDFileArgDescription()) \
47
+_(Command, "command", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_CommandArgDescription()) \
48
+_(ContainerId, "container-id", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_ContainerIdArgDescription()) \
49
+_(Cpus, "cpus", NO_ALIAS, Kind::Value, int64_t, Localization::WSLCCLI_CpusArgDescription()) \
50
+_(Force, "force", L"f", Kind::Flag, NoConversion, Localization::WSLCCLI_ForceArgDescription()) \
51
+_(Detach, "detach", L"d", Kind::Flag, NoConversion, Localization::WSLCCLI_DetachArgDescription()) \
52
+_(DNS, "dns", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_DNSArgDescription()) \
53
+/*_(DNSDomain, "dns-domain", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_DNSDomainArgDescription())*/ \
54
+_(DNSOption, "dns-option", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_DNSOptionArgDescription()) \
55
+_(DNSSearch, "dns-search", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_DNSSearchArgDescription()) \
56
+_(Domainname, "domainname", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_DomainnameArgDescription()) \
57
+_(Driver, "driver", L"d", Kind::Value, NoConversion, Localization::WSLCCLI_DriverOptionDescription()) \
58
+_(DriverOpt, "driver-opt", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_DriverOptArgDescription()) \
59
+_(Entrypoint, "entrypoint", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_EntrypointArgDescription()) \
60
+_(Env, "env", L"e", Kind::Value, NoConversion, Localization::WSLCCLI_EnvArgDescription()) \
61
+_(EnvFile, "env-file", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_EnvFileArgDescription()) \
62
+_(File, "file", L"f", Kind::Value, NoConversion, Localization::WSLCCLI_FileArgDescription()) \
63
+_(Filter, "filter", L"f", Kind::Value, KeyValuePair, Localization::WSLCCLI_FilterArgDescription()) \
64
+_(Follow, "follow", L"f", Kind::Flag, NoConversion, Localization::WSLCCLI_FollowArgDescription()) \
65
+_(Timestamps, "timestamps", L"t", Kind::Flag, NoConversion, Localization::WSLCCLI_TimestampsArgDescription()) \
66
+_(Since, "since", NO_ALIAS, Kind::Value, ULONGLONG, Localization::WSLCCLI_SinceArgDescription()) \
67
+_(Until, "until", NO_ALIAS, Kind::Value, ULONGLONG, Localization::WSLCCLI_UntilArgDescription()) \
68
+_(Format, "format", NO_ALIAS, Kind::Value, FormatType, Localization::WSLCCLI_FormatArgDescription()) \
69
+_(ForwardArgs, "arguments", NO_ALIAS, Kind::Forward, NoConversion, Localization::WSLCCLI_ForwardArgsDescription()) \
70
+_(Gateway, "gateway", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_NetworkGatewayArgDescription()) \
71
+_(Gpus, "gpus", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_GpusArgDescription()) \
72
+/*_(GroupId, "groupid", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_GroupIdArgDescription())*/ \
73
+_(HealthCmd, "health-cmd", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_HealthCmdArgDescription()) \
74
+_(HealthInterval, "health-interval", NO_ALIAS, Kind::Value, int64_t, Localization::WSLCCLI_HealthIntervalArgDescription()) \
75
+_(HealthRetries, "health-retries", NO_ALIAS, Kind::Value, int, Localization::WSLCCLI_HealthRetriesArgDescription()) \
76
+_(HealthStartPeriod,"health-start-period", NO_ALIAS, Kind::Value, int64_t, Localization::WSLCCLI_HealthStartPeriodArgDescription()) \
77
+_(HealthTimeout, "health-timeout", NO_ALIAS, Kind::Value, int64_t, Localization::WSLCCLI_HealthTimeoutArgDescription()) \
78
+_(Help, "help", WSLC_CLI_HELP_ARG,Kind::Flag, NoConversion, Localization::WSLCCLI_HelpArgDescription()) \
79
+_(Hostname, "hostname", L"h", Kind::Value, NoConversion, Localization::WSLCCLI_HostnameArgDescription()) \
80
+_(ImageForce, "force", L"f", Kind::Flag, NoConversion, Localization::WSLCCLI_ImageForceArgDescription()) \
81
+_(ImageId, "image", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_ImageIdArgDescription()) \
82
+_(ImportFile, "file", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_ImportFileArgDescription()) \
83
+_(IidFile, "iidfile", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_IidFileArgDescription()) \
84
+_(Input, "input", L"i", Kind::Value, NoConversion, Localization::WSLCCLI_InputArgDescription()) \
85
+_(InspectFormat, "format", NO_ALIAS, Kind::Value, JsonIndent, Localization::WSLCCLI_InspectFormatArgDescription()) \
86
+_(Interactive, "interactive", L"i", Kind::Flag, NoConversion, Localization::WSLCCLI_InteractiveArgDescription()) \
87
+_(Internal, "internal", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_NetworkInternalArgDescription()) \
88
+_(IpAddress, "ip", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_IpAddressArgDescription()) \
89
+_(IpRange, "ip-range", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_NetworkIpRangeArgDescription()) \
90
+_(Label, "label", L"l", Kind::Value, KeyValuePair, Localization::WSLCCLI_LabelArgDescription()) \
91
+_(Last, "last", L"n", Kind::Value, int, Localization::WSLCCLI_LastArgDescription()) \
92
+_(Latest, "latest", L"l", Kind::Flag, NoConversion, Localization::WSLCCLI_LatestArgDescription()) \
93
+_(Link, "link", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_LinkArgDescription()) \
94
+_(LinkLocalIp, "link-local-ip", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_LinkLocalIpArgDescription()) \
95
+_(Memory, "memory", L"m", Kind::Value, int64_t, Localization::WSLCCLI_MemoryArgDescription()) \
96
+_(Name, "name", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_NameArgDescription()) \
97
+_(Network, "network", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_NetworkArgDescription()) \
98
+_(NetworkAlias, "network-alias", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_NetworkAliasArgDescription()) \
99
+_(NetworkName, "network-name", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_NetworkNameArgDescription()) \
100
+/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_NoDNSArgDescription())*/ \
101
+_(NoCache, "no-cache", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_NoCacheArgDescription()) \
102
+_(NoColor, "no-color", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_NoColorArgDescription()) \
103
+_(NoHealthcheck, "no-healthcheck", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_NoHealthcheckArgDescription()) \
104
+_(NoPrune, "no-prune", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_NoPruneArgDescription()) \
105
+_(NoTrunc, "no-trunc", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_NoTruncArgDescription()) \
106
+_(ObjectId, "object-id", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_ObjectIdArgDescription()) \
107
+_(Options, "opt", L"o", Kind::Value, KeyValuePair, Localization::WSLCCLI_OptionsArgDescription()) \
108
+_(Output, "output", L"o", Kind::Value, NoConversion, Localization::WSLCCLI_OutputArgDescription()) \
109
+_(Password, "password", L"p", Kind::Value, NoConversion, Localization::WSLCCLI_LoginPasswordArgDescription()) \
110
+_(PasswordStdin, "password-stdin", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_LoginPasswordStdinArgDescription()) \
111
+_(Path, "path", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_PathArgDescription()) \
112
+/*_(Progress, "progress", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_ProgressArgDescription())*/ \
113
+_(Publish, "publish", L"p", Kind::Value, NoConversion, Localization::WSLCCLI_PublishArgDescription()) \
114
+_(PublishAll, "publish-all", L"P", Kind::Flag, NoConversion, Localization::WSLCCLI_PublishAllArgDescription()) \
115
+/*_(Pull, "pull", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_PullArgDescription())*/ \
116
+_(Quiet, "quiet", L"q", Kind::Flag, NoConversion, Localization::WSLCCLI_QuietArgDescription()) \
117
+_(Remove, "rm", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_RemoveArgDescription()) \
118
+/*_(Scheme, "scheme", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_SchemeArgDescription())*/ \
119
+_(Secret, "secret", NO_ALIAS, Kind::Value, BuildSecret, Localization::WSLCCLI_SecretArgDescription()) \
120
+_(Server, "server", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_LoginServerArgDescription()) \
121
+_(Session, "session", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_SessionIdArgDescription()) \
122
+_(ShmSize, "shm-size", NO_ALIAS, Kind::Value, int64_t, Localization::WSLCCLI_ShmSizeArgDescription()) \
123
+_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_StoragePathArgDescription()) \
124
+_(Signal, "signal", L"s", Kind::Value, WSLCSignal, Localization::WSLCCLI_SignalArgDescription()) \
125
+_(Source, "source", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_SourceArgDescription()) \
126
+_(StopSignal, "stop-signal", NO_ALIAS, Kind::Value, WSLCSignal, Localization::WSLCCLI_StopSignalArgDescription()) \
127
+_(StopTimeout, "stop-timeout", NO_ALIAS, Kind::Value, int, Localization::WSLCCLI_StopTimeoutArgDescription()) \
128
+_(Subnet, "subnet", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_NetworkSubnetArgDescription()) \
129
+_(Tail, "tail", L"n", Kind::Value, ULONGLONG, Localization::WSLCCLI_TailArgDescription()) \
130
+_(Tag, "tag", L"t", Kind::Value, NoConversion, Localization::WSLCCLI_TagArgDescription()) \
131
+_(Target, "target", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_TargetArgDescription()) \
132
+_(Time, "time", L"t", Kind::Value, LONG, Localization::WSLCCLI_TimeArgDescription()) \
133
+_(TMPFS, "tmpfs", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_TMPFSArgDescription()) \
134
+_(TTY, "tty", L"t", Kind::Flag, NoConversion, Localization::WSLCCLI_TTYArgDescription()) \
135
+_(Type, "type", L"t", Kind::Value, InspectType, Localization::WSLCCLI_TypeArgDescription()) \
136
+_(Ulimit, "ulimit", NO_ALIAS, Kind::Value, UlimitValue, Localization::WSLCCLI_UlimitArgDescription()) \
137
+_(User, "user", L"u", Kind::Value, NoConversion, Localization::WSLCCLI_UserArgDescription()) \
138
+_(Username, "username", L"u", Kind::Value, NoConversion, Localization::WSLCCLI_LoginUsernameArgDescription()) \
139
+_(Verbose, "verbose", NO_ALIAS, Kind::Flag, NoConversion, Localization::WSLCCLI_VerboseArgDescription()) \
140
+_(Version, "version", L"v", Kind::Flag, NoConversion, Localization::WSLCCLI_VersionArgDescription()) \
141
+/*_(Virtual, "virtualization", NO_ALIAS, Kind::Value, NoConversion, Localization::WSLCCLI_VirtualArgDescription())*/ \
142
+_(Volume, "volume", L"v", Kind::Value, NoConversion, Localization::WSLCCLI_VolumeArgDescription()) \
143
+_(VolumeName, "volume-name", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_VolumeNameArgDescription()) \
144
+_(Volumes, "volumes", L"v", Kind::Flag, NoConversion, Localization::WSLCCLI_RemoveVolumesArgDescription()) \
145
+_(WorkDir, "workdir", L"w", Kind::Value, NoConversion, Localization::WSLCCLI_WorkingDirArgDescription()) \
146
// clang-format on
src/windows/wslc/arguments/ArgumentParser.cpp
+1
-1
@@ -136,7 +136,7 @@ void ParseArgumentsStateMachine::SetFlag(ArgType type, bool value)
136
// Boolean flags store their explicit parsed value (true or false) so a flag whose behavior
137
// is on by default can be turned off with "--flag=false". Clearing first collapses CLI
138
// duplicates to a single entry and gives docker's last-wins behavior for repeated flags
139
- // (e.g. "--flag --flag=false" ends up false). Read flags back via ArgMap::GetFlag, which
139
+ // (e.g. "--flag --flag=false" ends up false). Read flags back via ArgMap::GetValue(defaultValue), which
140
// folds the presence check and the stored value into one test, rather than a bare Contains().
141
ClearArgument(type);
142
m_executionArgs.Add(type, value);
src/windows/wslc/arguments/ArgumentParser.h
+2
-2
@@ -15,7 +15,7 @@ Abstract:
15
#include "Argument.h"
16
#include "Exceptions.h"
17
#include "Invocation.h"
18
-#include "ArgumentTypes.h"
18
+#include "ArgMap.h"
19
20
#include <optional>
21
#include <string>
@@ -133,7 +133,7 @@ private:
133
// Sets a boolean flag by storing its explicit parsed value (true or false). Clearing first
134
// collapses CLI duplicates to a single entry, so a repeated flag is docker-style last-wins
135
// (e.g. "--flag --flag=false" ends up false) and a duplicate "--flag --flag" folds to one
136
- // entry. Consumers read the flag with ArgMap::GetFlag (Contains ? stored value : default),
136
+ // entry. Consumers read the flag with ArgMap::GetValue(defaultValue),
137
// which lets a flag default to on and be disabled with "--flag=false".
138
void SetFlag(ArgType type, bool value);
139
src/windows/wslc/arguments/ArgumentTypes.h
+17
-25
@@ -13,11 +13,11 @@ Abstract:
13
--*/
14
#pragma once
15
#include "ArgumentDefinitions.h"
16
-#include "EnumVariantMap.h"
16
#include <string>
17
#include <vector>
18
#include <array>
19
#include <type_traits>
20
+#include <utility>
21
22
namespace wsl::windows::wslc::argument {
23
// General format: commandname [Flag | Value]* [Positional]* [Forward]
@@ -52,7 +52,7 @@ enum class Limit
52
// Generate ArgType enum from X-macro
53
enum class ArgType : size_t
54
{
55
-#define WSLC_ARG_ENUM(EnumName, Name, Alias, Kind, Desc) EnumName,
55
+#define WSLC_ARG_ENUM(EnumName, Name, Alias, Kind, ConvertedType, Desc) EnumName,
56
WSLC_ARGUMENTS(WSLC_ARG_ENUM)
57
#undef WSLC_ARG_ENUM
58
@@ -95,38 +95,30 @@ namespace details {
95
};
96
97
// Generate data mappings from X-macro - Kind determines the type
98
-#define WSLC_ARG_MAPPING(EnumName, Name, Alias, ArgumentKind, Desc) \
98
+#define WSLC_ARG_MAPPING(EnumName, Name, Alias, ArgumentKind, ConvertedType, Desc) \
99
template <> \
100
struct ArgDataMapping<ArgType::EnumName> \
101
{ \
102
using value_t = typename KindToType<ArgumentKind>::type; \
103
+ static constexpr Kind c_kind = ArgumentKind; \
104
};
105
106
WSLC_ARGUMENTS(WSLC_ARG_MAPPING)
107
#undef WSLC_ARG_MAPPING
108
108
-} // namespace details
109
-
110
-// This is the main ArgType map used for storing parsed arguments.
111
-struct ArgMap : wsl::windows::wslc::EnumBasedVariantMap<ArgType, wsl::windows::wslc::argument::details::ArgDataMapping>
112
-{
113
- // Reads a boolean (Kind::Flag) argument's effective value in one call. A flag stores its
114
- // explicit parsed value when specified (docker-style "--flag"/"--flag=true" => true,
115
- // "--flag=false" => false) and is absent when not specified. Prefer this over a bare
116
- // Contains() for flags: Contains() only tells you the flag was seen, while GetFlag() folds
117
- // the presence check and the stored value into a single "is this flag effectively on?" test.
118
- //
119
- // if (args.GetFlag<ArgType::Quiet>()) { ... } // default-off flag
120
- // bool removeOnExit = args.GetFlag<ArgType::Remove>(true); // default-on flag; --rm=false disables
121
- //
122
- // defaultValue is returned when the flag was not specified; pass true for flags whose
123
- // behavior is on by default and must be turned off with "--flag=false".
124
- template <ArgType E>
125
- bool GetFlag(bool defaultValue = false) const
109
+ // Sentinel type for arguments that are not converted to a typed value during validation
110
+ // (their raw string is used directly at execution). Arguments mapped to NoConversion cannot
111
+ // be read from or written to the validated cache; doing so is a compile error.
112
+ struct NoConversion
113
{
127
- static_assert(std::is_same_v<mapping_t<E>, bool>, "GetFlag is only valid for Kind::Flag arguments");
128
- return Contains(E) ? Get<E>() : defaultValue;
129
- }
130
-};
114
+ };
115
+
116
+ // Maps an ArgType to the type its string value is converted to during validation. Declared here
117
+ // so ArgMap's cache accessors can name the converted type without depending on the domain headers
118
+ // that define it; the specializations live in ArgumentConvertedTypes.h.
119
+ template <ArgType D>
120
+ struct ArgConvertedTypeMapping;
121
+
122
+} // namespace details
123
124
} // namespace wsl::windows::wslc::argument
src/windows/wslc/arguments/ArgumentValidation.cpp
+129
-51
@@ -14,13 +14,15 @@ Abstract:
14
15
#include "precomp.h"
16
#include "Argument.h"
17
-#include "ArgumentTypes.h"
17
+#include "ArgMap.h"
18
#include "ArgumentValidation.h"
19
#include "ContainerModel.h"
20
#include "Exceptions.h"
21
#include "ImageService.h"
22
#include "Localization.h"
23
#include <algorithm>
24
+#include <type_traits>
25
+#include <utility>
26
#include <wslc.h>
27
28
using namespace wsl::windows::common;
@@ -28,50 +30,104 @@ using namespace wsl::shared;
30
using namespace wsl::shared::string;
31
32
namespace wsl::windows::wslc {
31
-// Common argument validation that occurs across multiple commands.
32
-void Argument::Validate(const ArgMap& execArgs) const
33
+
34
+namespace argument::details {
35
+ struct RawArgMapAccess
36
+ {
37
+ template <ArgType E>
38
+ static auto GetAll(const ArgMap& map)
39
+ {
40
+ return map.GetAll<E>();
41
+ }
42
+ };
43
+} // namespace argument::details
44
+
45
+namespace {
46
+ using argument::details::RawArgMapAccess;
47
+
48
+ // Converts each raw value for argument A using the provided converter and caches the result on
49
+ // the ArgMap. This is the single point where an argument's string input is converted; execution
50
+ // later reads the cached value via GetValue/GetAllValues.
51
+ template <ArgType A, typename Converter>
52
+ void CacheConverted(ArgMap& execArgs, const std::wstring& argName, Converter&& convert)
53
+ {
54
+ using value_t = typename details::ArgConvertedTypeMapping<A>::value_t;
55
+ using converted_t = decltype(convert(std::declval<const std::wstring&>(), std::declval<const std::wstring&>()));
56
+ static_assert(
57
+ std::is_same_v<converted_t, value_t>,
58
+ "converter return type must exactly match the argument's declared ConvertedType in ArgumentDefinitions.h");
59
+
60
+ for (const auto& value : RawArgMapAccess::GetAll<A>(execArgs))
61
+ {
62
+ execArgs.AddValidated<A>(convert(value, argName));
63
+ }
64
+
65
+ // Sanity check: each raw value for this argument must produce exactly one cached value.
66
+ WI_ASSERT(execArgs.CountValidated(A) == execArgs.Count(A));
67
+ }
68
+} // namespace
69
+
70
+// Common per-argument validation, run both by the up-front pass and on demand from ArgMap's read
71
+// path. Arguments with a converted type are converted and cached here; the type is recorded as
72
+// validated on success.
73
+void Argument::Validate(ArgMap& execArgs) const
74
{
75
switch (m_argType)
76
{
77
+ case ArgType::BuildLabel:
78
+ for (const auto& value : RawArgMapAccess::GetAll<ArgType::BuildLabel>(execArgs))
79
+ {
80
+ validation::ParseLabel(value);
81
+ }
82
+ break;
83
+
84
+ case ArgType::BuildOutput:
85
+ CacheConverted<ArgType::BuildOutput>(
86
+ execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseOutputSpec(value); });
87
+ break;
88
+
89
case ArgType::Format:
37
- validation::ValidateFormatTypeFromString(execArgs.GetAll<ArgType::Format>(), m_name);
90
+ CacheConverted<ArgType::Format>(execArgs, m_name, validation::GetFormatTypeFromString);
91
break;
92
93
case ArgType::InspectFormat:
41
- validation::ValidateInspectFormatTypeFromString(execArgs.GetAll<ArgType::InspectFormat>(), m_name);
94
+ CacheConverted<ArgType::InspectFormat>(execArgs, m_name, validation::GetInspectJsonIndentFromString);
95
break;
96
97
case ArgType::Signal:
45
- validation::ValidateWSLCSignalFromString(execArgs.GetAll<ArgType::Signal>(), m_name);
98
+ CacheConverted<ArgType::Signal>(execArgs, m_name, validation::GetWSLCSignalFromString);
99
break;
100
101
case ArgType::StopSignal:
49
- validation::ValidateWSLCSignalFromString(execArgs.GetAll<ArgType::StopSignal>(), m_name);
102
+ CacheConverted<ArgType::StopSignal>(execArgs, m_name, validation::GetWSLCSignalFromString);
103
break;
104
105
case ArgType::StopTimeout:
53
- validation::ValidateIntegerFromString<long>(execArgs.GetAll<ArgType::StopTimeout>(), m_name);
106
+ CacheConverted<ArgType::StopTimeout>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
107
+ return validation::GetIntegerFromString<int>(value, name);
108
+ });
109
break;
110
111
case ArgType::ShmSize:
57
- validation::ValidateMemorySize(execArgs.GetAll<ArgType::ShmSize>(), m_name);
112
+ CacheConverted<ArgType::ShmSize>(execArgs, m_name, validation::GetMemorySizeFromString);
113
break;
114
115
case ArgType::HealthInterval:
61
- validation::ValidateDuration(execArgs.GetAll<ArgType::HealthInterval>(), m_name);
116
+ CacheConverted<ArgType::HealthInterval>(execArgs, m_name, validation::GetDurationNanosFromString);
117
break;
118
119
case ArgType::HealthTimeout:
65
- validation::ValidateDuration(execArgs.GetAll<ArgType::HealthTimeout>(), m_name);
120
+ CacheConverted<ArgType::HealthTimeout>(execArgs, m_name, validation::GetDurationNanosFromString);
121
break;
122
123
case ArgType::HealthStartPeriod:
69
- validation::ValidateDuration(execArgs.GetAll<ArgType::HealthStartPeriod>(), m_name);
124
+ CacheConverted<ArgType::HealthStartPeriod>(execArgs, m_name, validation::GetDurationNanosFromString);
125
break;
126
127
case ArgType::HealthRetries:
73
- validation::ValidateIntegerFromString<int>(
74
- execArgs.GetAll<ArgType::HealthRetries>(), m_name, [](int value) { return value >= 0; });
128
+ CacheConverted<ArgType::HealthRetries>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
129
+ return validation::GetIntegerFromString<int>(value, name, [](int v) { return v >= 0; });
130
+ });
131
break;
132
133
case ArgType::NoHealthcheck:
@@ -83,73 +139,91 @@ void Argument::Validate(const ArgMap& execArgs) const
139
break;
140
141
case ArgType::Memory:
86
- validation::ValidateMemorySize(execArgs.GetAll<ArgType::Memory>(), m_name);
142
+ CacheConverted<ArgType::Memory>(execArgs, m_name, validation::GetMemorySizeFromString);
143
break;
144
145
case ArgType::Cpus:
90
- validation::ValidateNanoCpus(execArgs.GetAll<ArgType::Cpus>(), m_name);
146
+ CacheConverted<ArgType::Cpus>(execArgs, m_name, validation::GetNanoCpusFromString);
147
break;
148
149
case ArgType::Ulimit:
94
- validation::ValidateUlimit(execArgs.GetAll<ArgType::Ulimit>(), m_name);
150
+ CacheConverted<ArgType::Ulimit>(execArgs, m_name, validation::ParseUlimit);
151
break;
152
153
case ArgType::Tail:
98
- validation::ValidateIntegerFromString<ULONGLONG>(
99
- execArgs.GetAll<ArgType::Tail>(), m_name, [](auto value) { return value != 0; });
154
+ CacheConverted<ArgType::Tail>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
155
+ return validation::GetIntegerFromString<ULONGLONG>(value, name, [](ULONGLONG v) { return v != 0; });
156
+ });
157
break;
158
159
case ArgType::Time:
103
- validation::ValidateIntegerFromString<LONGLONG>(execArgs.GetAll<ArgType::Time>(), m_name);
160
+ CacheConverted<ArgType::Time>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
161
+ return validation::GetIntegerFromString<LONG>(value, name);
162
+ });
163
break;
164
165
case ArgType::Secret:
107
- {
108
- for (const auto& spec : execArgs.GetAll<ArgType::Secret>())
109
- {
110
- std::ignore = validation::ParseSecretSpec(spec);
111
- }
166
+ CacheConverted<ArgType::Secret>(
167
+ execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseSecretSpec(value); });
168
break;
113
- }
169
170
case ArgType::Since:
116
- validation::ValidateTimestamp(execArgs.GetAll<ArgType::Since>(), m_name);
171
+ CacheConverted<ArgType::Since>(execArgs, m_name, validation::GetTimestampFromString);
172
break;
173
174
case ArgType::Until:
120
- validation::ValidateTimestamp(execArgs.GetAll<ArgType::Until>(), m_name);
175
+ CacheConverted<ArgType::Until>(execArgs, m_name, validation::GetTimestampFromString);
176
break;
177
178
case ArgType::Last:
124
- validation::ValidateIntegerFromString<int>(execArgs.GetAll<ArgType::Last>(), m_name);
179
+ CacheConverted<ArgType::Last>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
180
+ return validation::GetIntegerFromString<int>(value, name);
181
+ });
182
break;
183
184
case ArgType::Filter:
128
- validation::ValidateFilter(execArgs.GetAll<ArgType::Filter>());
185
+ CacheConverted<ArgType::Filter>(
186
+ execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseFilter(value); });
187
+ break;
188
+
189
+ case ArgType::Label:
190
+ CacheConverted<ArgType::Label>(
191
+ execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseLabel(value); });
192
+ break;
193
+
194
+ case ArgType::Options:
195
+ CacheConverted<ArgType::Options>(
196
+ execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseDriverOption(value); });
197
+ break;
198
+
199
+ case ArgType::Type:
200
+ CacheConverted<ArgType::Type>(execArgs, m_name, validation::GetInspectTypeFromString);
201
break;
202
203
case ArgType::Gpus:
132
- validation::ValidateGpus(execArgs.GetAll<ArgType::Gpus>(), m_name);
204
+ validation::ValidateGpus(RawArgMapAccess::GetAll<ArgType::Gpus>(execArgs), m_name);
205
break;
206
207
case ArgType::Volume:
136
- validation::ValidateVolumeMount(execArgs.GetAll<ArgType::Volume>());
208
+ validation::ValidateVolumeMount(RawArgMapAccess::GetAll<ArgType::Volume>(execArgs));
209
break;
210
211
case ArgType::WorkDir:
212
{
141
- const auto& value = execArgs.Get<ArgType::WorkDir>();
142
- if (value.empty() ||
143
- std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
213
+ for (const auto& value : RawArgMapAccess::GetAll<ArgType::WorkDir>(execArgs))
214
{
145
- throw ArgumentException(Localization::WSLCCLI_WorkingDirEmptyError(m_name));
215
+ if (value.empty() ||
216
+ std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
217
+ {
218
+ throw ArgumentException(Localization::WSLCCLI_WorkingDirEmptyError(m_name));
219
+ }
220
}
221
break;
222
}
223
224
case ArgType::Network:
225
{
152
- for (const auto& value : execArgs.GetAll<ArgType::Network>())
226
+ for (const auto& value : RawArgMapAccess::GetAll<ArgType::Network>(execArgs))
227
{
228
if (value.empty() ||
229
std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
@@ -167,7 +241,7 @@ void Argument::Validate(const ArgMap& execArgs) const
241
242
case ArgType::NetworkAlias:
243
{
170
- for (const auto& value : execArgs.GetAll<ArgType::NetworkAlias>())
244
+ for (const auto& value : RawArgMapAccess::GetAll<ArgType::NetworkAlias>(execArgs))
245
{
246
if (value.empty() ||
247
std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
@@ -181,9 +255,26 @@ void Argument::Validate(const ArgMap& execArgs) const
255
default:
256
break;
257
}
258
+
259
+ // Mark validated only on success: a throw above (invalid value) skips this, so the next read
260
+ // re-validates and reports the same error again.
261
+ execArgs.MarkValidated(m_argType);
262
}
263
} // namespace wsl::windows::wslc
264
265
+namespace wsl::windows::wslc::argument {
266
+
267
+// On-demand validation for ArgMap's read path. Clears any stale converted cache first (idempotent),
268
+// then Argument::Validate re-checks the raw values, throwing for an invalid one and recording the
269
+// type as validated on success.
270
+void EnsureArgumentValidated(ArgMap& map, ArgType type)
271
+{
272
+ map.InvalidateValidated(type);
273
+ Argument::Create(type).Validate(map);
274
+}
275
+
276
+} // namespace wsl::windows::wslc::argument
277
+
278
namespace wsl::windows::wslc::validation {
279
280
void ValidateWSLCSignalFromString(const std::vector<std::wstring>& values, const std::wstring& argName)
@@ -228,19 +319,6 @@ void ValidateFormatTypeFromString(const std::vector<std::wstring>& values, const
319
}
320
}
321
231
-void ValidateInspectFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName)
232
-{
233
- constexpr std::wstring_view supportedValues = L"json";
234
-
235
- for (const auto& value : values)
236
- {
237
- if (!IsEqual(value, L"json"))
238
- {
239
- throw ArgumentException(Localization::WSLCCLI_InvalidFormatValueError(argName, value, supportedValues));
240
- }
241
- }
242
-}
243
-
322
void ValidateGpus(const std::vector<std::wstring>& values, const std::wstring& argName)
323
{
324
for (const auto& value : values)
src/windows/wslc/arguments/ArgumentValidation.h
+1
-3
@@ -16,6 +16,7 @@ Abstract:
16
#include "Exceptions.h"
17
#include "ContainerModel.h"
18
#include "InspectModel.h"
19
+#include "ArgumentConvertedTypes.h"
20
#include "SpecParsing.h"
21
#include <string>
22
#include <tuple>
@@ -74,9 +75,6 @@ void ValidateUlimit(const std::vector<std::wstring>& values, const std::wstring&
75
76
void ValidateFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName);
77
77
-// The inspect family only renders JSON, so `json` (single line) is the sole accepted value.
78
-void ValidateInspectFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName);
79
-
78
void ValidateGpus(const std::vector<std::wstring>& values, const std::wstring& argName);
79
80
void ValidateVolumeMount(const std::vector<std::wstring>& values);
src/windows/wslc/arguments/SpecParsing.cpp
+28
-19
@@ -612,35 +612,44 @@ ULONGLONG GetTimestampFromString(const std::wstring& value, const std::wstring&
612
613
models::FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName)
614
{
615
- if (IsEqual(input, L"json"))
616
- {
617
- return models::FormatType::Json;
618
- }
619
- else if (IsEqual(input, L"table"))
615
+ // Single source of truth for the accepted format values. It drives both parsing and the error
616
+ // message's supported-values list, so adding a type here updates both automatically.
617
+ static constexpr std::pair<std::wstring_view, models::FormatType> c_formatTypes[] = {
618
+ {L"json", models::FormatType::Json},
619
+ {L"table", models::FormatType::Table},
620
+ };
621
+
622
+ for (const auto& [name, type] : c_formatTypes)
623
{
621
- return models::FormatType::Table;
624
+ if (IsEqual(input, name))
625
+ {
626
+ return type;
627
+ }
628
}
623
- else
629
+
630
+ std::wstring supportedValues;
631
+ for (const auto& formatType : c_formatTypes)
632
{
625
- constexpr std::wstring_view supportedValues = L"json, table";
626
- throw ArgumentException(Localization::WSLCCLI_InvalidFormatValueError(argName, input, supportedValues));
633
+ if (!supportedValues.empty())
634
+ {
635
+ supportedValues += L", ";
636
+ }
637
+
638
+ supportedValues += formatType.first;
639
}
640
+
641
+ throw ArgumentException(Localization::WSLCCLI_InvalidFormatValueError(argName, input, supportedValues));
642
}
643
630
-models::FormatType GetOutputFormat(const argument::ArgMap& args)
644
+int GetInspectJsonIndentFromString(const std::wstring& input, const std::wstring& argName)
645
{
632
- if (!args.Contains(argument::ArgType::Format))
646
+ if (!IsEqual(input, L"json"))
647
{
634
- return models::FormatType::Table;
648
+ constexpr std::wstring_view supportedValues = L"json";
649
+ throw ArgumentException(Localization::WSLCCLI_InvalidFormatValueError(argName, input, supportedValues));
650
}
651
637
- return GetFormatTypeFromString(args.Get<argument::ArgType::Format>());
638
-}
639
-
640
-int GetInspectJsonIndent(const argument::ArgMap& args)
641
-{
642
- // Validation guarantees the only accepted value is "json", so its presence alone selects compact.
643
- return args.Contains(argument::ArgType::InspectFormat) ? wsl::shared::c_jsonCompactIndent : wsl::shared::c_jsonPrettyPrintIndent;
652
+ return wsl::shared::c_jsonCompactIndent;
653
}
654
655
models::InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstring& argName)
src/windows/wslc/arguments/SpecParsing.h
+2
-6
@@ -14,7 +14,6 @@ Abstract:
14
--*/
15
#pragma once
16
17
-#include "ArgumentTypes.h"
17
#include "ContainerModel.h"
18
#include "InspectModel.h"
19
#include <string>
@@ -82,11 +81,8 @@ ULONGLONG GetTimestampFromString(const std::wstring& value, const std::wstring&
81
// Parses an output format ("json"/"table") into a FormatType.
82
models::FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {});
83
85
-// Resolves the --format argument, falling back to table when it was not supplied.
86
-models::FormatType GetOutputFormat(const argument::ArgMap& args);
87
-
88
-// Returns the json::dump() indent for the inspect family: compact for `--format json`, indented otherwise.
89
-int GetInspectJsonIndent(const argument::ArgMap& args);
84
+// Parses the inspect family's sole supported format ("json") into its compact json::dump() indent.
85
+int GetInspectJsonIndentFromString(const std::wstring& input, const std::wstring& argName = {});
86
87
// Parses an inspect target ("image"/"container"/"network"/"volume") into an InspectType.
88
models::InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstring& argName);
src/windows/wslc/commands/ContainerAttachCommand.cpp
+2
-1
@@ -12,6 +12,7 @@ Abstract:
12
13
--*/
14
15
+#include "ArgumentConvertedTypes.h"
16
#include "ContainerCommand.h"
17
#include "CLIExecutionContext.h"
18
#include "ContainerTasks.h"
@@ -45,6 +46,6 @@ void ContainerAttachCommand::ExecuteInternal(CLIExecutionContext& context) const
46
{
47
context //
48
<< ResolveSession //
48
- << AttachContainer(context.Args.Get<ArgType::ContainerId>());
49
+ << AttachContainer(context.Args.GetValue<ArgType::ContainerId>());
50
}
51
} // namespace wsl::windows::wslc
src/windows/wslc/commands/ContainerCommand.h
+1
-1
@@ -150,7 +150,7 @@ struct ContainerListCommand final : public Command
150
151
protected:
152
void ExecuteInternal(CLIExecutionContext& context) const override;
153
- void ValidateArgumentsInternal(const ArgMap& execArgs) const override;
153
+ void ValidateArgumentsInternal(ArgMap& execArgs) const override;
154
};
155
156
// Logs Command
src/windows/wslc/commands/ContainerListCommand.cpp
+2
-2
@@ -58,9 +58,9 @@ void ContainerListCommand::ExecuteInternal(CLIExecutionContext& context) const
58
}
59
// clang-format on
60
61
-void ContainerListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const
61
+void ContainerListCommand::ValidateArgumentsInternal(ArgMap& execArgs) const
62
{
63
- if (execArgs.Contains(ArgType::Last) && execArgs.GetFlag<ArgType::Latest>())
63
+ if (execArgs.Contains(ArgType::Last) && execArgs.GetValue<ArgType::Latest>())
64
{
65
throw CommandException(Localization::WSLCCLI_MultipleExclusiveArgumentsProvided(L"--last, --latest"));
66
}
src/windows/wslc/commands/ImageBuildCommand.cpp
+2
-2
@@ -33,9 +33,9 @@ std::vector<Argument> ImageBuildCommand::GetArguments() const
33
Argument::Create(ArgType::BuildTarget),
34
Argument::Create(ArgType::File),
35
Argument::Create(ArgType::IidFile),
36
- Argument::Create(ArgType::Label, false, Limit::Unlimited),
36
+ Argument::Create(ArgType::BuildLabel, false, Limit::Unlimited),
37
Argument::Create(ArgType::NoCache),
38
- Argument::Create(ArgType::Output, false, std::nullopt, Localization::WSLCCLI_BuildOutputArgDescription()),
38
+ Argument::Create(ArgType::BuildOutput, false, std::nullopt, Localization::WSLCCLI_BuildOutputArgDescription()),
39
Argument::Create(ArgType::Secret, false, Limit::Unlimited),
40
Argument::Create(ArgType::Tag, false, Limit::Unlimited),
41
Argument::Create(ArgType::Verbose),
src/windows/wslc/commands/RegistryCommand.cpp
+4
-4
@@ -74,14 +74,14 @@ std::wstring RegistryLoginCommand::LongDescription() const
74
return Localization::WSLCCLI_LoginLongDesc();
75
}
76
77
-void RegistryLoginCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const
77
+void RegistryLoginCommand::ValidateArgumentsInternal(ArgMap& execArgs) const
78
{
79
- if (execArgs.Contains(ArgType::Password) && execArgs.GetFlag<ArgType::PasswordStdin>())
79
+ if (execArgs.Contains(ArgType::Password) && execArgs.GetValue<ArgType::PasswordStdin>())
80
{
81
throw CommandException(Localization::WSLCCLI_LoginPasswordAndStdinMutuallyExclusive());
82
}
83
84
- if (execArgs.GetFlag<ArgType::PasswordStdin>() && !execArgs.Contains(ArgType::Username))
84
+ if (execArgs.GetValue<ArgType::PasswordStdin>() && !execArgs.Contains(ArgType::Username))
85
{
86
throw CommandException(Localization::WSLCCLI_LoginPasswordStdinRequiresUsername());
87
}
@@ -98,7 +98,7 @@ void RegistryLoginCommand::ExecuteInternal(CLIExecutionContext& context) const
98
// Resolve password: --password, --password-stdin, or interactive prompt.
99
if (!context.Args.Contains(ArgType::Password))
100
{
101
- if (context.Args.GetFlag<ArgType::PasswordStdin>())
101
+ if (context.Args.GetValue<ArgType::PasswordStdin>())
102
{
103
context.Args.Add(ArgType::Password, context.Terminal.ReadLine().value_or(std::wstring{}));
104
}
src/windows/wslc/commands/RegistryCommand.h
+1
-1
@@ -47,7 +47,7 @@ struct RegistryLoginCommand final : public Command
47
std::wstring LongDescription() const override;
48
49
protected:
50
- void ValidateArgumentsInternal(const ArgMap& execArgs) const override;
50
+ void ValidateArgumentsInternal(ArgMap& execArgs) const override;
51
void ExecuteInternal(CLIExecutionContext& context) const override;
52
};
53
src/windows/wslc/commands/RootCommand.cpp
+1
-1
@@ -103,7 +103,7 @@ std::wstring RootCommand::LongDescription() const
103
104
void RootCommand::ExecuteInternal(CLIExecutionContext& context) const
105
{
106
- if (context.Args.GetFlag<ArgType::Version>())
106
+ if (context.Args.GetValue<ArgType::Version>())
107
{
108
VersionCommand::PrintVersion(context.Terminal);
109
return;
src/windows/wslc/commands/VersionCommand.cpp
+2
-2
@@ -13,7 +13,7 @@ Abstract:
13
--*/
14
15
#include "VersionCommand.h"
16
-#include "ArgumentValidation.h"
16
+#include "ArgumentConvertedTypes.h"
17
#include "CLIExecutionContext.h"
18
#include "JsonUtils.h"
19
@@ -47,7 +47,7 @@ void VersionCommand::PrintVersion(Terminal& terminal)
47
48
void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const
49
{
50
- FormatType format = validation::GetOutputFormat(context.Args);
50
+ const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
51
52
switch (format)
53
{
src/windows/wslc/core/CLIExecutionContext.cpp
+1
-1
@@ -19,7 +19,7 @@ HANDLE CLIExecutionContext::CreateCancelEvent()
19
// This method should be idempotent.
20
void CLIExecutionContext::ApplyGlobalOptions()
21
{
22
- if (GlobalArgs.GetFlag<ArgType::NoColor>())
22
+ if (GlobalArgs.GetValue<ArgType::NoColor>())
23
{
24
Terminal.SetNoColor(true);
25
}
src/windows/wslc/core/CLIExecutionContext.h
+1
-1
@@ -12,7 +12,7 @@ Abstract:
12
13
--*/
14
#pragma once
15
-#include "ArgumentTypes.h"
15
+#include "ArgMap.h"
16
#include "ExecutionContextData.h"
17
#include "Terminal.h"
18
#include <optional>
src/windows/wslc/core/Command.cpp
+4
-4
@@ -430,9 +430,9 @@ void Command::ParseArguments(
430
// that all required arguments are present. Count limits are enforced during parsing
431
// (single-value args are last-wins), so they are not re-checked here.
432
// Any defined validation for specific ArgTypes are also run.
433
-void Command::ValidateArguments(const ArgMap& source, const std::vector<Argument>& definedArgs, bool runInternalHook) const
433
+void Command::ValidateArguments(ArgMap& source, const std::vector<Argument>& definedArgs, bool runInternalHook) const
434
{
435
- if (source.GetFlag<ArgType::Help>())
435
+ if (source.GetValue<ArgType::Help>())
436
{
437
return;
438
}
@@ -459,7 +459,7 @@ void Command::ValidateArguments(const ArgMap& source, const std::vector<Argument
459
void Command::Execute(CLIExecutionContext& context) const
460
{
461
// If Help was part of the validated argument set, we will output help instead of executing.
462
- if (context.Args.GetFlag<ArgType::Help>())
462
+ if (context.Args.GetValue<ArgType::Help>())
463
{
464
OutputHelp(context.Terminal);
465
}
@@ -476,7 +476,7 @@ void Execute(CLIExecutionContext& context, std::unique_ptr<Command>& command)
476
command->Execute(context);
477
}
478
479
-void Command::ValidateArgumentsInternal(const ArgMap&) const
479
+void Command::ValidateArgumentsInternal(ArgMap&) const
480
{
481
// Commands may not need any extra validation; they'll override if they do.
482
}
src/windows/wslc/core/Command.h
+11
-4
@@ -14,7 +14,7 @@ Abstract:
14
#pragma once
15
#include "Argument.h"
16
#include "Exceptions.h"
17
-#include "ArgumentTypes.h"
17
+#include "ArgMap.h"
18
#include "CLIExecutionContext.h"
19
#include "Invocation.h"
20
#include "ArgumentParser.h"
@@ -127,9 +127,9 @@ struct Command
127
ParseArguments(inv, target, GetAllArguments());
128
}
129
130
- void ValidateArguments(const ArgMap& source, const std::vector<Argument>& definedArgs, bool runInternalHook) const;
130
+ void ValidateArguments(ArgMap& source, const std::vector<Argument>& definedArgs, bool runInternalHook) const;
131
132
- void ValidateArguments(const ArgMap& source) const
132
+ void ValidateArguments(ArgMap& source) const
133
{
134
ValidateArguments(source, GetAllArguments(), true);
135
}
@@ -137,7 +137,14 @@ struct Command
137
virtual void Execute(CLIExecutionContext& context) const;
138
139
protected:
140
- virtual void ValidateArgumentsInternal(const ArgMap& source) const;
140
+ // Command-specific validation hook, run after the shared per-argument Argument::Validate pass.
141
+ // Override to enforce cross-argument rules that per-argument validation cannot express, such as
142
+ // mutually-exclusive arguments or required argument combinations.
143
+ //
144
+ // Contract: this hook enforces relationships between already-validated arguments. It receives a
145
+ // GetValue/GetAllValues make the selected argument immutable after returning it. Converted
146
+ // arguments are validated on demand if needed.
147
+ virtual void ValidateArgumentsInternal(ArgMap& source) const;
148
virtual void ExecuteInternal(CLIExecutionContext& context) const = 0;
149
150
private:
src/windows/wslc/core/EnvironmentOptions.h
+1
-1
@@ -13,7 +13,7 @@ Abstract:
13
--*/
14
#pragma once
15
#include "Argument.h"
16
-#include "ArgumentTypes.h"
16
+#include "ArgMap.h"
17
18
#include <vector>
19
src/windows/wslc/tasks/ContainerTasks.cpp
+76
-117
@@ -12,7 +12,7 @@ Abstract:
12
13
--*/
14
#include "Argument.h"
15
-#include "ArgumentValidation.h"
15
+#include "ArgumentConvertedTypes.h"
16
#include "AsyncExecution.h"
17
#include "CLIExecutionContext.h"
18
#include "ContainerModel.h"
@@ -169,7 +169,7 @@ void CreateContainer(CLIExecutionContext& context)
169
auto result = ContainerService::Create(
170
context.Terminal,
171
context.Data.Get<Data::Session>(),
172
- WideToMultiByte(context.Args.Get<ArgType::ImageId>()),
172
+ WideToMultiByte(context.Args.GetValue<ArgType::ImageId>()),
173
context.Data.Get<Data::ContainerOptions>());
174
context.Terminal.Output(L"{}\n", MultiByteToWide(result.Id));
175
}
@@ -182,7 +182,7 @@ void ExecContainer(CLIExecutionContext& context)
182
context.ExitCode = ContainerService::Exec(
183
context.Terminal,
184
context.Data.Get<Data::Session>(),
185
- WideToMultiByte(context.Args.Get<ArgType::ContainerId>()),
185
+ WideToMultiByte(context.Args.GetValue<ArgType::ContainerId>()),
186
context.Data.Get<Data::ContainerOptions>());
187
}
188
@@ -195,35 +195,24 @@ void GetContainers(CLIExecutionContext& context)
195
196
if (context.Args.Contains(ArgType::Last))
197
{
198
- limit = validation::GetIntegerFromString<int>(context.Args.Get<ArgType::Last>(), L"--last");
198
+ limit = context.Args.GetValue<ArgType::Last>();
199
}
200
- else if (context.Args.GetFlag<ArgType::Latest>())
200
+ else if (context.Args.GetValue<ArgType::Latest>())
201
{
202
limit = 1;
203
}
204
205
- // Filter syntax (`key=value`) is enforced upstream; here we just split on the first '='.
206
- std::vector<std::pair<std::string, std::string>> filters;
207
- if (context.Args.Contains(ArgType::Filter))
208
- {
209
- for (const auto& wideValue : context.Args.GetAll<ArgType::Filter>())
210
- {
211
- std::string raw = WideToMultiByte(wideValue);
212
- const auto eq = raw.find('=');
213
- WI_ASSERT(eq != std::string::npos);
205
+ // Filter values are parsed and cached during argument validation.
206
+ auto filters = context.Args.GetAllValues<ArgType::Filter>();
207
215
- filters.emplace_back(raw.substr(0, eq), raw.substr(eq + 1));
216
- }
217
- }
218
-
219
- context.Data.Add<Data::Containers>(ContainerService::List(session, context.Args.GetFlag<ArgType::All>(), limit, filters));
208
+ context.Data.Add<Data::Containers>(ContainerService::List(session, context.Args.GetValue<ArgType::All>(), limit, filters));
209
}
210
211
void InspectContainers(CLIExecutionContext& context)
212
{
213
WI_ASSERT(context.Data.Contains(Data::Session));
214
auto& session = context.Data.Get<Data::Session>();
226
- auto containerIds = context.Args.GetAll<ArgType::ContainerId>();
215
+ auto containerIds = context.Args.GetAllValues<ArgType::ContainerId>();
216
std::vector<wsl::windows::common::wslc_schema::InspectContainer> result;
217
for (const auto& id : containerIds)
218
{
@@ -238,7 +227,7 @@ void InspectContainers(CLIExecutionContext& context)
227
}
228
}
229
241
- auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
230
+ auto json = ToJson(result, context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent));
231
context.Terminal.Output(L"{}\n", MultiByteToWide(json));
232
}
233
@@ -246,11 +235,11 @@ void KillContainers(CLIExecutionContext& context)
235
{
236
WI_ASSERT(context.Data.Contains(Data::Session));
237
auto& session = context.Data.Get<Data::Session>();
249
- auto containerIds = context.Args.GetAll<ArgType::ContainerId>();
238
+ auto containerIds = context.Args.GetAllValues<ArgType::ContainerId>();
239
WSLCSignal signal = WSLCSignalSIGKILL;
240
if (context.Args.Contains(ArgType::Signal))
241
{
253
- signal = validation::GetWSLCSignalFromString(context.Args.Get<ArgType::Signal>());
242
+ signal = context.Args.GetValue<ArgType::Signal>();
243
}
244
245
for (const auto& id : containerIds)
@@ -265,11 +254,11 @@ void ExportContainer(CLIExecutionContext& context)
254
WI_ASSERT(context.Data.Contains(Data::Session));
255
WI_ASSERT(context.Args.Contains(ArgType::ContainerId));
256
auto& session = context.Data.Get<Data::Session>();
268
- auto containerId = WideToMultiByte(context.Args.Get<ArgType::ContainerId>());
257
+ auto containerId = WideToMultiByte(context.Args.GetValue<ArgType::ContainerId>());
258
259
if (context.Args.Contains(ArgType::Output))
260
{
272
- auto& output = context.Args.Get<ArgType::Output>();
261
+ auto& output = context.Args.GetValue<ArgType::Output>();
262
ContainerService::Export(session, containerId, output);
263
}
264
else
@@ -291,8 +280,8 @@ void ContainerCp(CLIExecutionContext& context)
280
WI_ASSERT(context.Args.Contains(ArgType::Target));
281
282
auto& session = context.Data.Get<Data::Session>();
294
- const auto& source = context.Args.Get<ArgType::Source>();
295
- const auto& target = context.Args.Get<ArgType::Target>();
283
+ const auto& source = context.Args.GetValue<ArgType::Source>();
284
+ const auto& target = context.Args.GetValue<ArgType::Target>();
285
286
// Determine copy direction by looking for CONTAINER:PATH patterns.
287
// A single letter before ':' is a Windows drive path (e.g. C:\path), not a container reference.
@@ -554,7 +543,7 @@ void ListContainers(CLIExecutionContext& context)
543
// Note: --all and --filter status= are honored by the Docker daemon when
544
// GetContainers ran; no post-filtering needed here.
545
557
- if (context.Args.GetFlag<ArgType::Quiet>())
546
+ if (context.Args.GetValue<ArgType::Quiet>())
547
{
548
// Print only the container ids
549
for (const auto& container : containers)
@@ -565,7 +554,7 @@ void ListContainers(CLIExecutionContext& context)
554
return;
555
}
556
568
- FormatType format = validation::GetOutputFormat(context.Args);
557
+ const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
558
559
switch (format)
560
{
@@ -577,7 +566,7 @@ void ListContainers(CLIExecutionContext& context)
566
}
567
case FormatType::Table:
568
{
580
- bool trunc = !context.Args.GetFlag<ArgType::NoTrunc>();
569
+ bool trunc = !context.Args.GetValue<ArgType::NoTrunc>();
570
using enum ColumnOverflow;
571
572
// Create table with or without column limits based on --no-trunc flag
@@ -624,9 +613,9 @@ void RemoveContainers(CLIExecutionContext& context)
613
{
614
WI_ASSERT(context.Data.Contains(Data::Session));
615
auto& session = context.Data.Get<Data::Session>();
627
- auto containerIds = context.Args.GetAll<ArgType::ContainerId>();
628
- bool force = context.Args.GetFlag<ArgType::Force>();
629
- bool deleteVolumes = context.Args.GetFlag<ArgType::Volumes>();
616
+ auto containerIds = context.Args.GetAllValues<ArgType::ContainerId>();
617
+ bool force = context.Args.GetValue<ArgType::Force>();
618
+ bool deleteVolumes = context.Args.GetValue<ArgType::Volumes>();
619
for (const auto& id : containerIds)
620
{
621
ContainerService::Delete(session, WideToMultiByte(id), force, deleteVolumes);
@@ -642,7 +631,7 @@ void RunContainer(CLIExecutionContext& context)
631
context.ExitCode = ContainerService::Run(
632
context.Terminal,
633
context.Data.Get<Data::Session>(),
645
- WideToMultiByte(context.Args.Get<ArgType::ImageId>()),
634
+ WideToMultiByte(context.Args.GetValue<ArgType::ImageId>()),
635
context.Data.Get<Data::ContainerOptions>());
636
}
637
@@ -652,32 +641,21 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
641
642
if (context.Args.Contains(ArgType::CIDFile))
643
{
655
- options.CidFile = context.Args.Get<ArgType::CIDFile>();
644
+ options.CidFile = context.Args.GetValue<ArgType::CIDFile>();
645
}
646
647
if (context.Args.Contains(ArgType::Name))
648
{
660
- options.Name = WideToMultiByte(context.Args.Get<ArgType::Name>());
649
+ options.Name = WideToMultiByte(context.Args.GetValue<ArgType::Name>());
650
}
651
663
- if (context.Args.GetFlag<ArgType::TTY>())
664
- {
665
- options.TTY = true;
666
- }
667
-
668
- if (context.Args.GetFlag<ArgType::Detach>())
669
- {
670
- options.Detach = true;
671
- }
672
-
673
- if (context.Args.GetFlag<ArgType::Interactive>())
674
- {
675
- options.Interactive = true;
676
- }
652
+ options.TTY = context.Args.GetValue<ArgType::TTY>();
653
+ options.Detach = context.Args.GetValue<ArgType::Detach>();
654
+ options.Interactive = context.Args.GetValue<ArgType::Interactive>();
655
656
if (context.Args.Contains(ArgType::Publish))
657
{
680
- auto ports = context.Args.GetAll<ArgType::Publish>();
658
+ auto ports = context.Args.GetAllValues<ArgType::Publish>();
659
options.Ports.reserve(options.Ports.size() + ports.size());
660
for (const auto& port : ports)
661
{
@@ -685,10 +663,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
663
}
664
}
665
688
- if (context.Args.GetFlag<ArgType::PublishAll>())
689
- {
690
- options.PublishAll = true;
691
- }
666
+ options.PublishAll = context.Args.GetValue<ArgType::PublishAll>();
667
668
if (context.Args.Contains(ArgType::Gpus))
669
{
@@ -697,7 +672,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
672
673
if (context.Args.Contains(ArgType::Volume))
674
{
700
- auto volumes = context.Args.GetAll<ArgType::Volume>();
675
+ auto volumes = context.Args.GetAllValues<ArgType::Volume>();
676
options.Volumes.reserve(options.Volumes.size() + volumes.size());
677
for (const auto& volume : volumes)
678
{
@@ -705,82 +680,70 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
680
}
681
}
682
708
- if (context.Args.GetFlag<ArgType::Remove>())
709
- {
710
- options.Remove = true;
711
- }
683
+ options.Remove = context.Args.GetValue<ArgType::Remove>();
684
685
if (context.Args.Contains(ArgType::StopSignal))
686
{
715
- options.StopSignal = validation::GetWSLCSignalFromString(context.Args.Get<ArgType::StopSignal>());
687
+ options.StopSignal = context.Args.GetValue<ArgType::StopSignal>();
688
}
689
690
if (context.Args.Contains(ArgType::StopTimeout))
691
{
720
- options.StopTimeout = validation::GetIntegerFromString<int>(context.Args.Get<ArgType::StopTimeout>());
692
+ options.StopTimeout = context.Args.GetValue<ArgType::StopTimeout>();
693
}
694
695
if (context.Args.Contains(ArgType::ShmSize))
696
{
725
- options.ShmSize = validation::GetMemorySizeFromString(context.Args.Get<ArgType::ShmSize>());
697
+ options.ShmSize = context.Args.GetValue<ArgType::ShmSize>();
698
}
699
700
if (context.Args.Contains(ArgType::HealthCmd))
701
{
730
- options.HealthCmd = WideToMultiByte(context.Args.Get<ArgType::HealthCmd>());
702
+ options.HealthCmd = WideToMultiByte(context.Args.GetValue<ArgType::HealthCmd>());
703
}
704
705
if (context.Args.Contains(ArgType::HealthInterval))
706
{
735
- options.HealthInterval = validation::GetDurationNanosFromString(context.Args.Get<ArgType::HealthInterval>());
707
+ options.HealthInterval = context.Args.GetValue<ArgType::HealthInterval>();
708
}
709
710
if (context.Args.Contains(ArgType::HealthTimeout))
711
{
740
- options.HealthTimeout = validation::GetDurationNanosFromString(context.Args.Get<ArgType::HealthTimeout>());
712
+ options.HealthTimeout = context.Args.GetValue<ArgType::HealthTimeout>();
713
}
714
715
if (context.Args.Contains(ArgType::HealthStartPeriod))
716
{
745
- options.HealthStartPeriod = validation::GetDurationNanosFromString(context.Args.Get<ArgType::HealthStartPeriod>());
717
+ options.HealthStartPeriod = context.Args.GetValue<ArgType::HealthStartPeriod>();
718
}
719
720
if (context.Args.Contains(ArgType::HealthRetries))
721
{
750
- options.HealthRetries = validation::GetIntegerFromString<int>(context.Args.Get<ArgType::HealthRetries>());
722
+ options.HealthRetries = context.Args.GetValue<ArgType::HealthRetries>();
723
}
724
753
- if (context.Args.GetFlag<ArgType::NoHealthcheck>())
754
- {
755
- options.NoHealthcheck = true;
756
- }
725
+ options.NoHealthcheck = context.Args.GetValue<ArgType::NoHealthcheck>();
726
727
if (context.Args.Contains(ArgType::Memory))
728
{
760
- options.MemoryBytes = validation::GetMemorySizeFromString(context.Args.Get<ArgType::Memory>());
729
+ options.MemoryBytes = context.Args.GetValue<ArgType::Memory>();
730
}
731
732
if (context.Args.Contains(ArgType::Cpus))
733
{
765
- options.NanoCpus = validation::GetNanoCpusFromString(context.Args.Get<ArgType::Cpus>());
734
+ options.NanoCpus = context.Args.GetValue<ArgType::Cpus>();
735
}
736
768
- if (context.Args.Contains(ArgType::Ulimit))
769
- {
770
- for (const auto& value : context.Args.GetAll<ArgType::Ulimit>())
771
- {
772
- options.Ulimits.emplace_back(validation::ParseUlimit(value));
773
- }
774
- }
737
+ options.Ulimits = context.Args.GetAllValues<ArgType::Ulimit>();
738
739
if (context.Args.Contains(ArgType::Command))
740
{
778
- options.Arguments.emplace_back(WideToMultiByte(context.Args.Get<ArgType::Command>()));
741
+ options.Arguments.emplace_back(WideToMultiByte(context.Args.GetValue<ArgType::Command>()));
742
}
743
744
if (context.Args.Contains(ArgType::EnvFile))
745
{
783
- auto const& envFiles = context.Args.GetAll<ArgType::EnvFile>();
746
+ auto envFiles = context.Args.GetAllValues<ArgType::EnvFile>();
747
for (const auto& envFile : envFiles)
748
{
749
auto parsedEnvVars = EnvironmentVariable::ParseFile(envFile);
@@ -793,7 +756,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
756
757
if (context.Args.Contains(ArgType::Env))
758
{
796
- auto const& envArgs = context.Args.GetAll<ArgType::Env>();
759
+ auto envArgs = context.Args.GetAllValues<ArgType::Env>();
760
for (const auto& arg : envArgs)
761
{
762
auto envVar = EnvironmentVariable::Parse(arg);
@@ -806,22 +769,22 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
769
770
if (context.Args.Contains(ArgType::Entrypoint))
771
{
809
- options.Entrypoint.push_back(WideToMultiByte(context.Args.Get<ArgType::Entrypoint>()));
772
+ options.Entrypoint.push_back(WideToMultiByte(context.Args.GetValue<ArgType::Entrypoint>()));
773
}
774
775
if (context.Args.Contains(ArgType::Hostname))
776
{
814
- options.Hostname = WideToMultiByte(context.Args.Get<ArgType::Hostname>());
777
+ options.Hostname = WideToMultiByte(context.Args.GetValue<ArgType::Hostname>());
778
}
779
780
if (context.Args.Contains(ArgType::Domainname))
781
{
819
- options.Domainname = WideToMultiByte(context.Args.Get<ArgType::Domainname>());
782
+ options.Domainname = WideToMultiByte(context.Args.GetValue<ArgType::Domainname>());
783
}
784
785
if (context.Args.Contains(ArgType::DNS))
786
{
824
- auto dnsServers = context.Args.GetAll<ArgType::DNS>();
787
+ auto dnsServers = context.Args.GetAllValues<ArgType::DNS>();
788
options.DnsServers.reserve(options.DnsServers.size() + dnsServers.size());
789
for (const auto& value : dnsServers)
790
{
@@ -831,7 +794,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
794
795
if (context.Args.Contains(ArgType::DNSSearch))
796
{
834
- auto dnsSearch = context.Args.GetAll<ArgType::DNSSearch>();
797
+ auto dnsSearch = context.Args.GetAllValues<ArgType::DNSSearch>();
798
options.DnsSearchDomains.reserve(options.DnsSearchDomains.size() + dnsSearch.size());
799
for (const auto& value : dnsSearch)
800
{
@@ -841,7 +804,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
804
805
if (context.Args.Contains(ArgType::DNSOption))
806
{
844
- auto dnsOptions = context.Args.GetAll<ArgType::DNSOption>();
807
+ auto dnsOptions = context.Args.GetAllValues<ArgType::DNSOption>();
808
options.DnsOptions.reserve(options.DnsOptions.size() + dnsOptions.size());
809
for (const auto& value : dnsOptions)
810
{
@@ -851,7 +814,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
814
815
if (context.Args.Contains(ArgType::Network))
816
{
854
- auto networks = context.Args.GetAll<ArgType::Network>();
817
+ auto networks = context.Args.GetAllValues<ArgType::Network>();
818
options.Networks.reserve(options.Networks.size() + networks.size());
819
for (const auto& value : networks)
820
{
@@ -861,7 +824,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
824
825
if (context.Args.Contains(ArgType::NetworkAlias))
826
{
864
- auto aliases = context.Args.GetAll<ArgType::NetworkAlias>();
827
+ auto aliases = context.Args.GetAllValues<ArgType::NetworkAlias>();
828
options.NetworkAliases.reserve(aliases.size());
829
for (const auto& value : aliases)
830
{
@@ -871,12 +834,12 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
834
835
if (context.Args.Contains(ArgType::User))
836
{
874
- options.User = WideToMultiByte(context.Args.Get<ArgType::User>());
837
+ options.User = WideToMultiByte(context.Args.GetValue<ArgType::User>());
838
}
839
840
if (context.Args.Contains(ArgType::TMPFS))
841
{
879
- auto tmpfs = context.Args.GetAll<ArgType::TMPFS>();
842
+ auto tmpfs = context.Args.GetAllValues<ArgType::TMPFS>();
843
options.Tmpfs.reserve(options.Tmpfs.size() + tmpfs.size());
844
for (const auto& value : tmpfs)
845
{
@@ -884,18 +847,14 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
847
}
848
}
849
887
- if (context.Args.Contains(ArgType::Label))
850
+ for (const auto& label : context.Args.GetAllValues<ArgType::Label>())
851
{
889
- for (const auto& label : context.Args.GetAll<ArgType::Label>())
890
- {
891
- auto parsed = validation::ParseLabel(label);
892
- options.Labels.emplace_back(parsed.first, parsed.second);
893
- }
852
+ options.Labels.push_back(label);
853
}
854
855
if (context.Args.Contains(ArgType::ForwardArgs))
856
{
898
- auto const& forwardArgs = context.Args.Get<ArgType::ForwardArgs>();
857
+ auto const& forwardArgs = context.Args.GetValue<ArgType::ForwardArgs>();
858
options.Arguments.reserve(options.Arguments.size() + forwardArgs.size());
859
for (const auto& arg : forwardArgs)
860
{
@@ -905,7 +864,7 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
864
865
if (context.Args.Contains(ArgType::WorkDir))
866
{
908
- options.WorkingDirectory = WideToMultiByte(context.Args.Get<ArgType::WorkDir>());
867
+ options.WorkingDirectory = WideToMultiByte(context.Args.GetValue<ArgType::WorkDir>());
868
}
869
870
context.Data.Add<Data::ContainerOptions>(std::move(options));
@@ -916,7 +875,7 @@ void ShowContainerStats(CLIExecutionContext& context)
875
WI_ASSERT(context.Data.Contains(Data::Session));
876
auto& session = context.Data.Get<Data::Session>();
877
919
- auto containers = context.Args.GetAll<ArgType::ContainerId>();
878
+ auto containers = context.Args.GetAllValues<ArgType::ContainerId>();
879
880
// If any are specified we use those, otherwise we show all containers.
881
const bool userSpecifiedContainers = !containers.empty();
@@ -927,7 +886,7 @@ void ShowContainerStats(CLIExecutionContext& context)
886
for (const auto& container : allContainers)
887
{
888
// Skip non-running containers unless --all is specified.
930
- if (!context.Args.GetFlag<ArgType::All>() && container.State != WSLCContainerState::WslcContainerStateRunning)
889
+ if (!context.Args.GetValue<ArgType::All>() && container.State != WSLCContainerState::WslcContainerStateRunning)
890
{
891
continue;
892
}
@@ -972,7 +931,7 @@ void ShowContainerStats(CLIExecutionContext& context)
931
10 // Batch Size - chosen to be around typical expected container use while protecting against extreme cases.
932
);
933
975
- FormatType format = validation::GetOutputFormat(context.Args);
934
+ const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
935
936
switch (format)
937
{
@@ -983,7 +942,7 @@ void ShowContainerStats(CLIExecutionContext& context)
942
}
943
case FormatType::Table:
944
{
986
- bool trunc = !context.Args.GetFlag<ArgType::NoTrunc>();
945
+ bool trunc = !context.Args.GetValue<ArgType::NoTrunc>();
946
using enum ColumnOverflow;
947
948
auto table = trunc ? wsl::windows::wslc::TableOutput<8>(
@@ -1035,8 +994,8 @@ void StartContainer(CLIExecutionContext& context)
994
{
995
WI_ASSERT(context.Data.Contains(Data::Session));
996
WI_ASSERT(context.Args.Contains(ArgType::ContainerId));
1038
- const auto& containerId = context.Args.Get<ArgType::ContainerId>();
1039
- const bool attach = context.Args.GetFlag<ArgType::Attach>();
997
+ const auto& containerId = context.Args.GetValue<ArgType::ContainerId>();
998
+ const bool attach = context.Args.GetValue<ArgType::Attach>();
999
context.ExitCode = ContainerService::Start(context.Terminal, context.Data.Get<Data::Session>(), WideToMultiByte(containerId), attach);
1000
1001
if (!attach)
@@ -1049,16 +1008,16 @@ void StopContainers(CLIExecutionContext& context)
1008
{
1009
WI_ASSERT(context.Data.Contains(Data::Session));
1010
auto& session = context.Data.Get<Data::Session>();
1052
- auto containersToStop = context.Args.GetAll<ArgType::ContainerId>();
1011
+ auto containersToStop = context.Args.GetAllValues<ArgType::ContainerId>();
1012
StopContainerOptions options;
1013
if (context.Args.Contains(ArgType::Signal))
1014
{
1056
- options.Signal = validation::GetWSLCSignalFromString(context.Args.Get<ArgType::Signal>());
1015
+ options.Signal = context.Args.GetValue<ArgType::Signal>();
1016
}
1017
1018
if (context.Args.Contains(ArgType::Time))
1019
{
1061
- options.Timeout = validation::GetIntegerFromString<LONG>(context.Args.Get<ArgType::Time>());
1020
+ options.Timeout = context.Args.GetValue<ArgType::Time>();
1021
}
1022
1023
for (const auto& id : containersToStop)
@@ -1072,14 +1031,14 @@ void ViewContainerLogs(CLIExecutionContext& context)
1031
{
1032
WI_ASSERT(context.Data.Contains(Data::Session));
1033
auto& session = context.Data.Get<Data::Session>();
1075
- auto containerId = context.Args.Get<ArgType::ContainerId>();
1076
- bool follow = context.Args.GetFlag<ArgType::Follow>();
1077
- bool timestamps = context.Args.GetFlag<ArgType::Timestamps>();
1034
+ auto containerId = context.Args.GetValue<ArgType::ContainerId>();
1035
+ bool follow = context.Args.GetValue<ArgType::Follow>();
1036
+ bool timestamps = context.Args.GetValue<ArgType::Timestamps>();
1037
1038
ULONGLONG tail = 0;
1039
if (context.Args.Contains(ArgType::Tail))
1040
{
1082
- tail = validation::GetIntegerFromString<ULONGLONG>(context.Args.Get<ArgType::Tail>());
1041
+ tail = context.Args.GetValue<ArgType::Tail>();
1042
}
1043
1044
// N.B. since=0 and until=0 mean "unset" — the Docker API omits the parameter when the value is 0,
@@ -1088,13 +1047,13 @@ void ViewContainerLogs(CLIExecutionContext& context)
1047
ULONGLONG since = 0;
1048
if (context.Args.Contains(ArgType::Since))
1049
{
1091
- since = validation::GetTimestampFromString(context.Args.Get<ArgType::Since>());
1050
+ since = context.Args.GetValue<ArgType::Since>();
1051
}
1052
1053
ULONGLONG until = 0;
1054
if (context.Args.Contains(ArgType::Until))
1055
{
1097
- until = validation::GetTimestampFromString(context.Args.Get<ArgType::Until>());
1056
+ until = context.Args.GetValue<ArgType::Until>();
1057
}
1058
1059
ContainerService::Logs(session, WideToMultiByte(containerId), follow, timestamps, since, until, tail);
src/windows/wslc/tasks/ImageTasks.cpp
+40
-76
@@ -12,7 +12,7 @@ Abstract:
12
13
--*/
14
#include "Argument.h"
15
-#include "ArgumentValidation.h"
15
+#include "ArgumentConvertedTypes.h"
16
#include "BuildImageCallback.h"
17
#include "CLIExecutionContext.h"
18
#include "ContainerService.h"
@@ -95,58 +95,44 @@ void BuildImage(CLIExecutionContext& context)
95
WI_ASSERT(context.Data.Contains(Data::Session));
96
WI_ASSERT(context.Args.Contains(ArgType::Path));
97
auto& session = context.Data.Get<Data::Session>();
98
- auto& contextPath = context.Args.Get<ArgType::Path>();
98
+ auto& contextPath = context.Args.GetValue<ArgType::Path>();
99
100
- auto tags = context.Args.GetAll<ArgType::Tag>();
101
- auto buildArgs = context.Args.GetAll<ArgType::BuildArg>();
102
- auto labels = context.Args.GetAll<ArgType::Label>();
103
- for (const auto& label : labels)
104
- {
105
- validation::ParseLabel(label);
106
- }
107
-
108
- std::vector<services::BuildSecret> secrets;
109
- if (context.Args.Contains(ArgType::Secret))
110
- {
111
- for (const auto& spec : context.Args.GetAll<ArgType::Secret>())
112
- {
113
- secrets.push_back(validation::ParseSecretSpec(spec));
114
- }
115
- }
100
+ auto tags = context.Args.GetAllValues<ArgType::Tag>();
101
+ auto buildArgs = context.Args.GetAllValues<ArgType::BuildArg>();
102
+ auto labels = context.Args.GetAllValues<ArgType::BuildLabel>();
103
+ auto secrets = context.Args.GetAllValues<ArgType::Secret>();
104
105
std::wstring dockerfilePath;
106
if (context.Args.Contains(ArgType::File))
107
{
120
- dockerfilePath = context.Args.Get<ArgType::File>();
108
+ dockerfilePath = context.Args.GetValue<ArgType::File>();
109
}
110
111
std::wstring target;
112
if (context.Args.Contains(ArgType::BuildTarget))
113
{
126
- target = context.Args.Get<ArgType::BuildTarget>();
114
+ target = context.Args.GetValue<ArgType::BuildTarget>();
115
}
116
117
std::optional<services::BuildOutput> output;
130
- if (context.Args.Contains(ArgType::Output))
118
+ if (context.Args.Contains(ArgType::BuildOutput))
119
{
132
- // Validate and normalize the spec client-side; ImageService::Build decides how to route the
133
- // exporter (stream a destination file/dir back over a handle, or run entirely in the VM).
134
- output = validation::ParseOutputSpec(context.Args.Get<ArgType::Output>());
120
+ output = context.Args.GetValue<ArgType::BuildOutput>();
121
}
122
123
std::optional<std::wstring> iidFilePath;
124
if (context.Args.Contains(ArgType::IidFile))
125
{
140
- iidFilePath = context.Args.Get<ArgType::IidFile>();
126
+ iidFilePath = context.Args.GetValue<ArgType::IidFile>();
127
}
128
129
WSLCBuildImageFlags flags = WSLCBuildImageFlagsNone;
144
- WI_SetFlagIf(flags, WSLCBuildImageFlagsVerbose, context.Args.GetFlag<ArgType::Verbose>());
145
- WI_SetFlagIf(flags, WSLCBuildImageFlagsNoCache, context.Args.GetFlag<ArgType::NoCache>());
146
- WI_SetFlagIf(flags, WSLCBuildImageFlagsPull, context.Args.GetFlag<ArgType::BuildPull>());
130
+ WI_SetFlagIf(flags, WSLCBuildImageFlagsVerbose, context.Args.GetValue<ArgType::Verbose>());
131
+ WI_SetFlagIf(flags, WSLCBuildImageFlagsNoCache, context.Args.GetValue<ArgType::NoCache>());
132
+ WI_SetFlagIf(flags, WSLCBuildImageFlagsPull, context.Args.GetValue<ArgType::BuildPull>());
133
134
auto cancelEvent = context.CreateCancelEvent();
149
- BuildImageCallback callback(context.Terminal, cancelEvent, context.Args.GetFlag<ArgType::Verbose>());
135
+ BuildImageCallback callback(context.Terminal, cancelEvent, context.Args.GetValue<ArgType::Verbose>());
136
services::ImageService::Build(
137
session, contextPath, tags, buildArgs, labels, secrets, dockerfilePath, target, output, iidFilePath, flags, &callback, cancelEvent);
138
}
@@ -156,19 +142,8 @@ void GetImages(CLIExecutionContext& context)
142
WI_ASSERT(context.Data.Contains(Data::Session));
143
auto& session = context.Data.Get<Data::Session>();
144
159
- // Filter syntax (`key=value`) is enforced upstream; here we just split on the first '='.
160
- std::vector<std::pair<std::string, std::string>> filters;
161
- if (context.Args.Contains(ArgType::Filter))
162
- {
163
- for (const auto& wideValue : context.Args.GetAll<ArgType::Filter>())
164
- {
165
- std::string raw = WideToMultiByte(wideValue);
166
- const auto eq = raw.find('=');
167
- WI_ASSERT(eq != std::string::npos);
168
-
169
- filters.emplace_back(raw.substr(0, eq), raw.substr(eq + 1));
170
- }
171
- }
145
+ // Filter values are parsed and cached during argument validation.
146
+ auto filters = context.Args.GetAllValues<ArgType::Filter>();
147
148
auto images = ImageService::List(session, filters);
149
context.Data.Add<Data::Images>(std::move(images));
@@ -179,9 +154,9 @@ void ListImages(CLIExecutionContext& context)
154
WI_ASSERT(context.Data.Contains(Data::Images));
155
auto& images = context.Data.Get<Data::Images>();
156
182
- if (context.Args.GetFlag<ArgType::Quiet>())
157
+ if (context.Args.GetValue<ArgType::Quiet>())
158
{
184
- bool trunc = !context.Args.GetFlag<ArgType::NoTrunc>();
159
+ bool trunc = !context.Args.GetValue<ArgType::NoTrunc>();
160
for (const auto& image : images)
161
{
162
context.Terminal.Output(L"{}\n", trunc ? TruncateId(image.Id, true) : image.Id);
@@ -190,7 +165,7 @@ void ListImages(CLIExecutionContext& context)
165
return;
166
}
167
193
- FormatType format = validation::GetOutputFormat(context.Args);
168
+ const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
169
170
switch (format)
171
{
@@ -202,7 +177,7 @@ void ListImages(CLIExecutionContext& context)
177
}
178
case FormatType::Table:
179
{
205
- bool trunc = !context.Args.GetFlag<ArgType::NoTrunc>();
180
+ bool trunc = !context.Args.GetValue<ArgType::NoTrunc>();
181
using enum ColumnOverflow;
182
183
// Create table — only IMAGE ID uses fixed width; other columns shrink to fit the console.
@@ -243,8 +218,8 @@ void PullImage(CLIExecutionContext& context)
218
WI_ASSERT(context.Data.Contains(Data::Session));
219
WI_ASSERT(context.Args.Contains(ArgType::ImageId));
220
auto& session = context.Data.Get<Data::Session>();
246
- const auto image = WideToMultiByte(context.Args.Get<ArgType::ImageId>());
247
- const bool quiet = context.Args.GetFlag<ArgType::Quiet>();
221
+ const auto image = WideToMultiByte(context.Args.GetValue<ArgType::ImageId>());
222
+ const bool quiet = context.Args.GetValue<ArgType::Quiet>();
223
224
// Match `docker pull`: for a name-only reference (no tag or digest) the tag defaults to "latest". Unless quiet,
225
// the client reports this on stdout before contacting the registry.
@@ -274,7 +249,7 @@ void PushImage(CLIExecutionContext& context)
249
WI_ASSERT(context.Data.Contains(Data::Session));
250
WI_ASSERT(context.Args.Contains(ArgType::ImageId));
251
auto& session = context.Data.Get<Data::Session>();
277
- auto& imageId = context.Args.Get<ArgType::ImageId>();
252
+ auto& imageId = context.Args.GetValue<ArgType::ImageId>();
253
254
ImageProgressCallback callback(context.Terminal, Terminal::Level::Output);
255
services::ImageService::Push(context.Terminal, session, WideToMultiByte(imageId), &callback);
@@ -284,9 +259,9 @@ void DeleteImage(CLIExecutionContext& context)
259
{
260
WI_ASSERT(context.Data.Contains(Data::Session));
261
auto& session = context.Data.Get<Data::Session>();
287
- const auto& imageIds = context.Args.GetAll<ArgType::ImageId>();
288
- bool force = context.Args.GetFlag<ArgType::ImageForce>();
289
- bool noPrune = context.Args.GetFlag<ArgType::NoPrune>();
262
+ auto imageIds = context.Args.GetAllValues<ArgType::ImageId>();
263
+ bool force = context.Args.GetValue<ArgType::ImageForce>();
264
+ bool noPrune = context.Args.GetValue<ArgType::NoPrune>();
265
for (const auto& id : imageIds)
266
{
267
services::ImageService::Delete(session, WideToMultiByte(id), force, noPrune);
@@ -300,7 +275,7 @@ void LoadImage(CLIExecutionContext& context)
275
276
if (context.Args.Contains(ArgType::Input))
277
{
303
- auto& input = context.Args.Get<ArgType::Input>();
278
+ auto& input = context.Args.GetValue<ArgType::Input>();
279
auto callback = wil::MakeOrThrow<WSLCImageLoadCallback>(context.Terminal);
280
services::ImageService::Load(context.Terminal, session, input, callback.Get());
281
return;
@@ -319,14 +294,14 @@ void ImportImage(CLIExecutionContext& context)
294
std::string imageName;
295
if (context.Args.Contains(ArgType::ImageId))
296
{
322
- imageName = WideToMultiByte(context.Args.Get<ArgType::ImageId>());
297
+ imageName = WideToMultiByte(context.Args.GetValue<ArgType::ImageId>());
298
}
299
325
- auto& input = context.Args.Get<ArgType::ImportFile>();
300
+ auto& input = context.Args.GetValue<ArgType::ImportFile>();
301
auto imageId = services::ImageService::Import(context.Terminal, session, input, imageName);
302
if (!imageId.empty())
303
{
329
- bool trunc = !context.Args.GetFlag<ArgType::NoTrunc>();
304
+ bool trunc = !context.Args.GetValue<ArgType::NoTrunc>();
305
context.Terminal.Output(L"{}\n", MultiByteToWide(TruncateId(imageId, trunc)));
306
}
307
}
@@ -336,7 +311,7 @@ void InspectImages(CLIExecutionContext& context)
311
WI_ASSERT(context.Data.Contains(Data::Session));
312
WI_ASSERT(context.Args.Contains(ArgType::ImageId));
313
auto& session = context.Data.Get<Data::Session>();
339
- auto imageIds = context.Args.GetAll<ArgType::ImageId>();
314
+ auto imageIds = context.Args.GetAllValues<ArgType::ImageId>();
315
316
std::vector<wsl::windows::common::wslc_schema::InspectImage> result;
317
for (const auto& id : imageIds)
@@ -352,7 +327,7 @@ void InspectImages(CLIExecutionContext& context)
327
}
328
}
329
355
- auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
330
+ auto json = ToJson(result, context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent));
331
context.Terminal.Output(L"{}\n", MultiByteToWide(json));
332
}
333
@@ -361,7 +336,7 @@ void SaveImage(CLIExecutionContext& context)
336
WI_ASSERT(context.Data.Contains(Data::Session));
337
WI_ASSERT(context.Args.Contains(ArgType::ImageId));
338
auto& session = context.Data.Get<Data::Session>();
364
- auto imageIds = context.Args.GetAll<ArgType::ImageId>();
339
+ auto imageIds = context.Args.GetAllValues<ArgType::ImageId>();
340
341
std::vector<std::string> images;
342
images.reserve(imageIds.size());
@@ -372,7 +347,7 @@ void SaveImage(CLIExecutionContext& context)
347
348
if (context.Args.Contains(ArgType::Output))
349
{
375
- auto& output = context.Args.Get<ArgType::Output>();
350
+ auto& output = context.Args.GetValue<ArgType::Output>();
351
services::ImageService::Save(session, images, output, context.CreateCancelEvent());
352
}
353
else
@@ -391,8 +366,8 @@ void TagImage(CLIExecutionContext& context)
366
{
367
WI_ASSERT(context.Data.Contains(Data::Session));
368
auto& session = context.Data.Get<Data::Session>();
394
- auto& source = context.Args.Get<ArgType::Source>();
395
- auto& target = context.Args.Get<ArgType::Target>();
369
+ auto& source = context.Args.GetValue<ArgType::Source>();
370
+ auto& target = context.Args.GetValue<ArgType::Target>();
371
services::ImageService::Tag(session, WideToMultiByte(source), WideToMultiByte(target));
372
}
373
@@ -401,21 +376,10 @@ void PruneImages(CLIExecutionContext& context)
376
WI_ASSERT(context.Data.Contains(Data::Session));
377
auto& session = context.Data.Get<Data::Session>();
378
404
- bool all = context.Args.GetFlag<ArgType::All>();
379
+ bool all = context.Args.GetValue<ArgType::All>();
380
406
- // Filter syntax (`key=value`) is enforced upstream; here we just split on the first '='.
407
- std::vector<std::pair<std::string, std::string>> filters;
408
- if (context.Args.Contains(ArgType::Filter))
409
- {
410
- for (const auto& wideValue : context.Args.GetAll<ArgType::Filter>())
411
- {
412
- std::string raw = WideToMultiByte(wideValue);
413
- const auto eq = raw.find('=');
414
- WI_ASSERT(eq != std::string::npos);
415
-
416
- filters.emplace_back(raw.substr(0, eq), raw.substr(eq + 1));
417
- }
418
- }
381
+ // Filter values are parsed and cached during argument validation.
382
+ auto filters = context.Args.GetAllValues<ArgType::Filter>();
383
384
auto result = ImageService::Prune(session, all, filters);
385
src/windows/wslc/tasks/InspectTasks.cpp
+4
-4
@@ -12,7 +12,7 @@ Abstract:
12
--*/
13
14
#include "Argument.h"
15
-#include "ArgumentValidation.h"
15
+#include "ArgumentConvertedTypes.h"
16
#include "InspectTasks.h"
17
#include "InspectModel.h"
18
#include "ImageService.h"
@@ -72,13 +72,13 @@ void Inspect(CLIExecutionContext& context)
72
{
73
WI_ASSERT(context.Data.Contains(Data::Session));
74
auto& session = context.Data.Get<Data::Session>();
75
- auto objectIds = context.Args.GetAll<ArgType::ObjectId>();
75
+ auto objectIds = context.Args.GetAllValues<ArgType::ObjectId>();
76
77
nlohmann::json array = nlohmann::json::array();
78
auto type = InspectType::All;
79
if (context.Args.Contains(ArgType::Type))
80
{
81
- type = validation::GetInspectTypeFromString(context.Args.Get<ArgType::Type>(), L"type");
81
+ type = context.Args.GetValue<ArgType::Type>();
82
}
83
84
for (const auto& objectId : objectIds)
@@ -113,6 +113,6 @@ void Inspect(CLIExecutionContext& context)
113
}
114
115
// Always print the array, even if it's empty or an error was encountered
116
- context.Terminal.Output(L"{}\n", MultiByteToWide(array.dump(validation::GetInspectJsonIndent(context.Args))));
116
+ context.Terminal.Output(L"{}\n", MultiByteToWide(array.dump(context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent))));
117
}
118
} // namespace wsl::windows::wslc::task
src/windows/wslc/tasks/NetworkTasks.cpp
+28
-31
@@ -12,7 +12,7 @@ Abstract:
12
13
--*/
14
#include "Argument.h"
15
-#include "ArgumentValidation.h"
15
+#include "ArgumentConvertedTypes.h"
16
#include "CLIExecutionContext.h"
17
#include "NetworkModel.h"
18
#include "NetworkService.h"
@@ -78,38 +78,38 @@ void CreateNetwork(CLIExecutionContext& context)
78
WI_ASSERT(context.Args.Contains(ArgType::NetworkName));
79
80
models::CreateNetworkOptions options{};
81
- options.Name = WideToMultiByte(context.Args.Get<ArgType::NetworkName>());
81
+ options.Name = WideToMultiByte(context.Args.GetValue<ArgType::NetworkName>());
82
83
- for (const auto& option : context.Args.GetAll<ArgType::Options>())
83
+ for (const auto& option : context.Args.GetAllValues<ArgType::Options>())
84
{
85
- options.DriverOpts.push_back(validation::ParseDriverOption(option));
85
+ options.DriverOpts.push_back(option);
86
}
87
88
- for (const auto& label : context.Args.GetAll<ArgType::Label>())
88
+ for (const auto& label : context.Args.GetAllValues<ArgType::Label>())
89
{
90
- options.Labels.push_back(validation::ParseLabel(label));
90
+ options.Labels.push_back(label);
91
}
92
93
if (context.Args.Contains(ArgType::Driver))
94
{
95
- options.Driver = WideToMultiByte(context.Args.Get<ArgType::Driver>());
95
+ options.Driver = WideToMultiByte(context.Args.GetValue<ArgType::Driver>());
96
}
97
98
- options.Internal = context.Args.GetFlag<ArgType::Internal>();
98
+ options.Internal = context.Args.GetValue<ArgType::Internal>();
99
100
if (context.Args.Contains(ArgType::Subnet))
101
{
102
- options.Subnet = WideToMultiByte(context.Args.Get<ArgType::Subnet>());
102
+ options.Subnet = WideToMultiByte(context.Args.GetValue<ArgType::Subnet>());
103
}
104
105
if (context.Args.Contains(ArgType::Gateway))
106
{
107
- options.Gateway = WideToMultiByte(context.Args.Get<ArgType::Gateway>());
107
+ options.Gateway = WideToMultiByte(context.Args.GetValue<ArgType::Gateway>());
108
}
109
110
if (context.Args.Contains(ArgType::IpRange))
111
{
112
- options.IpRange = WideToMultiByte(context.Args.Get<ArgType::IpRange>());
112
+ options.IpRange = WideToMultiByte(context.Args.GetValue<ArgType::IpRange>());
113
}
114
115
NetworkService::Create(context.Terminal, context.Data.Get<Data::Session>(), options);
@@ -120,8 +120,8 @@ void DeleteNetworks(CLIExecutionContext& context)
120
{
121
WI_ASSERT(context.Data.Contains(Data::Session));
122
auto& session = context.Data.Get<Data::Session>();
123
- auto networkNames = context.Args.GetAll<ArgType::NetworkName>();
124
- const bool force = context.Args.GetFlag<ArgType::Force>();
123
+ auto networkNames = context.Args.GetAllValues<ArgType::NetworkName>();
124
+ const bool force = context.Args.GetValue<ArgType::Force>();
125
for (const auto& name : networkNames)
126
{
127
if (TryDeleteNetwork(context.Terminal, session, WideToMultiByte(name), force))
@@ -146,7 +146,7 @@ void InspectNetworks(CLIExecutionContext& context)
146
{
147
WI_ASSERT(context.Data.Contains(Data::Session));
148
auto& session = context.Data.Get<Data::Session>();
149
- auto networkNames = context.Args.GetAll<ArgType::NetworkName>();
149
+ auto networkNames = context.Args.GetAllValues<ArgType::NetworkName>();
150
std::vector<wsl::windows::common::wslc_schema::Network> result;
151
for (const auto& name : networkNames)
152
{
@@ -161,7 +161,7 @@ void InspectNetworks(CLIExecutionContext& context)
161
}
162
}
163
164
- auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
164
+ auto json = ToJson(result, context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent));
165
context.Terminal.Output(L"{}\n", MultiByteToWide(json));
166
}
167
@@ -170,7 +170,7 @@ void ListNetworks(CLIExecutionContext& context)
170
WI_ASSERT(context.Data.Contains(Data::Networks));
171
auto& networks = context.Data.Get<Data::Networks>();
172
173
- if (context.Args.GetFlag<ArgType::Quiet>())
173
+ if (context.Args.GetValue<ArgType::Quiet>())
174
{
175
for (const auto& network : networks)
176
{
@@ -180,7 +180,7 @@ void ListNetworks(CLIExecutionContext& context)
180
return;
181
}
182
183
- FormatType format = validation::GetOutputFormat(context.Args);
183
+ const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
184
185
switch (format)
186
{
@@ -215,11 +215,8 @@ void PruneNetworks(CLIExecutionContext& context)
215
WI_ASSERT(context.Data.Contains(Data::Session));
216
auto& session = context.Data.Get<Data::Session>();
217
218
- std::vector<std::pair<std::string, std::string>> filters;
219
- for (const auto& value : context.Args.GetAll<ArgType::Filter>())
220
- {
221
- filters.push_back(validation::ParseFilter(value));
222
- }
218
+ // Filter values are parsed and cached during argument validation.
219
+ auto filters = context.Args.GetAllValues<ArgType::Filter>();
220
221
auto result = NetworkService::Prune(session, filters);
222
@@ -238,8 +235,8 @@ void ConnectNetwork(CLIExecutionContext& context)
235
236
const auto& endpoint = context.Data.Get<Data::NetworkEndpointOptions>();
237
models::ConnectNetworkOptions options{};
241
- options.NetworkName = WideToMultiByte(context.Args.Get<ArgType::NetworkName>());
242
- options.ContainerId = WideToMultiByte(context.Args.Get<ArgType::ContainerId>());
238
+ options.NetworkName = WideToMultiByte(context.Args.GetValue<ArgType::NetworkName>());
239
+ options.ContainerId = WideToMultiByte(context.Args.GetValue<ArgType::ContainerId>());
240
options.Aliases = endpoint.Aliases;
241
options.IpAddress = endpoint.IpAddress;
242
options.Links = endpoint.Links;
@@ -255,8 +252,8 @@ void DisconnectNetwork(CLIExecutionContext& context)
252
WI_ASSERT(context.Args.Contains(ArgType::NetworkName));
253
WI_ASSERT(context.Args.Contains(ArgType::ContainerId));
254
258
- const auto networkName = WideToMultiByte(context.Args.Get<ArgType::NetworkName>());
259
- const auto containerId = WideToMultiByte(context.Args.Get<ArgType::ContainerId>());
255
+ const auto networkName = WideToMultiByte(context.Args.GetValue<ArgType::NetworkName>());
256
+ const auto containerId = WideToMultiByte(context.Args.GetValue<ArgType::ContainerId>());
257
NetworkService::Disconnect(context.Data.Get<Data::Session>(), networkName, containerId);
258
}
259
@@ -264,27 +261,27 @@ void SetNetworkEndpointOptionsFromArgs(CLIExecutionContext& context)
261
{
262
models::NetworkEndpointOptions options{};
263
267
- for (const auto& alias : context.Args.GetAll<ArgType::NetworkAlias>())
264
+ for (const auto& alias : context.Args.GetAllValues<ArgType::NetworkAlias>())
265
{
266
options.Aliases.emplace_back(WideToMultiByte(alias));
267
}
268
269
if (context.Args.Contains(ArgType::IpAddress))
270
{
274
- options.IpAddress = WideToMultiByte(context.Args.Get<ArgType::IpAddress>());
271
+ options.IpAddress = WideToMultiByte(context.Args.GetValue<ArgType::IpAddress>());
272
}
273
277
- for (const auto& link : context.Args.GetAll<ArgType::Link>())
274
+ for (const auto& link : context.Args.GetAllValues<ArgType::Link>())
275
{
276
options.Links.emplace_back(WideToMultiByte(link));
277
}
278
282
- for (const auto& linkLocalIp : context.Args.GetAll<ArgType::LinkLocalIp>())
279
+ for (const auto& linkLocalIp : context.Args.GetAllValues<ArgType::LinkLocalIp>())
280
{
281
options.LinkLocalIps.emplace_back(WideToMultiByte(linkLocalIp));
282
}
283
287
- for (const auto& driverOpt : context.Args.GetAll<ArgType::DriverOpt>())
284
+ for (const auto& driverOpt : context.Args.GetAllValues<ArgType::DriverOpt>())
285
{
286
options.DriverOpts.emplace_back(WideToMultiByte(driverOpt));
287
}
src/windows/wslc/tasks/RegistryTasks.cpp
+5
-4
@@ -12,6 +12,7 @@ Abstract:
12
13
--*/
14
#include "Argument.h"
15
+#include "ArgumentConvertedTypes.h"
16
#include "CLIExecutionContext.h"
17
#include "RegistryService.h"
18
#include "RegistryTasks.h"
@@ -33,14 +34,14 @@ void Login(CLIExecutionContext& context)
34
35
auto& session = context.Data.Get<Data::Session>();
36
36
- auto username = WideToMultiByte(context.Args.Get<ArgType::Username>());
37
- auto password = WideToMultiByte(context.Args.Get<ArgType::Password>());
37
+ auto username = WideToMultiByte(context.Args.GetValue<ArgType::Username>());
38
+ auto password = WideToMultiByte(context.Args.GetValue<ArgType::Password>());
39
40
auto serverAddress = std::string(RegistryService::DefaultServer);
41
42
if (context.Args.Contains(ArgType::Server))
43
{
43
- serverAddress = WideToMultiByte(context.Args.Get<ArgType::Server>());
44
+ serverAddress = WideToMultiByte(context.Args.GetValue<ArgType::Server>());
45
}
46
47
auto [credUsername, credSecret] = RegistryService::Authenticate(session, serverAddress, username, password);
@@ -55,7 +56,7 @@ void Logout(CLIExecutionContext& context)
56
57
if (context.Args.Contains(ArgType::Server))
58
{
58
- serverAddress = WideToMultiByte(context.Args.Get<ArgType::Server>());
59
+ serverAddress = WideToMultiByte(context.Args.GetValue<ArgType::Server>());
60
}
61
62
RegistryService::Erase(serverAddress);
src/windows/wslc/tasks/SessionTasks.cpp
+7
-6
@@ -12,6 +12,7 @@ Abstract:
12
13
--*/
14
#include "Argument.h"
15
+#include "ArgumentConvertedTypes.h"
16
#include "CLIExecutionContext.h"
17
#include "SessionService.h"
18
#include "SessionTasks.h"
@@ -37,7 +38,7 @@ void OpenSessionIfSpecified(CLIExecutionContext& context)
38
{
39
if (context.GlobalArgs.Contains(ArgType::Session))
40
{
40
- const auto& sessionName = context.GlobalArgs.Get<ArgType::Session>();
41
+ const auto& sessionName = context.GlobalArgs.GetValue<ArgType::Session>();
42
context.Data.Add<Data::Session>(SessionService::OpenSession(sessionName));
43
}
44
}
@@ -67,7 +68,7 @@ void ResolveSession(CLIExecutionContext& context)
68
void ListSessions(CLIExecutionContext& context)
69
{
70
auto sessions = SessionService::List();
70
- if (context.Args.GetFlag<ArgType::Verbose>())
71
+ if (context.Args.GetValue<ArgType::Verbose>())
72
{
73
const wchar_t* plural = sessions.size() == 1 ? L"" : L"s";
74
context.Terminal.Output(L"[wslc] Found {} session{}\n", sessions.size(), plural);
@@ -100,10 +101,10 @@ void RunInSession(CLIExecutionContext& context)
101
auto& session = context.Data.Get<Data::Session>();
102
103
std::vector<std::string> arguments;
103
- arguments.emplace_back(wsl::windows::common::string::WideToMultiByte(context.Args.Get<ArgType::Command>()));
104
+ arguments.emplace_back(wsl::windows::common::string::WideToMultiByte(context.Args.GetValue<ArgType::Command>()));
105
if (context.Args.Contains(ArgType::ForwardArgs))
106
{
106
- for (const auto& arg : context.Args.Get<ArgType::ForwardArgs>())
107
+ for (const auto& arg : context.Args.GetValue<ArgType::ForwardArgs>())
108
{
109
arguments.emplace_back(wsl::windows::common::string::WideToMultiByte(arg));
110
}
@@ -114,12 +115,12 @@ void RunInSession(CLIExecutionContext& context)
115
116
void EnterSession(CLIExecutionContext& context)
117
{
117
- auto storagePath = std::filesystem::absolute(context.Args.Get<ArgType::StoragePath>());
118
+ auto storagePath = std::filesystem::absolute(context.Args.GetValue<ArgType::StoragePath>());
119
120
std::wstring sessionName;
121
if (context.Args.Contains(ArgType::Name))
122
{
122
- sessionName = context.Args.Get<ArgType::Name>();
123
+ sessionName = context.Args.GetValue<ArgType::Name>();
124
}
125
else
126
{
src/windows/wslc/tasks/VolumeTasks.cpp
+16
-21
@@ -12,7 +12,7 @@ Abstract:
12
13
--*/
14
#include "Argument.h"
15
-#include "ArgumentValidation.h"
15
+#include "ArgumentConvertedTypes.h"
16
#include "CLIExecutionContext.h"
17
#include "VolumeModel.h"
18
#include "VolumeService.h"
@@ -79,24 +79,22 @@ void CreateVolume(CLIExecutionContext& context)
79
models::CreateVolumeOptions options{};
80
if (context.Args.Contains(ArgType::VolumeName))
81
{
82
- options.Name = WideToMultiByte(context.Args.Get<ArgType::VolumeName>());
82
+ options.Name = WideToMultiByte(context.Args.GetValue<ArgType::VolumeName>());
83
}
84
85
- for (const auto& option : context.Args.GetAll<ArgType::Options>())
85
+ for (const auto& option : context.Args.GetAllValues<ArgType::Options>())
86
{
87
- auto parsed = validation::ParseDriverOption(option);
88
- options.DriverOpts.emplace_back(parsed.first, parsed.second);
87
+ options.DriverOpts.push_back(option);
88
}
89
91
- for (const auto& label : context.Args.GetAll<ArgType::Label>())
90
+ for (const auto& label : context.Args.GetAllValues<ArgType::Label>())
91
{
93
- auto parsed = validation::ParseLabel(label);
94
- options.Labels.emplace_back(parsed.first, parsed.second);
92
+ options.Labels.push_back(label);
93
}
94
95
if (context.Args.Contains(ArgType::Driver))
96
{
99
- options.Driver = WideToMultiByte(context.Args.Get<ArgType::Driver>());
97
+ options.Driver = WideToMultiByte(context.Args.GetValue<ArgType::Driver>());
98
}
99
100
auto result = VolumeService::Create(context.Data.Get<Data::Session>(), options);
@@ -107,8 +105,8 @@ void DeleteVolumes(CLIExecutionContext& context)
105
{
106
WI_ASSERT(context.Data.Contains(Data::Session));
107
auto& session = context.Data.Get<Data::Session>();
110
- auto volumeNames = context.Args.GetAll<ArgType::VolumeName>();
111
- const bool force = context.Args.GetFlag<ArgType::Force>();
108
+ auto volumeNames = context.Args.GetAllValues<ArgType::VolumeName>();
109
+ const bool force = context.Args.GetValue<ArgType::Force>();
110
for (const auto& name : volumeNames)
111
{
112
if (TryDeleteVolume(context.Terminal, session, WideToMultiByte(name), force))
@@ -133,7 +131,7 @@ void InspectVolumes(CLIExecutionContext& context)
131
{
132
WI_ASSERT(context.Data.Contains(Data::Session));
133
auto& session = context.Data.Get<Data::Session>();
136
- auto volumeNames = context.Args.GetAll<ArgType::VolumeName>();
134
+ auto volumeNames = context.Args.GetAllValues<ArgType::VolumeName>();
135
std::vector<wsl::windows::common::wslc_schema::InspectVolume> result;
136
for (const auto& name : volumeNames)
137
{
@@ -148,7 +146,7 @@ void InspectVolumes(CLIExecutionContext& context)
146
}
147
}
148
151
- auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
149
+ auto json = ToJson(result, context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent));
150
context.Terminal.Output(L"{}\n", MultiByteToWide(json));
151
}
152
@@ -157,7 +155,7 @@ void ListVolumes(CLIExecutionContext& context)
155
WI_ASSERT(context.Data.Contains(Data::Volumes));
156
auto& volumes = context.Data.Get<Data::Volumes>();
157
160
- if (context.Args.GetFlag<ArgType::Quiet>())
158
+ if (context.Args.GetValue<ArgType::Quiet>())
159
{
160
for (const auto& volume : volumes)
161
{
@@ -167,7 +165,7 @@ void ListVolumes(CLIExecutionContext& context)
165
return;
166
}
167
170
- FormatType format = validation::GetOutputFormat(context.Args);
168
+ const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
169
170
switch (format)
171
{
@@ -201,13 +199,10 @@ void PruneVolumes(CLIExecutionContext& context)
199
WI_ASSERT(context.Data.Contains(Data::Session));
200
auto& session = context.Data.Get<Data::Session>();
201
204
- const bool all = context.Args.GetFlag<ArgType::All>();
202
+ const bool all = context.Args.GetValue<ArgType::All>();
203
206
- std::vector<std::pair<std::string, std::string>> filters;
207
- for (const auto& value : context.Args.GetAll<ArgType::Filter>())
208
- {
209
- filters.push_back(validation::ParseFilter(value));
210
- }
204
+ // Filter values are parsed and cached during argument validation.
205
+ auto filters = context.Args.GetAllValues<ArgType::Filter>();
206
207
auto result = VolumeService::Prune(context.Terminal, session, all, filters);
208
test/windows/wslc/ParserTestCases.h
+1
-1
@@ -179,7 +179,7 @@ WSLC_PARSER_TEST_CASE(List, false, LR"(wslc cont1 cont2 --invalidarg)") \
179
\
180
/* Boolean flag value tests: named and alias forms accept true/false/1/0 (case-insensitive), \
181
* and reject non-boolean tokens. Adjoined false forms store the flag with an explicit false \
182
- * value (so it reads back via GetFlag) and parsing still succeeds. Avoid --rm / changing the \
182
+ * value (so it reads back via GetValue) and parsing still succeeds. Avoid --rm / changing the \
183
* image1 positional so the harness spot-checks below stay valid. */ \
184
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --interactive=false image1)") \
185
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --interactive=true image1)") \
test/windows/wslc/WSLCCLIArgumentUnitTests.cpp
+453
-17
@@ -17,8 +17,10 @@ Abstract:
17
#include "WSLCCLITestHelpers.h"
18
19
#include "Argument.h"
20
-#include "ArgumentTypes.h"
20
+#include "ArgMap.h"
21
#include "ArgumentValidation.h"
22
+#include "ImageService.h"
23
+#include "JsonUtils.h"
24
#include "Exceptions.h"
25
#include <wslc.h>
26
@@ -31,6 +33,12 @@ using namespace WEX::Common;
33
using namespace WEX::TestExecution;
34
35
namespace WSLCCLIArgumentUnitTests {
36
+using RawArgMapBase = EnumBasedVariantMap<ArgType, wsl::windows::wslc::argument::details::ArgDataMapping, &ArgMapInvalidateValidatedCache>;
37
+
38
+static_assert(!std::is_convertible_v<ArgMap*, RawArgMapBase*>);
39
+static_assert(!std::is_copy_assignable_v<ArgMap>);
40
+static_assert(!std::is_move_assignable_v<ArgMap>);
41
+
42
class WSLCCLIArgumentUnitTests
43
{
44
WSLC_TEST_CLASS(WSLCCLIArgumentUnitTests)
@@ -168,11 +176,11 @@ class WSLCCLIArgumentUnitTests
176
VERIFY_IS_TRUE(argsContainer.Contains(ArgType::ForwardArgs));
177
178
// Verify basic retrieval
171
- auto retrievedBool = argsContainer.Get<ArgType::Help>();
179
+ auto retrievedBool = argsContainer.GetValue<ArgType::Help>();
180
VERIFY_ARE_EQUAL(retrievedBool, true);
173
- auto retrievedString = argsContainer.Get<ArgType::ContainerId>();
181
+ auto retrievedString = argsContainer.GetValue<ArgType::ContainerId>();
182
VERIFY_ARE_EQUAL(retrievedString, std::wstring(L"test"));
175
- auto retrievedStringSet = argsContainer.Get<ArgType::ForwardArgs>();
183
+ auto retrievedStringSet = argsContainer.GetValue<ArgType::ForwardArgs>();
184
VERIFY_ARE_EQUAL(retrievedStringSet[0], std::wstring(L"test1"));
185
VERIFY_ARE_EQUAL(retrievedStringSet[1], std::wstring(L"test2"));
186
@@ -181,22 +189,25 @@ class WSLCCLIArgumentUnitTests
189
argsContainer.Add(ArgType::Publish, std::wstring(L"test2"));
190
argsContainer.Add(ArgType::Publish, std::wstring(L"test3"));
191
VERIFY_ARE_EQUAL(argsContainer.Count(ArgType::Publish), 3);
184
- auto publishArgs = argsContainer.GetAll<ArgType::Publish>();
192
+ auto publishArgs = argsContainer.GetAllValues<ArgType::Publish>();
193
VERIFY_ARE_EQUAL(publishArgs.size(), 3);
194
VERIFY_ARE_EQUAL(publishArgs[0], std::wstring(L"test1"));
195
VERIFY_ARE_EQUAL(publishArgs[1], std::wstring(L"test2"));
196
VERIFY_ARE_EQUAL(publishArgs[2], std::wstring(L"test3"));
197
198
// Verify Remove
191
- argsContainer.Remove(ArgType::Publish);
192
- VERIFY_ARE_EQUAL(argsContainer.Count(ArgType::Publish), 0);
199
+ ArgMap removeArgs;
200
+ removeArgs.Add<ArgType::Publish>(L"test");
201
+ removeArgs.Remove(ArgType::Publish);
202
+ VERIFY_ARE_EQUAL(removeArgs.Count(ArgType::Publish), 0);
203
204
// Verify compile time add works like runtime add for multimap types.
195
- argsContainer.Add<ArgType::Publish>(L"test1");
196
- argsContainer.Add<ArgType::Publish>(L"test2");
197
- argsContainer.Add<ArgType::Publish>(L"test3");
198
- VERIFY_ARE_EQUAL(argsContainer.Count(ArgType::Publish), 3);
199
- publishArgs = argsContainer.GetAll<ArgType::Publish>();
205
+ ArgMap compileTimeArgs;
206
+ compileTimeArgs.Add<ArgType::Publish>(L"test1");
207
+ compileTimeArgs.Add<ArgType::Publish>(L"test2");
208
+ compileTimeArgs.Add<ArgType::Publish>(L"test3");
209
+ VERIFY_ARE_EQUAL(compileTimeArgs.Count(ArgType::Publish), 3);
210
+ publishArgs = compileTimeArgs.GetAllValues<ArgType::Publish>();
211
VERIFY_ARE_EQUAL(publishArgs.size(), 3);
212
VERIFY_ARE_EQUAL(publishArgs[0], std::wstring(L"test1"));
213
VERIFY_ARE_EQUAL(publishArgs[1], std::wstring(L"test2"));
@@ -216,12 +227,437 @@ class WSLCCLIArgumentUnitTests
227
VERIFY_ARE_EQUAL(argsContainer.Count(ArgType::Publish), 3);
228
VERIFY_ARE_EQUAL(argsContainer.Count(ArgType::ForwardArgs), 1);
229
VERIFY_ARE_EQUAL(argsContainer.GetCount(), 6); // 1 Help + 1 ContainerId + 3 Publish + 1 ForwardArgs
219
- argsContainer.Remove(ArgType::Help);
220
- argsContainer.Remove(ArgType::ContainerId);
221
- argsContainer.Remove(ArgType::Publish);
222
- argsContainer.Remove(ArgType::ForwardArgs);
223
- VERIFY_ARE_EQUAL(argsContainer.GetCount(), 0);
230
}
231
+
232
+ // Test: Verify the validated-value cache stores and returns converted results so that a
233
+ // conversion performed during validation is reused during execution. Access is by a compile-time
234
+ // ArgType, so the value type is fixed by the argument's ConvertedType and cannot be mismatched.
235
+ TEST_METHOD(ValidatedCache_StoresAndRetrievesConvertedValues)
236
+ {
237
+ ArgMap args;
238
+
239
+ // Populate raw arguments so the validated-cache invariant (raw count == validated count,
240
+ // enforced by a debug assert in the cache readers) holds when values are read below.
241
+ args.Add(ArgType::StopTimeout, std::wstring(L"30"));
242
+ args.Add(ArgType::Filter, std::wstring(L"status=running"));
243
+ args.Add(ArgType::Filter, std::wstring(L"label=env=prod"));
244
+
245
+ // Nothing cached yet.
246
+ VERIFY_IS_FALSE(args.ContainsValidated(ArgType::StopTimeout));
247
+
248
+ // A conversion that produces a non-string type (string -> int). The value type is fixed by
249
+ // ArgType::StopTimeout's ConvertedType (int), so no type is supplied by the caller.
250
+ args.AddValidated<ArgType::StopTimeout>(30);
251
+ VERIFY_IS_TRUE(args.ContainsValidated(ArgType::StopTimeout));
252
+ VERIFY_ARE_EQUAL(args.CountValidated(ArgType::StopTimeout), static_cast<size_t>(1));
253
+ VERIFY_ARE_EQUAL(args.GetValue<ArgType::StopTimeout>(), 30);
254
+
255
+ // Multiple cached values for one argument preserve insertion order.
256
+ args.AddValidated<ArgType::Filter>(std::pair<std::string, std::string>{"status", "running"});
257
+ args.AddValidated<ArgType::Filter>(std::pair<std::string, std::string>{"label", "env=prod"});
258
+ VERIFY_ARE_EQUAL(args.CountValidated(ArgType::Filter), static_cast<size_t>(2));
259
+ auto filters = args.GetAllValues<ArgType::Filter>();
260
+ VERIFY_ARE_EQUAL(filters.size(), static_cast<size_t>(2));
261
+ VERIFY_ARE_EQUAL(filters[0].first, std::string("status"));
262
+ VERIFY_ARE_EQUAL(filters[0].second, std::string("running"));
263
+ VERIFY_ARE_EQUAL(filters[1].first, std::string("label"));
264
+ VERIFY_ARE_EQUAL(filters[1].second, std::string("env=prod"));
265
+
266
+ // GetAllValidated returns empty when nothing is cached for the argument.
267
+ auto empty = args.GetAllValues<ArgType::Signal>();
268
+ VERIFY_IS_TRUE(empty.empty());
269
+
270
+ // An absent argument resolves to its value type's default-constructed value.
271
+ VERIFY_IS_FALSE(args.ContainsValidated(ArgType::Memory));
272
+ VERIFY_ARE_EQUAL(args.CountValidated(ArgType::Memory), static_cast<size_t>(0));
273
+ VERIFY_ARE_EQUAL(args.GetValue<ArgType::Memory>(), int64_t{});
274
+ VERIFY_IS_FALSE(args.Contains(ArgType::Memory));
275
+ }
276
+
277
+ // Helper: run validation for a single-value argument and return the converted result (type fixed
278
+ // by the argument's ConvertedType). Drives both paths for every converted ArgType its callers
279
+ // exercise: the eager path (an explicit validation pass) and the on-demand path (a converted read
280
+ // with no prior validation pass, which must self-validate). The returned value is the on-demand
281
+ // result, so callers' expected-value assertions verify the on-demand output equals what the
282
+ // validation pass produces. Both paths run the same Argument::Validate, so their results match by
283
+ // construction; this asserts the on-demand trigger fires and caches an equal number of values.
284
+ template <ArgType E>
285
+ static auto ValidateAndGetCached(const std::wstring& raw)
286
+ {
287
+ ArgMap eager;
288
+ eager.Add(E, std::wstring(raw));
289
+ Argument::Create(E).Validate(eager);
290
+ VERIFY_IS_TRUE(eager.ContainsValidated(E));
291
+
292
+ ArgMap onDemand;
293
+ onDemand.Add(E, std::wstring(raw));
294
+ VERIFY_IS_FALSE(onDemand.ContainsValidated(E)); // no validation pass ran
295
+ auto value = onDemand.GetValue<E>(); // triggers on-demand validation
296
+ VERIFY_IS_TRUE(onDemand.ContainsValidated(E));
297
+ VERIFY_ARE_EQUAL(onDemand.CountValidated(E), eager.CountValidated(E));
298
+ return value;
299
+ }
300
+
301
+ // Helper: as ValidateAndGetCached, for an argument that appears multiple times (ArgMap is a
302
+ // multimap). Runs the eager and on-demand paths and returns every on-demand converted value in
303
+ // insertion order.
304
+ template <ArgType E>
305
+ static auto ValidateAndGetAllCached(const std::vector<std::wstring>& raws)
306
+ {
307
+ ArgMap eager;
308
+ for (const auto& raw : raws)
309
+ {
310
+ eager.Add(E, std::wstring(raw));
311
+ }
312
+
313
+ Argument::Create(E).Validate(eager);
314
+
315
+ // The cache must hold exactly one converted value per raw value in the map.
316
+ VERIFY_ARE_EQUAL(eager.CountValidated(E), eager.Count(E));
317
+ VERIFY_ARE_EQUAL(eager.CountValidated(E), raws.size());
318
+
319
+ ArgMap onDemand;
320
+ for (const auto& raw : raws)
321
+ {
322
+ onDemand.Add(E, std::wstring(raw));
323
+ }
324
+
325
+ VERIFY_IS_FALSE(onDemand.ContainsValidated(E)); // no validation pass ran
326
+ auto values = onDemand.GetAllValues<E>(); // triggers on-demand validation
327
+ VERIFY_ARE_EQUAL(onDemand.CountValidated(E), eager.CountValidated(E));
328
+ return values;
329
+ }
330
+
331
+ // Test: Every ArgType whose validation converts its raw string into a typed value must cache
332
+ // that value on the ArgMap during Argument::Validate, so execution reads it back without
333
+ // re-converting. This drives the real validation + caching path for each converted ArgType.
334
+ TEST_METHOD(ArgumentValidate_ConvertsAndCachesEveryConvertedArgType)
335
+ {
336
+ // string -> FormatType
337
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Format>(L"json"), FormatType::Json);
338
+
339
+ // string -> json::dump() indentation
340
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::InspectFormat>(L"json"), wsl::shared::c_jsonCompactIndent);
341
+
342
+ // string -> WSLCSignal (Signal and StopSignal share the converter)
343
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Signal>(L"SIGTERM"), WSLCSignalSIGTERM);
344
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::StopSignal>(L"SIGKILL"), WSLCSignalSIGKILL);
345
+
346
+ // string -> int
347
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::StopTimeout>(L"30"), 30);
348
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::HealthRetries>(L"3"), 3);
349
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Last>(L"5"), 5);
350
+
351
+ // string -> LONG
352
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Time>(L"5"), 5L);
353
+
354
+ // string -> ULONGLONG (Tail is a raw integer; Since/Until go through the timestamp parser)
355
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Tail>(L"10"), 10ULL);
356
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Since>(L"100"), validation::GetTimestampFromString(L"100"));
357
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Until>(L"200"), validation::GetTimestampFromString(L"200"));
358
+
359
+ // string -> int64_t (memory sizes). The cached value matches the converter's result.
360
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Memory>(L"512M"), validation::GetMemorySizeFromString(L"512M"));
361
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::ShmSize>(L"64M"), validation::GetMemorySizeFromString(L"64M"));
362
+
363
+ // string -> int64_t (durations, in nanoseconds)
364
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::HealthInterval>(L"30s"), validation::GetDurationNanosFromString(L"30s"));
365
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::HealthTimeout>(L"30s"), validation::GetDurationNanosFromString(L"30s"));
366
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::HealthStartPeriod>(L"30s"), validation::GetDurationNanosFromString(L"30s"));
367
+
368
+ // string -> int64_t (nano CPUs)
369
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Cpus>(L"1.5"), validation::GetNanoCpusFromString(L"1.5"));
370
+
371
+ // string -> tuple<name, soft, hard> (ulimit)
372
+ auto ulimit = ValidateAndGetCached<ArgType::Ulimit>(L"nofile=1024:2048");
373
+ VERIFY_ARE_EQUAL(std::get<0>(ulimit), std::string("nofile"));
374
+ VERIFY_ARE_EQUAL(std::get<1>(ulimit), 1024LL);
375
+ VERIFY_ARE_EQUAL(std::get<2>(ulimit), 2048LL);
376
+
377
+ // string -> pair<key, value> (filter). A single Validate call caches every raw value in order.
378
+ {
379
+ ArgMap args;
380
+ args.Add(ArgType::Filter, std::wstring(L"status=running"));
381
+ args.Add(ArgType::Filter, std::wstring(L"label=env=prod")); // split on first '='
382
+ Argument::Create(ArgType::Filter).Validate(args);
383
+ auto filters = args.GetAllValues<ArgType::Filter>();
384
+ VERIFY_ARE_EQUAL(filters.size(), static_cast<size_t>(2));
385
+ VERIFY_ARE_EQUAL(filters[0].first, std::string("status"));
386
+ VERIFY_ARE_EQUAL(filters[0].second, std::string("running"));
387
+ VERIFY_ARE_EQUAL(filters[1].first, std::string("label"));
388
+ VERIFY_ARE_EQUAL(filters[1].second, std::string("env=prod"));
389
+ }
390
+
391
+ // string -> InspectType (inspect object type)
392
+ VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Type>(L"container"), InspectType::Container);
393
+
394
+ // string -> BuildOutput (docker-style build exporter spec)
395
+ {
396
+ auto output = ValidateAndGetCached<ArgType::BuildOutput>(L"type=tar,dest=-");
397
+ VERIFY_ARE_EQUAL(output.Type, std::wstring(L"tar"));
398
+ VERIFY_ARE_EQUAL(output.Dest, std::wstring(L"-"));
399
+ }
400
+
401
+ // string -> pair<key, value> (label and driver option share the key=value shape)
402
+ {
403
+ auto label = ValidateAndGetCached<ArgType::Label>(L"env=prod");
404
+ VERIFY_ARE_EQUAL(label.first, std::string("env"));
405
+ VERIFY_ARE_EQUAL(label.second, std::string("prod"));
406
+
407
+ auto option = ValidateAndGetCached<ArgType::Options>(L"com.docker.network.bridge.name=br0");
408
+ VERIFY_ARE_EQUAL(option.first, std::string("com.docker.network.bridge.name"));
409
+ VERIFY_ARE_EQUAL(option.second, std::string("br0"));
410
+ }
411
+
412
+ // string -> BuildSecret (docker-style --secret spec resolved to an id and value bytes)
413
+ {
414
+ ScopedEnvVariable env(L"WSLC_UT_CONV_SECRET", L"conv-value");
415
+ auto secret = ValidateAndGetCached<ArgType::Secret>(L"id=convtest,env=WSLC_UT_CONV_SECRET");
416
+ VERIFY_ARE_EQUAL(secret.Id, std::wstring(L"convtest"));
417
+ const std::string expected = "conv-value";
418
+ VERIFY_IS_TRUE(std::vector<BYTE>(expected.begin(), expected.end()) == secret.Value);
419
+ }
420
+ }
421
+
422
+ // Test: Because ArgMap is a multimap and any command may allow an argument to repeat, a single
423
+ // Argument::Validate call must convert and cache every occurrence, in order. Covers the
424
+ // different converter result shapes (integer, enum, tuple, pair). The helper also asserts the
425
+ // cached count matches the number of raw values in the map.
426
+ TEST_METHOD(ArgumentValidate_CachesEveryValueForRepeatedArg)
427
+ {
428
+ // Integer converter, multiple values -> all cached in order.
429
+ auto retries = ValidateAndGetAllCached<ArgType::HealthRetries>({L"1", L"2", L"3"});
430
+ VERIFY_ARE_EQUAL(retries.size(), static_cast<size_t>(3));
431
+ VERIFY_ARE_EQUAL(retries[0], 1);
432
+ VERIFY_ARE_EQUAL(retries[1], 2);
433
+ VERIFY_ARE_EQUAL(retries[2], 3);
434
+
435
+ // int64_t converter (memory sizes), multiple values -> all cached in order.
436
+ auto memories = ValidateAndGetAllCached<ArgType::Memory>({L"128M", L"256M"});
437
+ VERIFY_ARE_EQUAL(memories.size(), static_cast<size_t>(2));
438
+ VERIFY_ARE_EQUAL(memories[0], validation::GetMemorySizeFromString(L"128M"));
439
+ VERIFY_ARE_EQUAL(memories[1], validation::GetMemorySizeFromString(L"256M"));
440
+
441
+ // Enum converter, multiple values -> all cached in order.
442
+ auto signals = ValidateAndGetAllCached<ArgType::Signal>({L"SIGTERM", L"SIGKILL", L"SIGHUP"});
443
+ VERIFY_ARE_EQUAL(signals.size(), static_cast<size_t>(3));
444
+ VERIFY_ARE_EQUAL(signals[0], WSLCSignalSIGTERM);
445
+ VERIFY_ARE_EQUAL(signals[1], WSLCSignalSIGKILL);
446
+ VERIFY_ARE_EQUAL(signals[2], WSLCSignalSIGHUP);
447
+
448
+ // Tuple converter (ulimit), multiple values -> all cached in order.
449
+ auto ulimits = ValidateAndGetAllCached<ArgType::Ulimit>({L"nofile=1024:2048", L"nproc=512:1024"});
450
+ VERIFY_ARE_EQUAL(ulimits.size(), static_cast<size_t>(2));
451
+ VERIFY_ARE_EQUAL(std::get<0>(ulimits[0]), std::string("nofile"));
452
+ VERIFY_ARE_EQUAL(std::get<1>(ulimits[0]), 1024LL);
453
+ VERIFY_ARE_EQUAL(std::get<2>(ulimits[0]), 2048LL);
454
+ VERIFY_ARE_EQUAL(std::get<0>(ulimits[1]), std::string("nproc"));
455
+ VERIFY_ARE_EQUAL(std::get<1>(ulimits[1]), 512LL);
456
+ VERIFY_ARE_EQUAL(std::get<2>(ulimits[1]), 1024LL);
457
+
458
+ // Pair converter (filter), multiple values -> all cached in order.
459
+ auto filters = ValidateAndGetAllCached<ArgType::Filter>({L"status=running", L"name=web", L"label=env=prod"});
460
+ VERIFY_ARE_EQUAL(filters.size(), static_cast<size_t>(3));
461
+ VERIFY_ARE_EQUAL(filters[0].first, std::string("status"));
462
+ VERIFY_ARE_EQUAL(filters[0].second, std::string("running"));
463
+ VERIFY_ARE_EQUAL(filters[1].first, std::string("name"));
464
+ VERIFY_ARE_EQUAL(filters[1].second, std::string("web"));
465
+ VERIFY_ARE_EQUAL(filters[2].first, std::string("label"));
466
+ VERIFY_ARE_EQUAL(filters[2].second, std::string("env=prod"));
467
+
468
+ // BuildSecret converter (secret specs), multiple values -> all cached in order.
469
+ {
470
+ ScopedEnvVariable envA(L"WSLC_UT_CONV_SECRET_A", L"value-a");
471
+ ScopedEnvVariable envB(L"WSLC_UT_CONV_SECRET_B", L"value-b");
472
+ auto secrets = ValidateAndGetAllCached<ArgType::Secret>(
473
+ {L"id=seca,env=WSLC_UT_CONV_SECRET_A", L"id=secb,env=WSLC_UT_CONV_SECRET_B"});
474
+ VERIFY_ARE_EQUAL(secrets.size(), static_cast<size_t>(2));
475
+ VERIFY_ARE_EQUAL(secrets[0].Id, std::wstring(L"seca"));
476
+ VERIFY_ARE_EQUAL(secrets[1].Id, std::wstring(L"secb"));
477
+ const std::string expectedA = "value-a";
478
+ const std::string expectedB = "value-b";
479
+ VERIFY_IS_TRUE(std::vector<BYTE>(expectedA.begin(), expectedA.end()) == secrets[0].Value);
480
+ VERIFY_IS_TRUE(std::vector<BYTE>(expectedB.begin(), expectedB.end()) == secrets[1].Value);
481
+ }
482
+ }
483
+
484
+ // Test: Every validate-only ArgType (checked during validation but not converted into a
485
+ // distinct typed value that execution consumes) must NOT populate the cache. Execution reads
486
+ // the raw value for these instead.
487
+ TEST_METHOD(ArgumentValidate_ValidateOnlyArgsAreNotCached)
488
+ {
489
+ struct Case
490
+ {
491
+ ArgType Type;
492
+ std::wstring Value;
493
+ };
494
+
495
+ const std::vector<Case> cases = {
496
+ {ArgType::Gpus, L"all"},
497
+ {ArgType::Volume, LR"(C:\hostPath:/containerPath)"},
498
+ {ArgType::WorkDir, L"/app"},
499
+ {ArgType::Network, L"bridge"},
500
+ {ArgType::NetworkAlias, L"myalias"},
501
+ };
502
+
503
+ for (const auto& c : cases)
504
+ {
505
+ ArgMap args;
506
+ args.Add(c.Type, std::wstring(c.Value));
507
+ Argument::Create(c.Type).Validate(args);
508
+ VERIFY_IS_FALSE(args.ContainsValidated(c.Type));
509
+ }
510
+
511
+ // NoHealthcheck is a flag whose validation only rejects conflicting health options. With
512
+ // no conflicts present it passes and caches nothing.
513
+ ArgMap noHealthcheck;
514
+ noHealthcheck.Add(ArgType::NoHealthcheck, true);
515
+ Argument::Create(ArgType::NoHealthcheck).Validate(noHealthcheck);
516
+ VERIFY_IS_FALSE(noHealthcheck.ContainsValidated(ArgType::NoHealthcheck));
517
+ }
518
+
519
+ // Test: When conversion fails during validation, Validate throws and nothing is cached.
520
+ TEST_METHOD(ArgumentValidate_InvalidValueThrowsAndCachesNothing)
521
+ {
522
+ ArgMap args;
523
+ args.Add(ArgType::Format, std::wstring(L"xml"));
524
+ VERIFY_THROWS(Argument::Create(ArgType::Format).Validate(args), ArgumentException);
525
+ VERIFY_IS_FALSE(args.ContainsValidated(ArgType::Format));
526
+ }
527
+
528
+ // Note: on-demand validation for every converted ArgType (reading with no prior validation pass
529
+ // and getting the same result the pass produces) is covered by the tests above:
530
+ // ValidateAndGetCached / ValidateAndGetAllCached drive both the eager and on-demand paths and
531
+ // return the on-demand value, so those tests' expected-value assertions verify on-demand output
532
+ // for all converted shapes. The tests below cover the behaviors unique to the on-demand trigger:
533
+ // a bad value fails the same way as on the command line, a value added after the validation pass
534
+ // is re-validated, and validate-only arguments (no converted value) are checked on demand too.
535
+
536
+ // Test: An invalid value read on demand (no prior validation pass) throws ArgumentException --
537
+ // the same failure the up-front validation pass raises for that value. This proves an argument
538
+ // populated during execution routes to the same user error path as a bad command-line value,
539
+ // and that a failed on-demand validation leaves nothing cached.
540
+ TEST_METHOD(ArgumentValidate_OnDemandInvalidValueThrows)
541
+ {
542
+ ArgMap args;
543
+ args.Add(ArgType::Format, std::wstring(L"xml")); // not a valid FormatType
544
+ VERIFY_IS_FALSE(args.ContainsValidated(ArgType::Format));
545
+ VERIFY_THROWS(args.GetValue<ArgType::Format>(), ArgumentException);
546
+ VERIFY_IS_FALSE(args.ContainsValidated(ArgType::Format));
547
+ }
548
+
549
+ // Test: Raw values can change after the up-front validation pass until the argument is read.
550
+ // The mutation invalidates the cache, and the first read validates the final values.
551
+ TEST_METHOD(ArgumentValidate_PostValidationAddBeforeReadRevalidates)
552
+ {
553
+ ArgMap args;
554
+ args.Add(ArgType::Signal, std::wstring(L"SIGTERM"));
555
+ Argument::Create(ArgType::Signal).Validate(args);
556
+ VERIFY_ARE_EQUAL(args.CountValidated(ArgType::Signal), static_cast<size_t>(1));
557
+
558
+ // Add a second raw value before the first read. The map-action callback drops the cache.
559
+ args.Add(ArgType::Signal, std::wstring(L"SIGKILL"));
560
+ VERIFY_ARE_EQUAL(args.CountValidated(ArgType::Signal), static_cast<size_t>(0));
561
+
562
+ // The first read re-validates both raw values on demand, in insertion order.
563
+ auto signals = args.GetAllValues<ArgType::Signal>();
564
+ VERIFY_ARE_EQUAL(signals.size(), static_cast<size_t>(2));
565
+ VERIFY_ARE_EQUAL(signals[0], WSLCSignalSIGTERM);
566
+ VERIFY_ARE_EQUAL(signals[1], WSLCSignalSIGKILL);
567
+ VERIFY_ARE_EQUAL(args.CountValidated(ArgType::Signal), static_cast<size_t>(2));
568
+ }
569
+
570
+ // Test: A validate-only argument (checked during validation but not converted into a cached
571
+ // value) is validated on demand when read, so a value added after the up-front pass is checked
572
+ // exactly as a command-line value. These arguments have no converted cache, so the earlier
573
+ // converted-path tests do not cover them; the read path still runs their range/format checks.
574
+ // Network rejects "host" mode and unsupported values.
575
+ TEST_METHOD(ArgumentValidate_OnDemandValidateOnlyArgIsChecked)
576
+ {
577
+ ArgMap labels;
578
+ labels.Add(ArgType::BuildLabel, std::wstring(L"foo"));
579
+ labels.Add(ArgType::BuildLabel, std::wstring(L"foo="));
580
+ auto labelValues = labels.GetAllValues<ArgType::BuildLabel>();
581
+ VERIFY_ARE_EQUAL(labelValues.size(), static_cast<size_t>(2));
582
+ VERIFY_ARE_EQUAL(labelValues[0], std::wstring(L"foo"));
583
+ VERIFY_ARE_EQUAL(labelValues[1], std::wstring(L"foo="));
584
+
585
+ ArgMap invalidLabel;
586
+ invalidLabel.Add(ArgType::BuildLabel, std::wstring(L"=value"));
587
+ VERIFY_THROWS(invalidLabel.GetAllValues<ArgType::BuildLabel>(), wil::ResultException);
588
+
589
+ // Valid value, no prior validation pass: the read validates on demand and returns the raw value.
590
+ ArgMap valid;
591
+ valid.Add(ArgType::Network, std::wstring(L"bridge"));
592
+ auto networks = valid.GetAllValues<ArgType::Network>();
593
+ VERIFY_ARE_EQUAL(networks.size(), static_cast<size_t>(1));
594
+ VERIFY_ARE_EQUAL(networks[0], std::wstring(L"bridge"));
595
+
596
+ // Invalid value, no prior validation pass: the read validates on demand and throws, matching
597
+ // the failure the up-front pass raises for the same value.
598
+ ArgMap invalid;
599
+ invalid.Add(ArgType::Network, std::wstring(L"host"));
600
+ VERIFY_THROWS(invalid.GetAllValues<ArgType::Network>(), ArgumentException);
601
+
602
+ // Valid up-front, then an unsupported value added before the first read: the map-action
603
+ // callback clears the validated record, so the read re-validates on demand and throws.
604
+ ArgMap added;
605
+ added.Add(ArgType::Network, std::wstring(L"bridge"));
606
+ Argument::Create(ArgType::Network).Validate(added);
607
+ added.Add(ArgType::Network, std::wstring(L"host"));
608
+ VERIFY_THROWS(added.GetAllValues<ArgType::Network>(), ArgumentException);
609
+ }
610
+
611
+ TEST_METHOD(ArgumentValidate_ReadMakesArgumentImmutable)
612
+ {
613
+ ArgMap args;
614
+ args.Add(ArgType::Signal, std::wstring(L"SIGTERM"));
615
+ VERIFY_ARE_EQUAL(args.GetValue<ArgType::Signal>(), WSLCSignalSIGTERM);
616
+
617
+ const auto verifyImmutableFailure = [](const auto& operation) {
618
+ VERIFY_THROWS_SPECIFIC(operation(), wil::ResultException, [](const wil::ResultException& e) {
619
+ return e.GetErrorCode() == E_ILLEGAL_METHOD_CALL;
620
+ });
621
+ };
622
+
623
+ verifyImmutableFailure([&] { args.Add(ArgType::Signal, std::wstring(L"SIGKILL")); });
624
+ verifyImmutableFailure([&] { args.Remove(ArgType::Signal); });
625
+ verifyImmutableFailure([&] { args.InvalidateValidated(ArgType::Signal); });
626
+ verifyImmutableFailure([&] { args.AddValidated<ArgType::Signal>(WSLCSignalSIGKILL); });
627
+ verifyImmutableFailure([&] { args.MarkValidated(ArgType::Signal); });
628
+
629
+ // Immutability is per argument; other arguments remain writable until they are read.
630
+ args.Add(ArgType::StopTimeout, std::wstring(L"30"));
631
+ VERIFY_ARE_EQUAL(args.GetValue<ArgType::StopTimeout>(), 30);
632
+ }
633
+
634
+ TEST_METHOD(ArgumentValidate_FlagReadValidatesAndMakesArgumentImmutable)
635
+ {
636
+ const auto verifyImmutableFailure = [](const auto& operation) {
637
+ VERIFY_THROWS_SPECIFIC(operation(), wil::ResultException, [](const wil::ResultException& e) {
638
+ return e.GetErrorCode() == E_ILLEGAL_METHOD_CALL;
639
+ });
640
+ };
641
+
642
+ ArgMap absent;
643
+ VERIFY_IS_FALSE(absent.GetValue<ArgType::Quiet>());
644
+ VERIFY_IS_FALSE(absent.GetValue<ArgType::Quiet>(true));
645
+ VERIFY_IS_FALSE(absent.GetValue<ArgType::Quiet>());
646
+ VERIFY_IS_FALSE(absent.Contains(ArgType::Quiet));
647
+ verifyImmutableFailure([&] { absent.Add(ArgType::Quiet, true); });
648
+
649
+ ArgMap present;
650
+ present.Add(ArgType::NoHealthcheck, true);
651
+ present.Add(ArgType::HealthCmd, std::wstring(L"CMD echo healthy"));
652
+ VERIFY_THROWS(present.GetValue<ArgType::NoHealthcheck>(), ArgumentException);
653
+
654
+ // A failed read does not freeze the argument, so correcting the conflicting input permits
655
+ // a subsequent successful read.
656
+ present.Remove(ArgType::HealthCmd);
657
+ VERIFY_IS_TRUE(present.GetValue<ArgType::NoHealthcheck>());
658
+ verifyImmutableFailure([&] { present.Remove(ArgType::NoHealthcheck); });
659
+ }
660
+
661
// Timestamp parsing unit tests (exercises TryParseRfc3339 and integer path via GetTimestampFromString)
662
663
TEST_METHOD(ValidateTimestamp_ValidUnixEpochSeconds)
test/windows/wslc/WSLCCLICommandUnitTests.cpp
+1
-1
@@ -250,7 +250,7 @@ class WSLCCLICommandUnitTests
250
{
251
// Build a lookup table from ArgType -> enum name string using the same X-macro.
252
static constexpr const wchar_t* c_argTypeNames[] = {
253
-#define WSLC_ARG_ENUM(EnumName, Name, Alias, Kind, Desc) L## #EnumName,
253
+#define WSLC_ARG_ENUM(EnumName, Name, Alias, Kind, ConvertedType, Desc) L## #EnumName,
254
WSLC_ARGUMENTS(WSLC_ARG_ENUM)
255
#undef WSLC_ARG_ENUM
256
};
test/windows/wslc/WSLCCLIEnvironmentOptionsUnitTests.cpp
+6
-5
@@ -17,7 +17,8 @@ Abstract:
17
#include "WSLCCLITestHelpers.h"
18
19
#include "Argument.h"
20
-#include "ArgumentTypes.h"
20
+#include "ArgumentConvertedTypes.h"
21
+#include "ArgMap.h"
22
#include "EnvironmentOptions.h"
23
24
using namespace wsl::windows::wslc;
@@ -57,7 +58,7 @@ class WSLCCLIEnvironmentOptionsUnitTests
58
ApplyEnvironmentOptions(target, NoColorDefs());
59
60
VERIFY_IS_TRUE(target.Contains(ArgType::NoColor));
60
- VERIFY_IS_TRUE(target.Get<ArgType::NoColor>());
61
+ VERIFY_IS_TRUE(target.GetValue<ArgType::NoColor>());
62
}
63
64
// NO_COLOR spec: "0" / "false" / "no" / "off" are not opt-outs.
@@ -72,7 +73,7 @@ class WSLCCLIEnvironmentOptionsUnitTests
73
74
LogComment(std::wstring(L"NO_COLOR=") + value);
75
VERIFY_IS_TRUE(target.Contains(ArgType::NoColor));
75
- VERIFY_IS_TRUE(target.Get<ArgType::NoColor>());
76
+ VERIFY_IS_TRUE(target.GetValue<ArgType::NoColor>());
77
78
m_noColor->Clear();
79
}
@@ -86,7 +87,7 @@ class WSLCCLIEnvironmentOptionsUnitTests
87
ApplyEnvironmentOptions(target, NoColorDefs());
88
89
VERIFY_IS_TRUE(target.Contains(ArgType::NoColor));
89
- VERIFY_IS_TRUE(target.Get<ArgType::NoColor>());
90
+ VERIFY_IS_TRUE(target.GetValue<ArgType::NoColor>());
91
}
92
93
TEST_METHOD(ApplyEnvironmentOptions_NoColorAbsent_DoesNotSetFlag)
@@ -108,7 +109,7 @@ class WSLCCLIEnvironmentOptionsUnitTests
109
ApplyEnvironmentOptions(target, NoColorDefs());
110
111
VERIFY_ARE_EQUAL(1U, target.Count(ArgType::NoColor));
111
- VERIFY_IS_FALSE(target.Get<ArgType::NoColor>());
112
+ VERIFY_IS_FALSE(target.GetValue<ArgType::NoColor>());
113
}
114
115
// Bindings outside definedArgs are ignored even if the env var is set.
test/windows/wslc/WSLCCLIParserUnitTests.cpp
+50
-51
@@ -17,7 +17,8 @@ Abstract:
17
#include "WSLCCLITestHelpers.h"
18
19
#include "Argument.h"
20
-#include "ArgumentTypes.h"
20
+#include "ArgumentConvertedTypes.h"
21
+#include "ArgMap.h"
22
#include "ArgumentParser.h"
23
#include "Invocation.h"
24
#include "ParserTestCases.h"
@@ -102,14 +103,14 @@ class WSLCCLIParserUnitTests
103
if (testCase.commandLine.find(L"image1") != std::wstring::npos && testCase.argumentSet == ArgumentSet::Run)
104
{
105
VERIFY_IS_TRUE(args.Contains(ArgType::ImageId));
105
- auto imageId = args.Get<ArgType::ImageId>();
106
+ auto imageId = args.GetValue<ArgType::ImageId>();
107
VERIFY_ARE_EQUAL(L"image1", imageId);
108
}
109
110
if (testCase.commandLine.find(L"cont1") != std::wstring::npos && testCase.argumentSet == ArgumentSet::List)
111
{
112
VERIFY_IS_TRUE(args.Contains(ArgType::ContainerId));
112
- auto containerId = args.Get<ArgType::ContainerId>();
113
+ auto containerId = args.GetValue<ArgType::ContainerId>();
114
VERIFY_ARE_EQUAL(L"cont1", containerId);
115
}
116
@@ -121,14 +122,14 @@ class WSLCCLIParserUnitTests
122
if (testCase.commandLine.find(L"command") != std::wstring::npos && testCase.argumentSet == ArgumentSet::Run)
123
{
124
VERIFY_IS_TRUE(args.Contains(ArgType::Command));
124
- auto command = args.Get<ArgType::Command>();
125
+ auto command = args.GetValue<ArgType::Command>();
126
VERIFY_IS_TRUE(command.find(L"command") != std::wstring::npos);
127
}
128
129
if (testCase.commandLine.find(L"forward") != std::wstring::npos && testCase.argumentSet == ArgumentSet::Run)
130
{
131
VERIFY_IS_TRUE(args.Contains(ArgType::ForwardArgs));
131
- auto forwardArgs = args.Get<ArgType::ForwardArgs>();
132
+ auto forwardArgs = args.GetValue<ArgType::ForwardArgs>();
133
std::wstring forwardArgsConcat = wsl::shared::string::Join(forwardArgs, L' ');
134
VERIFY_IS_TRUE(forwardArgsConcat.find(L"hello world") != std::wstring::npos); // Forward args should contain hello world
135
VERIFY_IS_TRUE(forwardArgsConcat.find(L"image1") == std::wstring::npos); // Forward args should not contain the imageId
@@ -139,7 +140,7 @@ class WSLCCLIParserUnitTests
140
if (testCase.commandLine.find(L"443") != std::wstring::npos)
141
{
142
VERIFY_IS_TRUE(args.Contains(ArgType::Publish));
142
- auto publishArgs = args.GetAll<ArgType::Publish>();
143
+ auto publishArgs = args.GetAllValues<ArgType::Publish>();
144
VERIFY_ARE_EQUAL(2, publishArgs.size()); // Should have both publish args
145
VERIFY_ARE_NOT_EQUAL(publishArgs[0], publishArgs[1]); // Both publish args should be different
146
}
@@ -159,7 +160,7 @@ class WSLCCLIParserUnitTests
160
if (testCase.commandLine.find(L"--session") != std::wstring::npos)
161
{
162
VERIFY_IS_TRUE(args.Contains(ArgType::Session));
162
- VERIFY_ARE_EQUAL(std::wstring(L"foo"), args.Get<ArgType::Session>());
163
+ VERIFY_ARE_EQUAL(std::wstring(L"foo"), args.GetValue<ArgType::Session>());
164
}
165
}
166
}
@@ -266,7 +267,7 @@ class WSLCCLIParserUnitTests
267
}
268
269
VERIFY_IS_TRUE(args.Contains(ArgType::Signal));
269
- VERIFY_ARE_EQUAL(std::wstring(L"9"), args.Get<ArgType::Signal>());
270
+ VERIFY_ARE_EQUAL(WSLCSignalSIGKILL, args.GetValue<ArgType::Signal>());
271
VERIFY_ARE_EQUAL(std::wstring(L"image1"), *sm.Position());
272
}
273
@@ -284,7 +285,7 @@ class WSLCCLIParserUnitTests
285
}
286
287
VERIFY_IS_TRUE(args.Contains(ArgType::Signal));
287
- VERIFY_ARE_EQUAL(std::wstring(L"9"), args.Get<ArgType::Signal>());
288
+ VERIFY_ARE_EQUAL(WSLCSignalSIGKILL, args.GetValue<ArgType::Signal>());
289
VERIFY_ARE_EQUAL(std::wstring(L"image1"), *sm.Position());
290
}
291
@@ -369,9 +370,9 @@ class WSLCCLIParserUnitTests
370
}
371
372
VERIFY_IS_TRUE(subArgs.Contains(ArgType::ImageId));
372
- VERIFY_ARE_EQUAL(std::wstring(L"image1"), subArgs.Get<ArgType::ImageId>());
373
+ VERIFY_ARE_EQUAL(std::wstring(L"image1"), subArgs.GetValue<ArgType::ImageId>());
374
VERIFY_IS_TRUE(subArgs.Contains(ArgType::Signal));
374
- VERIFY_ARE_EQUAL(std::wstring(L"9"), subArgs.Get<ArgType::Signal>());
375
+ VERIFY_ARE_EQUAL(WSLCSignalSIGKILL, subArgs.GetValue<ArgType::Signal>());
376
}
377
378
// stopOnUnknown: unknown -alias / --name / lone '-' / bare '--' tokens
@@ -466,7 +467,7 @@ class WSLCCLIParserUnitTests
467
sm.ThrowIfError();
468
469
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Signal));
469
- VERIFY_ARE_EQUAL(std::wstring(L"9"), args.Get<ArgType::Signal>());
470
+ VERIFY_ARE_EQUAL(WSLCSignalSIGKILL, args.GetValue<ArgType::Signal>());
471
}
472
473
// A preloaded (env-style) default followed by multiple CLI values collapses to the
@@ -488,7 +489,7 @@ class WSLCCLIParserUnitTests
489
sm.ThrowIfError();
490
491
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Signal));
491
- VERIFY_ARE_EQUAL(std::wstring(L"1"), args.Get<ArgType::Signal>());
492
+ VERIFY_ARE_EQUAL(WSLCSignalSIGHUP, args.GetValue<ArgType::Signal>());
493
}
494
495
// Preloaded flag default plus CLI mention of the same flag stays a single
@@ -510,7 +511,7 @@ class WSLCCLIParserUnitTests
511
sm.ThrowIfError();
512
513
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Verbose));
513
- VERIFY_IS_TRUE(args.Get<ArgType::Verbose>());
514
+ VERIFY_IS_TRUE(args.GetValue<ArgType::Verbose>());
515
}
516
517
// Duplicate flag on the CLI (no env preload) folds to one entry: docker-style.
@@ -529,7 +530,7 @@ class WSLCCLIParserUnitTests
530
sm.ThrowIfError();
531
532
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Verbose));
532
- VERIFY_IS_TRUE(args.Get<ArgType::Verbose>());
533
+ VERIFY_IS_TRUE(args.GetValue<ArgType::Verbose>());
534
}
535
536
// Duplicate single-value arg on the CLI (no preload) is last-wins (docker-style):
@@ -549,7 +550,7 @@ class WSLCCLIParserUnitTests
550
sm.ThrowIfError();
551
552
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Signal));
552
- VERIFY_ARE_EQUAL(std::wstring(L"1"), args.Get<ArgType::Signal>());
553
+ VERIFY_ARE_EQUAL(WSLCSignalSIGHUP, args.GetValue<ArgType::Signal>());
554
}
555
556
// Unlimited value args are exempt from last-wins: every CLI occurrence accumulates.
@@ -561,7 +562,7 @@ class WSLCCLIParserUnitTests
562
}
563
564
// Boolean flags store their explicit parsed value: present with true or false when the
564
- // flag is specified, absent when it is not. Consumers read them via ArgMap::GetFlag,
565
+ // flag is specified, absent when it is not. Consumers read them via ArgMap::GetValue(defaultValue),
566
// which returns the stored value if present or a caller-supplied default if absent. The
567
// helper parses a single command line against the supplied defs and returns the resulting
568
// ArgMap so each case can assert the stored flag value.
@@ -596,13 +597,12 @@ class WSLCCLIParserUnitTests
597
598
VERIFY_IS_TRUE(args.Contains(ArgType::Verbose));
599
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Verbose));
599
- VERIFY_IS_TRUE(args.Get<ArgType::Verbose>());
600
- VERIFY_IS_TRUE(args.GetFlag<ArgType::Verbose>());
600
+ VERIFY_IS_TRUE(args.GetValue<ArgType::Verbose>());
601
}
602
}
603
604
// Every recognized false form stores the flag present with value false (a docker-style
605
- // "--flag=false"), so Contains() is true but GetFlag() reports false. The single-letter
605
+ // "--flag=false"), so Contains() is true but GetValue() reports false. The single-letter
606
// "f"/"F" forms are Docker-parity extensions enabled for the CLI flag path.
607
TEST_METHOD(Flag_FalseForms_StoreSingleFalseEntry)
608
{
@@ -614,8 +614,7 @@ class WSLCCLIParserUnitTests
614
615
VERIFY_IS_TRUE(args.Contains(ArgType::Verbose));
616
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Verbose));
617
- VERIFY_IS_FALSE(args.Get<ArgType::Verbose>());
618
- VERIFY_IS_FALSE(args.GetFlag<ArgType::Verbose>());
617
+ VERIFY_IS_FALSE(args.GetValue<ArgType::Verbose>());
618
}
619
}
620
@@ -654,26 +653,26 @@ class WSLCCLIParserUnitTests
653
L"wslc --verbose true", {Argument::Create(ArgType::Verbose), Argument::Create(ArgType::ContainerId, false, Limit::Unlimited)});
654
655
VERIFY_IS_TRUE(args.Contains(ArgType::Verbose));
657
- VERIFY_IS_TRUE(args.Get<ArgType::Verbose>());
656
+ VERIFY_IS_TRUE(args.GetValue<ArgType::Verbose>());
657
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::ContainerId));
659
- VERIFY_ARE_EQUAL(std::wstring(L"true"), args.Get<ArgType::ContainerId>());
658
+ VERIFY_ARE_EQUAL(std::wstring(L"true"), args.GetValue<ArgType::ContainerId>());
659
}
660
661
// Alias forms honor adjoined booleans just like the long name.
662
TEST_METHOD(Flag_AliasAdjoinedBoolean)
663
{
665
- VERIFY_IS_TRUE(ParseFlags(L"wslc -q", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
666
- VERIFY_IS_TRUE(ParseFlags(L"wslc -q=true", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
667
- VERIFY_IS_FALSE(ParseFlags(L"wslc -q=false", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
664
+ VERIFY_IS_TRUE(ParseFlags(L"wslc -q", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
665
+ VERIFY_IS_TRUE(ParseFlags(L"wslc -q=true", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
666
+ VERIFY_IS_FALSE(ParseFlags(L"wslc -q=false", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
667
}
668
669
// Docker-parity single-letter forms ("t"/"T"/"f"/"F") are honored on the alias form too.
670
TEST_METHOD(Flag_AliasShortBooleanForms)
671
{
673
- VERIFY_IS_TRUE(ParseFlags(L"wslc -q=t", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
674
- VERIFY_IS_TRUE(ParseFlags(L"wslc -q=T", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
675
- VERIFY_IS_FALSE(ParseFlags(L"wslc -q=f", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
676
- VERIFY_IS_FALSE(ParseFlags(L"wslc -q=F", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
672
+ VERIFY_IS_TRUE(ParseFlags(L"wslc -q=t", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
673
+ VERIFY_IS_TRUE(ParseFlags(L"wslc -q=T", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
674
+ VERIFY_IS_FALSE(ParseFlags(L"wslc -q=f", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
675
+ VERIFY_IS_FALSE(ParseFlags(L"wslc -q=F", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
676
}
677
678
// An adjoined boolean value may be wrapped in double quotes (e.g. --flag="true"), just like
@@ -681,12 +680,12 @@ class WSLCCLIParserUnitTests
680
// the named and alias forms.
681
TEST_METHOD(Flag_QuotedAdjoinedBoolean)
682
{
684
- VERIFY_IS_TRUE(ParseFlags(L"wslc --verbose=\"true\"", {Argument::Create(ArgType::Verbose)}).GetFlag<ArgType::Verbose>());
685
- VERIFY_IS_FALSE(ParseFlags(L"wslc --verbose=\"false\"", {Argument::Create(ArgType::Verbose)}).GetFlag<ArgType::Verbose>());
686
- VERIFY_IS_TRUE(ParseFlags(L"wslc -q=\"true\"", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
687
- VERIFY_IS_FALSE(ParseFlags(L"wslc -q=\"false\"", {Argument::Create(ArgType::Quiet)}).GetFlag<ArgType::Quiet>());
688
- VERIFY_IS_TRUE(ParseFlags(L"wslc --verbose=\"t\"", {Argument::Create(ArgType::Verbose)}).GetFlag<ArgType::Verbose>());
689
- VERIFY_IS_FALSE(ParseFlags(L"wslc --verbose=\"f\"", {Argument::Create(ArgType::Verbose)}).GetFlag<ArgType::Verbose>());
683
+ VERIFY_IS_TRUE(ParseFlags(L"wslc --verbose=\"true\"", {Argument::Create(ArgType::Verbose)}).GetValue<ArgType::Verbose>());
684
+ VERIFY_IS_FALSE(ParseFlags(L"wslc --verbose=\"false\"", {Argument::Create(ArgType::Verbose)}).GetValue<ArgType::Verbose>());
685
+ VERIFY_IS_TRUE(ParseFlags(L"wslc -q=\"true\"", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
686
+ VERIFY_IS_FALSE(ParseFlags(L"wslc -q=\"false\"", {Argument::Create(ArgType::Quiet)}).GetValue<ArgType::Quiet>());
687
+ VERIFY_IS_TRUE(ParseFlags(L"wslc --verbose=\"t\"", {Argument::Create(ArgType::Verbose)}).GetValue<ArgType::Verbose>());
688
+ VERIFY_IS_FALSE(ParseFlags(L"wslc --verbose=\"f\"", {Argument::Create(ArgType::Verbose)}).GetValue<ArgType::Verbose>());
689
}
690
691
// In an alias chain, leading flags are true and a trailing "=false" turns only the
@@ -696,25 +695,25 @@ class WSLCCLIParserUnitTests
695
std::vector<Argument> defs = {Argument::Create(ArgType::Quiet), Argument::Create(ArgType::Interactive)};
696
697
ArgMap all = ParseFlags(L"wslc -qi", defs);
699
- VERIFY_IS_TRUE(all.GetFlag<ArgType::Quiet>());
700
- VERIFY_IS_TRUE(all.GetFlag<ArgType::Interactive>());
698
+ VERIFY_IS_TRUE(all.GetValue<ArgType::Quiet>());
699
+ VERIFY_IS_TRUE(all.GetValue<ArgType::Interactive>());
700
701
ArgMap trailingFalse = ParseFlags(L"wslc -qi=false", defs);
703
- VERIFY_IS_TRUE(trailingFalse.GetFlag<ArgType::Quiet>());
704
- VERIFY_IS_FALSE(trailingFalse.GetFlag<ArgType::Interactive>());
702
+ VERIFY_IS_TRUE(trailingFalse.GetValue<ArgType::Quiet>());
703
+ VERIFY_IS_FALSE(trailingFalse.GetValue<ArgType::Interactive>());
704
}
705
706
// Repeated flags are last-wins (matching docker) and never accumulate multiple entries:
707
// "--flag --flag=false" ends up false, the reverse ends up true. The flag is stored either
709
- // way (a single entry), so GetFlag reports the winning value.
708
+ // way (a single entry), so GetValue() reports the winning value.
709
TEST_METHOD(Flag_Repeated_LastWins)
710
{
711
ArgMap trueThenFalse = ParseFlags(L"wslc --verbose --verbose=false", {Argument::Create(ArgType::Verbose)});
713
- VERIFY_IS_FALSE(trueThenFalse.GetFlag<ArgType::Verbose>());
712
+ VERIFY_IS_FALSE(trueThenFalse.GetValue<ArgType::Verbose>());
713
VERIFY_ARE_EQUAL(1u, trueThenFalse.Count(ArgType::Verbose));
714
715
ArgMap falseThenTrue = ParseFlags(L"wslc --verbose=false --verbose", {Argument::Create(ArgType::Verbose)});
717
- VERIFY_IS_TRUE(falseThenTrue.GetFlag<ArgType::Verbose>());
716
+ VERIFY_IS_TRUE(falseThenTrue.GetValue<ArgType::Verbose>());
717
VERIFY_ARE_EQUAL(1u, falseThenTrue.Count(ArgType::Verbose));
718
719
ArgMap duplicateTrue = ParseFlags(L"wslc --verbose --verbose=true", {Argument::Create(ArgType::Verbose)});
@@ -722,7 +721,7 @@ class WSLCCLIParserUnitTests
721
}
722
723
// "--flag=false" overrides a preloaded (env-style) default of true, replacing it with a
725
- // single stored false rather than leaving a lingering true. GetFlag then reports false.
724
+ // single stored false rather than leaving a lingering true. GetValue() then reports false.
725
TEST_METHOD(Flag_FalseOverridesPreloadedDefault)
726
{
727
auto inv = WSLCTestHelpers::CreateInvocationFromCommandLine(L"wslc --verbose=false");
@@ -741,27 +740,27 @@ class WSLCCLIParserUnitTests
740
741
VERIFY_IS_TRUE(args.Contains(ArgType::Verbose));
742
VERIFY_ARE_EQUAL(1u, args.Count(ArgType::Verbose));
744
- VERIFY_IS_FALSE(args.GetFlag<ArgType::Verbose>());
743
+ VERIFY_IS_FALSE(args.GetValue<ArgType::Verbose>());
744
}
745
747
- // A flag whose behavior is on by default is read with GetFlag(true): absent yields the
746
+ // A flag whose behavior is on by default is read with GetValue(true): absent yields the
747
// default (true), "--flag=false" yields false, and "--flag" yields true. A bare Contains()
748
// cannot express this: it reports true for both "--flag" and "--flag=false" and false when
749
// the flag is absent, so it distinguishes neither the two stored values nor absent-as-default.
751
- TEST_METHOD(Flag_GetFlagDefaultTrue_DefaultOnFlag)
750
+ TEST_METHOD(Flag_GetValueDefaultTrue_DefaultOnFlag)
751
{
752
std::vector<Argument> defs = {Argument::Create(ArgType::Remove)};
753
754
ArgMap absent = ParseFlags(L"wslc", defs);
755
VERIFY_IS_FALSE(absent.Contains(ArgType::Remove));
757
- VERIFY_IS_TRUE(absent.GetFlag<ArgType::Remove>(true));
756
+ VERIFY_IS_TRUE(absent.GetValue<ArgType::Remove>(true));
757
758
ArgMap disabled = ParseFlags(L"wslc --rm=false", defs);
759
VERIFY_IS_TRUE(disabled.Contains(ArgType::Remove));
761
- VERIFY_IS_FALSE(disabled.GetFlag<ArgType::Remove>(true));
760
+ VERIFY_IS_FALSE(disabled.GetValue<ArgType::Remove>(true));
761
762
ArgMap enabled = ParseFlags(L"wslc --rm", defs);
764
- VERIFY_IS_TRUE(enabled.GetFlag<ArgType::Remove>(true));
763
+ VERIFY_IS_TRUE(enabled.GetValue<ArgType::Remove>(true));
764
}
765
};
766