diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3207547a..df4d9ee3 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,6 @@ +### 6.1.5 +* Fix the regression of the [#127](https://github.com/fsprojects/Argu/pull/127) merged in 6.1.2 and fix Mandatory arguments in nested subcommands. [#220](https://github.com/fsprojects/Argu/issues/220) [@fpellet](https://github.com/fpellet) + ### 6.1.4 * Fix: remove incorrect `ReproducibleBuilds` reference [introduced in `6.1.3`](https://github.com/fsprojects/Argu/pull/174) [#202](https://github.com/fsprojects/Argu/pull/202) diff --git a/src/Argu/Parsers/Cli.fs b/src/Argu/Parsers/Cli.fs index 679e6425..084ad0c8 100644 --- a/src/Argu/Parsers/Cli.fs +++ b/src/Argu/Parsers/Cli.fs @@ -79,6 +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() member val IsUsageRequested = false with get,set @@ -121,13 +122,22 @@ type CliParseResultAggregator internal (argInfo : UnionArgInfo, stack : CliParse if stack.TryDispatchResult result then () else unrecognizedParseResults.Add result.Value + member x.AppendResultWithNestedResults(caseInfo, context, arguments, nestedResults) = + missingMandatoryCasesOfNestedResults.AddRange(nestedResults.MissingMandatoryCases) + x.AppendResult caseInfo context arguments + member _.AppendUnrecognized(token:string) = unrecognized.Add token member x.ToUnionParseResults() = { Cases = results.Value |> Array.map (fun c -> c.ToArray()) ; UnrecognizedCliParams = Seq.toList unrecognized ; UnrecognizedCliParseResults = Seq.toList unrecognizedParseResults ; - IsUsageRequested = x.IsUsageRequested } + IsUsageRequested = x.IsUsageRequested + MissingMandatoryCases = [ + yield! missingMandatoryCasesOfNestedResults + yield! argInfo.Cases.Value |> Seq.filter (fun case -> case.IsMandatory.Value && results.Value[case.Tag].Count = 0) + ] + } // this rudimentary stack implementation assumes that only one subcommand // can occur within any particular context; no need implement popping etc. @@ -423,7 +433,7 @@ let rec private parseCommandLinePartial (state : CliParseState) (argInfo : Union member _.Invoke<'Template when 'Template :> IArgParserTemplate> () = new ParseResults<'Template>(nestedUnion, nestedResults, state.ProgramName, state.Description, state.UsageStringCharWidth, state.Exiter) :> obj } - aggregator.AppendResult caseInfo name [|result|] + aggregator.AppendResultWithNestedResults(caseInfo, name, [|result|], nestedResults) | NullarySubCommand -> aggregator.AppendResult caseInfo name [||] diff --git a/src/Argu/Parsers/Common.fs b/src/Argu/Parsers/Common.fs index d6c20613..dfc44946 100644 --- a/src/Argu/Parsers/Common.fs +++ b/src/Argu/Parsers/Common.fs @@ -43,6 +43,7 @@ let mkParseResultFromValues (info : UnionArgInfo) (exiter : IExiter) (width : in UnrecognizedCliParams = [] UnrecognizedCliParseResults = [] Cases = agg |> Array.map (fun rs -> rs.ToArray()) + MissingMandatoryCases = [] } new ParseResults<'Template>(info, results, programName, description, width, exiter) @@ -68,25 +69,11 @@ let postProcessResults (argInfo : UnionArgInfo) (ignoreMissingMandatory : bool) | Choice1Of2 ts, ts' when caseInfo.GatherAllSources.Value -> Array.append ts ts' | _, ts' -> ts' - let rec searchCaseInfoForError caseInfo = - match caseInfo.ParameterInfo.Value with - | SubCommand (_, unionArg, __) -> - match unionArg.Cases.Value with - | [| case |] -> - if case.IsMandatory.Value && not ignoreMissingMandatory then - Some (error unionArg ErrorCode.PostProcess "missing parameter '%s'." case.Name.Value) - else - searchCaseInfoForError case - | _ -> None - | _ -> None + match combined, commandLineResults with + | _, Some { MissingMandatoryCases = missingCase::_ } when not ignoreMissingMandatory -> + error argInfo ErrorCode.PostProcess "missing parameter '%s'." missingCase.Name.Value - match combined with - | [| sub |] -> - match searchCaseInfoForError sub.CaseInfo with - | Some error -> error - | _ -> combined - - | [||] when caseInfo.IsMandatory.Value && not ignoreMissingMandatory -> + | [||], _ when caseInfo.IsMandatory.Value && not ignoreMissingMandatory -> error argInfo ErrorCode.PostProcess "missing parameter '%s'." caseInfo.Name.Value | _ -> combined @@ -95,4 +82,5 @@ let postProcessResults (argInfo : UnionArgInfo) (ignoreMissingMandatory : bool) UnrecognizedCliParams = match commandLineResults with Some clr -> clr.UnrecognizedCliParams | None -> [] UnrecognizedCliParseResults = match commandLineResults with Some clr -> clr.UnrecognizedCliParseResults | None -> [] IsUsageRequested = commandLineResults |> Option.exists (fun r -> r.IsUsageRequested) + MissingMandatoryCases = commandLineResults |> Option.map (fun c -> c.MissingMandatoryCases) |> Option.defaultValue [] } diff --git a/src/Argu/UnionArgInfo.fs b/src/Argu/UnionArgInfo.fs index a07852f9..631c383c 100644 --- a/src/Argu/UnionArgInfo.fs +++ b/src/Argu/UnionArgInfo.fs @@ -204,6 +204,7 @@ type UnionParseResults = UnrecognizedCliParseResults : obj list /// Usage string requested by the caller IsUsageRequested : bool + MissingMandatoryCases: UnionCaseArgInfo list } type UnionCaseArgInfo with diff --git a/tests/Argu.Tests/Tests.fs b/tests/Argu.Tests/Tests.fs index 691394b0..d46f729a 100644 --- a/tests/Argu.Tests/Tests.fs +++ b/tests/Argu.Tests/Tests.fs @@ -474,6 +474,20 @@ module ``Argu Tests Main List`` = raisesWith <@ parser.ParseCommandLine args @> (fun e -> <@ e.FirstLine.Contains "--branch" @>) + [] + let ``Main command parsing should not fail on missing mandatory sub command parameter if ignoreMissing`` () = + let args = [|"--mandatory-arg" ; "true" ; "checkout" |] + let results = parser.ParseCommandLine(args, ignoreMissing = true) + let nested = results.GetResult <@ Checkout @> + test <@ not (nested.Contains <@ Branch @>) @> + + [] + let ``Main command parsing should allow sub command if not missing mandatory parameter`` () = + let args = [|"--mandatory-arg" ; "true" ; "checkout"; "--branch"; "origin" |] + let results = parser.ParseCommandLine(args) + let nested = results.GetResult <@ Checkout @> + test <@ nested.GetResults <@ Branch @> = ["origin"] @> + [] let ``Main command parsing should fail on missing mandatory sub command's sub command parameter`` () = let args = [|"--mandatory-arg"; "true"; "tag"; "--new"; |]