diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a3f2c4ff..5ee49664 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,8 +2,9 @@ * Fix NRE in derivation of programName introduced in 6.2.2 [#292](https://github.com/SwensenSoftware/unquote/pull/292) [@dimension-zero](https://github.com/dimension-zero) * Fix Clarify exception in expr2Uci [#293](https://github.com/SwensenSoftware/unquote/pull/293) [@dimension-zero](https://github.com/dimension-zero) * Fix Report all missing args in error message, not just first level [#297](https://github.com/SwensenSoftware/unquote/pull/297) [@dimension-zero](https://github.com/dimension-zero) -* Add `Argu.Samples.Introspect` sample [#298](https://github.com/SwensenSoftware/unquote/pull/298) [@dimension-zero](https://github.com/dimension-zero) * Fix Limit min wordwrap column to 20 [#302](https://github.com/SwensenSoftware/unquote/pull/302) [@dimension-zero](https://github.com/dimension-zero) +* Add `Argu.Samples.Introspect` sample [#298](https://github.com/SwensenSoftware/unquote/pull/298) [@dimension-zero](https://github.com/dimension-zero) +* Add `ArgumentParser.Parse(ParseConfig)` [#307](https://github.com/SwensenSoftware/unquote/pull/307) [@dimension-zero](https://github.com/dimension-zero) ### 6.2.5 * Drop Package `FSharp.Core` dependency to `6.0.0` [#264](https://github.com/fsprojects/Argu/pull/264) diff --git a/src/Argu/ArgumentParser.fs b/src/Argu/ArgumentParser.fs index f5816773..cf185651 100644 --- a/src/Argu/ArgumentParser.fs +++ b/src/Argu/ArgumentParser.fs @@ -3,6 +3,35 @@ open FSharp.Quotations open Argu.UnionArgInfo +/// Configuration record for . +/// Each field carries the same meaning as the matching optional parameter on +/// the existing Parse overload. Use +/// as a starting point and override only the fields you care about. +[] +type ParseConfig = + { + /// The command line input. None takes the inputs from System.Environment. + Inputs : string [] option + /// Configuration reader used to source AppSettings-style arguments. + /// None uses the AppSettings configuration of the current process. + ConfigurationReader : IConfigurationReader option + /// Ignore errors caused by the Mandatory attribute. + IgnoreMissing : bool + /// Ignore CLI arguments that do not match the schema. + IgnoreUnrecognized : bool + /// Treat '--help' parameters as parse errors. + RaiseOnUsage : bool + } + /// Default parse configuration, matching the historical Parse(...) defaults: + /// inputs and configurationReader inherited from the environment, do not ignore + /// missing or unrecognized arguments, and raise on '--help'. + static member Default : ParseConfig = + { Inputs = None + ConfigurationReader = None + IgnoreMissing = false + IgnoreUnrecognized = false + RaiseOnUsage = true } + /// The Argu type generates an argument parser given a type argument /// that is an F# discriminated union. It can then be used to parse command line arguments /// or XML configuration. @@ -156,6 +185,19 @@ and [] with ParserExn (errorCode, msg) -> errorHandler.Exit (msg, errorCode) + /// Parse both command line args and supplied configuration reader, using + /// a record. Useful when callers want to construct + /// the parameter set programmatically (e.g. layering host defaults over user + /// overrides) without juggling many optional method arguments. + /// The parse configuration. See ParseConfig.Default. + member self.Parse (config : ParseConfig) : ParseResults<'Template> = + self.Parse( + ?inputs = config.Inputs, + ?configurationReader = config.ConfigurationReader, + ignoreMissing = config.IgnoreMissing, + ignoreUnrecognized = config.IgnoreUnrecognized, + raiseOnUsage = config.RaiseOnUsage) + /// Parse both command line args and supplied configuration reader. /// Results are merged with command line args overriding configuration parameters. /// The command line input. Taken from System.Environment if not specified. @@ -229,8 +271,8 @@ and [] mkCommandLineArgs argInfo (Seq.cast args) |> Seq.toArray /// Prints parameters in command line format. Useful for argument string generation. - member ap.PrintCommandLineArgumentsFlat (args : 'Template list) : string = - ap.PrintCommandLineArguments args |> flattenCliTokens + member self.PrintCommandLineArgumentsFlat (args : 'Template list) : string = + self.PrintCommandLineArguments args |> flattenCliTokens /// Prints parameters in App.Config format. /// The parameters that fill out the XML document. diff --git a/tests/Argu.Tests/Argu.Tests.fsproj b/tests/Argu.Tests/Argu.Tests.fsproj index 343246e8..9cb686f7 100644 --- a/tests/Argu.Tests/Argu.Tests.fsproj +++ b/tests/Argu.Tests/Argu.Tests.fsproj @@ -7,6 +7,7 @@ + diff --git a/tests/Argu.Tests/ParseConfigTests.fs b/tests/Argu.Tests/ParseConfigTests.fs new file mode 100644 index 00000000..68edf22e --- /dev/null +++ b/tests/Argu.Tests/ParseConfigTests.fs @@ -0,0 +1,85 @@ +module Argu.Tests.ParseConfigTests + +open System.Collections.Generic +open Swensen.Unquote +open Xunit + +open Argu + +type Args = + | [] Port of int + | Verbose + | Tag of string + interface IArgParserTemplate with + member this.Usage = + match this with + | Port _ -> "port" + | Verbose -> "verbose" + | Tag _ -> "tag" + +let private parser () = ArgumentParser.Create() +[] +let ``ParseConfig.Default holds historical defaults`` () = + let d = ParseConfig.Default + test <@ d.Inputs = None @> + test <@ d.ConfigurationReader = None @> + test <@ d.IgnoreMissing = false @> + test <@ d.IgnoreUnrecognized = false @> + test <@ d.RaiseOnUsage = true @> + +[] +let ``Parse(config with explicit inputs) parses those inputs`` () = + let p = parser () + let argv = [| "--port"; "8080"; "--verbose" |] + let cfg = { ParseConfig.Default with Inputs = Some argv ; RaiseOnUsage = false } + let results = p.Parse(cfg) + test <@ results.GetResult(Port) = 8080 @> + test <@ results.Contains(Verbose) @> + +[] +let ``Parse(config) matches Parse(?inputs, ...) for the same parameters`` () = + let p = parser () + let argv = [| "--port"; "1234"; "--tag"; "v1" |] + let viaConfig = + let cfg = { ParseConfig.Default with Inputs = Some argv ; RaiseOnUsage = false } + p.Parse(cfg) + let viaOptional = p.Parse(inputs = argv, raiseOnUsage = false) + test <@ viaConfig.GetResult(Port) = viaOptional.GetResult(Port) @> + test <@ viaConfig.GetResult(Tag) = viaOptional.GetResult(Tag) @> + +[] +let ``Parse(config with IgnoreMissing=true) skips mandatory check`` () = + let p = parser () + let cfg = { ParseConfig.Default with Inputs = Some [||] ; IgnoreMissing = true } + let results = p.Parse(cfg) + test <@ results.TryGetResult(Port) = None @> + +[] +let ``Parse(config with IgnoreUnrecognized=true) collects unknown args`` () = + let p = parser () + let cfg = + { ParseConfig.Default with + Inputs = Some [| "--port"; "1"; "--bogus" |] + IgnoreUnrecognized = true + RaiseOnUsage = false } + let results = p.Parse(cfg) + test <@ results.UnrecognizedCliParams |> List.contains "--bogus" @> + +/// Argu's missing-mandatory check fires from the CLI even when AppSettings provides a value (pre-existing behavior), +/// so the AppSettings round-trip test uses a non-mandatory schema. +type AppSettingsArgs = + | TagKey of string + interface IArgParserTemplate with member this.Usage = "tag" + +[] +let ``Parse(config with ConfigurationReader) sources AppSettings`` () = + let p = ArgumentParser.Create() + let dict = Dictionary() + dict["tagkey"] <- "v1" + let reader = ConfigurationReader.FromDictionary dict + let cfg = + { ParseConfig.Default with + Inputs = Some [||] + ConfigurationReader = Some reader } + let results = p.Parse(cfg) + test <@ results.GetResult(TagKey) = "v1" @>