mirror of
https://github.com/Smaug123/WoofWare.Myriad
synced 2025-12-14 21:05:39 +00:00
Compare commits
2 Commits
WoofWare.M
...
WoofWare.M
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
038b424906 | ||
|
|
2fc8ba958c |
@@ -13,6 +13,12 @@
|
||||
"commands": [
|
||||
"fsharp-analyzers"
|
||||
]
|
||||
},
|
||||
"woofware.nunittestrunner": {
|
||||
"version": "0.3.9",
|
||||
"commands": [
|
||||
"woofware.nunittestrunner"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,3 +235,27 @@ type FlagsIntoPositionalArgs' =
|
||||
[<PositionalArgs false>]
|
||||
DontGrabEverything : string list
|
||||
}
|
||||
|
||||
[<ArgParser>]
|
||||
[<ArgumentHelpText "Parse command-line arguments for a basic configuration. This help text appears before the argument list.">]
|
||||
type WithTypeHelp =
|
||||
{
|
||||
[<ArgumentHelpText "The configuration file path">]
|
||||
ConfigFile : string
|
||||
[<ArgumentHelpText "Enable verbose output">]
|
||||
Verbose : bool
|
||||
Port : int
|
||||
}
|
||||
|
||||
[<ArgParser>]
|
||||
[<ArgumentHelpText "This is a multiline help text example.
|
||||
It spans multiple lines to test that multiline strings work correctly.
|
||||
You can use this to provide detailed documentation for your argument parser.">]
|
||||
type WithMultilineTypeHelp =
|
||||
{
|
||||
[<ArgumentHelpText "Input file to process">]
|
||||
InputFile : string
|
||||
[<ArgumentHelpText "Output directory">]
|
||||
OutputDir : string
|
||||
Force : bool
|
||||
}
|
||||
|
||||
@@ -55,3 +55,15 @@ type TypeWithProperties =
|
||||
abstract Mem1 : string option -> string[] Async
|
||||
abstract Prop1 : int
|
||||
abstract Prop2 : unit Async
|
||||
|
||||
[<GenerateCapturingMock>]
|
||||
type TypeWithAsyncDisposable =
|
||||
inherit IAsyncDisposable
|
||||
abstract Mem1 : string option -> string[] Async
|
||||
abstract Mem2 : unit -> string[] Async
|
||||
|
||||
[<GenerateCapturingMock>]
|
||||
type TypeWithBothDisposables =
|
||||
inherit IDisposable
|
||||
inherit IAsyncDisposable
|
||||
abstract Mem1 : string -> int
|
||||
|
||||
@@ -4346,3 +4346,438 @@ module FlagsIntoPositionalArgs'ArgParse =
|
||||
|
||||
static member parse (args : string list) : FlagsIntoPositionalArgs' =
|
||||
FlagsIntoPositionalArgs'.parse' (System.Environment.GetEnvironmentVariable >> Option.ofObj) args
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Methods to parse arguments for the type WithTypeHelp
|
||||
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module WithTypeHelp =
|
||||
type private ParseState_WithTypeHelp =
|
||||
/// Ready to consume a key or positional arg
|
||||
| AwaitingKey
|
||||
/// Waiting to receive a value for the key we've already consumed
|
||||
| AwaitingValue of key : string
|
||||
|
||||
let parse' (getEnvironmentVariable : string -> string option) (args : string list) : WithTypeHelp =
|
||||
let ArgParser_errors = ResizeArray ()
|
||||
|
||||
let helpText () =
|
||||
[
|
||||
"Parse command-line arguments for a basic configuration. This help text appears before the argument list."
|
||||
""
|
||||
|
||||
(sprintf
|
||||
"%s string%s%s"
|
||||
(sprintf "--%s" "config-file")
|
||||
""
|
||||
(sprintf " : %s" ("The configuration file path")))
|
||||
|
||||
(sprintf "%s bool%s%s" (sprintf "--%s" "verbose") "" (sprintf " : %s" ("Enable verbose output")))
|
||||
(sprintf "%s int32%s%s" (sprintf "--%s" "port") "" "")
|
||||
]
|
||||
|> String.concat "\n"
|
||||
|
||||
let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
|
||||
let mutable arg_0 : string option = None
|
||||
let mutable arg_1 : bool option = None
|
||||
let mutable arg_2 : int option = None
|
||||
|
||||
/// Processes the key-value pair, returning Error if no key was matched.
|
||||
/// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error(<the message>).
|
||||
/// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
|
||||
let processKeyValue (key : string) (value : string) : Result<unit, string option> =
|
||||
if System.String.Equals (key, sprintf "--%s" "port", System.StringComparison.OrdinalIgnoreCase) then
|
||||
match arg_2 with
|
||||
| Some x ->
|
||||
sprintf
|
||||
"Argument '%s' was supplied multiple times: %s and %s"
|
||||
(sprintf "--%s" "port")
|
||||
(x.ToString ())
|
||||
(value.ToString ())
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Ok ()
|
||||
| None ->
|
||||
try
|
||||
arg_2 <- value |> (fun x -> System.Int32.Parse x) |> Some
|
||||
Ok ()
|
||||
with _ as exc ->
|
||||
exc.Message |> Some |> Error
|
||||
else if System.String.Equals (key, sprintf "--%s" "verbose", System.StringComparison.OrdinalIgnoreCase) then
|
||||
match arg_1 with
|
||||
| Some x ->
|
||||
sprintf
|
||||
"Argument '%s' was supplied multiple times: %s and %s"
|
||||
(sprintf "--%s" "verbose")
|
||||
(x.ToString ())
|
||||
(value.ToString ())
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Ok ()
|
||||
| None ->
|
||||
try
|
||||
arg_1 <- value |> (fun x -> System.Boolean.Parse x) |> Some
|
||||
Ok ()
|
||||
with _ as exc ->
|
||||
exc.Message |> Some |> Error
|
||||
else if
|
||||
System.String.Equals (key, sprintf "--%s" "config-file", System.StringComparison.OrdinalIgnoreCase)
|
||||
then
|
||||
match arg_0 with
|
||||
| Some x ->
|
||||
sprintf
|
||||
"Argument '%s' was supplied multiple times: %s and %s"
|
||||
(sprintf "--%s" "config-file")
|
||||
(x.ToString ())
|
||||
(value.ToString ())
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Ok ()
|
||||
| None ->
|
||||
try
|
||||
arg_0 <- value |> (fun x -> x) |> Some
|
||||
Ok ()
|
||||
with _ as exc ->
|
||||
exc.Message |> Some |> Error
|
||||
else
|
||||
Error None
|
||||
|
||||
/// Returns false if we didn't set a value.
|
||||
let setFlagValue (key : string) : bool =
|
||||
if System.String.Equals (key, sprintf "--%s" "verbose", System.StringComparison.OrdinalIgnoreCase) then
|
||||
match arg_1 with
|
||||
| Some x ->
|
||||
sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "verbose")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
true
|
||||
| None ->
|
||||
arg_1 <- true |> Some
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
let rec go (state : ParseState_WithTypeHelp) (args : string list) =
|
||||
match args with
|
||||
| [] ->
|
||||
match state with
|
||||
| ParseState_WithTypeHelp.AwaitingKey -> ()
|
||||
| ParseState_WithTypeHelp.AwaitingValue key ->
|
||||
if setFlagValue key then
|
||||
()
|
||||
else
|
||||
sprintf
|
||||
"Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
|
||||
key
|
||||
|> ArgParser_errors.Add
|
||||
| "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
|
||||
| arg :: args ->
|
||||
match state with
|
||||
| ParseState_WithTypeHelp.AwaitingKey ->
|
||||
if arg.StartsWith ("--", System.StringComparison.Ordinal) then
|
||||
if arg = "--help" then
|
||||
helpText () |> failwithf "Help text requested.\n%s"
|
||||
else
|
||||
let equals = arg.IndexOf (char 61)
|
||||
|
||||
if equals < 0 then
|
||||
args |> go (ParseState_WithTypeHelp.AwaitingValue arg)
|
||||
else
|
||||
let key = arg.[0 .. equals - 1]
|
||||
let value = arg.[equals + 1 ..]
|
||||
|
||||
match processKeyValue key value with
|
||||
| Ok () -> go ParseState_WithTypeHelp.AwaitingKey args
|
||||
| Error x ->
|
||||
match x with
|
||||
| None ->
|
||||
failwithf "Unable to process argument %s as key %s and value %s" arg key value
|
||||
| Some msg ->
|
||||
sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
|
||||
go ParseState_WithTypeHelp.AwaitingKey args
|
||||
else
|
||||
arg |> (fun x -> x) |> parser_LeftoverArgs.Add
|
||||
go ParseState_WithTypeHelp.AwaitingKey args
|
||||
| ParseState_WithTypeHelp.AwaitingValue key ->
|
||||
match processKeyValue key arg with
|
||||
| Ok () -> go ParseState_WithTypeHelp.AwaitingKey args
|
||||
| Error exc ->
|
||||
if setFlagValue key then
|
||||
go ParseState_WithTypeHelp.AwaitingKey (arg :: args)
|
||||
else
|
||||
match exc with
|
||||
| None ->
|
||||
failwithf "Unable to process supplied arg %s. Help text follows.\n%s" key (helpText ())
|
||||
| Some msg -> msg |> ArgParser_errors.Add
|
||||
|
||||
go ParseState_WithTypeHelp.AwaitingKey args
|
||||
|
||||
let parser_LeftoverArgs =
|
||||
if 0 = parser_LeftoverArgs.Count then
|
||||
()
|
||||
else
|
||||
parser_LeftoverArgs
|
||||
|> String.concat " "
|
||||
|> sprintf "There were leftover args: %s"
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
|
||||
let arg_0 =
|
||||
match arg_0 with
|
||||
| None ->
|
||||
sprintf "Required argument '%s' received no value" (sprintf "--%s" "config-file")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
| Some x -> x
|
||||
|
||||
let arg_1 =
|
||||
match arg_1 with
|
||||
| None ->
|
||||
sprintf "Required argument '%s' received no value" (sprintf "--%s" "verbose")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
| Some x -> x
|
||||
|
||||
let arg_2 =
|
||||
match arg_2 with
|
||||
| None ->
|
||||
sprintf "Required argument '%s' received no value" (sprintf "--%s" "port")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
| Some x -> x
|
||||
|
||||
if 0 = ArgParser_errors.Count then
|
||||
{
|
||||
ConfigFile = arg_0
|
||||
Port = arg_2
|
||||
Verbose = arg_1
|
||||
}
|
||||
else
|
||||
ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
|
||||
|
||||
let parse (args : string list) : WithTypeHelp =
|
||||
parse' (System.Environment.GetEnvironmentVariable >> Option.ofObj) args
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Methods to parse arguments for the type WithMultilineTypeHelp
|
||||
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module WithMultilineTypeHelp =
|
||||
type private ParseState_WithMultilineTypeHelp =
|
||||
/// Ready to consume a key or positional arg
|
||||
| AwaitingKey
|
||||
/// Waiting to receive a value for the key we've already consumed
|
||||
| AwaitingValue of key : string
|
||||
|
||||
let parse' (getEnvironmentVariable : string -> string option) (args : string list) : WithMultilineTypeHelp =
|
||||
let ArgParser_errors = ResizeArray ()
|
||||
|
||||
let helpText () =
|
||||
[
|
||||
"This is a multiline help text example.
|
||||
It spans multiple lines to test that multiline strings work correctly.
|
||||
You can use this to provide detailed documentation for your argument parser."
|
||||
|
||||
""
|
||||
(sprintf "%s string%s%s" (sprintf "--%s" "input-file") "" (sprintf " : %s" ("Input file to process")))
|
||||
(sprintf "%s string%s%s" (sprintf "--%s" "output-dir") "" (sprintf " : %s" ("Output directory")))
|
||||
(sprintf "%s bool%s%s" (sprintf "--%s" "force") "" "")
|
||||
]
|
||||
|> String.concat "\n"
|
||||
|
||||
let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
|
||||
let mutable arg_0 : string option = None
|
||||
let mutable arg_1 : string option = None
|
||||
let mutable arg_2 : bool option = None
|
||||
|
||||
/// Processes the key-value pair, returning Error if no key was matched.
|
||||
/// If the key is an arg which can have arity 1, but throws when consuming that arg, we return Error(<the message>).
|
||||
/// This can nevertheless be a successful parse, e.g. when the key may have arity 0.
|
||||
let processKeyValue (key : string) (value : string) : Result<unit, string option> =
|
||||
if System.String.Equals (key, sprintf "--%s" "force", System.StringComparison.OrdinalIgnoreCase) then
|
||||
match arg_2 with
|
||||
| Some x ->
|
||||
sprintf
|
||||
"Argument '%s' was supplied multiple times: %s and %s"
|
||||
(sprintf "--%s" "force")
|
||||
(x.ToString ())
|
||||
(value.ToString ())
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Ok ()
|
||||
| None ->
|
||||
try
|
||||
arg_2 <- value |> (fun x -> System.Boolean.Parse x) |> Some
|
||||
Ok ()
|
||||
with _ as exc ->
|
||||
exc.Message |> Some |> Error
|
||||
else if
|
||||
System.String.Equals (key, sprintf "--%s" "output-dir", System.StringComparison.OrdinalIgnoreCase)
|
||||
then
|
||||
match arg_1 with
|
||||
| Some x ->
|
||||
sprintf
|
||||
"Argument '%s' was supplied multiple times: %s and %s"
|
||||
(sprintf "--%s" "output-dir")
|
||||
(x.ToString ())
|
||||
(value.ToString ())
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Ok ()
|
||||
| None ->
|
||||
try
|
||||
arg_1 <- value |> (fun x -> x) |> Some
|
||||
Ok ()
|
||||
with _ as exc ->
|
||||
exc.Message |> Some |> Error
|
||||
else if
|
||||
System.String.Equals (key, sprintf "--%s" "input-file", System.StringComparison.OrdinalIgnoreCase)
|
||||
then
|
||||
match arg_0 with
|
||||
| Some x ->
|
||||
sprintf
|
||||
"Argument '%s' was supplied multiple times: %s and %s"
|
||||
(sprintf "--%s" "input-file")
|
||||
(x.ToString ())
|
||||
(value.ToString ())
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Ok ()
|
||||
| None ->
|
||||
try
|
||||
arg_0 <- value |> (fun x -> x) |> Some
|
||||
Ok ()
|
||||
with _ as exc ->
|
||||
exc.Message |> Some |> Error
|
||||
else
|
||||
Error None
|
||||
|
||||
/// Returns false if we didn't set a value.
|
||||
let setFlagValue (key : string) : bool =
|
||||
if System.String.Equals (key, sprintf "--%s" "force", System.StringComparison.OrdinalIgnoreCase) then
|
||||
match arg_2 with
|
||||
| Some x ->
|
||||
sprintf "Flag '%s' was supplied multiple times" (sprintf "--%s" "force")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
true
|
||||
| None ->
|
||||
arg_2 <- true |> Some
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
let rec go (state : ParseState_WithMultilineTypeHelp) (args : string list) =
|
||||
match args with
|
||||
| [] ->
|
||||
match state with
|
||||
| ParseState_WithMultilineTypeHelp.AwaitingKey -> ()
|
||||
| ParseState_WithMultilineTypeHelp.AwaitingValue key ->
|
||||
if setFlagValue key then
|
||||
()
|
||||
else
|
||||
sprintf
|
||||
"Trailing argument %s had no value. Use a double-dash to separate positional args from key-value args."
|
||||
key
|
||||
|> ArgParser_errors.Add
|
||||
| "--" :: rest -> parser_LeftoverArgs.AddRange (rest |> Seq.map (fun x -> x))
|
||||
| arg :: args ->
|
||||
match state with
|
||||
| ParseState_WithMultilineTypeHelp.AwaitingKey ->
|
||||
if arg.StartsWith ("--", System.StringComparison.Ordinal) then
|
||||
if arg = "--help" then
|
||||
helpText () |> failwithf "Help text requested.\n%s"
|
||||
else
|
||||
let equals = arg.IndexOf (char 61)
|
||||
|
||||
if equals < 0 then
|
||||
args |> go (ParseState_WithMultilineTypeHelp.AwaitingValue arg)
|
||||
else
|
||||
let key = arg.[0 .. equals - 1]
|
||||
let value = arg.[equals + 1 ..]
|
||||
|
||||
match processKeyValue key value with
|
||||
| Ok () -> go ParseState_WithMultilineTypeHelp.AwaitingKey args
|
||||
| Error x ->
|
||||
match x with
|
||||
| None ->
|
||||
failwithf "Unable to process argument %s as key %s and value %s" arg key value
|
||||
| Some msg ->
|
||||
sprintf "%s (at arg %s)" msg arg |> ArgParser_errors.Add
|
||||
go ParseState_WithMultilineTypeHelp.AwaitingKey args
|
||||
else
|
||||
arg |> (fun x -> x) |> parser_LeftoverArgs.Add
|
||||
go ParseState_WithMultilineTypeHelp.AwaitingKey args
|
||||
| ParseState_WithMultilineTypeHelp.AwaitingValue key ->
|
||||
match processKeyValue key arg with
|
||||
| Ok () -> go ParseState_WithMultilineTypeHelp.AwaitingKey args
|
||||
| Error exc ->
|
||||
if setFlagValue key then
|
||||
go ParseState_WithMultilineTypeHelp.AwaitingKey (arg :: args)
|
||||
else
|
||||
match exc with
|
||||
| None ->
|
||||
failwithf "Unable to process supplied arg %s. Help text follows.\n%s" key (helpText ())
|
||||
| Some msg -> msg |> ArgParser_errors.Add
|
||||
|
||||
go ParseState_WithMultilineTypeHelp.AwaitingKey args
|
||||
|
||||
let parser_LeftoverArgs =
|
||||
if 0 = parser_LeftoverArgs.Count then
|
||||
()
|
||||
else
|
||||
parser_LeftoverArgs
|
||||
|> String.concat " "
|
||||
|> sprintf "There were leftover args: %s"
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
|
||||
let arg_0 =
|
||||
match arg_0 with
|
||||
| None ->
|
||||
sprintf "Required argument '%s' received no value" (sprintf "--%s" "input-file")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
| Some x -> x
|
||||
|
||||
let arg_1 =
|
||||
match arg_1 with
|
||||
| None ->
|
||||
sprintf "Required argument '%s' received no value" (sprintf "--%s" "output-dir")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
| Some x -> x
|
||||
|
||||
let arg_2 =
|
||||
match arg_2 with
|
||||
| None ->
|
||||
sprintf "Required argument '%s' received no value" (sprintf "--%s" "force")
|
||||
|> ArgParser_errors.Add
|
||||
|
||||
Unchecked.defaultof<_>
|
||||
| Some x -> x
|
||||
|
||||
if 0 = ArgParser_errors.Count then
|
||||
{
|
||||
Force = arg_2
|
||||
InputFile = arg_0
|
||||
OutputDir = arg_1
|
||||
}
|
||||
else
|
||||
ArgParser_errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
|
||||
|
||||
let parse (args : string list) : WithMultilineTypeHelp =
|
||||
parse' (System.Environment.GetEnvironmentVariable >> Option.ofObj) args
|
||||
|
||||
@@ -36,7 +36,7 @@ type internal PublicTypeMock =
|
||||
Mem3 : int * System.Threading.CancellationToken option -> string
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PublicTypeMock =
|
||||
{
|
||||
Calls = PublicTypeMockCalls.Calls.Empty ()
|
||||
@@ -89,7 +89,7 @@ type public PublicTypeInternalFalseMock =
|
||||
Mem3 : int * System.Threading.CancellationToken option -> string
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PublicTypeInternalFalseMock =
|
||||
{
|
||||
Calls = PublicTypeInternalFalseMockCalls.Calls.Empty ()
|
||||
@@ -139,7 +139,7 @@ type internal InternalTypeMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : InternalTypeMock =
|
||||
{
|
||||
Calls = InternalTypeMockCalls.Calls.Empty ()
|
||||
@@ -184,7 +184,7 @@ type private PrivateTypeMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PrivateTypeMock =
|
||||
{
|
||||
Calls = PrivateTypeMockCalls.Calls.Empty ()
|
||||
@@ -229,7 +229,7 @@ type private PrivateTypeInternalFalseMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PrivateTypeInternalFalseMock =
|
||||
{
|
||||
Calls = PrivateTypeInternalFalseMockCalls.Calls.Empty ()
|
||||
@@ -271,7 +271,7 @@ type internal VeryPublicTypeMock<'a, 'b> =
|
||||
Mem1 : 'a -> 'b
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : VeryPublicTypeMock<'a, 'b> =
|
||||
{
|
||||
Calls = VeryPublicTypeMockCalls.Calls.Empty ()
|
||||
@@ -365,7 +365,7 @@ type internal CurriedMock<'a> =
|
||||
Mem6 : int * string -> 'a * int -> string
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : CurriedMock<'a> =
|
||||
{
|
||||
Calls = CurriedMockCalls.Calls.Empty ()
|
||||
@@ -486,7 +486,7 @@ type internal TypeWithInterfaceMock =
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : TypeWithInterfaceMock =
|
||||
{
|
||||
Calls = TypeWithInterfaceMockCalls.Calls.Empty ()
|
||||
@@ -540,7 +540,7 @@ type internal TypeWithPropertiesMock =
|
||||
Prop2 : unit -> unit Async
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : TypeWithPropertiesMock =
|
||||
{
|
||||
Calls = TypeWithPropertiesMockCalls.Calls.Empty ()
|
||||
@@ -560,3 +560,103 @@ type internal TypeWithPropertiesMock =
|
||||
|
||||
interface System.IDisposable with
|
||||
member this.Dispose () : unit = this.Dispose ()
|
||||
namespace SomeNamespace.CapturingMock
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal TypeWithAsyncDisposableMockCalls =
|
||||
/// All the calls made to a TypeWithAsyncDisposableMock mock
|
||||
type internal Calls =
|
||||
{
|
||||
Mem1 : ResizeArray<string option>
|
||||
Mem2 : ResizeArray<unit>
|
||||
}
|
||||
|
||||
/// A fresh calls object which has not yet had any calls made.
|
||||
static member Empty () : Calls =
|
||||
{
|
||||
Mem1 = ResizeArray ()
|
||||
Mem2 = ResizeArray ()
|
||||
}
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal TypeWithAsyncDisposableMock =
|
||||
{
|
||||
Calls : TypeWithAsyncDisposableMockCalls.Calls
|
||||
/// Implementation of IAsyncDisposable.DisposeAsync
|
||||
DisposeAsync : unit -> System.Threading.Tasks.ValueTask
|
||||
Mem1 : string option -> string[] Async
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : TypeWithAsyncDisposableMock =
|
||||
{
|
||||
Calls = TypeWithAsyncDisposableMockCalls.Calls.Empty ()
|
||||
DisposeAsync = (fun () -> (System.Threading.Tasks.ValueTask ()))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface TypeWithAsyncDisposable with
|
||||
member this.Mem1 arg_0_0 =
|
||||
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0))
|
||||
this.Mem1 (arg_0_0)
|
||||
|
||||
member this.Mem2 () =
|
||||
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (()))
|
||||
this.Mem2 (())
|
||||
|
||||
interface System.IAsyncDisposable with
|
||||
member this.DisposeAsync () : System.Threading.Tasks.ValueTask = this.DisposeAsync ()
|
||||
namespace SomeNamespace.CapturingMock
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal TypeWithBothDisposablesMockCalls =
|
||||
/// All the calls made to a TypeWithBothDisposablesMock mock
|
||||
type internal Calls =
|
||||
{
|
||||
Mem1 : ResizeArray<string>
|
||||
}
|
||||
|
||||
/// A fresh calls object which has not yet had any calls made.
|
||||
static member Empty () : Calls =
|
||||
{
|
||||
Mem1 = ResizeArray ()
|
||||
}
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal TypeWithBothDisposablesMock =
|
||||
{
|
||||
Calls : TypeWithBothDisposablesMockCalls.Calls
|
||||
/// Implementation of IDisposable.Dispose
|
||||
Dispose : unit -> unit
|
||||
/// Implementation of IAsyncDisposable.DisposeAsync
|
||||
DisposeAsync : unit -> System.Threading.Tasks.ValueTask
|
||||
Mem1 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : TypeWithBothDisposablesMock =
|
||||
{
|
||||
Calls = TypeWithBothDisposablesMockCalls.Calls.Empty ()
|
||||
Dispose = (fun () -> ())
|
||||
DisposeAsync = (fun () -> (System.Threading.Tasks.ValueTask ()))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
}
|
||||
|
||||
interface TypeWithBothDisposables with
|
||||
member this.Mem1 arg_0_0 =
|
||||
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0))
|
||||
this.Mem1 (arg_0_0)
|
||||
|
||||
interface System.IDisposable with
|
||||
member this.Dispose () : unit = this.Dispose ()
|
||||
|
||||
interface System.IAsyncDisposable with
|
||||
member this.DisposeAsync () : System.Threading.Tasks.ValueTask = this.DisposeAsync ()
|
||||
|
||||
@@ -35,7 +35,7 @@ type internal PublicTypeNoAttrMock =
|
||||
Mem3 : int * System.Threading.CancellationToken option -> string
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PublicTypeNoAttrMock =
|
||||
{
|
||||
Calls = PublicTypeNoAttrMockCalls.Calls.Empty ()
|
||||
@@ -87,7 +87,7 @@ type public PublicTypeInternalFalseNoAttrMock =
|
||||
Mem3 : int * System.Threading.CancellationToken option -> string
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PublicTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Calls = PublicTypeInternalFalseNoAttrMockCalls.Calls.Empty ()
|
||||
@@ -136,7 +136,7 @@ type internal InternalTypeNoAttrMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : InternalTypeNoAttrMock =
|
||||
{
|
||||
Calls = InternalTypeNoAttrMockCalls.Calls.Empty ()
|
||||
@@ -180,7 +180,7 @@ type private PrivateTypeNoAttrMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PrivateTypeNoAttrMock =
|
||||
{
|
||||
Calls = PrivateTypeNoAttrMockCalls.Calls.Empty ()
|
||||
@@ -224,7 +224,7 @@ type private PrivateTypeInternalFalseNoAttrMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : PrivateTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Calls = PrivateTypeInternalFalseNoAttrMockCalls.Calls.Empty ()
|
||||
@@ -265,7 +265,7 @@ type internal VeryPublicTypeNoAttrMock<'a, 'b> =
|
||||
Mem1 : 'a -> 'b
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> =
|
||||
{
|
||||
Calls = VeryPublicTypeNoAttrMockCalls.Calls.Empty ()
|
||||
@@ -358,7 +358,7 @@ type internal CurriedNoAttrMock<'a> =
|
||||
Mem6 : int * string -> 'a * int -> string
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : CurriedNoAttrMock<'a> =
|
||||
{
|
||||
Calls = CurriedNoAttrMockCalls.Calls.Empty ()
|
||||
@@ -478,7 +478,7 @@ type internal TypeWithInterfaceNoAttrMock =
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every non-unit method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : TypeWithInterfaceNoAttrMock =
|
||||
{
|
||||
Calls = TypeWithInterfaceNoAttrMockCalls.Calls.Empty ()
|
||||
|
||||
@@ -16,7 +16,7 @@ type internal PublicTypeMock =
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PublicTypeMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -41,7 +41,7 @@ type public PublicTypeInternalFalseMock =
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PublicTypeInternalFalseMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -65,7 +65,7 @@ type internal InternalTypeMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : InternalTypeMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -87,7 +87,7 @@ type private PrivateTypeMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PrivateTypeMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -109,7 +109,7 @@ type private PrivateTypeInternalFalseMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PrivateTypeInternalFalseMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -130,7 +130,7 @@ type internal VeryPublicTypeMock<'a, 'b> =
|
||||
Mem1 : 'a -> 'b
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : VeryPublicTypeMock<'a, 'b> =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -154,7 +154,7 @@ type internal CurriedMock<'a> =
|
||||
Mem6 : int * string -> 'a * int -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : CurriedMock<'a> =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -192,7 +192,7 @@ type internal TypeWithInterfaceMock =
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : TypeWithInterfaceMock =
|
||||
{
|
||||
Dispose = (fun () -> ())
|
||||
@@ -221,7 +221,7 @@ type internal TypeWithPropertiesMock =
|
||||
Mem1 : string option -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : TypeWithPropertiesMock =
|
||||
{
|
||||
Dispose = (fun () -> ())
|
||||
@@ -237,3 +237,62 @@ type internal TypeWithPropertiesMock =
|
||||
|
||||
interface System.IDisposable with
|
||||
member this.Dispose () : unit = this.Dispose ()
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal TypeWithAsyncDisposableMock =
|
||||
{
|
||||
/// Implementation of IAsyncDisposable.DisposeAsync
|
||||
DisposeAsync : unit -> System.Threading.Tasks.ValueTask
|
||||
Mem1 : string option -> string[] Async
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : TypeWithAsyncDisposableMock =
|
||||
{
|
||||
DisposeAsync = (fun () -> (System.Threading.Tasks.ValueTask ()))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface TypeWithAsyncDisposable with
|
||||
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
|
||||
member this.Mem2 () = this.Mem2 (())
|
||||
|
||||
interface System.IAsyncDisposable with
|
||||
member this.DisposeAsync () : System.Threading.Tasks.ValueTask = this.DisposeAsync ()
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal TypeWithBothDisposablesMock =
|
||||
{
|
||||
/// Implementation of IDisposable.Dispose
|
||||
Dispose : unit -> unit
|
||||
/// Implementation of IAsyncDisposable.DisposeAsync
|
||||
DisposeAsync : unit -> System.Threading.Tasks.ValueTask
|
||||
Mem1 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : TypeWithBothDisposablesMock =
|
||||
{
|
||||
Dispose = (fun () -> ())
|
||||
DisposeAsync = (fun () -> (System.Threading.Tasks.ValueTask ()))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
}
|
||||
|
||||
interface TypeWithBothDisposables with
|
||||
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
|
||||
|
||||
interface System.IDisposable with
|
||||
member this.Dispose () : unit = this.Dispose ()
|
||||
|
||||
interface System.IAsyncDisposable with
|
||||
member this.DisposeAsync () : System.Threading.Tasks.ValueTask = this.DisposeAsync ()
|
||||
|
||||
@@ -15,7 +15,7 @@ type internal PublicTypeNoAttrMock =
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PublicTypeNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -39,7 +39,7 @@ type public PublicTypeInternalFalseNoAttrMock =
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PublicTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -62,7 +62,7 @@ type internal InternalTypeNoAttrMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : InternalTypeNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -83,7 +83,7 @@ type private PrivateTypeNoAttrMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PrivateTypeNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -104,7 +104,7 @@ type private PrivateTypeInternalFalseNoAttrMock =
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : PrivateTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -124,7 +124,7 @@ type internal VeryPublicTypeNoAttrMock<'a, 'b> =
|
||||
Mem1 : 'a -> 'b
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -147,7 +147,7 @@ type internal CurriedNoAttrMock<'a> =
|
||||
Mem6 : int * string -> 'a * int -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty () : CurriedNoAttrMock<'a> =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
@@ -184,7 +184,7 @@ type internal TypeWithInterfaceNoAttrMock =
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
/// An implementation where every non-disposal method throws.
|
||||
static member Empty : TypeWithInterfaceNoAttrMock =
|
||||
{
|
||||
Dispose = (fun () -> ())
|
||||
|
||||
@@ -55,3 +55,15 @@ type TypeWithProperties =
|
||||
abstract Mem1 : string option -> string[] Async
|
||||
abstract Prop1 : int
|
||||
abstract Prop2 : unit Async
|
||||
|
||||
[<GenerateMock>]
|
||||
type TypeWithAsyncDisposable =
|
||||
inherit IAsyncDisposable
|
||||
abstract Mem1 : string option -> string[] Async
|
||||
abstract Mem2 : unit -> string[] Async
|
||||
|
||||
[<GenerateMock>]
|
||||
type TypeWithBothDisposables =
|
||||
inherit IDisposable
|
||||
inherit IAsyncDisposable
|
||||
abstract Mem1 : string -> int
|
||||
|
||||
@@ -62,8 +62,10 @@ type ArgumentDefaultFunctionAttribute () =
|
||||
type ArgumentDefaultEnvironmentVariableAttribute (envVar : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute indicating that this field shall have the given help text, when `--help` is invoked
|
||||
/// Attribute indicating that this field or type shall have the given help text, when `--help` is invoked
|
||||
/// or when a parse error causes us to print help text.
|
||||
/// When applied to a record type, the help text appears at the top of the help output, before the field descriptions.
|
||||
/// When applied to a field, the help text appears next to that field's description.
|
||||
type ArgumentHelpTextAttribute (helpText : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
|
||||
@@ -444,7 +444,7 @@ Required argument '--exact' received no value"""
|
||||
]
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof (boolCases))>]
|
||||
[<TestCaseSource(nameof boolCases)>]
|
||||
let ``Bool env vars can be populated`` (envValue : string, boolValue : bool) =
|
||||
let getEnvVar (s : string) =
|
||||
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
|
||||
@@ -704,3 +704,87 @@ Required argument '--exact' received no value"""
|
||||
// Again, we don't try to detect that the user has missed out the desired argument to `--a`.
|
||||
exc.Message
|
||||
|> shouldEqual """Unable to process argument --c=hi as key --c and value hi"""
|
||||
|
||||
[<Test>]
|
||||
let ``Type-level help text appears in help output`` () =
|
||||
let getEnvVar (_ : string) = None
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> WithTypeHelp.parse' getEnvVar [ "--help" ] |> ignore<WithTypeHelp>)
|
||||
|
||||
exc.Message
|
||||
|> shouldContainText
|
||||
"Parse command-line arguments for a basic configuration. This help text appears before the argument list."
|
||||
|
||||
exc.Message
|
||||
|> shouldContainText "--config-file string : The configuration file path"
|
||||
|
||||
exc.Message |> shouldContainText "--verbose bool : Enable verbose output"
|
||||
exc.Message |> shouldContainText "--port int32"
|
||||
|
||||
[<Test>]
|
||||
let ``Type-level help text appears before field help`` () =
|
||||
let getEnvVar (_ : string) = None
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> WithTypeHelp.parse' getEnvVar [ "--help" ] |> ignore<WithTypeHelp>)
|
||||
|
||||
// Verify that the type help appears before the field help
|
||||
let typeHelpIndex =
|
||||
exc.Message.IndexOf "Parse command-line arguments for a basic configuration"
|
||||
|
||||
let fieldHelpIndex = exc.Message.IndexOf "--config-file"
|
||||
|
||||
typeHelpIndex |> shouldBeSmallerThan fieldHelpIndex
|
||||
|
||||
[<Test>]
|
||||
let ``Multiline type-level help text works`` () =
|
||||
let getEnvVar (_ : string) = None
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
WithMultilineTypeHelp.parse' getEnvVar [ "--help" ]
|
||||
|> ignore<WithMultilineTypeHelp>
|
||||
)
|
||||
|
||||
exc.Message |> shouldContainText "This is a multiline help text example."
|
||||
|
||||
exc.Message
|
||||
|> shouldContainText "It spans multiple lines to test that multiline strings work correctly."
|
||||
|
||||
exc.Message
|
||||
|> shouldContainText "You can use this to provide detailed documentation for your argument parser."
|
||||
|
||||
exc.Message |> shouldContainText "--input-file string : Input file to process"
|
||||
exc.Message |> shouldContainText "--output-dir string : Output directory"
|
||||
exc.Message |> shouldContainText "--force bool"
|
||||
|
||||
[<Test>]
|
||||
let ``Type-level help text appears in error messages`` () =
|
||||
let getEnvVar (_ : string) = None
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
WithTypeHelp.parse' getEnvVar [ "--unknown-arg" ; "value" ]
|
||||
|> ignore<WithTypeHelp>
|
||||
)
|
||||
|
||||
// Verify that the type help appears in error messages too
|
||||
exc.Message
|
||||
|> shouldContainText
|
||||
"Parse command-line arguments for a basic configuration. This help text appears before the argument list."
|
||||
|
||||
exc.Message |> shouldContainText "--config-file"
|
||||
|
||||
[<Test>]
|
||||
let ``Types without type-level help still work`` () =
|
||||
let getEnvVar (_ : string) = None
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar [ "--help" ] |> ignore<Basic>)
|
||||
|
||||
// Should not contain any type-level help, just the field help
|
||||
exc.Message |> shouldContainText "--foo int32 : This is a foo!"
|
||||
exc.Message |> shouldContainText "--bar string"
|
||||
// Make sure there's no extra blank line at the beginning
|
||||
exc.Message.StartsWith '\n' |> shouldEqual false
|
||||
|
||||
@@ -70,3 +70,63 @@ module TestCapturingMockGenerator =
|
||||
bar = 3
|
||||
Arg1 = "hello"
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use IAsyncDisposable`` () =
|
||||
let mock' =
|
||||
{ TypeWithAsyncDisposableMock.Empty () with
|
||||
Mem1 = fun i -> async { return Option.toArray i }
|
||||
Mem2 = fun () -> async { return [||] }
|
||||
}
|
||||
|
||||
let mock = mock' :> TypeWithAsyncDisposable
|
||||
|
||||
mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |]
|
||||
mock.Mem2 () |> Async.RunSynchronously |> shouldEqual [||]
|
||||
|
||||
// Test that DisposeAsync returns a completed ValueTask
|
||||
let asyncDisposable = mock :> IAsyncDisposable
|
||||
let valueTask = asyncDisposable.DisposeAsync ()
|
||||
valueTask.IsCompleted |> shouldEqual true
|
||||
|
||||
// Verify calls were captured
|
||||
lock mock'.Calls.Mem1 (fun () -> Seq.toList mock'.Calls.Mem1)
|
||||
|> List.exactlyOne
|
||||
|> shouldEqual (Some "hi")
|
||||
|
||||
lock mock'.Calls.Mem2 (fun () -> Seq.toList mock'.Calls.Mem2)
|
||||
|> List.exactlyOne
|
||||
|> shouldEqual ()
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: Both IDisposable and IAsyncDisposable`` () =
|
||||
let mutable disposed = false
|
||||
let mutable disposedAsync = false
|
||||
|
||||
let mock' =
|
||||
{ TypeWithBothDisposablesMock.Empty () with
|
||||
Dispose = fun () -> disposed <- true
|
||||
DisposeAsync =
|
||||
fun () ->
|
||||
disposedAsync <- true
|
||||
System.Threading.Tasks.ValueTask ()
|
||||
Mem1 = fun s -> s.Length
|
||||
}
|
||||
|
||||
let mock = mock' :> TypeWithBothDisposables
|
||||
|
||||
mock.Mem1 "hello" |> shouldEqual 5
|
||||
mock.Mem1 "world" |> shouldEqual 5
|
||||
|
||||
// Test IDisposable.Dispose
|
||||
(mock :> IDisposable).Dispose ()
|
||||
disposed |> shouldEqual true
|
||||
|
||||
// Test IAsyncDisposable.DisposeAsync
|
||||
let valueTask = (mock :> IAsyncDisposable).DisposeAsync ()
|
||||
valueTask.IsCompleted |> shouldEqual true
|
||||
disposedAsync |> shouldEqual true
|
||||
|
||||
// Verify calls were captured
|
||||
lock mock'.Calls.Mem1 (fun () -> Seq.toList mock'.Calls.Mem1)
|
||||
|> shouldEqual [ "hello" ; "world" ]
|
||||
|
||||
@@ -47,3 +47,45 @@ module TestMockGenerator =
|
||||
mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |]
|
||||
|
||||
mock.Prop1 |> shouldEqual 44
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: IAsyncDisposable`` () =
|
||||
let mock : TypeWithAsyncDisposable =
|
||||
{ TypeWithAsyncDisposableMock.Empty with
|
||||
Mem1 = fun i -> async { return Option.toArray i }
|
||||
}
|
||||
:> _
|
||||
|
||||
mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |]
|
||||
|
||||
// Test that DisposeAsync returns a completed ValueTask
|
||||
let asyncDisposable = mock :> IAsyncDisposable
|
||||
let valueTask = asyncDisposable.DisposeAsync ()
|
||||
valueTask.IsCompleted |> shouldEqual true
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: Both IDisposable and IAsyncDisposable`` () =
|
||||
let mutable disposed = false
|
||||
let mutable disposedAsync = false
|
||||
|
||||
let mock : TypeWithBothDisposables =
|
||||
{ TypeWithBothDisposablesMock.Empty with
|
||||
Dispose = fun () -> disposed <- true
|
||||
DisposeAsync =
|
||||
fun () ->
|
||||
disposedAsync <- true
|
||||
System.Threading.Tasks.ValueTask ()
|
||||
Mem1 = fun s -> s.Length
|
||||
}
|
||||
:> _
|
||||
|
||||
mock.Mem1 "hello" |> shouldEqual 5
|
||||
|
||||
// Test IDisposable.Dispose
|
||||
(mock :> IDisposable).Dispose ()
|
||||
disposed |> shouldEqual true
|
||||
|
||||
// Test IAsyncDisposable.DisposeAsync
|
||||
let valueTask = (mock :> IAsyncDisposable).DisposeAsync ()
|
||||
valueTask.IsCompleted |> shouldEqual true
|
||||
disposedAsync |> shouldEqual true
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
I have not yet seen a single instance where I care about this warning
|
||||
-->
|
||||
<NoWarn>$(NoWarn),NU1903</NoWarn>
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -769,6 +769,7 @@ module internal ArgParserGenerator =
|
||||
|
||||
/// let helpText : string = ...
|
||||
let private helpText
|
||||
(typeHelp : SynExpr option)
|
||||
(typeName : Ident)
|
||||
(positional : ParseFunctionPositional option)
|
||||
(args : ParseFunctionNonPositional list)
|
||||
@@ -850,12 +851,24 @@ module internal ArgParserGenerator =
|
||||
|> SynExpr.applyTo helpText
|
||||
|> SynExpr.paren
|
||||
|
||||
args
|
||||
|> List.map (toPrintable describeNonPositional)
|
||||
|> fun l ->
|
||||
match positional with
|
||||
| None -> l
|
||||
| Some pos -> l @ [ toPrintable describePositional pos ]
|
||||
let fieldHelp =
|
||||
args
|
||||
|> List.map (toPrintable describeNonPositional)
|
||||
|> fun l ->
|
||||
match positional with
|
||||
| None -> l
|
||||
| Some pos -> l @ [ toPrintable describePositional pos ]
|
||||
|
||||
let allHelp =
|
||||
match typeHelp with
|
||||
| Some helpExpr ->
|
||||
// Prepend type help, followed by blank line, then field help
|
||||
[ helpExpr ; SynExpr.CreateConst "" ] @ fieldHelp
|
||||
| None ->
|
||||
// No type help, just field help
|
||||
fieldHelp
|
||||
|
||||
allHelp
|
||||
|> SynExpr.listLiteral
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.applyFunction (SynExpr.createLongIdent [ "String" ; "concat" ]) (SynExpr.CreateConst @"\n")
|
||||
@@ -1560,6 +1573,7 @@ module internal ArgParserGenerator =
|
||||
|
||||
/// Takes a single argument, `args : string list`, and returns something of the type indicated by `recordType`.
|
||||
let createRecordParse
|
||||
(typeHelpText : SynExpr option)
|
||||
(parseState : Ident)
|
||||
(flagDus : FlagDu list)
|
||||
(ambientRecords : RecordType list)
|
||||
@@ -1626,7 +1640,7 @@ module internal ArgParserGenerator =
|
||||
|> SynExpr.applyTo (SynExpr.CreateConst ())
|
||||
|> SynBinding.basic [ argParseErrors ] []
|
||||
|
||||
let helpText = helpText recordType.Name pos nonPos
|
||||
let helpText = helpText typeHelpText recordType.Name pos nonPos
|
||||
|
||||
let bindings = errorCollection :: helpText :: bindings
|
||||
|
||||
@@ -1923,14 +1937,25 @@ module internal ArgParserGenerator =
|
||||
| _ -> None
|
||||
)
|
||||
|
||||
let taggedType =
|
||||
let taggedType, typeHelpText =
|
||||
match taggedType with
|
||||
| SynTypeDefn.SynTypeDefn (sci,
|
||||
| SynTypeDefn.SynTypeDefn (SynComponentInfo.SynComponentInfo (attributes = attrs) as sci,
|
||||
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (access, fields, _), _),
|
||||
smd,
|
||||
_,
|
||||
_,
|
||||
_) -> RecordType.OfRecord sci smd access fields
|
||||
_) ->
|
||||
let typeHelp =
|
||||
attrs
|
||||
|> SynAttributes.toAttrs
|
||||
|> List.tryPick (fun a ->
|
||||
match (List.last a.TypeName.LongIdent).idText with
|
||||
| "ArgumentHelpTextAttribute"
|
||||
| "ArgumentHelpText" -> Some a.ArgExpr
|
||||
| _ -> None
|
||||
)
|
||||
|
||||
RecordType.OfRecord sci smd access fields, typeHelp
|
||||
| _ -> failwith "[<ArgParser>] currently only supports being placed on records."
|
||||
|
||||
let modAttrs, modName =
|
||||
@@ -1988,7 +2013,7 @@ module internal ArgParserGenerator =
|
||||
|> SynPat.annotateType (SynType.appPostfix "list" SynType.string)
|
||||
|
||||
let parsePrime =
|
||||
createRecordParse parseStateIdent flagDus allRecordTypes taggedType
|
||||
createRecordParse typeHelpText parseStateIdent flagDus allRecordTypes taggedType
|
||||
|> SynBinding.basic
|
||||
[ Ident.create "parse'" ]
|
||||
[
|
||||
|
||||
@@ -19,7 +19,9 @@ module internal CapturingInterfaceMockGenerator =
|
||||
open Fantomas.FCS.Text.Range
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type private KnownInheritance = | IDisposable
|
||||
type private KnownInheritance =
|
||||
| IDisposable
|
||||
| IAsyncDisposable
|
||||
|
||||
/// Expects the input `args` list to have more than one element.
|
||||
let private createTypeForArgs
|
||||
@@ -209,6 +211,8 @@ module internal CapturingInterfaceMockGenerator =
|
||||
| [] -> failwith "Unexpected empty identifier in inheritance declaration"
|
||||
| [ "IDisposable" ]
|
||||
| [ "System" ; "IDisposable" ] -> KnownInheritance.IDisposable
|
||||
| [ "IAsyncDisposable" ]
|
||||
| [ "System" ; "IAsyncDisposable" ] -> KnownInheritance.IAsyncDisposable
|
||||
| _ -> failwithf $"Unrecognised inheritance identifier: %+A{name}"
|
||||
| x -> failwithf $"Unrecognised type in inheritance: %+A{x}"
|
||||
)
|
||||
@@ -250,12 +254,26 @@ module internal CapturingInterfaceMockGenerator =
|
||||
|
||||
let emptyRecordFieldInstantiations =
|
||||
let interfaceExtras =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
let unitFun = SynExpr.createThunk (SynExpr.CreateConst ())
|
||||
let disposable =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
let unitFun = SynExpr.createThunk (SynExpr.CreateConst ())
|
||||
[ SynLongIdent.createS "Dispose", unitFun ]
|
||||
else
|
||||
[]
|
||||
|
||||
[ SynLongIdent.createS "Dispose", unitFun ]
|
||||
else
|
||||
[]
|
||||
let asyncDisposable =
|
||||
if inherits.Contains KnownInheritance.IAsyncDisposable then
|
||||
let valueTaskCtor =
|
||||
SynExpr.createLongIdent [ "System" ; "Threading" ; "Tasks" ; "ValueTask" ]
|
||||
|> SynExpr.applyTo (SynExpr.CreateConst ())
|
||||
|> SynExpr.paren
|
||||
|> SynExpr.createLambda "()"
|
||||
|
||||
[ SynLongIdent.createS "DisposeAsync", valueTaskCtor ]
|
||||
else
|
||||
[]
|
||||
|
||||
disposable @ asyncDisposable
|
||||
|
||||
let originalMembers =
|
||||
fields
|
||||
@@ -275,23 +293,42 @@ module internal CapturingInterfaceMockGenerator =
|
||||
[ Ident.create "Empty" ]
|
||||
[ SynPat.unit ]
|
||||
(SynExpr.createRecord None emptyRecordFieldInstantiations)
|
||||
|> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every non-unit method throws.")
|
||||
|> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every non-disposal method throws.")
|
||||
|> SynBinding.withReturnAnnotation constructorReturnType
|
||||
|> SynMemberDefn.staticMember
|
||||
|
||||
let recordFields =
|
||||
let extras =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "Dispose")
|
||||
Type = SynType.funFromDomain SynType.unit SynType.unit
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (PreXmlDoc.create "Implementation of IDisposable.Dispose")
|
||||
|> List.singleton
|
||||
else
|
||||
[]
|
||||
let disposable =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "Dispose")
|
||||
Type = SynType.funFromDomain SynType.unit SynType.unit
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (PreXmlDoc.create "Implementation of IDisposable.Dispose")
|
||||
|> List.singleton
|
||||
else
|
||||
[]
|
||||
|
||||
let asyncDisposable =
|
||||
if inherits.Contains KnownInheritance.IAsyncDisposable then
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "DisposeAsync")
|
||||
Type =
|
||||
SynType.funFromDomain
|
||||
SynType.unit
|
||||
(SynType.createLongIdent' [ "System" ; "Threading" ; "Tasks" ; "ValueTask" ])
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (PreXmlDoc.create "Implementation of IAsyncDisposable.DisposeAsync")
|
||||
|> List.singleton
|
||||
else
|
||||
[]
|
||||
|
||||
disposable @ asyncDisposable
|
||||
|
||||
let nonExtras =
|
||||
fields |> Map.toSeq |> Seq.map (fun (_, (field, _)) -> field) |> Seq.toList
|
||||
@@ -528,6 +565,23 @@ module internal CapturingInterfaceMockGenerator =
|
||||
Some [ mem ],
|
||||
range0
|
||||
)
|
||||
|
||||
| KnownInheritance.IAsyncDisposable ->
|
||||
let mem =
|
||||
SynExpr.createLongIdent [ "this" ; "DisposeAsync" ]
|
||||
|> SynExpr.applyTo (SynExpr.CreateConst ())
|
||||
|> SynBinding.basic [ Ident.create "this" ; Ident.create "DisposeAsync" ] [ SynPat.unit ]
|
||||
|> SynBinding.withReturnAnnotation (
|
||||
SynType.createLongIdent' [ "System" ; "Threading" ; "Tasks" ; "ValueTask" ]
|
||||
)
|
||||
|> SynMemberDefn.memberImplementation
|
||||
|
||||
SynMemberDefn.Interface (
|
||||
SynType.createLongIdent' [ "System" ; "IAsyncDisposable" ],
|
||||
Some range0,
|
||||
Some [ mem ],
|
||||
range0
|
||||
)
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ module internal InterfaceMockGenerator =
|
||||
| Some id -> id
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type private KnownInheritance = | IDisposable
|
||||
type private KnownInheritance =
|
||||
| IDisposable
|
||||
| IAsyncDisposable
|
||||
|
||||
let createType
|
||||
(spec : GenerateMockOutputSpec)
|
||||
@@ -39,6 +41,8 @@ module internal InterfaceMockGenerator =
|
||||
| [] -> failwith "Unexpected empty identifier in inheritance declaration"
|
||||
| [ "IDisposable" ]
|
||||
| [ "System" ; "IDisposable" ] -> KnownInheritance.IDisposable
|
||||
| [ "IAsyncDisposable" ]
|
||||
| [ "System" ; "IAsyncDisposable" ] -> KnownInheritance.IAsyncDisposable
|
||||
| _ -> failwithf "Unrecognised inheritance identifier: %+A" name
|
||||
| x -> failwithf "Unrecognised type in inheritance: %+A" x
|
||||
)
|
||||
@@ -69,12 +73,26 @@ module internal InterfaceMockGenerator =
|
||||
|
||||
let constructorFields =
|
||||
let extras =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
let unitFun = SynExpr.createThunk (SynExpr.CreateConst ())
|
||||
let disposable =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
let unitFun = SynExpr.createThunk (SynExpr.CreateConst ())
|
||||
[ SynLongIdent.createS "Dispose", unitFun ]
|
||||
else
|
||||
[]
|
||||
|
||||
[ SynLongIdent.createS "Dispose", unitFun ]
|
||||
else
|
||||
[]
|
||||
let asyncDisposable =
|
||||
if inherits.Contains KnownInheritance.IAsyncDisposable then
|
||||
let valueTaskCtor =
|
||||
SynExpr.createLongIdent [ "System" ; "Threading" ; "Tasks" ; "ValueTask" ]
|
||||
|> SynExpr.applyTo (SynExpr.CreateConst ())
|
||||
|> SynExpr.paren
|
||||
|> SynExpr.createLambda "()"
|
||||
|
||||
[ SynLongIdent.createS "DisposeAsync", valueTaskCtor ]
|
||||
else
|
||||
[]
|
||||
|
||||
disposable @ asyncDisposable
|
||||
|
||||
let nonExtras =
|
||||
fields
|
||||
@@ -90,23 +108,42 @@ module internal InterfaceMockGenerator =
|
||||
else
|
||||
[ SynPat.unit ])
|
||||
(SynExpr.createRecord None constructorFields)
|
||||
|> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every method throws.")
|
||||
|> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every non-disposal method throws.")
|
||||
|> SynBinding.withReturnAnnotation constructorReturnType
|
||||
|> SynMemberDefn.staticMember
|
||||
|
||||
let fields =
|
||||
let extras =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "Dispose")
|
||||
Type = SynType.funFromDomain SynType.unit SynType.unit
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (PreXmlDoc.create "Implementation of IDisposable.Dispose")
|
||||
|> List.singleton
|
||||
else
|
||||
[]
|
||||
let disposable =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "Dispose")
|
||||
Type = SynType.funFromDomain SynType.unit SynType.unit
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (PreXmlDoc.create "Implementation of IDisposable.Dispose")
|
||||
|> List.singleton
|
||||
else
|
||||
[]
|
||||
|
||||
let asyncDisposable =
|
||||
if inherits.Contains KnownInheritance.IAsyncDisposable then
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "DisposeAsync")
|
||||
Type =
|
||||
SynType.funFromDomain
|
||||
SynType.unit
|
||||
(SynType.createLongIdent' [ "System" ; "Threading" ; "Tasks" ; "ValueTask" ])
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (PreXmlDoc.create "Implementation of IAsyncDisposable.DisposeAsync")
|
||||
|> List.singleton
|
||||
else
|
||||
[]
|
||||
|
||||
disposable @ asyncDisposable
|
||||
|
||||
extras @ fields
|
||||
|
||||
@@ -212,6 +249,23 @@ module internal InterfaceMockGenerator =
|
||||
Some [ mem ],
|
||||
range0
|
||||
)
|
||||
|
||||
| KnownInheritance.IAsyncDisposable ->
|
||||
let mem =
|
||||
SynExpr.createLongIdent [ "this" ; "DisposeAsync" ]
|
||||
|> SynExpr.applyTo (SynExpr.CreateConst ())
|
||||
|> SynBinding.basic [ Ident.create "this" ; Ident.create "DisposeAsync" ] [ SynPat.unit ]
|
||||
|> SynBinding.withReturnAnnotation (
|
||||
SynType.createLongIdent' [ "System" ; "Threading" ; "Tasks" ; "ValueTask" ]
|
||||
)
|
||||
|> SynMemberDefn.memberImplementation
|
||||
|
||||
SynMemberDefn.Interface (
|
||||
SynType.createLongIdent' [ "System" ; "IAsyncDisposable" ],
|
||||
Some range0,
|
||||
Some [ mem ],
|
||||
range0
|
||||
)
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
|
||||
@@ -389,6 +389,11 @@
|
||||
"version": "0.8.4",
|
||||
"hash": "sha256-UI7f2nt4g4Gg1Ke/IChrA4fpVOYAChXpvR6zkKfkmzE="
|
||||
},
|
||||
{
|
||||
"pname": "WoofWare.NUnitTestRunner",
|
||||
"version": "0.3.9",
|
||||
"hash": "sha256-+QVx5NYdY1JZoMcWfJRwFgvEj2dBxWlJU0mu1Hmnlhs="
|
||||
},
|
||||
{
|
||||
"pname": "WoofWare.Whippet.Fantomas",
|
||||
"version": "0.6.4",
|
||||
|
||||
Reference in New Issue
Block a user