forked from dotnet/fsharp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWriteCodeFragment.fs
More file actions
166 lines (138 loc) · 7.25 KB
/
Copy pathWriteCodeFragment.fs
File metadata and controls
166 lines (138 loc) · 7.25 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Build
open System
open System.Collections
open System.IO
open System.Text
open Microsoft.Build.Framework
open Microsoft.Build.Utilities
type WriteCodeFragment() as this =
inherit Task()
let mutable _outputDirectory: ITaskItem MaybeNull = null
let mutable _outputFile: ITaskItem MaybeNull = null
let mutable _language: string = ""
let mutable _assemblyAttributes: ITaskItem[] = [||]
let failTask fmt =
Printf.ksprintf
(fun msg ->
this.Log.LogError msg
raise TaskFailed)
fmt
static let escapeString (str: string) =
let sb =
str.ToCharArray()
|> Seq.fold
(fun (sb: StringBuilder) (c: char) ->
match c with
| '\n'
| '\u2028'
| '\u2028' -> sb.Append("\\n")
| '\r' -> sb.Append("\\r")
| '\t' -> sb.Append("\\t")
| '\'' -> sb.Append("\\'")
| '\\' -> sb.Append("\\\\")
| '"' -> sb.Append("\\\"")
| '\u0000' -> sb.Append("\\0")
| _ -> sb.Append(c))
(StringBuilder().Append("\""))
sb.Append("\"").ToString()
member _.GenerateAttribute(item: ITaskItem, language: string) =
let attributeName = item.ItemSpec
let args =
// mimicking the behavior from https://github.com/Microsoft/msbuild/blob/70ce7e9ccb891b63f0859f1f7f0b955693ed3742/src/Tasks/WriteCodeFragment.cs#L355-L415
// Split parameters into unnamed positional (e.g., key is "_Parameter1", etc.) and proper named parameters.
let customMetadata = item.CloneCustomMetadata()
let parameterPairs =
// normalize everything to strings
seq { for entry in customMetadata -> entry :?> DictionaryEntry }
|> Seq.toList
|> List.map (fun entry ->
let key = entry.Key :?> string
let value =
match entry.Value with
| null -> "null"
| :? string as value -> escapeString value
| value -> value.ToString()
(key, value))
let orderedParameters, namedParameters =
parameterPairs |> List.partition (fun (key, _) -> key.StartsWith("_Parameter"))
let orderedParametersWithIndex =
orderedParameters
|> List.map (fun (key, value) ->
let indexString = key.Substring("_Parameter".Length)
match Int32.TryParse indexString with
| (true, index) -> (index, value)
| (false, _) -> failTask "Unable to parse '%s' as an index" indexString)
|> List.sortBy fst
// assign ordered parameters to array
let orderedParametersArray =
if List.isEmpty orderedParametersWithIndex then
[||]
else
Array.create (List.last orderedParametersWithIndex |> fst) "null"
List.iter (fun (index, value) -> orderedParametersArray.[index - 1] <- value) orderedParametersWithIndex
// construct ordered parameter lists
let combinedOrderedParameters = String.Join(", ", orderedParametersArray)
let combinedNamedParameters =
String.Join(", ", List.map (fun (key, value) -> sprintf "%s = %s" key value) namedParameters)
// construct the final argument string; positional arguments followed by named
match (combinedOrderedParameters.Length, combinedNamedParameters.Length) with
| (0, 0) -> "" // no arguments
| (0, _) -> combinedNamedParameters // only named arguments
| (_, 0) -> combinedOrderedParameters // only positional arguments
| (_, _) -> combinedOrderedParameters + ", " + combinedNamedParameters // both positional and named arguments
match language.ToLowerInvariant() with
| "f#" -> sprintf "[<assembly: %s(%s)>]" attributeName args
| "c#" -> sprintf "[assembly: %s(%s)]" attributeName args
| "vb" -> sprintf "<Assembly: %s(%s)>" attributeName args
| _ -> failTask "Language name must be one of F#, C# or VB"
// adding this property to maintain API equivalence with the MSBuild task
member _.Language
with get () = _language
and set (value) = _language <- value
member _.AssemblyAttributes
with get () = _assemblyAttributes
and set (value) = _assemblyAttributes <- value
member _.OutputDirectory
with get () = _outputDirectory
and set (value) = _outputDirectory <- value
[<Output>]
member _.OutputFile
with get () = _outputFile
and set (value) = _outputFile <- value
override this.Execute() =
try
match _outputFile with
| Null -> failTask "Output location must be specified"
| NonNull outputFile ->
let boilerplate =
match _language.ToLowerInvariant() with
| "f#" ->
"// <auto-generated>\n// Generated by the FSharp WriteCodeFragment class.\n// </auto-generated>\nnamespace FSharp\n\nopen System\nopen System.Reflection\n"
| "c#" ->
"// <auto-generated>\n// Generated by the FSharp WriteCodeFragment class.\n// </auto-generated>\n\nusing System;\nusing System.Reflection;"
| "vb" ->
"'------------------------------------------------------------------------------\n' <auto-generated>\n' Generated by the FSharp WriteCodeFragment class.\n' </auto-generated>\n'------------------------------------------------------------------------------\n\nOption Strict Off\nOption Explicit On\n\nImports System\nImports System.Reflection"
| _ -> failTask "Language name must be one of F#, C# or VB"
let sb = StringBuilder().AppendLine(boilerplate).AppendLine()
let code =
(sb, _assemblyAttributes)
||> Array.fold (fun (sb: StringBuilder) (item: ITaskItem) ->
sb.AppendLine(this.GenerateAttribute(item, _language.ToLowerInvariant())))
if _language.ToLowerInvariant() = "f#" then
code.AppendLine("do()") |> ignore
let fileName = outputFile.ItemSpec
let outputFileItem =
match _outputDirectory with
| Null -> outputFile
| NonNull outputDirectory ->
if Path.IsPathRooted(fileName) then
outputFile
else
TaskItem(Path.Combine(outputDirectory.ItemSpec, fileName)) :> ITaskItem
let codeText = code.ToString()
File.WriteAllText(fileName, codeText)
_outputFile <- outputFileItem
not this.Log.HasLoggedErrors
with TaskFailed ->
false