diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bebfe5a3..1f034c9b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 6.2.3 +* Improve error message on missing cases on a subcommands (display all missing cases) [#236](https://github.com/fsprojects/Argu/pull/236) [@fpellet](https://github.com/fpellet) +* Fix the regression of the [#127](https://github.com/fsprojects/Argu/pull/127) merged in 6.1.2 and fix usage display when there are missing case in subcommands. [#236](https://github.com/fsprojects/Argu/pull/236) [@fpellet](https://github.com/fpellet) + ### 6.2.2 * Fix default `programName` when invoking via a wrapper such as `dotnet.exe` [#233](https://github.com/fsprojects/Argu/pull/233) diff --git a/src/Argu/Parsers/Cli.fs b/src/Argu/Parsers/Cli.fs index 084ad0c8..4403363f 100644 --- a/src/Argu/Parsers/Cli.fs +++ b/src/Argu/Parsers/Cli.fs @@ -79,7 +79,7 @@ type CliParseResultAggregator internal (argInfo : UnionArgInfo, stack : CliParse let unrecognized = ResizeArray() let unrecognizedParseResults = ResizeArray() let results = lazy(argInfo.Cases.Value |> Array.map (fun _ -> ResizeArray())) - let missingMandatoryCasesOfNestedResults = ResizeArray() + let missingMandatoryCasesOfNestedResults = ResizeArray() member val IsUsageRequested = false with get,set @@ -135,7 +135,11 @@ type CliParseResultAggregator internal (argInfo : UnionArgInfo, stack : CliParse IsUsageRequested = x.IsUsageRequested MissingMandatoryCases = [ yield! missingMandatoryCasesOfNestedResults - yield! argInfo.Cases.Value |> Seq.filter (fun case -> case.IsMandatory.Value && results.Value[case.Tag].Count = 0) + + match argInfo.Cases.Value |> Seq.filter (fun case -> case.IsMandatory.Value && results.Value[case.Tag].Count = 0) |> Seq.toList with + | [] -> () + | missingCases -> + yield argInfo, missingCases ] } diff --git a/src/Argu/Parsers/Common.fs b/src/Argu/Parsers/Common.fs index dfc44946..888119a3 100644 --- a/src/Argu/Parsers/Common.fs +++ b/src/Argu/Parsers/Common.fs @@ -70,8 +70,9 @@ let postProcessResults (argInfo : UnionArgInfo) (ignoreMissingMandatory : bool) | _, ts' -> ts' match combined, commandLineResults with - | _, Some { MissingMandatoryCases = missingCase::_ } when not ignoreMissingMandatory -> - error argInfo ErrorCode.PostProcess "missing parameter '%s'." missingCase.Name.Value + | _, Some { MissingMandatoryCases = (caseArgInfo, missingCases)::_ } when not ignoreMissingMandatory -> + let allCasesFormatted = missingCases |> Seq.map (fun c -> c.Name.Value) |> fun v -> System.String.Join("', '", v) + error caseArgInfo ErrorCode.PostProcess "missing parameter '%s'." allCasesFormatted | [||], _ when caseInfo.IsMandatory.Value && not ignoreMissingMandatory -> error argInfo ErrorCode.PostProcess "missing parameter '%s'." caseInfo.Name.Value diff --git a/src/Argu/UnionArgInfo.fs b/src/Argu/UnionArgInfo.fs index 631c383c..b1d6b131 100644 --- a/src/Argu/UnionArgInfo.fs +++ b/src/Argu/UnionArgInfo.fs @@ -204,7 +204,7 @@ type UnionParseResults = UnrecognizedCliParseResults : obj list /// Usage string requested by the caller IsUsageRequested : bool - MissingMandatoryCases: UnionCaseArgInfo list + MissingMandatoryCases: (UnionArgInfo * UnionCaseArgInfo list) list } type UnionCaseArgInfo with diff --git a/tests/Argu.Tests/Tests.fs b/tests/Argu.Tests/Tests.fs index 8c164c4c..9d1a5101 100644 --- a/tests/Argu.Tests/Tests.fs +++ b/tests/Argu.Tests/Tests.fs @@ -80,6 +80,13 @@ module ``Argu Tests Main List`` = interface IArgParserTemplate with member this.Usage = "gus" + type MultipleMandatoriesSubCommand = + | [] ValueA of int + | [] ValueB of int + | [] ValueC of int + | ValueD of int + interface IArgParserTemplate with member this.Usage = "multiple mandatories subcommand arg" + type Argument = | [] Verbose | Working_Directory of string @@ -116,6 +123,7 @@ module ``Argu Tests Main List`` = | [] Clean of ParseResults | [] Required of ParseResults | [] Unrecognized of ParseResults + | [] Multiple_Mandatories of ParseResults | [] Nullary_Sub interface IArgParserTemplate with member a.Usage = @@ -150,6 +158,7 @@ module ``Argu Tests Main List`` = | Clean _ -> "clean state" | Required _ -> "required subcommand" | Unrecognized _ -> "unrecognized subcommand" + | Multiple_Mandatories _ -> "multiple mandatories subcommand" | Nullary_Sub -> "nullary subcommand" | List _ -> "variadic params" | Optional _ -> "optional params" @@ -474,6 +483,12 @@ module ``Argu Tests Main List`` = raisesWith <@ parser.ParseCommandLine args @> (fun e -> <@ e.FirstLine.Contains "--branch" @>) + [] + let ``Main command parsing should fail and display all missing mandatories sub command parameters`` () = + let args = [|"--mandatory-arg" ; "true" ; "multiple-mandatories" ; "--valuea"; "5"|] + raisesWith <@ parser.ParseCommandLine args @> + (fun e -> <@ e.FirstLine.Contains "ERROR: missing parameter '--valueb', '--valuec'." @>) + [] let ``Main command parsing should not fail on missing mandatory sub command parameter if ignoreMissing`` () = let args = [|"--mandatory-arg" ; "true" ; "checkout" |] @@ -661,7 +676,7 @@ module ``Argu Tests Main List`` = [] let ``Get all subcommand parsers`` () = let subcommands = parser.GetSubCommandParsers() - test <@ subcommands.Length = 6 @> + test <@ subcommands.Length = 7 @> test <@ subcommands |> List.forall (fun sc -> sc.IsSubCommandParser) @> [] @@ -852,6 +867,12 @@ module ``Argu Tests Main List`` = let results = parser.ParseCommandLine (args, raiseOnUsage = false) test <@ results.IsUsageRequested @> + [] + let ``Should fail if mandatory case is missing on a subcommand and display usage of subcommand and not main command`` () = + let args = [|"--mandatory-arg" ; "true" ; "multiple-mandatories" ; "--valuea"; "5"|] + raisesWith <@ parser.ParseCommandLine args @> + (fun e -> <@ e.FirstLine.Contains "ERROR: missing parameter '--valueb', '--valuec'" + && e.Message.Contains $"USAGE: {parser.ProgramName} multiple-mandatories [--help] --valuea " @>) [] []