This repository was archived by the owner on Jan 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 313
Expand file tree
/
Copy pathFSharpEmbedResXSource.fs
More file actions
144 lines (129 loc) · 7.48 KB
/
FSharpEmbedResXSource.fs
File metadata and controls
144 lines (129 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.FSharp.Build
open System
open System.Collections
open System.Globalization
open System.IO
open System.Linq
open System.Text
open System.Xml.Linq
open Microsoft.Build.Framework
open Microsoft.Build.Utilities
type FSharpEmbedResXSource() =
let mutable _buildEngine : IBuildEngine = null
let mutable _hostObject : ITaskHost = null
let mutable _embeddedText : ITaskItem[] = [||]
let mutable _generatedSource : ITaskItem[] = [||]
let mutable _outputPath : string = ""
let mutable _targetFramework : string = ""
let boilerplate = @"// <auto-generated>
namespace {0}
open System.Reflection
module internal {1} =
type private C (_dummy:System.Object) = class end
let mutable Culture = System.Globalization.CultureInfo.CurrentUICulture
let ResourceManager = new System.Resources.ResourceManager(""{2}"", C(null).GetType().GetTypeInfo().Assembly)
let GetString(name:System.String) : System.String = ResourceManager.GetString(name, Culture)"
let boilerplateGetObject = " let GetObject(name:System.String) : System.Object = ResourceManager.GetObject(name, Culture)"
let generateSource (resx:string) (fullModuleName:string) (generateLegacy:bool) (generateLiteral:bool) =
try
let printMessage = printfn "FSharpEmbedResXSource: %s"
let justFileName = Path.GetFileNameWithoutExtension(resx)
let sourcePath = Path.Combine(_outputPath, justFileName + ".fs")
// simple up-to-date check
if File.Exists(resx) && File.Exists(sourcePath) &&
File.GetLastWriteTimeUtc(resx) <= File.GetLastWriteTimeUtc(sourcePath) then
printMessage (sprintf "Skipping generation: '%s' since it is up-to-date." sourcePath)
Some(sourcePath)
else
let namespaceName, moduleName =
let parts = fullModuleName.Split('.')
if parts.Length = 1 then ("global", parts.[0])
else (String.Join(".", parts, 0, parts.Length - 1), parts.[parts.Length - 1])
let generateGetObject = not (_targetFramework.StartsWith("netstandard1.") || _targetFramework.StartsWith("netcoreapp1."))
printMessage (sprintf "Generating code for target framework %s" _targetFramework)
let sb = StringBuilder().AppendLine(String.Format(boilerplate, namespaceName, moduleName, justFileName))
if generateGetObject then sb.AppendLine(boilerplateGetObject) |> ignore
printMessage <| sprintf "Generating: %s" sourcePath
let body =
let xname = XName.op_Implicit
XDocument.Load(resx).Descendants(xname "data")
|> Seq.fold (fun (sb:StringBuilder) (node:XElement) ->
let name =
match node.Attribute(xname "name") with
| null -> failwith (sprintf "Missing resource name on element '%s'" (node.ToString()))
| attr -> attr.Value
let docComment =
match node.Elements(xname "value").FirstOrDefault() with
| null -> failwith <| sprintf "Missing resource value for '%s'" name
| element -> element.Value.Trim()
let identifier = if Char.IsLetter(name.[0]) || name.[0] = '_' then name else "_" + name
let commentBody =
XElement(xname "summary", docComment).ToString().Split([|"\r\n"; "\r"; "\n"|], StringSplitOptions.None)
|> Array.fold (fun (sb:StringBuilder) line -> sb.AppendLine(" /// " + line)) (StringBuilder())
// add the resource
let accessorBody =
match (generateLegacy, generateLiteral) with
| (true, true) -> sprintf " [<Literal>]\n let %s = \"%s\"" identifier name
| (true, false) -> sprintf " let %s = \"%s\"" identifier name // the [<Literal>] attribute can't be used for FSharp.Core
| (false, _) ->
let isStringResource = node.Attribute(xname "type") |> isNull
match (isStringResource, generateGetObject) with
| (true, _) -> sprintf " let %s() = GetString(\"%s\")" identifier name
| (false, true) -> sprintf " let %s() = GetObject(\"%s\")" identifier name
| (false, false) -> "" // the target runtime doesn't support non-string resources
// TODO: When calling the `GetObject` version, parse the `type` attribute to discover the proper return type
sb.AppendLine().Append(commentBody).AppendLine(accessorBody)
) sb
File.WriteAllText(sourcePath, body.ToString())
printMessage <| sprintf "Done: %s" sourcePath
Some(sourcePath)
with e ->
printf "An exception occurred when processing '%s'\n%s" resx (e.ToString())
None
[<Required>]
member this.EmbeddedResource
with get() = _embeddedText
and set(value) = _embeddedText <- value
[<Required>]
member this.IntermediateOutputPath
with get() = _outputPath
and set(value) = _outputPath <- value
member this.TargetFramework
with get() = _targetFramework
and set(value) = _targetFramework <- value
[<Output>]
member this.GeneratedSource
with get() = _generatedSource
interface ITask with
member this.BuildEngine
with get() = _buildEngine
and set(value) = _buildEngine <- value
member this.HostObject
with get() = _hostObject
and set(value) = _hostObject <- value
member this.Execute() =
let getBooleanMetadata (metadataName:string) (defaultValue:bool) (item:ITaskItem) =
match item.GetMetadata(metadataName) with
| value when String.IsNullOrWhiteSpace(value) -> defaultValue
| value ->
match value.ToLowerInvariant() with
| "true" -> true
| "false" -> false
| _ -> failwith (sprintf "Expected boolean value for '%s' found '%s'" metadataName value)
let mutable success = true
let generatedSource =
[| for item in this.EmbeddedResource do
if getBooleanMetadata "GenerateSource" false item then
let moduleName =
match item.GetMetadata("GeneratedModuleName") with
| null | "" -> Path.GetFileNameWithoutExtension(item.ItemSpec)
| value -> value
let generateLegacy = getBooleanMetadata "GenerateLegacyCode" false item
let generateLiteral = getBooleanMetadata "GenerateLiterals" true item
match generateSource item.ItemSpec moduleName generateLegacy generateLiteral with
| Some (source) -> yield TaskItem(source) :> ITaskItem
| None -> success <- false
|]
_generatedSource <- generatedSource
success