From 68690f8bbc31e26af1593ccb1c2d95dc1bf67b76 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 30 Jun 2016 20:51:01 +0300 Subject: [PATCH] subparser bugfix --- RELEASE_NOTES.md | 2 +- src/Argu/ParseResult.fs | 4 ++++ src/Argu/Parsers/Cli.fs | 26 ++++++++++++++++---------- src/Argu/Parsers/Common.fs | 2 ++ src/Argu/UnionArgInfo.fs | 2 ++ tests/Argu.Tests/Tests.fs | 6 ++++++ 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 16823bf2..febcc4c7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,4 +1,4 @@ -### 3.0.0-alpha009 +### 3.0.0-alpha010 * Add subcommand support. * Add support for list and option parameters. * Add support for grouped switches. diff --git a/src/Argu/ParseResult.fs b/src/Argu/ParseResult.fs index 7fd26c5b..38cdd433 100644 --- a/src/Argu/ParseResult.fs +++ b/src/Argu/ParseResult.fs @@ -54,6 +54,10 @@ type ParseResult<'Template when 'Template :> IArgParserTemplate> /// accumulates if parsed with 'ignoreUnrecognized = true' member __.UnrecognizedCliParams = results.UnrecognizedCliParams + /// Gets all parse results that are not part of the current parsing context + /// This is only applicable to subcommand parsing operations + member __.UnrecognizedCliParseResults = results.UnrecognizedCliParseResults + /// Query parse results for parameterless argument. /// The name of the parameter, expressed as quotation of DU constructor. /// Optional source restriction: AppSettings or CommandLine. diff --git a/src/Argu/Parsers/Cli.fs b/src/Argu/Parsers/Cli.fs index 0b338ac8..3149ca30 100644 --- a/src/Argu/Parsers/Cli.fs +++ b/src/Argu/Parsers/Cli.fs @@ -58,6 +58,7 @@ type CliParseResultAggregator internal (argInfo : UnionArgInfo, stack : CliParse let mutable resultCount = 0 let mutable isSubCommandDefined = false let unrecognized = new ResizeArray() + let unrecognizedParseResults = new ResizeArray() let results = argInfo.Cases |> Array.map (fun _ -> new ResizeArray ()) member val IsUsageRequested = false with get,set @@ -65,8 +66,7 @@ type CliParseResultAggregator internal (argInfo : UnionArgInfo, stack : CliParse member __.IsSubCommandDefined = isSubCommandDefined member __.AppendResult(result : UnionCaseParseResult) = - match result.CaseInfo.Depth with - | d when argInfo.Depth = d -> + if result.CaseInfo.Depth = argInfo.Depth then resultCount <- resultCount + 1 let agg = results.[result.Tag] if result.CaseInfo.IsUnique && agg.Count > 0 then @@ -76,30 +76,36 @@ type CliParseResultAggregator internal (argInfo : UnionArgInfo, stack : CliParse isSubCommandDefined <- true agg.Add result - - | d -> + else // this parse result corresponds to an inherited parameter // from a parent syntax. Use the ResultAggregator stack to // re-route the result to its matching aggregator - stack.[d].AppendResult result + if stack.TryDispatchResult result then () + else unrecognizedParseResults.Add result.Value member __.AppendUnrecognized(token:string) = unrecognized.Add token member __.ToUnionParseResults() = { Cases = results |> Array.map (fun c -> c.ToArray()) ; UnrecognizedCliParams = Seq.toList unrecognized ; + UnrecognizedCliParseResults = Seq.toList unrecognizedParseResults ; IsUsageRequested = __.IsUsageRequested } // this rudimentary stack implementation assumes that only one subcommand // can occur within any particular context; no need implement popping etc. // Note that inheritting subcommands is explicitly prohibited by the library. -and CliParseResultAggregatorStack () = - let stack = new ResizeArray(capacity = 5) +and CliParseResultAggregatorStack (context : UnionArgInfo) = + let offset = context.Depth + let stack = new ResizeArray(capacity = 2) - member __.Item (depth : int) : CliParseResultAggregator = stack.[depth] + member self.TryDispatchResult(result : UnionCaseParseResult) = + if result.CaseInfo.Depth < offset then false + else + stack.[result.CaseInfo.Depth - offset].AppendResult result + true member self.CreateNextAggregator(argInfo : UnionArgInfo) = - assert(stack.Count = argInfo.Depth) + assert(stack.Count = argInfo.Depth - offset) let agg = new CliParseResultAggregator(argInfo, self) stack.Add agg agg @@ -281,7 +287,7 @@ and parseCommandLine (argInfo : UnionArgInfo) (programName : string) (descriptio let state = { Reader = new CliTokenReader(inputs) ProgramName = programName - ResultStack = new CliParseResultAggregatorStack() + ResultStack = new CliParseResultAggregatorStack(argInfo) Description = description UsageStringCharWidth = width RaiseOnUsage = raiseOnUsage diff --git a/src/Argu/Parsers/Common.fs b/src/Argu/Parsers/Common.fs index d3e783b9..56d3c61d 100644 --- a/src/Argu/Parsers/Common.fs +++ b/src/Argu/Parsers/Common.fs @@ -42,6 +42,7 @@ let mkParseResultFromValues (info : UnionArgInfo) (exiter : IExiter) (width : in { IsUsageRequested = false UnrecognizedCliParams = [] + UnrecognizedCliParseResults = [] Cases = agg |> Array.map (fun rs -> rs.ToArray()) } @@ -76,5 +77,6 @@ let postProcessResults (argInfo : UnionArgInfo) (ignoreMissingMandatory : bool) { Cases = argInfo.Cases |> Array.map combineSingle 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) } \ No newline at end of file diff --git a/src/Argu/UnionArgInfo.fs b/src/Argu/UnionArgInfo.fs index 604470fe..73b306a4 100644 --- a/src/Argu/UnionArgInfo.fs +++ b/src/Argu/UnionArgInfo.fs @@ -183,6 +183,8 @@ type UnionParseResults = Cases : UnionCaseParseResult[][] /// CLI tokens not recognized by the parser UnrecognizedCliParams : string list + /// CLI parse objects not belonging to the current parser context + UnrecognizedCliParseResults : obj list /// Usage string requested by the caller IsUsageRequested : bool } diff --git a/tests/Argu.Tests/Tests.fs b/tests/Argu.Tests/Tests.fs index c834c9a5..628b2df0 100644 --- a/tests/Argu.Tests/Tests.fs +++ b/tests/Argu.Tests/Tests.fs @@ -268,6 +268,12 @@ module ``Argu Tests`` = test <@ match results.TryGetSubCommand() with Some (Clean _) -> true | _ -> false @> test <@ nested.GetAllResults() = [F; D; X] @> + [] + let ``SubParsers should correctly handle inherited params`` () = + let subParser = parser.GetSubCommandParser <@ Clean @> + let result = subParser.ParseCommandLine [|"-fdxv"|] + test <@ match result.UnrecognizedCliParseResults with [:? Argument as c ] -> c = Verbose | _ -> false @> + [] let ``Doubly nested subcommand parsing`` () = let args = [|"required" ; "--foo" ; "sub" ; "-fdx" |]