CLI: Align WSLC argument parser with Docker flag and value semantics - #41160
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the WSLC CLI argument parsing and consumption model to match Dockerâs flag/value semantics, reducing surprising parse failures and enabling explicit boolean flag values.
Changes:
- Implement Docker-style boolean flag value parsing (including
=true/false/1/0/t/f, case-insensitive) and ensure space-separated tokens after flags remain positional. - Change single-value arguments (including flags) to âlast-winsâ behavior and model argument multiplicity via
Limit::{Single,Unlimited}. - Migrate flag consumers to
ArgMap::GetFlag<...>(defaultValue)and extend sharedParseBoolwith an opt-in extended-forms parameter; update/expand parser unit tests.
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/windows/wslc/WSLCCLIParserUnitTests.cpp | Updates expectations for last-wins semantics; adds targeted tests for stored flag values and Docker-style boolean forms. |
| test/windows/wslc/ParserTestCases.h | Extends parser test matrix for last-wins single-value args and boolean =value flag parsing rules. |
| test/windows/SimpleTests.cpp | Adds coverage for ParseBool(..., AllowExtendedForms=true) including t/f forms. |
| src/windows/wslc/tasks/VolumeTasks.cpp | Switches flag checks from Contains() to GetFlag() for Docker-style explicit false handling. |
| src/windows/wslc/tasks/SessionTasks.cpp | Switches verbose flag usage to GetFlag(). |
| src/windows/wslc/tasks/NetworkTasks.cpp | Switches internal/quiet/force/all flag usage to GetFlag(). |
| src/windows/wslc/tasks/ImageTasks.cpp | Switches multiple flags (verbose/quiet/no-trunc/etc.) to GetFlag() to honor explicit false. |
| src/windows/wslc/tasks/ContainerTasks.cpp | Switches numerous flags to GetFlag() to align runtime behavior with stored boolean values. |
| src/windows/wslc/core/Command.cpp | Adjusts help rendering for unlimited args and switches help checks to GetFlag(); removes now-obsolete âtoo manyâ validation. |
| src/windows/wslc/core/CLIExecutionContext.cpp | Switches global --no-color handling to GetFlag(). |
| src/windows/wslc/commands/*.cpp | Replaces NO_LIMIT with Limit::Unlimited across command argument definitions; updates certain mutual-exclusion checks to use GetFlag() for flags. |
| src/windows/wslc/arguments/ArgumentTypes.h | Introduces Limit enum and ArgMap::GetFlag helper. |
| src/windows/wslc/arguments/ArgumentParser.h / .cpp | Implements stored flag values, shared quote stripping, last-wins single-value behavior, and Docker-style adjoined boolean parsing. |
| src/windows/wslc/arguments/Argument.h / .cpp | Replaces integer count limit with Limit and adds IsSingle()/IsUnlimited() helpers. |
| src/shared/inc/stringshared.h | Extends ParseBool with an opt-in AllowExtendedForms parameter supporting t/f. |
| localization/strings/en-US/Resources.resw | Removes the unused âtoo many argumentsâ string after semantics change. |
Blue (OneBlue)
left a comment
There was a problem hiding this comment.
LGTM, minor suggestion
| // the final value replaces the earlier one instead of accumulating. | ||
| TEST_METHOD(DuplicateValueOnCli_LastWins) | ||
| { | ||
| auto inv = WSLCTestHelpers::CreateInvocationFromCommandLine(L"wslc --signal 9 --signal 1"); |
There was a problem hiding this comment.
nit: If an override happens, I think outputting a warning would be a good idea
There was a problem hiding this comment.
I agree that it may indicate a user mistake and a warning makes sense, however Docker does not do this and overwrites are silent. The reason for this is partially that it is the POSIX/GNU convention and Docker inherited it from dependent packages. The functional reason is that the idea is defaults layer and later things can override. This behavior would also allow us to have a feature for configurable defaults that are later overridden by the command line, but some users may do this today via scripting.
Given that this behavior has been in docker for a decade and some consumers may rely on it, I am inclined to not put in a warning on overrides as that would violate the convention and might pose problems for users and scripts and future features that may rely on this override behavior.
I think it is worth discussing this behavior and whether we want to intentionally diverge, but that can be a follow-up PR.
Summary of the Pull Request
Purpose: a valid Docker command line, mapped to the equivalent wslc command, should produce a valid wslc command. This PR brings the wslc CLI argument parser in line with Docker's command-line semantics so that users coming from Docker are not surprised by parse failures or different results.
Boolean flags now accept Docker-style adjoined values:
--flag,--flag=true,--flag=1,--flag=t, and their false counterparts--flag=false,--flag=0,--flag=f(all case-insensitive), on both long names and alias forms including chains (-ab=false). A space-separated token after a flag is never consumed as the flag's value (--flag valueleavesvalueas a positional), matching Docker. Adjoined values may be double-quoted (--flag="true"), consistent with value arguments. Repeated flags and single-value arguments are last-wins, and unlimited value arguments accumulate, all matching Docker.Flags now store their explicit parsed value (true or false) rather than being encoded purely by presence. A new
ArgMap::GetFlag<ArgType::X>(defaultValue = false)shorthand folds the presence check and the stored value into a single call, and all flag on/off tests in the CLI now use it. This closes a forward-looking Docker-parity gap: a flag whose behavior is on by default can be represented and disabled with--flag=falseviaGetFlag<...>(true), which pure presence-encoding could not express.Contains()is unchanged and still used for "was it specified" checks (value arguments and mutual-exclusion validation).Supporting changes:
ParseBoolgains an opt-inAllowExtendedFormsparameter (default preserves today's behavior for every other caller; the CLI opts in to also acceptt/f); the count limit is modeled asenum class Limit { Single, Unlimited }instead of an int-plus-sentinel; and a latent bug where unlimited positionals never rendered the help "..." indicator is fixed.PR Checklist
Detailed Description of the Pull Request / Additional comments
Docker-style boolean flag values (
ArgumentParser.cpp/.h)ApplyFlagValuestrips one pair of surrounding double quotes, then parses the token as a Docker-style boolean. Invalid tokens (for example--flag=maybe) raise an argument error instead of being silently ignored.StripSurroundingQuotesis shared by the flag and value adjoined-value paths so both treat="value"identically.ApplyFlagValue, so-q=false,-qi=false, and--flag=falsebehave the same.Flag value storage +
GetFlagshorthand (ArgumentTypes.h,ArgumentParser.cpp)SetFlag(type, value)clears any prior entry (including an env-preloaded overridable default), then stores the explicit value. Clearing first keeps a flag at a single entry and gives last-wins for repeats (--flag --flag=falseends up false).ArgMap::GetFlag<E>(defaultValue = false)returns the stored value when present, ordefaultValuewhen absent, with astatic_assertthatEis aKind::Flag. Passtruefor a default-on flag.Consumer migration (43 sites)
Contains(ArgType::Flag)toGetFlag<ArgType::Flag>().Contains()checks are untouched.ParseBoolextended forms (src/shared/inc/stringshared.h)AllowExtendedFormsparameter, defaulting to false soconfigfile,CommandLine.h, andOptionParserare unaffected. When true,t/f(case-insensitive) are accepted in addition totrue/false/1/0. The CLI flag path opts in.Count limit model + latent fix
enum class Limit { Single, Unlimited }replaces the int count plusNO_LIMITsentinel acrossArgument, the parser, and all commands. Single value args are last-wins; unlimited args accumulate.Command.cpphelp rendering usedLimit() > 1(always false under the old model), so unlimited positionals never showed "..."; it now usesIsUnlimited().Validation Steps Performed
WSLCCLIParserUnitTests.cpp: value-aware assertions viaGetFlag;Flag_FalseForms_StoreSingleFalseEntry(present with false);Flag_FalseOverridesPreloadedDefault; andFlag_GetFlagDefaultTrue_DefaultOnFlagdemonstrating the default-on pattern.ParserTestCases.hcovers true/false/1/0/t/f, quoted, alias, and reject cases.SimpleTests.cppcoversParseBooldefault vs extended modes.