Skip to content

Commit

Permalink
fix: mandatory parameters handling (fsprojects#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
fpellet authored and bartelink committed Feb 28, 2024
1 parent 997e12c commit 8718d74
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 20 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
14 changes: 12 additions & 2 deletions src/Argu/Parsers/Cli.fs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type CliParseResultAggregator internal (argInfo : UnionArgInfo, stack : CliParse
let unrecognized = ResizeArray<string>()
let unrecognizedParseResults = ResizeArray<obj>()
let results = lazy(argInfo.Cases.Value |> Array.map (fun _ -> ResizeArray<UnionCaseParseResult>()))
let missingMandatoryCasesOfNestedResults = ResizeArray<UnionCaseArgInfo>()

member val IsUsageRequested = false with get,set

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 [||]
Expand Down
24 changes: 6 additions & 18 deletions src/Argu/Parsers/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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 []
}
1 change: 1 addition & 0 deletions src/Argu/UnionArgInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ type UnionParseResults =
UnrecognizedCliParseResults : obj list
/// Usage string requested by the caller
IsUsageRequested : bool
MissingMandatoryCases: UnionCaseArgInfo list
}

type UnionCaseArgInfo with
Expand Down
14 changes: 14 additions & 0 deletions tests/Argu.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,20 @@ module ``Argu Tests Main List`` =
raisesWith<ArguParseException> <@ parser.ParseCommandLine args @>
(fun e -> <@ e.FirstLine.Contains "--branch" @>)

[<Fact>]
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 @>) @>

[<Fact>]
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"] @>

[<Fact>]
let ``Main command parsing should fail on missing mandatory sub command's sub command parameter`` () =
let args = [|"--mandatory-arg"; "true"; "tag"; "--new"; |]
Expand Down

0 comments on commit 8718d74

Please sign in to comment.