Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
46 changes: 44 additions & 2 deletions src/Argu/ArgumentParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,35 @@
open FSharp.Quotations
open Argu.UnionArgInfo

/// Configuration record for <see cref="ArgumentParser`1.Parse(ParseConfig)"/>.
/// Each field carries the same meaning as the matching optional parameter on
/// the existing <c>Parse</c> overload. Use <see cref="ParseConfig.Default"/>
/// as a starting point and override only the fields you care about.
[<NoEquality; NoComparison>]
type ParseConfig =
{
/// The command line input. <c>None</c> takes the inputs from <c>System.Environment</c>.
Inputs : string [] option
/// Configuration reader used to source AppSettings-style arguments.
/// <c>None</c> 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 <c>Parse(...)</c> 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.
Expand Down Expand Up @@ -156,6 +185,19 @@ and [<Sealed; NoEquality; NoComparison; AutoSerializable(false)>]

with ParserExn (errorCode, msg) -> errorHandler.Exit (msg, errorCode)

/// <summary>Parse both command line args and supplied configuration reader, using
/// a <see cref="ParseConfig"/> 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.</summary>
/// <param name="config">The parse configuration. See <c>ParseConfig.Default</c>.</param>
member self.Parse (config : ParseConfig) : ParseResults<'Template> =
self.Parse(
?inputs = config.Inputs,
?configurationReader = config.ConfigurationReader,
ignoreMissing = config.IgnoreMissing,
ignoreUnrecognized = config.IgnoreUnrecognized,
raiseOnUsage = config.RaiseOnUsage)

/// <summary>Parse both command line args and supplied configuration reader.
/// Results are merged with command line args overriding configuration parameters.</summary>
/// <param name="inputs">The command line input. Taken from System.Environment if not specified.</param>
Expand Down Expand Up @@ -229,8 +271,8 @@ and [<Sealed; NoEquality; NoComparison; AutoSerializable(false)>]
mkCommandLineArgs argInfo (Seq.cast args) |> Seq.toArray

/// <summary>Prints parameters in command line format. Useful for argument string generation.</summary>
member ap.PrintCommandLineArgumentsFlat (args : 'Template list) : string =
ap.PrintCommandLineArguments args |> flattenCliTokens
member self.PrintCommandLineArgumentsFlat (args : 'Template list) : string =
self.PrintCommandLineArguments args |> flattenCliTokens

/// <summary>Prints parameters in App.Config format.</summary>
/// <param name="args">The parameters that fill out the XML document.</param>
Expand Down
1 change: 1 addition & 0 deletions tests/Argu.Tests/Argu.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Compile Include="PrimitiveTests.fs" />
<Compile Include="Tests.fs"/>
<Compile Include="CoverageTests.fs"/>
<Compile Include="ParseConfigTests.fs"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Argu\Argu.fsproj"/>
Expand Down
85 changes: 85 additions & 0 deletions tests/Argu.Tests/ParseConfigTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module Argu.Tests.ParseConfigTests

open System.Collections.Generic
open Swensen.Unquote
open Xunit

open Argu

type Args =
| [<Mandatory>] 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<Args>()
[<Fact>]
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 @>

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

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

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

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

[<Fact>]
let ``Parse(config with ConfigurationReader) sources AppSettings`` () =
let p = ArgumentParser.Create<AppSettingsArgs>()
let dict = Dictionary<string, string>()
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" @>
Loading