From 0652744c5757fe1d075739377917c97416b87e98 Mon Sep 17 00:00:00 2001
From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com>
Date: Wed, 2 Oct 2024 20:30:21 +0100
Subject: [PATCH] Allow using fsproj annotations instead of attributes (#275)
---
ConsumePlugin/ConsumePlugin.fsproj | 14 ++
ConsumePlugin/GeneratedMockNoAttributes.fs | 200 ++++++++++++++++++
ConsumePlugin/MockExampleNoAttributes.fs | 41 ++++
README.md | 30 +++
.../TestMockGeneratorNoAttr.fs | 36 ++++
.../WoofWare.Myriad.Plugins.Test.fsproj | 1 +
.../HttpClientGenerator.fs | 28 ++-
.../InterfaceMockGenerator.fs | 26 ++-
WoofWare.Myriad.Plugins/JsonParseGenerator.fs | 27 ++-
.../JsonSerializeGenerator.fs | 27 ++-
WoofWare.Myriad.Plugins/MyriadParamParser.fs | 64 ++++++
.../SwaggerClientGenerator.fs | 29 +--
.../SynExpr/SynTypeDefn.fs | 4 +
.../WoofWare.Myriad.Plugins.fsproj | 1 +
14 files changed, 501 insertions(+), 27 deletions(-)
create mode 100644 ConsumePlugin/GeneratedMockNoAttributes.fs
create mode 100644 ConsumePlugin/MockExampleNoAttributes.fs
create mode 100644 WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGeneratorNoAttr.fs
create mode 100644 WoofWare.Myriad.Plugins/MyriadParamParser.fs
diff --git a/ConsumePlugin/ConsumePlugin.fsproj b/ConsumePlugin/ConsumePlugin.fsproj
index 5b99cc1..d509e3b 100644
--- a/ConsumePlugin/ConsumePlugin.fsproj
+++ b/ConsumePlugin/ConsumePlugin.fsproj
@@ -32,6 +32,20 @@
MockExample.fs
+
+
+ MockExampleNoAttributes.fs
+
+ GenerateMock
+ GenerateMock(false)
+ GenerateMock
+ GenerateMock
+ GenerateMock(false)
+ GenerateMock
+ GenerateMock
+ GenerateMock
+
+
Vault.fs
diff --git a/ConsumePlugin/GeneratedMockNoAttributes.fs b/ConsumePlugin/GeneratedMockNoAttributes.fs
new file mode 100644
index 0000000..f9ea47c
--- /dev/null
+++ b/ConsumePlugin/GeneratedMockNoAttributes.fs
@@ -0,0 +1,200 @@
+//------------------------------------------------------------------------------
+// This code was generated by myriad.
+// Changes to this file will be lost when the code is regenerated.
+//------------------------------------------------------------------------------
+
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type internal PublicTypeNoAttrMock =
+ {
+ Mem1 : string * int -> string list
+ Mem2 : string -> int
+ Mem3 : int * option -> string
+ }
+
+ /// An implementation where every method throws.
+ static member Empty : PublicTypeNoAttrMock =
+ {
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
+ Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
+ }
+
+ interface IPublicTypeNoAttr with
+ member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
+ member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
+ member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type public PublicTypeInternalFalseNoAttrMock =
+ {
+ Mem1 : string * int -> string list
+ Mem2 : string -> int
+ Mem3 : int * option -> string
+ }
+
+ /// An implementation where every method throws.
+ static member Empty : PublicTypeInternalFalseNoAttrMock =
+ {
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
+ Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
+ }
+
+ interface IPublicTypeInternalFalseNoAttr with
+ member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
+ member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
+ member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type internal InternalTypeNoAttrMock =
+ {
+ Mem1 : string * int -> unit
+ Mem2 : string -> int
+ }
+
+ /// An implementation where every method throws.
+ static member Empty : InternalTypeNoAttrMock =
+ {
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
+ }
+
+ interface InternalTypeNoAttr with
+ member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
+ member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type private PrivateTypeNoAttrMock =
+ {
+ Mem1 : string * int -> unit
+ Mem2 : string -> int
+ }
+
+ /// An implementation where every method throws.
+ static member Empty : PrivateTypeNoAttrMock =
+ {
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
+ }
+
+ interface PrivateTypeNoAttr with
+ member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
+ member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type private PrivateTypeInternalFalseNoAttrMock =
+ {
+ Mem1 : string * int -> unit
+ Mem2 : string -> int
+ }
+
+ /// An implementation where every method throws.
+ static member Empty : PrivateTypeInternalFalseNoAttrMock =
+ {
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
+ }
+
+ interface PrivateTypeInternalFalseNoAttr with
+ member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
+ member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type internal VeryPublicTypeNoAttrMock<'a, 'b> =
+ {
+ Mem1 : 'a -> 'b
+ }
+
+ /// An implementation where every method throws.
+ static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> =
+ {
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ }
+
+ interface VeryPublicTypeNoAttr<'a, 'b> with
+ member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type internal CurriedNoAttrMock<'a> =
+ {
+ Mem1 : int -> 'a -> string
+ Mem2 : int * string -> 'a -> string
+ Mem3 : (int * string) -> 'a -> string
+ Mem4 : (int * string) -> ('a * int) -> string
+ Mem5 : int * string -> ('a * int) -> string
+ Mem6 : int * string -> 'a * int -> string
+ }
+
+ /// An implementation where every method throws.
+ static member Empty () : CurriedNoAttrMock<'a> =
+ {
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
+ Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
+ Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4"))
+ Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5"))
+ Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6"))
+ }
+
+ interface CurriedNoAttr<'a> with
+ member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
+ member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
+ member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
+
+ member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
+ this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
+
+ member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) =
+ this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
+
+ member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
+ this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
+namespace SomeNamespace
+
+open System
+
+/// Mock record type for an interface
+type internal TypeWithInterfaceNoAttrMock =
+ {
+ /// Implementation of IDisposable.Dispose
+ Dispose : unit -> unit
+ Mem1 : string option -> string[] Async
+ Mem2 : unit -> string[] Async
+ }
+
+ /// An implementation where every method throws.
+ static member Empty : TypeWithInterfaceNoAttrMock =
+ {
+ Dispose = (fun () -> ())
+ Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
+ Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
+ }
+
+ interface TypeWithInterfaceNoAttr with
+ member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
+ member this.Mem2 () = this.Mem2 (())
+
+ interface System.IDisposable with
+ member this.Dispose () : unit = this.Dispose ()
diff --git a/ConsumePlugin/MockExampleNoAttributes.fs b/ConsumePlugin/MockExampleNoAttributes.fs
new file mode 100644
index 0000000..3d181fb
--- /dev/null
+++ b/ConsumePlugin/MockExampleNoAttributes.fs
@@ -0,0 +1,41 @@
+namespace SomeNamespace
+
+open System
+
+type IPublicTypeNoAttr =
+ abstract Mem1 : string * int -> string list
+ abstract Mem2 : string -> int
+ abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
+
+type IPublicTypeInternalFalseNoAttr =
+ abstract Mem1 : string * int -> string list
+ abstract Mem2 : string -> int
+ abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
+
+type internal InternalTypeNoAttr =
+ abstract Mem1 : string * int -> unit
+ abstract Mem2 : string -> int
+
+type private PrivateTypeNoAttr =
+ abstract Mem1 : string * int -> unit
+ abstract Mem2 : string -> int
+
+type private PrivateTypeInternalFalseNoAttr =
+ abstract Mem1 : string * int -> unit
+ abstract Mem2 : string -> int
+
+type VeryPublicTypeNoAttr<'a, 'b> =
+ abstract Mem1 : 'a -> 'b
+
+type CurriedNoAttr<'a> =
+ abstract Mem1 : int -> 'a -> string
+ abstract Mem2 : int * string -> 'a -> string
+ abstract Mem3 : (int * string) -> 'a -> string
+ abstract Mem4 : (int * string) -> ('a * int) -> string
+ abstract Mem5 : x : int * string -> ('a * int) -> string
+ abstract Mem6 : int * string -> y : 'a * int -> string
+
+type TypeWithInterfaceNoAttr =
+ inherit IDisposable
+ abstract Mem1 : string option -> string[] Async
+ abstract Mem2 : unit -> string[] Async
diff --git a/README.md b/README.md
index 3cbacb9..9c8139e 100644
--- a/README.md
+++ b/README.md
@@ -604,6 +604,36 @@ For example, this specifies that Myriad is to use the contents of `Client.fs` to
```
+## Alternative use without the attributes
+
+You can avoid taking a reference on the `WoofWare.Myriad.Plugins.Attributes` assembly, instead putting all the configuration into the project file.
+This is implemented for everything except the SwaggerClientGenerator.
+
+```xml
+
+
+
+
+ Client.fs
+
+ GenerateMock(false)!JsonParse
+ GenerateMock
+
+
+
+
+
+
+
+
+```
+
+That is, you specify a `!`-delimited list of the attributes you *would* apply to the type.
+Supply "arguments" to the attribute name in the project file as you would to the attribute itself.
+
+(Yes, this is indeed incredibly cumbersome, and you're not interested in the reasons it's all so mad!
+I'm hopefully going to get round to writing a more powerful source generation system which won't have these limitations.)
+
### Myriad Gotchas
* MsBuild doesn't always realise that it needs to invoke Myriad during rebuild.
diff --git a/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGeneratorNoAttr.fs b/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGeneratorNoAttr.fs
new file mode 100644
index 0000000..bff8aba
--- /dev/null
+++ b/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGeneratorNoAttr.fs
@@ -0,0 +1,36 @@
+namespace WoofWare.Myriad.Plugins.Test
+
+open System
+open SomeNamespace
+open NUnit.Framework
+open FsUnitTyped
+
+[]
+module TestMockGeneratorNoAttr =
+
+ []
+ let ``Example of use: IPublicType`` () =
+ let mock : IPublicTypeNoAttr =
+ { PublicTypeNoAttrMock.Empty with
+ Mem1 = fun (s, count) -> List.replicate count s
+ }
+ :> _
+
+ let _ =
+ Assert.Throws (fun () -> mock.Mem2 "hi" |> ignore)
+
+ mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ]
+
+ []
+ let ``Example of use: curried args`` () =
+ let mock : CurriedNoAttr<_> =
+ { CurriedNoAttrMock.Empty () with
+ Mem1 = fun i c -> Array.replicate i c |> String
+ Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
+ Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
+ }
+ :> _
+
+ mock.Mem1 3 'a' |> shouldEqual "aaa"
+ mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
+ mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"
diff --git a/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj b/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj
index 4fd67a4..110a31e 100644
--- a/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj
+++ b/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj
@@ -26,6 +26,7 @@
+
diff --git a/WoofWare.Myriad.Plugins/HttpClientGenerator.fs b/WoofWare.Myriad.Plugins/HttpClientGenerator.fs
index 561f068..57cab3e 100644
--- a/WoofWare.Myriad.Plugins/HttpClientGenerator.fs
+++ b/WoofWare.Myriad.Plugins/HttpClientGenerator.fs
@@ -992,6 +992,10 @@ type HttpClientGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
+ let targetedTypes =
+ MyriadParamParser.render context.AdditionalParameters
+ |> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
+
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
@@ -1005,12 +1009,32 @@ type HttpClientGenerator () =
types
|> List.choose (fun typeDef ->
match Ast.getAttribute typeDef with
- | None -> None
+ | None ->
+ let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
+
+ match Map.tryFind name targetedTypes with
+ | Some desired ->
+ desired
+ |> List.tryPick (fun generator ->
+ match generator with
+ | DesiredGenerator.HttpClient arg ->
+ let spec =
+ {
+ ExtensionMethods =
+ arg
+ |> Option.defaultValue
+ HttpClientAttribute.DefaultIsExtensionMethod
+ }
+
+ Some (typeDef, spec)
+ | _ -> None
+ )
+ | _ -> None
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with
| SynExpr.Const (SynConst.Bool value, _) -> value
- | SynExpr.Const (SynConst.Unit, _) -> JsonParseAttribute.DefaultIsExtensionMethod
+ | SynExpr.Const (SynConst.Unit, _) -> HttpClientAttribute.DefaultIsExtensionMethod
| arg ->
failwith
$"Unrecognised argument %+A{arg} to [<%s{nameof HttpClientAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only."
diff --git a/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs b/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs
index a433767..b8fc07e 100644
--- a/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs
+++ b/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs
@@ -283,6 +283,10 @@ type InterfaceMockGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
+ let targetedTypes =
+ MyriadParamParser.render context.AdditionalParameters
+ |> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
+
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
@@ -294,7 +298,27 @@ type InterfaceMockGenerator () =
types
|> List.choose (fun typeDef ->
match Ast.getAttribute typeDef with
- | None -> None
+ | None ->
+ let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
+
+ match Map.tryFind name targetedTypes with
+ | Some desired ->
+ desired
+ |> List.tryPick (fun generator ->
+ match generator with
+ | DesiredGenerator.InterfaceMock arg ->
+ let spec =
+ {
+ IsInternal =
+ arg
+ |> Option.defaultValue GenerateMockAttribute.DefaultIsInternal
+ }
+
+ Some (typeDef, spec)
+ | _ -> None
+ )
+ | _ -> None
+
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with
diff --git a/WoofWare.Myriad.Plugins/JsonParseGenerator.fs b/WoofWare.Myriad.Plugins/JsonParseGenerator.fs
index a06c942..14f5f36 100644
--- a/WoofWare.Myriad.Plugins/JsonParseGenerator.fs
+++ b/WoofWare.Myriad.Plugins/JsonParseGenerator.fs
@@ -702,6 +702,10 @@ type JsonParseGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
+ let targetedTypes =
+ MyriadParamParser.render context.AdditionalParameters
+ |> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
+
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
@@ -724,7 +728,28 @@ type JsonParseGenerator () =
types
|> List.choose (fun typeDef ->
match Ast.getAttribute typeDef with
- | None -> None
+ | None ->
+ let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
+
+ match Map.tryFind name targetedTypes with
+ | Some desired ->
+ desired
+ |> List.tryPick (fun generator ->
+ match generator with
+ | DesiredGenerator.JsonParse arg ->
+ let spec =
+ {
+ ExtensionMethods =
+ arg
+ |> Option.defaultValue
+ JsonParseAttribute.DefaultIsExtensionMethod
+ }
+
+ Some (typeDef, spec)
+ | _ -> None
+ )
+ | _ -> None
+
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with
diff --git a/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs b/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs
index d6c72f5..594c45a 100644
--- a/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs
+++ b/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs
@@ -519,6 +519,10 @@ type JsonSerializeGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
+ let targetedTypes =
+ MyriadParamParser.render context.AdditionalParameters
+ |> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
+
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
@@ -541,7 +545,28 @@ type JsonSerializeGenerator () =
types
|> List.choose (fun typeDef ->
match Ast.getAttribute typeDef with
- | None -> None
+ | None ->
+ let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
+
+ match Map.tryFind name targetedTypes with
+ | Some desired ->
+ desired
+ |> List.tryPick (fun generator ->
+ match generator with
+ | DesiredGenerator.JsonSerialize arg ->
+ let spec =
+ {
+ ExtensionMethods =
+ arg
+ |> Option.defaultValue
+ JsonSerializeAttribute.DefaultIsExtensionMethod
+ }
+
+ Some (typeDef, spec)
+ | _ -> None
+ )
+ | _ -> None
+
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with
diff --git a/WoofWare.Myriad.Plugins/MyriadParamParser.fs b/WoofWare.Myriad.Plugins/MyriadParamParser.fs
new file mode 100644
index 0000000..82c4e3b
--- /dev/null
+++ b/WoofWare.Myriad.Plugins/MyriadParamParser.fs
@@ -0,0 +1,64 @@
+namespace WoofWare.Myriad.Plugins
+
+open System.Collections.Generic
+
+type internal DesiredGenerator =
+ | InterfaceMock of isInternal : bool option
+ | JsonParse of extensionMethod : bool option
+ | JsonSerialize of extensionMethod : bool option
+ | HttpClient of extensionMethod : bool option
+
+ static member Parse (s : string) =
+ match s with
+ | "GenerateMock" -> DesiredGenerator.InterfaceMock None
+ | "GenerateMock(true)" -> DesiredGenerator.InterfaceMock (Some true)
+ | "GenerateMock(false)" -> DesiredGenerator.InterfaceMock (Some false)
+ | "JsonParse" -> DesiredGenerator.JsonParse None
+ | "JsonParse(true)" -> DesiredGenerator.JsonParse (Some true)
+ | "JsonParse(false)" -> DesiredGenerator.JsonParse (Some false)
+ | "JsonSerialize" -> DesiredGenerator.JsonSerialize None
+ | "JsonSerialize(true)" -> DesiredGenerator.JsonSerialize (Some true)
+ | "JsonSerialize(false)" -> DesiredGenerator.JsonSerialize (Some false)
+ | "HttpClient" -> DesiredGenerator.HttpClient None
+ | "HttpClient(true)" -> DesiredGenerator.HttpClient (Some true)
+ | "HttpClient(false)" -> DesiredGenerator.HttpClient (Some false)
+ | _ -> failwith $"Failed to parse as a generator specification: %s{s}"
+
+[]
+module internal MyriadParamParser =
+ (*
+ An apparent bug in Myriad's argument parsing means that this:
+
+
+ bar
+ quux
+
+
+ leads to this:
+
+ Foo = "bar;Baz=quux"
+
+ I'm not going to put effort into fixing Myriad, though, because I want
+ to build something much more powerful instead.
+ *)
+
+ /// Call this with `context.AdditionalParameters`.
+ let render (pars : IDictionary) : Map =
+ match pars.Count with
+ | 0 -> Map.empty
+ | 1 ->
+ let (KeyValue (key, value)) = pars |> Seq.exactlyOne
+
+ match value.Split ';' |> Seq.toList with
+ | [] -> failwith "LOGIC ERROR"
+ | value :: rest ->
+ rest
+ |> Seq.map (fun v ->
+ let split = v.Split '='
+ split.[0], String.concat "=" split.[1..]
+ )
+ |> Seq.append (Seq.singleton (key, value))
+ |> Map.ofSeq
+ | _ ->
+ // assume the Myriad bug is fixed!
+ pars |> Seq.map (fun (KeyValue (k, v)) -> k, v) |> Map.ofSeq
diff --git a/WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs b/WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs
index 746734a..a5b1359 100644
--- a/WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs
+++ b/WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs
@@ -672,31 +672,16 @@ type SwaggerClientGenerator () =
|> Seq.toList
let config =
- // Bug in Myriad, their arg parsing is borked.
- let pars =
- context.AdditionalParameters
- |> Seq.map (fun (KeyValue (k, v)) -> k, v)
- |> Seq.toList
+ let pars = MyriadParamParser.render context.AdditionalParameters
let pars =
- match pars with
- | [] ->
- failwith "No parameters given. You must supply the parameter in ."
- | [ key, value ] ->
- let semicolon = value.IndexOf ';'
+ pars
+ |> Map.toSeq
+ |> Seq.map (fun (k, v) -> k.ToUpperInvariant (), v)
+ |> Map.ofSeq
- if semicolon >= 0 then
- let equals = value.IndexOf ('=', semicolon)
-
- [
- key, value.Substring (0, semicolon)
- value.Substring (semicolon + 1, equals - semicolon - 1), value.Substring (equals + 1)
- ]
- else
- [ key, value ]
- | rest -> rest
- |> List.map (fun (key, value) -> key.ToUpperInvariant (), value)
- |> Map.ofList
+ if pars.IsEmpty then
+ failwith "No parameters given. You must supply the parameter in ."
let createMock =
match Map.tryFind "GENERATEMOCKVISIBILITY" pars with
diff --git a/WoofWare.Myriad.Plugins/SynExpr/SynTypeDefn.fs b/WoofWare.Myriad.Plugins/SynExpr/SynTypeDefn.fs
index 966e3d6..529352a 100644
--- a/WoofWare.Myriad.Plugins/SynExpr/SynTypeDefn.fs
+++ b/WoofWare.Myriad.Plugins/SynExpr/SynTypeDefn.fs
@@ -25,3 +25,7 @@ module internal SynTypeDefn =
match r with
| SynTypeDefn (typeInfo, typeRepr, _, ctor, range, trivia) ->
SynTypeDefn.SynTypeDefn (typeInfo, typeRepr, members, ctor, range, trivia)
+
+ let getName (defn : SynTypeDefn) : LongIdent =
+ match defn with
+ | SynTypeDefn (SynComponentInfo.SynComponentInfo (_, _, _, id, _, _, _, _), _, _, _, _, _) -> id
diff --git a/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj b/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj
index 08aabe6..9af3bb2 100644
--- a/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj
+++ b/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj
@@ -53,6 +53,7 @@
+