-
Notifications
You must be signed in to change notification settings - Fork 161
Expand file tree
/
Copy pathFormatting.fs
More file actions
310 lines (261 loc) · 12.6 KB
/
Formatting.fs
File metadata and controls
310 lines (261 loc) · 12.6 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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
namespace FSharp.Formatting.Literate
open System
open System.IO
open System.Text.RegularExpressions
open FSharp.Formatting.Literate
open FSharp.Formatting.CodeFormat
open FSharp.Formatting.Markdown
open FSharp.Formatting.Templating
/// Internal module that converts a <see cref="T:FSharp.Formatting.Literate.LiterateDocument"/> into a
/// formatted output string (HTML, Latex, Markdown, .fsx, or .ipynb) and computes the
/// substitution key–value pairs used by the templating engine to populate page templates.
module internal Formatting =
// Compiled once at module load; reused for every HTML page's search-index text extraction.
let private htmlTagRegex = Regex("<.*?>", RegexOptions.Compiled ||| RegexOptions.Singleline)
/// Format document with the specified output kind
let format (doc: MarkdownDocument) generateAnchors outputKind substitutions crefResolver mdlinkResolver =
match outputKind with
| OutputKind.Fsx ->
Markdown.ToFsx(
doc,
substitutions = substitutions,
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver
)
| OutputKind.Markdown ->
Markdown.ToMd(
doc,
substitutions = substitutions,
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver
)
| OutputKind.Pynb ->
Markdown.ToPynb(
doc,
substitutions = substitutions,
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver
)
| OutputKind.Latex ->
Markdown.ToLatex(
doc,
substitutions = substitutions,
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver
)
| OutputKind.Html ->
let sb = System.Text.StringBuilder()
use wr = new StringWriter(sb)
HtmlFormatting.formatAsHtml
wr
generateAnchors
true
doc.DefinedLinks
substitutions
System.Environment.NewLine
crefResolver
mdlinkResolver
doc.Paragraphs
sb.ToString()
/// Try find first-level heading in the paragraph collection
let findHeadings paragraphs generateAnchors (outputKind: OutputKind) =
paragraphs
|> Seq.tryPick (fun para ->
match para with
| Heading(1, text, r) ->
match outputKind with
| OutputKind.Html
| OutputKind.Latex ->
let doc = MarkdownDocument([ Span(text, r) ], dict [])
Some(format doc generateAnchors outputKind [] (fun _ -> None) (fun _ -> None))
| _ -> None
| _ -> None)
/// Given literate document, get a new MarkdownDocument that represents the
/// entire source code of the specified document (with possible fsx formatting)
let getSourceDocument (doc: LiterateDocument) =
match doc.Source with
| LiterateSource.Markdown text ->
doc.With(paragraphs = [ CodeBlock(text, None, None, "", "", MarkdownRange.zero) ])
| LiterateSource.Script snippets ->
let mutable count = 0
let paragraphs =
[ for Snippet(name, lines) in snippets do
if snippets.Length > 1 then
yield Heading(3, [ Literal(name, MarkdownRange.zero) ], MarkdownRange.zero)
let id =
count <- count + 1
"cell" + string<int> count
let opts =
{ Evaluate = true
ExecutionCount = None
OutputName = id
Visibility = LiterateCodeVisibility.VisibleCode }
let popts = { Condition = None }
yield EmbedParagraphs(LiterateCode(lines, opts, popts), MarkdownRange.zero) ]
doc.With(paragraphs = paragraphs)
/// Given literate document, get the text for indexing for search
let getIndexText (doc: LiterateDocument) =
match doc.Source with
| LiterateSource.Markdown text -> text
| LiterateSource.Script snippets ->
[ for Snippet(_name, lines) in snippets do
for Line(line, _) in lines do
yield line ]
|> String.concat "\n"
/// Formats a literate document and produces the full set of template substitutions for the output page.
/// Extracts YAML front-matter metadata (title, category, index, description, keywords), builds
/// previous/next navigation links from <paramref name="filesWithFrontMatter"/>, formats the document
/// body, and returns a <see cref="T:FSharp.Formatting.Literate.FormattedDocument"/> record ready for
/// template rendering.
let transformDocument
// This array was sorted in BuildCommand.fs
(filesWithFrontMatter: FrontMatterFile array)
(doc: LiterateDocument)
(outputPath: string)
ctx
=
let findInFrontMatter key =
match doc.Paragraphs with
| YamlFrontmatter(lines, _) :: _ ->
lines
|> List.tryPick (fun line ->
let line = line.Trim()
if line.StartsWith(key + ":", StringComparison.Ordinal) then
let line = line.[(key + ":").Length ..]
let line = line.Trim()
Some line
else
None)
| _ -> None
let mkValidIndex (value: string) =
match System.Int32.TryParse value with
| true, i -> Some i
| false, _ -> None
let category = findInFrontMatter "category"
let categoryIndex = findInFrontMatter "categoryindex" |> Option.bind mkValidIndex
let index = findInFrontMatter "index" |> Option.bind mkValidIndex
let titleFromFrontMatter = findInFrontMatter "title"
let description = findInFrontMatter "description"
let tags = findInFrontMatter "keywords"
// If we want to include the source code of the script, then process
// the entire source and generate replacement {source} => ...some html...
let sourceSubstitutions =
let relativeSourceFileName =
match doc.RootInputFolder with
| None -> Path.GetFileName(doc.SourceFile)
| Some rootInputFolder -> Path.GetRelativePath(rootInputFolder, doc.SourceFile)
let relativeSourceFileBaseName = Path.ChangeExtension(relativeSourceFileName, null)
let relativeSourceFileName = relativeSourceFileName.Replace(@"\", "/")
let relativeSourceFileBaseName = relativeSourceFileBaseName.Replace(@"\", "/")
let doc = getSourceDocument doc |> Transformations.replaceLiterateParagraphs ctx
let source =
format doc.MarkdownDocument ctx.GenerateHeaderAnchors ctx.OutputKind [] (fun _ -> None) (fun _ -> None)
[ ParamKeys.``fsdocs-source-filename``, relativeSourceFileName
ParamKeys.``fsdocs-source-basename``, relativeSourceFileBaseName
ParamKeys.``fsdocs-source``, source ]
// Get page title (either heading or file name)
let pageTitle =
match titleFromFrontMatter with
| Some text -> text
| _ ->
let name = Path.GetFileNameWithoutExtension(outputPath)
defaultArg (findHeadings doc.Paragraphs ctx.GenerateHeaderAnchors ctx.OutputKind) name
// Replace all special elements with ordinary Html/Latex Markdown
let doc = Transformations.replaceLiterateParagraphs ctx doc
// construct previous and next urls
let nextPreviousPageSubstitutions =
let getLinksFromCurrentPageIdx currentPageIdx =
match currentPageIdx with
| None -> []
| Some currentPageIdx ->
let previousPage =
filesWithFrontMatter
|> Array.tryItem (currentPageIdx - 1)
|> Option.bind (fun { FileName = fileName } ->
ctx.MarkdownDirectLinkResolver fileName
|> Option.map (fun link -> ParamKeys.``fsdocs-previous-page-link``, link))
|> Option.toList
let nextPage =
filesWithFrontMatter
|> Array.tryItem (currentPageIdx + 1)
|> Option.bind (fun { FileName = fileName } ->
ctx.MarkdownDirectLinkResolver fileName
|> Option.map (fun link -> ParamKeys.``fsdocs-next-page-link``, link))
|> Option.toList
previousPage @ nextPage
match index, categoryIndex with
| None, None
| None, Some _ ->
// Typical uses case here is the main index page.
// If there is no frontmatter there, we want to propose the first available page
filesWithFrontMatter
|> Array.tryHead
|> Option.bind (fun { FileName = fileName } ->
ctx.MarkdownDirectLinkResolver fileName
|> Option.map (fun link -> ParamKeys.``fsdocs-next-page-link``, link))
|> Option.toList
| Some currentPageIdx, None ->
let currentPageIdx =
filesWithFrontMatter
|> Array.tryFindIndex (fun { Index = idx } -> idx = currentPageIdx)
getLinksFromCurrentPageIdx currentPageIdx
| Some currentPageIdx, Some currentCategoryIdx ->
let currentPageIdx =
filesWithFrontMatter
|> Array.tryFindIndex (fun { Index = idx; CategoryIndex = cIdx } ->
cIdx = currentCategoryIdx && idx = currentPageIdx)
getLinksFromCurrentPageIdx currentPageIdx
let meta =
let mkDescription description =
$"""<meta name="description" content="%s{description}">
<meta name="twitter:site" content="%s{description}">
<meta name="og:description" content="%s{description}">"""
let mkKeywords keywords =
$"""<meta name="keywords" content="%s{keywords}">"""
match description, tags with
| Some description, Some tags -> String.Concat(mkDescription description, "\n", mkKeywords tags)
| Some description, None -> mkDescription description
| None, Some keywords -> mkKeywords keywords
| None, None -> String.Empty
let substitutions0 =
[ yield ParamKeys.``fsdocs-page-title``, pageTitle
yield ParamKeys.``fsdocs-page-source``, doc.SourceFile
yield ParamKeys.``fsdocs-body-class``, "content"
yield ParamKeys.``fsdocs-meta-tags``, meta
yield! ctx.Substitutions
yield! sourceSubstitutions
yield! nextPreviousPageSubstitutions ]
let formattedDocument =
format
doc.MarkdownDocument
ctx.GenerateHeaderAnchors
ctx.OutputKind
substitutions0
ctx.CodeReferenceResolver
ctx.MarkdownDirectLinkResolver
let headingTexts, pageHeaders = FSharp.Formatting.Common.PageContentList.mkPageContentMenu formattedDocument
let tipsHtml = doc.FormattedTips
// Construct new Markdown document and write it
let substitutions =
substitutions0
@ [ ParamKeys.``fsdocs-content``, formattedDocument
ParamKeys.``fsdocs-tooltips``, tipsHtml
ParamKeys.``fsdocs-page-content-list``, pageHeaders ]
let indexText =
(match ctx.OutputKind with
| OutputKind.Html ->
// Strip the html tags
let fullText = htmlTagRegex.Replace(formattedDocument, "")
Some(IndexText(fullText, headingTexts))
| _ -> None)
{ OutputPath = outputPath
OutputKind = ctx.OutputKind
Title = pageTitle
Category = category
CategoryIndex = categoryIndex
Index = index
IndexText = indexText
Substitutions = substitutions
// No don't know this until later.
// See DocContent.GetNavigationEntries
IsActive = false }