starlight-fsharp-oracle 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Directory.Build.props +25 -0
- package/Directory.Packages.props +18 -0
- package/README.md +80 -0
- package/dist/Extensions/StringBuilder.js +28 -0
- package/dist/Extensions/TextNode.js +115 -0
- package/dist/FSharp.Oracle.Schema/Schema.js +674 -0
- package/dist/Generate.js +254 -0
- package/dist/Helpers.js +69 -0
- package/dist/Plugin.js +139 -0
- package/dist/Render/Documentation.js +87 -0
- package/dist/Render/Entries.js +148 -0
- package/dist/Render/Pages.js +292 -0
- package/dist/Render/Primitives.js +114 -0
- package/dist/Render/Types.js +30 -0
- package/dist/Render.js +12 -0
- package/dist/Themes.js +78 -0
- package/oracle-bin/FSharp.Compiler.Service.dll +0 -0
- package/oracle-bin/FSharp.Core.dll +0 -0
- package/oracle-bin/FSharp.DependencyManager.Nuget.dll +0 -0
- package/oracle-bin/FSharp.Oracle.Schema.dll +0 -0
- package/oracle-bin/FSharp.Oracle.Schema.pdb +0 -0
- package/oracle-bin/FSharp.Oracle.Schema.xml +219 -0
- package/oracle-bin/Fable.Core.dll +0 -0
- package/oracle-bin/Oracle +0 -0
- package/oracle-bin/Oracle.deps.json +280 -0
- package/oracle-bin/Oracle.dll +0 -0
- package/oracle-bin/Oracle.pdb +0 -0
- package/oracle-bin/Oracle.runtimeconfig.json +13 -0
- package/oracle-bin/Oracle.xml +111 -0
- package/oracle-bin/Thoth.Json.Core.Auto.dll +0 -0
- package/oracle-bin/Thoth.Json.Core.dll +0 -0
- package/oracle-bin/Thoth.Json.System.Text.Json.dll +0 -0
- package/oracle-bin/cs/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/cs/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/cs/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/de/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/de/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/de/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/es/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/es/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/es/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/fr/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/fr/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/fr/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/it/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/it/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/it/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/ja/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/ja/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/ja/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/ko/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/ko/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/ko/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/pl/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/pl/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/pl/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/pt-BR/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/pt-BR/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/pt-BR/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/ru/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/ru/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/ru/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/tr/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/tr/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/tr/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/zh-Hans/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/zh-Hans/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/zh-Hans/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/oracle-bin/zh-Hant/FSharp.Compiler.Service.resources.dll +0 -0
- package/oracle-bin/zh-Hant/FSharp.Core.resources.dll +0 -0
- package/oracle-bin/zh-Hant/FSharp.DependencyManager.Nuget.resources.dll +0 -0
- package/package.json +38 -0
- package/packages/FSharp.Oracle/Extractor/Assembly.fs +160 -0
- package/packages/FSharp.Oracle/Extractor/EntityExtractor.fs +608 -0
- package/packages/FSharp.Oracle/Extractor/Helpers.fs +150 -0
- package/packages/FSharp.Oracle/Extractor/MemberExtractor.fs +172 -0
- package/packages/FSharp.Oracle/Extractor/ModuleExtractor.fs +45 -0
- package/packages/FSharp.Oracle/Extractor/ParameterExtractor.fs +47 -0
- package/packages/FSharp.Oracle/Extractor/SignatureRendering.fs +464 -0
- package/packages/FSharp.Oracle/Extractor/ValueExtractor.fs +171 -0
- package/packages/FSharp.Oracle/FSharp.Oracle.fsproj +36 -0
- package/packages/FSharp.Oracle/Program.fs +66 -0
- package/packages/FSharp.Oracle/XmlDoc.fs +463 -0
- package/packages/FSharp.Oracle.Schema/FSharp.Oracle.Schema.fsproj +16 -0
- package/packages/FSharp.Oracle.Schema/Schema.fs +454 -0
- package/packages/Starlight.FSharp.Oracle/components/DocEntry.astro +256 -0
- package/packages/Starlight.FSharp.Oracle/components/FSharpDocPage.astro +121 -0
- package/packages/Starlight.FSharp.Oracle/components/fsharp-doc.css +58 -0
- package/packages/Starlight.FSharp.Oracle/layouts/FSharpDocLayout.astro +27 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module FSharp.Oracle.Program
|
|
2
|
+
|
|
3
|
+
open System
|
|
4
|
+
open FSharp.Compiler.CodeAnalysis
|
|
5
|
+
open FSharp.Oracle.Schema
|
|
6
|
+
open FSharp.Oracle.Extractor
|
|
7
|
+
open Thoth.Json.System.Text.Json
|
|
8
|
+
open Thoth.Json.Core.Auto
|
|
9
|
+
|
|
10
|
+
[<EntryPoint>]
|
|
11
|
+
let main argv =
|
|
12
|
+
let rec parseArgs outputBase dlls =
|
|
13
|
+
function
|
|
14
|
+
| "--output-base" :: value :: rest -> parseArgs value dlls rest
|
|
15
|
+
| path :: rest -> parseArgs outputBase (path :: dlls) rest
|
|
16
|
+
| [] -> outputBase, List.rev dlls |> List.toArray
|
|
17
|
+
|
|
18
|
+
let outputBase, dllPaths = parseArgs "api" [] (argv |> Array.toList)
|
|
19
|
+
|
|
20
|
+
match dllPaths with
|
|
21
|
+
| [||] ->
|
|
22
|
+
eprintfn "Usage: fsharp-docs-oracle [--output-base <base>] <path/to/Assembly.dll> [...]"
|
|
23
|
+
eprintfn "Output: JSON IR written to stdout"
|
|
24
|
+
1
|
|
25
|
+
| _ ->
|
|
26
|
+
let checker = FSharpChecker.Create()
|
|
27
|
+
|
|
28
|
+
// Gather every .dll in the same directories as the specified assemblies
|
|
29
|
+
// so FCS can resolve transitive dependencies. Also include the current
|
|
30
|
+
// .NET runtime directory because framework assemblies (e.g.
|
|
31
|
+
// System.Text.RegularExpressions) are not copied to the publish output.
|
|
32
|
+
let allDllPaths =
|
|
33
|
+
let runtimeDir =
|
|
34
|
+
System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()
|
|
35
|
+
|
|
36
|
+
[|
|
|
37
|
+
yield! IO.Directory.GetFiles(runtimeDir, "*.dll")
|
|
38
|
+
|
|
39
|
+
yield!
|
|
40
|
+
dllPaths
|
|
41
|
+
|> Array.collect (fun path ->
|
|
42
|
+
let dir = IO.Path.GetDirectoryName(path)
|
|
43
|
+
IO.Directory.GetFiles(dir, "*.dll")
|
|
44
|
+
)
|
|
45
|
+
|]
|
|
46
|
+
|> Array.map IO.Path.GetFullPath
|
|
47
|
+
|> Array.distinct
|
|
48
|
+
|
|
49
|
+
let assemblies =
|
|
50
|
+
dllPaths
|
|
51
|
+
|> Array.map (extractAssembly checker allDllPaths outputBase)
|
|
52
|
+
|> Array.toList
|
|
53
|
+
|
|
54
|
+
let root =
|
|
55
|
+
{
|
|
56
|
+
Assemblies = assemblies
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let json =
|
|
60
|
+
root
|
|
61
|
+
|> Encode.Auto.generateEncoder(losslessOption = true)
|
|
62
|
+
|> Encode.toString 4
|
|
63
|
+
|
|
64
|
+
Console.WriteLine(json)
|
|
65
|
+
|
|
66
|
+
0
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
module Oracle.XmlDoc
|
|
2
|
+
|
|
3
|
+
// XML doc formatter adapted from Ionide.XmlDocFormatter
|
|
4
|
+
// https://github.com/ionide/Ionide.XmlDocFormatter/blob/develop/src/Ionide.XmlDocFormatter.fs
|
|
5
|
+
|
|
6
|
+
open System
|
|
7
|
+
open System.Text.RegularExpressions
|
|
8
|
+
open FSharp.Oracle.Schema
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Formatter config and tag infrastructure
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
type private FormatterConfig =
|
|
15
|
+
{
|
|
16
|
+
Paragraph: string -> string
|
|
17
|
+
CodeBlock: string -> string -> string
|
|
18
|
+
InlineCode: string -> string
|
|
19
|
+
HandleMicrosoftOrList: string -> string
|
|
20
|
+
BeforeTransform: string -> string
|
|
21
|
+
AfterTransform: string -> string
|
|
22
|
+
Example: string -> string
|
|
23
|
+
Block: string -> string
|
|
24
|
+
// Reference / link formatters
|
|
25
|
+
MemberRef: string -> string
|
|
26
|
+
LangwordRef: string -> string
|
|
27
|
+
ExternalLink: string -> string -> string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let private tagPattern (tagName: string) =
|
|
31
|
+
sprintf
|
|
32
|
+
"""(?'void_element'<%s(?'void_attributes'\s+[^\/>]+)?\/>)|(?'non_void_element'<%s(?'non_void_attributes'\s+[^>]+)?>(?'non_void_innerText'(?:(?!<%s>)(?!<\/%s>)[\s\S])*)<\/%s\s*>)"""
|
|
33
|
+
tagName
|
|
34
|
+
tagName
|
|
35
|
+
tagName
|
|
36
|
+
tagName
|
|
37
|
+
tagName
|
|
38
|
+
|
|
39
|
+
type private TagInfo =
|
|
40
|
+
| VoidElement of attributes: Map<string, string>
|
|
41
|
+
| NonVoidElement of innerText: string * attributes: Map<string, string>
|
|
42
|
+
|
|
43
|
+
let private extractTextFromQuote (quotedText: string) =
|
|
44
|
+
quotedText.Substring(1, quotedText.Length - 2)
|
|
45
|
+
|
|
46
|
+
let private extractMemberText (text: string) =
|
|
47
|
+
let pattern = "(?'member_type'[a-z]{1}:)?(?'member_text'.*)"
|
|
48
|
+
let m = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
|
|
49
|
+
|
|
50
|
+
if m.Groups.["member_text"].Success then
|
|
51
|
+
m.Groups.["member_text"].Value
|
|
52
|
+
else
|
|
53
|
+
text
|
|
54
|
+
|
|
55
|
+
let private getAttributes (attributes: Group) =
|
|
56
|
+
if attributes.Success then
|
|
57
|
+
let pattern = """(?'key'\S+)=(?'value''[^']*'|"[^"]*")"""
|
|
58
|
+
|
|
59
|
+
Regex.Matches(attributes.Value, pattern, RegexOptions.IgnoreCase)
|
|
60
|
+
|> Seq.cast<Match>
|
|
61
|
+
|> Seq.map (fun m -> m.Groups.["key"].Value, extractTextFromQuote m.Groups.["value"].Value)
|
|
62
|
+
|> Map.ofSeq
|
|
63
|
+
else
|
|
64
|
+
Map.empty
|
|
65
|
+
|
|
66
|
+
let rec private applyFormatter
|
|
67
|
+
(info:
|
|
68
|
+
{|
|
|
69
|
+
TagName: string
|
|
70
|
+
Formatter: TagInfo -> string option
|
|
71
|
+
|})
|
|
72
|
+
text
|
|
73
|
+
=
|
|
74
|
+
let pattern = tagPattern info.TagName
|
|
75
|
+
|
|
76
|
+
match Regex.Match(text, pattern, RegexOptions.IgnoreCase) with
|
|
77
|
+
| m when m.Success ->
|
|
78
|
+
if m.Groups.["void_element"].Success then
|
|
79
|
+
let attributes = getAttributes m.Groups.["void_attributes"]
|
|
80
|
+
|
|
81
|
+
match VoidElement attributes |> info.Formatter with
|
|
82
|
+
| Some replacement ->
|
|
83
|
+
text.Replace(m.Groups.["void_element"].Value, replacement)
|
|
84
|
+
|> applyFormatter info
|
|
85
|
+
| None -> text
|
|
86
|
+
|
|
87
|
+
elif m.Groups.["non_void_element"].Success then
|
|
88
|
+
let innerText = m.Groups.["non_void_innerText"].Value
|
|
89
|
+
let attributes = getAttributes m.Groups.["non_void_attributes"]
|
|
90
|
+
|
|
91
|
+
match NonVoidElement(innerText, attributes) |> info.Formatter with
|
|
92
|
+
| Some replacement ->
|
|
93
|
+
text.Replace(m.Groups.["non_void_element"].Value, replacement)
|
|
94
|
+
|> applyFormatter info
|
|
95
|
+
| None -> text
|
|
96
|
+
else
|
|
97
|
+
text
|
|
98
|
+
| _ -> text
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Individual tag formatters
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
let private codeBlock (config: FormatterConfig) =
|
|
105
|
+
applyFormatter
|
|
106
|
+
{|
|
|
107
|
+
TagName = "code"
|
|
108
|
+
Formatter =
|
|
109
|
+
function
|
|
110
|
+
| VoidElement _ -> None
|
|
111
|
+
| NonVoidElement(innerText, attributes) ->
|
|
112
|
+
let lang = attributes |> Map.tryFind "lang" |> Option.defaultValue "fsharp"
|
|
113
|
+
|
|
114
|
+
let innerText = innerText.TrimEnd()
|
|
115
|
+
|
|
116
|
+
let normalizedText =
|
|
117
|
+
match
|
|
118
|
+
innerText.StartsWith("\n", StringComparison.Ordinal),
|
|
119
|
+
innerText.EndsWith("\n", StringComparison.Ordinal)
|
|
120
|
+
with
|
|
121
|
+
| true, true -> $"%s{innerText}"
|
|
122
|
+
| true, false -> $"%s{innerText}\n"
|
|
123
|
+
| false, true -> $"\n%s{innerText}"
|
|
124
|
+
| false, false -> $"\n%s{innerText}\n"
|
|
125
|
+
|
|
126
|
+
config.CodeBlock lang normalizedText |> Some
|
|
127
|
+
|}
|
|
128
|
+
|
|
129
|
+
let private example (config: FormatterConfig) =
|
|
130
|
+
applyFormatter
|
|
131
|
+
{|
|
|
132
|
+
TagName = "example"
|
|
133
|
+
Formatter =
|
|
134
|
+
function
|
|
135
|
+
| VoidElement _ -> None
|
|
136
|
+
| NonVoidElement(innerText, _) -> config.Example innerText |> Some
|
|
137
|
+
|}
|
|
138
|
+
|
|
139
|
+
let private codeInline (config: FormatterConfig) =
|
|
140
|
+
applyFormatter
|
|
141
|
+
{|
|
|
142
|
+
TagName = "c"
|
|
143
|
+
Formatter =
|
|
144
|
+
function
|
|
145
|
+
| VoidElement _ -> None
|
|
146
|
+
| NonVoidElement(innerText, _) -> config.InlineCode innerText |> Some
|
|
147
|
+
|}
|
|
148
|
+
|
|
149
|
+
let private anchor (config: FormatterConfig) =
|
|
150
|
+
applyFormatter
|
|
151
|
+
{|
|
|
152
|
+
TagName = "a"
|
|
153
|
+
Formatter =
|
|
154
|
+
function
|
|
155
|
+
| VoidElement attributes ->
|
|
156
|
+
match Map.tryFind "href" attributes with
|
|
157
|
+
| Some href -> Some(config.ExternalLink href href)
|
|
158
|
+
| None -> None
|
|
159
|
+
| NonVoidElement(innerText, attributes) ->
|
|
160
|
+
match Map.tryFind "href" attributes with
|
|
161
|
+
| Some href -> Some(config.ExternalLink href innerText)
|
|
162
|
+
| None -> Some(config.InlineCode innerText)
|
|
163
|
+
|}
|
|
164
|
+
|
|
165
|
+
let private paragraph (config: FormatterConfig) =
|
|
166
|
+
applyFormatter
|
|
167
|
+
{|
|
|
168
|
+
TagName = "para"
|
|
169
|
+
Formatter =
|
|
170
|
+
function
|
|
171
|
+
| VoidElement _ -> None
|
|
172
|
+
| NonVoidElement(innerText, _) -> config.Paragraph innerText |> Some
|
|
173
|
+
|}
|
|
174
|
+
|
|
175
|
+
let private block (config: FormatterConfig) =
|
|
176
|
+
applyFormatter
|
|
177
|
+
{|
|
|
178
|
+
TagName = "block"
|
|
179
|
+
Formatter =
|
|
180
|
+
function
|
|
181
|
+
| VoidElement _ -> None
|
|
182
|
+
| NonVoidElement(innerText, _) -> config.Block innerText |> Some
|
|
183
|
+
|}
|
|
184
|
+
|
|
185
|
+
let private see (config: FormatterConfig) =
|
|
186
|
+
applyFormatter
|
|
187
|
+
{|
|
|
188
|
+
TagName = "see"
|
|
189
|
+
Formatter =
|
|
190
|
+
let fromAttrs (attrs: Map<string, string>) =
|
|
191
|
+
match Map.tryFind "cref" attrs with
|
|
192
|
+
| Some cref -> Some(config.MemberRef(extractMemberText cref))
|
|
193
|
+
| None ->
|
|
194
|
+
match Map.tryFind "langword" attrs with
|
|
195
|
+
| Some langword -> Some(config.LangwordRef langword)
|
|
196
|
+
| None -> None
|
|
197
|
+
|
|
198
|
+
function
|
|
199
|
+
| VoidElement attributes -> fromAttrs attributes
|
|
200
|
+
| NonVoidElement(innerText, attributes) ->
|
|
201
|
+
if String.IsNullOrWhiteSpace innerText then
|
|
202
|
+
fromAttrs attributes
|
|
203
|
+
else
|
|
204
|
+
match Map.tryFind "href" attributes with
|
|
205
|
+
| Some externalUrl -> Some(config.ExternalLink externalUrl innerText)
|
|
206
|
+
| None -> Some(config.InlineCode innerText)
|
|
207
|
+
|}
|
|
208
|
+
|
|
209
|
+
let private paramRef (config: FormatterConfig) =
|
|
210
|
+
applyFormatter
|
|
211
|
+
{|
|
|
212
|
+
TagName = "paramref"
|
|
213
|
+
Formatter =
|
|
214
|
+
function
|
|
215
|
+
| VoidElement attributes ->
|
|
216
|
+
match Map.tryFind "name" attributes with
|
|
217
|
+
| Some name -> Some(config.InlineCode name)
|
|
218
|
+
| None -> None
|
|
219
|
+
| NonVoidElement(innerText, attributes) ->
|
|
220
|
+
if String.IsNullOrWhiteSpace innerText then
|
|
221
|
+
match Map.tryFind "name" attributes with
|
|
222
|
+
| Some name -> Some(config.InlineCode name)
|
|
223
|
+
| None -> None
|
|
224
|
+
else
|
|
225
|
+
Some(config.InlineCode innerText)
|
|
226
|
+
|}
|
|
227
|
+
|
|
228
|
+
let private typeParamRef (config: FormatterConfig) =
|
|
229
|
+
applyFormatter
|
|
230
|
+
{|
|
|
231
|
+
TagName = "typeparamref"
|
|
232
|
+
Formatter =
|
|
233
|
+
function
|
|
234
|
+
| VoidElement attributes ->
|
|
235
|
+
match Map.tryFind "name" attributes with
|
|
236
|
+
| Some name -> Some(config.InlineCode name)
|
|
237
|
+
| None -> None
|
|
238
|
+
| NonVoidElement(innerText, attributes) ->
|
|
239
|
+
if String.IsNullOrWhiteSpace innerText then
|
|
240
|
+
match Map.tryFind "name" attributes with
|
|
241
|
+
| Some name -> Some(config.InlineCode name)
|
|
242
|
+
| None -> None
|
|
243
|
+
else
|
|
244
|
+
Some(config.InlineCode innerText)
|
|
245
|
+
|}
|
|
246
|
+
|
|
247
|
+
let private unescapeSpecialCharacters (text: string) =
|
|
248
|
+
text
|
|
249
|
+
.Replace("<", "<")
|
|
250
|
+
.Replace(">", ">")
|
|
251
|
+
.Replace(""", "\"")
|
|
252
|
+
.Replace("'", "'")
|
|
253
|
+
.Replace("&", "&")
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Configs and main entry points
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
let private markdownConfig =
|
|
260
|
+
{
|
|
261
|
+
BeforeTransform = id
|
|
262
|
+
AfterTransform = unescapeSpecialCharacters
|
|
263
|
+
Paragraph = fun content -> Environment.NewLine + content + Environment.NewLine
|
|
264
|
+
CodeBlock = fun lang code -> "```" + lang + code + "```"
|
|
265
|
+
InlineCode = fun code -> "`" + code + "`"
|
|
266
|
+
HandleMicrosoftOrList = id
|
|
267
|
+
Example =
|
|
268
|
+
fun content ->
|
|
269
|
+
Environment.NewLine
|
|
270
|
+
+ Environment.NewLine
|
|
271
|
+
+ "**Example:**"
|
|
272
|
+
+ Environment.NewLine
|
|
273
|
+
+ Environment.NewLine
|
|
274
|
+
+ content
|
|
275
|
+
Block = fun content -> Environment.NewLine + content + Environment.NewLine
|
|
276
|
+
MemberRef = fun name -> $"``{name}``"
|
|
277
|
+
LangwordRef = fun word -> $"`{word}`"
|
|
278
|
+
ExternalLink = fun href text -> $"[`{text}`]({href})"
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let private formatToMarkdown (text: string) =
|
|
282
|
+
text
|
|
283
|
+
|> markdownConfig.BeforeTransform
|
|
284
|
+
|> paragraph markdownConfig
|
|
285
|
+
|> example markdownConfig
|
|
286
|
+
|> block markdownConfig
|
|
287
|
+
|> codeInline markdownConfig
|
|
288
|
+
|> codeBlock markdownConfig
|
|
289
|
+
|> see markdownConfig
|
|
290
|
+
|> paramRef markdownConfig
|
|
291
|
+
|> typeParamRef markdownConfig
|
|
292
|
+
|> anchor markdownConfig
|
|
293
|
+
|> markdownConfig.HandleMicrosoftOrList
|
|
294
|
+
|> markdownConfig.AfterTransform
|
|
295
|
+
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// Public API
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
let private emptyXmlDoc =
|
|
301
|
+
{
|
|
302
|
+
Summary = None
|
|
303
|
+
Remarks = None
|
|
304
|
+
Returns = None
|
|
305
|
+
Params = []
|
|
306
|
+
Examples = []
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let private extractFirst (tagName: string) (text: string) : string option =
|
|
310
|
+
match Regex.Match(text, tagPattern tagName, RegexOptions.IgnoreCase) with
|
|
311
|
+
| m when m.Success && m.Groups.["non_void_element"].Success ->
|
|
312
|
+
m.Groups.["non_void_innerText"].Value
|
|
313
|
+
|> formatToMarkdown
|
|
314
|
+
|> fun s -> s.Trim()
|
|
315
|
+
|> Some
|
|
316
|
+
| _ -> None
|
|
317
|
+
|
|
318
|
+
let private extractAll (tagName: string) (text: string) : string list =
|
|
319
|
+
Regex.Matches(text, tagPattern tagName, RegexOptions.IgnoreCase)
|
|
320
|
+
|> Seq.cast<Match>
|
|
321
|
+
|> Seq.choose (fun m ->
|
|
322
|
+
if m.Groups.["non_void_element"].Success then
|
|
323
|
+
m.Groups.["non_void_innerText"].Value
|
|
324
|
+
|> formatToMarkdown
|
|
325
|
+
|> fun s -> s.Trim()
|
|
326
|
+
|> Some
|
|
327
|
+
else
|
|
328
|
+
None
|
|
329
|
+
)
|
|
330
|
+
|> Seq.toList
|
|
331
|
+
|
|
332
|
+
let private extractParams (text: string) : XmlDocParam list =
|
|
333
|
+
Regex.Matches(text, tagPattern "param", RegexOptions.IgnoreCase)
|
|
334
|
+
|> Seq.cast<Match>
|
|
335
|
+
|> Seq.choose (fun m ->
|
|
336
|
+
if m.Groups.["non_void_element"].Success then
|
|
337
|
+
let attrs = getAttributes m.Groups.["non_void_attributes"]
|
|
338
|
+
let inner = m.Groups.["non_void_innerText"].Value
|
|
339
|
+
|
|
340
|
+
match Map.tryFind "name" attrs with
|
|
341
|
+
| Some name ->
|
|
342
|
+
Some
|
|
343
|
+
{
|
|
344
|
+
Name = name
|
|
345
|
+
Doc = formatToMarkdown inner |> fun s -> s.Trim()
|
|
346
|
+
}
|
|
347
|
+
| None -> None
|
|
348
|
+
else
|
|
349
|
+
None
|
|
350
|
+
)
|
|
351
|
+
|> Seq.toList
|
|
352
|
+
|
|
353
|
+
/// Parse the inner XML content of a <member> element into the structured
|
|
354
|
+
/// XmlDoc IR type, with all inline tags converted to Markdown.
|
|
355
|
+
let private parseXmlContent (text: string) : XmlDoc =
|
|
356
|
+
if String.IsNullOrWhiteSpace text then
|
|
357
|
+
emptyXmlDoc
|
|
358
|
+
else
|
|
359
|
+
{
|
|
360
|
+
Summary = extractFirst "summary" text
|
|
361
|
+
Remarks = extractFirst "remarks" text
|
|
362
|
+
Returns = extractFirst "returns" text
|
|
363
|
+
Params = extractParams text
|
|
364
|
+
Examples = extractAll "example" text
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// XML documentation file loader
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
/// Escape `<` and `>` characters that appear inside XML attribute values.
|
|
372
|
+
/// The F# compiler sometimes generates invalid XML for anonymous record
|
|
373
|
+
/// type signatures (e.g. `name="M:Foo(<>f__AnonymousType...)`).
|
|
374
|
+
let private fixXmlAttributeChars (xml: string) : string =
|
|
375
|
+
let sb = System.Text.StringBuilder(xml.Length)
|
|
376
|
+
let mutable inTag = false
|
|
377
|
+
let mutable inQuotes = false
|
|
378
|
+
let mutable i = 0
|
|
379
|
+
|
|
380
|
+
while i < xml.Length do
|
|
381
|
+
let c = xml.[i]
|
|
382
|
+
|
|
383
|
+
if not inTag then
|
|
384
|
+
if c = '<' then
|
|
385
|
+
inTag <- true
|
|
386
|
+
|
|
387
|
+
sb.Append(c) |> ignore
|
|
388
|
+
elif inQuotes then
|
|
389
|
+
if c = '"' then
|
|
390
|
+
inQuotes <- false
|
|
391
|
+
sb.Append(c) |> ignore
|
|
392
|
+
elif c = '<' then
|
|
393
|
+
sb.Append("<") |> ignore
|
|
394
|
+
elif c = '>' then
|
|
395
|
+
sb.Append(">") |> ignore
|
|
396
|
+
else
|
|
397
|
+
sb.Append(c) |> ignore
|
|
398
|
+
else
|
|
399
|
+
if c = '"' then
|
|
400
|
+
inQuotes <- true
|
|
401
|
+
elif c = '>' then
|
|
402
|
+
inTag <- false
|
|
403
|
+
|
|
404
|
+
sb.Append(c) |> ignore
|
|
405
|
+
|
|
406
|
+
i <- i + 1
|
|
407
|
+
|
|
408
|
+
sb.ToString()
|
|
409
|
+
|
|
410
|
+
/// Load the XML documentation file that lives next to a compiled DLL and
|
|
411
|
+
/// return a lookup map from the standard XML-doc member ID (e.g.
|
|
412
|
+
/// "M:My.Namespace.func(System.Int32)") to the inner XML content of that
|
|
413
|
+
/// <member> element.
|
|
414
|
+
let loadXmlDocFile (dllPath: string) : Map<string, string> =
|
|
415
|
+
let xmlPath = System.IO.Path.ChangeExtension(dllPath, ".xml")
|
|
416
|
+
|
|
417
|
+
if not (System.IO.File.Exists xmlPath) then
|
|
418
|
+
Map.empty
|
|
419
|
+
else
|
|
420
|
+
let xmlText = System.IO.File.ReadAllText(xmlPath)
|
|
421
|
+
|
|
422
|
+
let doc =
|
|
423
|
+
try
|
|
424
|
+
System.Xml.Linq.XDocument.Parse(xmlText)
|
|
425
|
+
with
|
|
426
|
+
| :? System.Xml.XmlException as ex ->
|
|
427
|
+
eprintfn "ERROR: Failed to parse XML documentation file: %s" xmlPath
|
|
428
|
+
let lines = xmlText.Split('\n')
|
|
429
|
+
let lineIdx = ex.LineNumber - 1
|
|
430
|
+
let startLine = max 0 (lineIdx - 2)
|
|
431
|
+
let endLine = min (lines.Length - 1) (lineIdx + 2)
|
|
432
|
+
|
|
433
|
+
for i in startLine .. endLine do
|
|
434
|
+
let marker = if i = lineIdx then ">>> " else " "
|
|
435
|
+
eprintfn "%sLine %d: %s" marker (i + 1) lines.[i]
|
|
436
|
+
|
|
437
|
+
reraise()
|
|
438
|
+
|
|
439
|
+
doc.Descendants(System.Xml.Linq.XName.Get "member")
|
|
440
|
+
|> Seq.choose (fun m ->
|
|
441
|
+
let nameAttr = m.Attribute(System.Xml.Linq.XName.Get "name")
|
|
442
|
+
|
|
443
|
+
if nameAttr <> null then
|
|
444
|
+
let innerXml = m.Nodes() |> Seq.map (fun n -> n.ToString()) |> String.concat "\n"
|
|
445
|
+
|
|
446
|
+
Some(nameAttr.Value, innerXml)
|
|
447
|
+
else
|
|
448
|
+
None
|
|
449
|
+
)
|
|
450
|
+
|> Map.ofSeq
|
|
451
|
+
|
|
452
|
+
/// Look up and parse the XML doc for a given member signature.
|
|
453
|
+
/// Returns an empty XmlDoc when the signature is not found in the map.
|
|
454
|
+
let xmlDocOf (xmlDocMap: Map<string, string>) (xmlDocSig: string) : XmlDoc =
|
|
455
|
+
xmlDocMap
|
|
456
|
+
|> Map.tryFind xmlDocSig
|
|
457
|
+
|> Option.map parseXmlContent
|
|
458
|
+
|> Option.defaultValue emptyXmlDoc
|
|
459
|
+
|
|
460
|
+
/// Look up just the summary for a module-level signature.
|
|
461
|
+
/// Used for Module.XmlDoc which is stored as a plain string in the IR.
|
|
462
|
+
let moduleDocOf (xmlDocMap: Map<string, string>) (xmlDocSig: string) : string option =
|
|
463
|
+
xmlDocMap |> Map.tryFind xmlDocSig |> Option.bind (extractFirst "summary")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<OutputType>Library</OutputType>
|
|
5
|
+
<TargetFramework>net10.0</TargetFramework>
|
|
6
|
+
</PropertyGroup>
|
|
7
|
+
|
|
8
|
+
<ItemGroup>
|
|
9
|
+
<Compile Include="Schema.fs" />
|
|
10
|
+
</ItemGroup>
|
|
11
|
+
|
|
12
|
+
<ItemGroup>
|
|
13
|
+
<PackageReference Include="Thoth.Json.Core" />
|
|
14
|
+
</ItemGroup>
|
|
15
|
+
|
|
16
|
+
</Project>
|