Skip to content

Commit

Permalink
feat: Add GetResult(expr, defThunk) (fsprojects#187)
Browse files Browse the repository at this point in the history
* Add GetResult(expr, defThunk)

* Update ParseResults.fs

Fell into same trap!

* Add changelog

* Add exception trapping + tests
  • Loading branch information
bartelink authored Dec 13, 2023
1 parent b6893ea commit 92b9315
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 16 deletions.
7 changes: 5 additions & 2 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
### 6.1.3
* Add ParseResults.GetResult(expr, unit -> 'T) helper for Catch [#187](https:/fsprojects/Argu/pull/187)

### 6.1.2
* Fix Mandatory arguments in nested subcommands. [#116](https:/fsprojects/Argu/issues/116)
* Fix Consistent handling of numeric decimal separators using invariant culture. [#159](https:/fsprojects/Argu/issues/159)
* Fix Mandatory arguments in nested subcommands. [#116](https:/fsprojects/Argu/issues/116) [@chestercodes](https:/chestercodes)
* Fix Consistent handling of numeric decimal separators using invariant culture. [#159](https:/fsprojects/Argu/issues/159) [@stmax82](https:/stmax82)

### 6.1.1
* Fix CustomAssignmentOrSpacedAttribute interop with optional fields.
Expand Down
37 changes: 25 additions & 12 deletions src/Argu/ParseResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type ParseResults<[<EqualityConditionalOn; ComparisonConditionalOn>]'Template wh
/// <summary>Returns the *last* specified parameter of given type.
/// Command line parameters have precedence over AppSettings parameters.</summary>
/// <param name="expr">The name of the parameter, expressed as quotation of DU constructor.</param>
/// <param name="defaultValue">Return this of no parameter of specific kind has been specified.</param>
/// <param name="defaultValue">Return this if no parameter of specific kind has been specified.</param>
/// <param name="source">Optional source restriction: AppSettings or CommandLine.</param>
member s.GetResult ([<ReflectedDefinition>] expr : Expr<'Template>, ?defaultValue : 'Template, ?source : ParseSource) : 'Template =
match defaultValue with
Expand All @@ -110,12 +110,25 @@ type ParseResults<[<EqualityConditionalOn; ComparisonConditionalOn>]'Template wh
/// <summary>Returns the *last* specified parameter of given type.
/// Command line parameters have precedence over AppSettings parameters.</summary>
/// <param name="expr">The name of the parameter, expressed as quotation of DU constructor.</param>
/// <param name="defaultValue">Return this of no parameter of specific kind has been specified.</param>
/// <param name="defaultValue">Return this if no parameter of specific kind has been specified.</param>
/// <param name="source">Optional source restriction: AppSettings or CommandLine.</param>
member s.GetResult ([<ReflectedDefinition>] expr : Expr<'Fields -> 'Template>, ?defaultValue : 'Fields , ?source : ParseSource) : 'Fields =
member s.GetResult ([<ReflectedDefinition>] expr : Expr<'Fields -> 'Template>, ?defaultValue : 'Fields, ?source : ParseSource) : 'Fields =
match defaultValue with
| None -> let r = getResult source expr in r.FieldContents :?> 'Fields
| Some def -> defaultArg (s.TryGetResult expr) def
| Some def -> defaultArg (s.TryGetResult(expr, ?source = source)) def

/// <summary>Returns the *last* specified parameter of given type.
/// Command line parameters have precedence over AppSettings parameters.</summary>
/// <param name="expr">The name of the parameter, expressed as quotation of DU constructor.</param>
/// <param name="defThunk">Function used to default if no parameter has been specified.
/// Any resulting Exception will be trapped, and the Exception's <c>.Message</c> will be used as the Failure Message as per <c>Raise</c> and <c>Catch</c>.</param>
/// <param name="source">Optional source restriction: AppSettings or CommandLine.</param>
/// <param name="errorCode">The error code to be returned.</param>
/// <param name="showUsage">Print usage together with error message.</param>
member s.GetResult([<ReflectedDefinition>] expr : Expr<'Fields -> 'Template>, defThunk : unit -> 'Fields, ?source : ParseSource, ?errorCode, ?showUsage) : 'Fields =
match s.TryGetResult(expr, ?source = source) with
| Some x -> x
| None -> s.Catch(defThunk, ?errorCode = errorCode, ?showUsage = showUsage)

/// <summary>Checks if parameter of specific kind has been specified.</summary>
/// <param name="expr">The name of the parameter, expressed as quotation of DU constructor.</param>
Expand All @@ -130,7 +143,7 @@ type ParseResults<[<EqualityConditionalOn; ComparisonConditionalOn>]'Template wh
/// <param name="msg">The error message to be displayed.</param>
/// <param name="errorCode">The error code to be returned.</param>
/// <param name="showUsage">Print usage together with error message.</param>
member _.Raise (msg : string, ?errorCode : ErrorCode, ?showUsage : bool) : 'T =
member _.Raise<'T>(msg : string, ?errorCode : ErrorCode, ?showUsage : bool) : 'T =
let errorCode = defaultArg errorCode ErrorCode.PostProcess
let showUsage = defaultArg showUsage true
error (not showUsage) errorCode msg
Expand All @@ -139,15 +152,15 @@ type ParseResults<[<EqualityConditionalOn; ComparisonConditionalOn>]'Template wh
/// <param name="error">The error to be displayed.</param>
/// <param name="errorCode">The error code to be returned.</param>
/// <param name="showUsage">Print usage together with error message.</param>
member r.Raise (error : exn, ?errorCode : ErrorCode, ?showUsage : bool) : 'T =
r.Raise (error.Message, ?errorCode = errorCode, ?showUsage = showUsage)
member r.Raise<'T>(error : exn, ?errorCode : ErrorCode, ?showUsage : bool) : 'T =
r.Raise(error.Message, ?errorCode = errorCode, ?showUsage = showUsage)

/// <summary>Handles any raised exception through the argument parser's exiter mechanism. Display usage optionally.</summary>
/// <summary>Handles any raised exception through the argument parser's exiter mechanism.</summary>
/// <param name="f">The operation to be executed.</param>
/// <param name="errorCode">The error code to be returned.</param>
/// <param name="showUsage">Print usage together with error message.</param>
member r.Catch (f : unit -> 'T, ?errorCode : ErrorCode, ?showUsage : bool) : 'T =
try f () with e -> r.Raise(e.Message, ?errorCode = errorCode, ?showUsage = showUsage)
/// <param name="showUsage">Print usage together with error message. Defaults to <c>true</c></param>
member r.Catch<'T>(f : unit -> 'T, ?errorCode : ErrorCode, ?showUsage : bool) : 'T =
try f () with e -> r.Raise(e, ?errorCode = errorCode, ?showUsage = showUsage)

/// <summary>Returns the *last* specified parameter of given type.
/// Command line parameters have precedence over AppSettings parameters.
Expand Down Expand Up @@ -252,4 +265,4 @@ type ParseResults<[<EqualityConditionalOn; ComparisonConditionalOn>]'Template wh
Unchecked.compare
r.CachedAllResults.Value
other.CachedAllResults.Value
| _ -> invalidArg "other" "cannot compare values of different types"
| _ -> invalidArg "other" "cannot compare values of different types"
16 changes: 14 additions & 2 deletions tests/Argu.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ module ``Argu Tests Main List`` =
test <@ nested.Contains <@ Force @> @>

[<Fact>]
let ``Main command parsing should not permit intermittent arguments`` () =
let ``Main command parsing should not permit interstitial arguments`` () =
let args = [|"push" ; "origin" ; "-f" ; "master"|]
raisesWith<ArguParseException> <@ parser.ParseCommandLine(args, ignoreMissing = true) @>
(fun e -> <@ e.FirstLine.Contains "but was '-f'" @>)
Expand Down Expand Up @@ -1006,4 +1006,16 @@ module ``Argu Tests Main Primitive`` =
let results = parser.ParseCommandLine(args, ignoreUnrecognized = true)
test <@ results.UnrecognizedCliParams = ["foobar"] @>
test <@ results.Contains <@ Detach @> @>
test <@ results.GetResult <@ Main @> = "main" @>
test <@ results.GetResult <@ Main @> = "main" @>

[<Fact>]
let ``Trap defaulting function exceptions`` () =
let results = parser.ParseCommandLine [| "--mandatory-arg" ; "true"; "command" |]
let defThunk (): string = failwith "Defaulting Failed"
raisesWith<ArguParseException>
<@ results.GetResult(Working_Directory, defThunk, showUsage = false) @>
<| fun e -> <@ e.Message = "Defaulting Failed" && e.ErrorCode = ErrorCode.PostProcess @>
raisesWith<ArguParseException>
<@ results.GetResult(Working_Directory, defThunk) @>
(fun e -> <@ e.Message.StartsWith "Defaulting Failed" && e.Message.Contains "--working-directory" @>)

0 comments on commit 92b9315

Please sign in to comment.