diff --git a/.gitignore b/.gitignore
index 937dec896..5e25d3253 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,3 +47,4 @@ tests/FSharp.Literate.Tests/output1/
.vscode/
.DS_Store
tests/FSharp.Literate.Tests/output2/
+tests/FSharp.Literate.Tests/previous-next-output/
\ No newline at end of file
diff --git a/docs/content.fsx b/docs/content.fsx
index 37e5ff6f0..1275e3a4c 100644
--- a/docs/content.fsx
+++ b/docs/content.fsx
@@ -146,6 +146,8 @@ See [Styling](styling.html) for information about template parameters and stylin
| `fsdocs-source-basename` | Name of original input source, excluding its extensions, relative to the `docs` root |
| `fsdocs-tooltips` | Generated hidden div elements for tooltips |
| `fsdocs-watch-script` | The websocket script used in watch mode to trigger hot reload |
+| `fsdocs-previous-page-link` | A relative link to the previous page based on the frontmatter index data |
+| `fsdocs-next-page-link` | A relative link to the next page based on the frontmatter index data |
The following substitutions are extracted from your project files and may or may not be used by the default
template:
diff --git a/src/FSharp.Formatting.Common/Templating.fs b/src/FSharp.Formatting.Common/Templating.fs
index 3e3d361b4..51745fa86 100644
--- a/src/FSharp.Formatting.Common/Templating.fs
+++ b/src/FSharp.Formatting.Common/Templating.fs
@@ -23,6 +23,48 @@ type ParamKey =
/// A list of parameters for substituting in templates, indexed by parameter keys
type Substitutions = (ParamKey * string) list
+/// Meta data from files that contains front matter
+/// Used to determine upfront which files have front matter so that previous and next substitutes can be discovered.
+type FrontMatterFile =
+ { FileName: string
+ Category: string
+ CategoryIndex: int
+ Index: int }
+
+ /// Parses the category, categoryindex and index from the frontmatter lines
+ static member ParseFromLines (fileName: string) (lines: string seq) =
+ let (|ValidIndex|_|) (value: string) =
+ match Int32.TryParse value with
+ | true, i -> Some i
+ | false, _ -> None
+
+ let keyValues =
+ lines
+ // Skip opening lines
+ |> Seq.skipWhile (fun line ->
+ let line = line.Trim()
+ line = "(**" || line = "---")
+ |> Seq.takeWhile (fun line ->
+ // Allow empty lines in frontmatter
+ let isBlankLine = String.IsNullOrWhiteSpace line
+ isBlankLine || line.Contains(":"))
+ |> Seq.filter (String.IsNullOrWhiteSpace >> not)
+ |> Seq.map (fun line ->
+ let parts = line.Split(":")
+ parts.[0].ToLowerInvariant(), parts.[1])
+ |> Map.ofSeq
+
+ match
+ Map.tryFind "category" keyValues, Map.tryFind "categoryindex" keyValues, Map.tryFind "index" keyValues
+ with
+ | Some category, Some(ValidIndex categoryindex), Some(ValidIndex index) ->
+ Some
+ { FileName = fileName
+ Category = category.Trim()
+ CategoryIndex = categoryindex
+ Index = index }
+ | _ -> None
+
///
/// Defines the parameter keys known to FSharp.Formatting processing code
///
@@ -134,6 +176,12 @@ module ParamKeys =
/// A parameter key known to FSharp.Formatting, available in _menu-item_template.html
let ``fsdocs-menu-item-id`` = ParamKey "fsdocs-menu-item-id"
+ /// A parameter key known to FSharp.Formatting, available when frontmatter is used correctly
+ let ``fsdocs-previous-page-link`` = ParamKey "fsdocs-previous-page-link"
+
+ /// A parameter key known to FSharp.Formatting, available when frontmatter is used correctly
+ let ``fsdocs-next-page-link`` = ParamKey "fsdocs-next-page-link"
+
module internal SimpleTemplating =
#if NETSTANDARD2_0
diff --git a/src/FSharp.Formatting.Literate/Contexts.fs b/src/FSharp.Formatting.Literate/Contexts.fs
index 519a64d9d..07a8a395f 100644
--- a/src/FSharp.Formatting.Literate/Contexts.fs
+++ b/src/FSharp.Formatting.Literate/Contexts.fs
@@ -63,10 +63,10 @@ type internal LiterateDocModel =
Category: string option
/// The category index in the front matter (determines the order of categories)
- CategoryIndex: string option
+ CategoryIndex: int option
/// The index in the front matter (Determines the order of files within a category)
- Index: string option
+ Index: int option
/// The relative output path
OutputPath: string
diff --git a/src/FSharp.Formatting.Literate/Formatting.fs b/src/FSharp.Formatting.Literate/Formatting.fs
index 95233b037..de0a70f3f 100644
--- a/src/FSharp.Formatting.Literate/Formatting.fs
+++ b/src/FSharp.Formatting.Literate/Formatting.fs
@@ -40,7 +40,7 @@ module internal Formatting =
mdlinkResolver = mdlinkResolver
)
| OutputKind.Html ->
- let sb = new System.Text.StringBuilder()
+ let sb = System.Text.StringBuilder()
use wr = new StringWriter(sb)
HtmlFormatting.formatAsHtml
@@ -105,11 +105,17 @@ module internal Formatting =
| LiterateSource.Markdown text -> text
| LiterateSource.Script snippets ->
[ for Snippet(_name, lines) in snippets do
- for (Line(line, _)) in lines do
+ for Line(line, _) in lines do
yield line ]
|> String.concat "\n"
- let transformDocument (doc: LiterateDocument) (outputPath: string) ctx =
+ let transformDocument
+ // This array was sorted in BuildCommand.fs
+ (filesWithFrontMatter: FrontMatterFile array)
+ (doc: LiterateDocument)
+ (outputPath: string)
+ ctx
+ =
let findInFrontMatter key =
match doc.Paragraphs with
@@ -126,10 +132,14 @@ module internal Formatting =
None)
| _ -> None
- let category = findInFrontMatter "category"
+ let mkValidIndex (value: string) =
+ match System.Int32.TryParse value with
+ | true, i -> Some i
+ | false, _ -> None
- let categoryIndex = findInFrontMatter "categoryindex"
- let index = findInFrontMatter "index"
+ let category = findInFrontMatter "category"
+ let categoryIndex = findInFrontMatter "categoryindex" |> Option.bind mkValidIndex
+ let index = findInFrontMatter "index" |> Option.bind mkValidIndex
let titleFromFrontMatter = findInFrontMatter "title"
// If we want to include the source code of the script, then process
@@ -167,10 +177,62 @@ module internal Formatting =
// 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 substitutions0 =
- [ ParamKeys.``fsdocs-page-title``, pageTitle; ParamKeys.``fsdocs-page-source``, doc.SourceFile ]
- @ ctx.Substitutions
- @ sourceSubstitutions
+ [ yield ParamKeys.``fsdocs-page-title``, pageTitle
+ yield ParamKeys.``fsdocs-page-source``, doc.SourceFile
+ yield! ctx.Substitutions
+ yield! sourceSubstitutions
+ yield! nextPreviousPageSubstitutions ]
let formattedDocument =
format
diff --git a/src/FSharp.Formatting.Literate/Literate.fs b/src/FSharp.Formatting.Literate/Literate.fs
index 33d008a4c..380a5842b 100644
--- a/src/FSharp.Formatting.Literate/Literate.fs
+++ b/src/FSharp.Formatting.Literate/Literate.fs
@@ -5,7 +5,6 @@ open System.Collections.Generic
open System.IO
open System.Runtime.CompilerServices
open FSharp.Formatting.Markdown
-open FSharp.Formatting.CodeFormat
open FSharp.Formatting.Templating
///
@@ -260,7 +259,7 @@ type Literate private () =
let doc =
MarkdownDocument(doc.Paragraphs @ [ InlineHtmlBlock(doc.FormattedTips, None, None) ], doc.DefinedLinks)
- let sb = new System.Text.StringBuilder()
+ let sb = System.Text.StringBuilder()
use wr = new StringWriter(sb)
HtmlFormatting.formatAsHtml
@@ -424,7 +423,8 @@ type Literate private () =
crefResolver,
mdlinkResolver,
parseOptions,
- onError
+ onError,
+ filesWithFrontMatter: FrontMatterFile array
) =
let parseOptions =
@@ -457,7 +457,7 @@ type Literate private () =
None
let doc = downloadImagesForDoc imageSaver doc
- let docModel = Formatting.transformDocument doc output ctx
+ let docModel = Formatting.transformDocument filesWithFrontMatter doc output ctx
docModel
/// Parse and transform an F# script file
@@ -477,7 +477,8 @@ type Literate private () =
rootInputFolder,
crefResolver,
mdlinkResolver,
- onError
+ onError,
+ filesWithFrontMatter: FrontMatterFile array
) =
let parseOptions =
@@ -509,7 +510,7 @@ type Literate private () =
None
let doc = downloadImagesForDoc imageSaver doc
- let docModel = Formatting.transformDocument doc output ctx
+ let docModel = Formatting.transformDocument filesWithFrontMatter doc output ctx
docModel
/// Convert a markdown file into HTML or another output kind
@@ -529,7 +530,8 @@ type Literate private () =
?rootInputFolder,
?crefResolver,
?mdlinkResolver,
- ?onError
+ ?onError,
+ ?filesWithFrontMatter
) =
let outputKind = defaultArg outputKind OutputKind.Html
@@ -537,6 +539,7 @@ type Literate private () =
let crefResolver = defaultArg crefResolver (fun _ -> None)
let mdlinkResolver = defaultArg mdlinkResolver (fun _ -> None)
let substitutions = defaultArg substitutions []
+ let filesWithFrontMatter = defaultArg filesWithFrontMatter Array.empty
let res =
Literate.ParseAndTransformMarkdownFile(
@@ -554,7 +557,8 @@ type Literate private () =
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver,
parseOptions = MarkdownParseOptions.AllowYamlFrontMatter,
- onError = onError
+ onError = onError,
+ filesWithFrontMatter = filesWithFrontMatter
)
SimpleTemplating.UseFileAsSimpleTemplate(res.Substitutions, template, output)
@@ -582,7 +586,8 @@ type Literate private () =
?rootInputFolder,
?crefResolver,
?mdlinkResolver,
- ?onError
+ ?onError,
+ ?filesWithFrontMatter
) =
let outputKind = defaultArg outputKind OutputKind.Html
@@ -590,6 +595,7 @@ type Literate private () =
let crefResolver = defaultArg crefResolver (fun _ -> None)
let mdlinkResolver = defaultArg mdlinkResolver (fun _ -> None)
let substitutions = defaultArg substitutions []
+ let filesWithFrontMatter = defaultArg filesWithFrontMatter Array.empty
let res =
Literate.ParseAndTransformScriptFile(
@@ -607,7 +613,8 @@ type Literate private () =
rootInputFolder = rootInputFolder,
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver,
- onError = onError
+ onError = onError,
+ filesWithFrontMatter = filesWithFrontMatter
)
SimpleTemplating.UseFileAsSimpleTemplate(res.Substitutions, template, output)
diff --git a/src/FSharp.Formatting.Literate/ParseScript.fs b/src/FSharp.Formatting.Literate/ParseScript.fs
index f022663bf..bbadafb46 100644
--- a/src/FSharp.Formatting.Literate/ParseScript.fs
+++ b/src/FSharp.Formatting.Literate/ParseScript.fs
@@ -1,6 +1,12 @@
namespace FSharp.Formatting.Literate
open System.Collections.Generic
+open System.Text
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.SyntaxTrivia
+open FSharp.Compiler.Text
+open FSharp.Formatting.Templating
open FSharp.Patterns
open FSharp.Formatting.CodeFormat
open FSharp.Formatting.Markdown
@@ -389,3 +395,50 @@ type internal ParseScript(parseOptions, ctx: CompilerContext) =
diagnostics = diagnostics,
rootInputFolder = rootInputFolder
)
+
+ /// Tries and parse the file to find and process the first block comment.
+ static member ParseFrontMatter(fileName: string) : FrontMatterFile option =
+ try
+ let sourceText = (System.IO.File.ReadAllText >> SourceText.ofString) fileName
+ let checker = FSharp.Formatting.Internal.CompilerServiceExtensions.FSharpAssemblyHelper.checker
+
+ let parseResult =
+ checker.ParseFile(
+ fileName,
+ sourceText,
+ { FSharpParsingOptions.Default with
+ SourceFiles = [| fileName |] }
+ )
+ |> Async.RunSynchronously
+
+ match parseResult.ParseTree with
+ | ParsedInput.SigFile _ -> None
+ | ParsedInput.ImplFile(ParsedImplFileInput.ParsedImplFileInput(trivia = { CodeComments = codeComments })) ->
+ codeComments
+ |> List.tryPick (function
+ | CommentTrivia.BlockComment mBlockComment -> Some mBlockComment
+ | CommentTrivia.LineComment _ -> None)
+ |> Option.bind (fun mBlockComment ->
+ // Grab the comment text from the ISourceText
+ let commentText =
+ let startLine = mBlockComment.StartLine - 1
+ let line = sourceText.GetLineString startLine
+
+ if mBlockComment.StartLine = mBlockComment.EndLine then
+ let length = mBlockComment.EndColumn - mBlockComment.StartColumn
+ line.Substring(mBlockComment.StartColumn, length)
+ else
+ let firstLineContent = line.Substring(mBlockComment.StartColumn)
+ let sb = StringBuilder().AppendLine(firstLineContent)
+
+ (sb, [ mBlockComment.StartLine .. mBlockComment.EndLine - 2 ])
+ ||> List.fold (fun sb lineNumber -> sb.AppendLine(sourceText.GetLineString lineNumber))
+ |> fun sb ->
+ let lastLine = sourceText.GetLineString(mBlockComment.EndLine - 1)
+ sb.Append(lastLine.Substring(0, mBlockComment.EndColumn)).ToString()
+
+ let lines = commentText.Split '\n'
+ FrontMatterFile.ParseFromLines fileName lines)
+ with ex ->
+ printfn "Failed to find frontmatter in %s, %A" fileName ex
+ None
diff --git a/src/fsdocs-tool/BuildCommand.fs b/src/fsdocs-tool/BuildCommand.fs
index 44df7b7ff..8ceeb73c8 100644
--- a/src/fsdocs-tool/BuildCommand.fs
+++ b/src/fsdocs-tool/BuildCommand.fs
@@ -173,6 +173,7 @@ type internal DocContent
outputFolderRelativeToRoot
imageSaver
mdlinkResolver
+ (filesWithFrontMatter: FrontMatterFile array)
=
[ let name = Path.GetFileName(inputFileFullPath)
@@ -276,7 +277,8 @@ type internal DocContent
rootInputFolder = rootInputFolder,
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver,
- onError = Some onError
+ onError = Some onError,
+ filesWithFrontMatter = filesWithFrontMatter
)
yield
@@ -313,7 +315,8 @@ type internal DocContent
crefResolver = crefResolver,
mdlinkResolver = mdlinkResolver,
parseOptions = MarkdownParseOptions.AllowYamlFrontMatter,
- onError = Some onError
+ onError = Some onError,
+ filesWithFrontMatter = filesWithFrontMatter
)
yield
@@ -353,6 +356,7 @@ type internal DocContent
(htmlTemplate, texTemplate, pynbTemplate, fsxTemplate, mdTemplate, isOtherLang, rootInputFolder, fullPathFileMap)
(inputFolderAsGiven: string)
outputFolderRelativeToRoot
+ (filesWithFrontMatter: FrontMatterFile array)
=
[
// Look for the presence of the _template.* files to activate the
@@ -426,6 +430,7 @@ type internal DocContent
fullPathFileMap,
OutputKind.Html
))
+ filesWithFrontMatter
yield!
processFile
@@ -442,6 +447,7 @@ type internal DocContent
fullPathFileMap,
OutputKind.Latex
))
+ filesWithFrontMatter
yield!
processFile
@@ -458,6 +464,7 @@ type internal DocContent
fullPathFileMap,
OutputKind.Pynb
))
+ filesWithFrontMatter
yield!
processFile
@@ -474,6 +481,7 @@ type internal DocContent
fullPathFileMap,
OutputKind.Fsx
))
+ filesWithFrontMatter
yield!
processFile
@@ -490,6 +498,7 @@ type internal DocContent
fullPathFileMap,
OutputKind.Markdown
))
+ filesWithFrontMatter
for subInputFolderFullPath in Directory.EnumerateDirectories(inputFolderAsGiven) do
let subInputFolderName = Path.GetFileName(subInputFolderFullPath)
@@ -511,7 +520,8 @@ type internal DocContent
rootInputFolder,
fullPathFileMap)
(Path.Combine(inputFolderAsGiven, subInputFolderName))
- (Path.Combine(outputFolderRelativeToRoot, subInputFolderName)) ]
+ (Path.Combine(outputFolderRelativeToRoot, subInputFolderName))
+ filesWithFrontMatter ]
member _.Convert(rootInputFolderAsGiven, htmlTemplate, extraInputs) =
@@ -523,12 +533,32 @@ type internal DocContent
yield! prepFolder rootInputFolderAsGiven outputFolderRelativeToRoot ]
|> Map.ofList
+ // In order to create {{next-page-url}} and {{previous-page-url}}
+ // We need to scan all *.fsx and *.md files for their frontmatter.
+ let filesWithFrontMatter =
+ fullPathFileMap
+ |> Map.keys
+ |> Seq.map fst
+ |> Seq.distinct
+ |> Seq.choose (fun fileName ->
+ let ext = Path.GetExtension fileName
+
+ if ext = ".fsx" then
+ ParseScript.ParseFrontMatter(fileName)
+ elif ext = ".md" then
+ File.ReadLines fileName |> FrontMatterFile.ParseFromLines fileName
+ else
+ None)
+ |> Seq.sortBy (fun { Index = idx; CategoryIndex = cIdx } -> cIdx, idx)
+ |> Seq.toArray
+
[ for (rootInputFolderAsGiven, outputFolderRelativeToRoot) in inputDirectories do
yield!
processFolder
(htmlTemplate, None, None, None, None, false, Some rootInputFolderAsGiven, fullPathFileMap)
rootInputFolderAsGiven
- outputFolderRelativeToRoot ]
+ outputFolderRelativeToRoot
+ filesWithFrontMatter ]
member _.GetSearchIndexEntries(docModels: (string * bool * LiterateDocModel) list) =
[| for (_inputFile, isOtherLang, model) in docModels do
@@ -564,7 +594,7 @@ type internal DocContent
Int32.MaxValue)
| None -> Int32.MaxValue)
- let orderList list =
+ let orderList (list: LiterateDocModel list) =
list
|> List.sortBy (fun model ->
match model.Index with
diff --git a/src/fsdocs-tool/packages.lock.json b/src/fsdocs-tool/packages.lock.json
index 24ec0c9b8..ceea008e7 100644
--- a/src/fsdocs-tool/packages.lock.json
+++ b/src/fsdocs-tool/packages.lock.json
@@ -42,6 +42,32 @@
"FSharp.Core": "0.0.0"
}
},
+ "Microsoft.Build": {
+ "type": "Transitive",
+ "resolved": "17.2.0",
+ "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==",
+ "dependencies": {
+ "Microsoft.Build.Framework": "17.2.0",
+ "Microsoft.NET.StringTools": "1.0.0",
+ "Microsoft.Win32.Registry": "4.3.0",
+ "System.Collections.Immutable": "5.0.0",
+ "System.Configuration.ConfigurationManager": "4.7.0",
+ "System.Reflection.Metadata": "1.6.0",
+ "System.Security.Principal.Windows": "4.7.0",
+ "System.Text.Encoding.CodePages": "4.0.1",
+ "System.Text.Json": "6.0.0",
+ "System.Threading.Tasks.Dataflow": "6.0.0"
+ }
+ },
+ "Microsoft.Build.Framework": {
+ "type": "Transitive",
+ "resolved": "17.2.0",
+ "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "4.3.0",
+ "System.Security.Permissions": "4.7.0"
+ }
+ },
"Microsoft.NET.StringTools": {
"type": "Transitive",
"resolved": "1.0.0",
@@ -419,34 +445,6 @@
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
- "Microsoft.Build": {
- "type": "CentralTransitive",
- "requested": "(, )",
- "resolved": "17.2.0",
- "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==",
- "dependencies": {
- "Microsoft.Build.Framework": "17.2.0",
- "Microsoft.NET.StringTools": "1.0.0",
- "Microsoft.Win32.Registry": "4.3.0",
- "System.Collections.Immutable": "5.0.0",
- "System.Configuration.ConfigurationManager": "4.7.0",
- "System.Reflection.Metadata": "1.6.0",
- "System.Security.Principal.Windows": "4.7.0",
- "System.Text.Encoding.CodePages": "4.0.1",
- "System.Text.Json": "6.0.0",
- "System.Threading.Tasks.Dataflow": "6.0.0"
- }
- },
- "Microsoft.Build.Framework": {
- "type": "CentralTransitive",
- "requested": "(, )",
- "resolved": "17.2.0",
- "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==",
- "dependencies": {
- "Microsoft.Win32.Registry": "4.3.0",
- "System.Security.Permissions": "4.7.0"
- }
- },
"System.Memory": {
"type": "CentralTransitive",
"requested": "[4.5.5, )",
diff --git a/tests/FSharp.Literate.Tests/DocContentTests.fs b/tests/FSharp.Literate.Tests/DocContentTests.fs
index 9230c6231..94a2f90fc 100644
--- a/tests/FSharp.Literate.Tests/DocContentTests.fs
+++ b/tests/FSharp.Literate.Tests/DocContentTests.fs
@@ -186,3 +186,42 @@ let ``Can build doc content using relative input path`` () =
let f2md2 = File.ReadAllText(rootOutputFolderAsGiven > "folder2" > "in-folder2.md")
f1md1 |> shouldContainText """../folder2/in-folder2.md"""
f2md2 |> shouldContainText """../folder1/in-folder1.md"""
+
+[]
+let ``Parses frontmatter correctly `` () =
+ let rootOutputFolderAsGiven = __SOURCE_DIRECTORY__ > "previous-next-output"
+
+ let relativeInputFolderAsGiven =
+ Path.GetRelativePath(System.Environment.CurrentDirectory, __SOURCE_DIRECTORY__ > "previous-next")
+
+ if Directory.Exists(rootOutputFolderAsGiven) then
+ Directory.Delete(rootOutputFolderAsGiven, true)
+
+ let content =
+ DocContent(
+ rootOutputFolderAsGiven,
+ Map.empty,
+ lineNumbers = None,
+ evaluate = false,
+ substitutions = [],
+ saveImages = None,
+ watch = false,
+ root = "https://en.wikipedia.org",
+ crefResolver = (fun _ -> None),
+ onError = failwith
+ )
+
+ let docModels = content.Convert(relativeInputFolderAsGiven, None, [])
+ let globals = []
+
+ for _thing, action in docModels do
+ action globals
+
+ let fellowshipHtml = rootOutputFolderAsGiven > "fellowship.html" |> File.ReadAllText
+ let twoTowersHtml = rootOutputFolderAsGiven > "two-tower.html" |> File.ReadAllText
+ let returnHtml = rootOutputFolderAsGiven > "return.html" |> File.ReadAllText
+
+ fellowshipHtml |> shouldContainText "Next"
+ twoTowersHtml |> shouldContainText "Previous"
+ twoTowersHtml |> shouldContainText "Next"
+ returnHtml |> shouldContainText "Previous"
diff --git a/tests/FSharp.Literate.Tests/packages.lock.json b/tests/FSharp.Literate.Tests/packages.lock.json
index e238f5064..805716cb4 100644
--- a/tests/FSharp.Literate.Tests/packages.lock.json
+++ b/tests/FSharp.Literate.Tests/packages.lock.json
@@ -59,6 +59,32 @@
"resolved": "4.5.0",
"contentHash": "s8JpqTe9bI2f49Pfr3dFRfoVSuFQyraTj68c3XXjIS/MRGvvkLnrg6RLqnTjdShX+AdFUCCU/4Xex58AdUfs6A=="
},
+ "Microsoft.Build": {
+ "type": "Transitive",
+ "resolved": "17.2.0",
+ "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==",
+ "dependencies": {
+ "Microsoft.Build.Framework": "17.2.0",
+ "Microsoft.NET.StringTools": "1.0.0",
+ "Microsoft.Win32.Registry": "4.3.0",
+ "System.Collections.Immutable": "5.0.0",
+ "System.Configuration.ConfigurationManager": "4.7.0",
+ "System.Reflection.Metadata": "1.6.0",
+ "System.Security.Principal.Windows": "4.7.0",
+ "System.Text.Encoding.CodePages": "4.0.1",
+ "System.Text.Json": "6.0.0",
+ "System.Threading.Tasks.Dataflow": "6.0.0"
+ }
+ },
+ "Microsoft.Build.Framework": {
+ "type": "Transitive",
+ "resolved": "17.2.0",
+ "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "4.3.0",
+ "System.Security.Permissions": "4.7.0"
+ }
+ },
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "17.7.2",
@@ -498,34 +524,6 @@
"resolved": "0.62.0",
"contentHash": "gKFts9WmiK4x7FCBPMesLXMkVUXTs9tL3VRY4nHNhabIa2pi7+POeXTQJ/NjjE4+ksJ8ygaUkgkPEjZ8gaoE7g=="
},
- "Microsoft.Build": {
- "type": "CentralTransitive",
- "requested": "(, )",
- "resolved": "17.2.0",
- "contentHash": "Rpf+7b8WHcgkrEQnGcV8FiAIl15ZC7Oit4t213fMoRdJb8SUYKdB71ZzWFaV5f9glN3y+R4cxRDY0dkwaIpwYw==",
- "dependencies": {
- "Microsoft.Build.Framework": "17.2.0",
- "Microsoft.NET.StringTools": "1.0.0",
- "Microsoft.Win32.Registry": "4.3.0",
- "System.Collections.Immutable": "5.0.0",
- "System.Configuration.ConfigurationManager": "4.7.0",
- "System.Reflection.Metadata": "1.6.0",
- "System.Security.Principal.Windows": "4.7.0",
- "System.Text.Encoding.CodePages": "4.0.1",
- "System.Text.Json": "6.0.0",
- "System.Threading.Tasks.Dataflow": "6.0.0"
- }
- },
- "Microsoft.Build.Framework": {
- "type": "CentralTransitive",
- "requested": "(, )",
- "resolved": "17.2.0",
- "contentHash": "5MtMF6vZeK8Nq6r9GctGpfiBa+r5+pTCnZJp8Bi6C74TysWl5ri6vmuLbsYhzvXqTPbnvDxCpKAI90z3m4m5mw==",
- "dependencies": {
- "Microsoft.Win32.Registry": "4.3.0",
- "System.Security.Permissions": "4.7.0"
- }
- },
"Newtonsoft.Json": {
"type": "CentralTransitive",
"requested": "[13.0.3, )",
diff --git a/tests/FSharp.Literate.Tests/previous-next/fellowship.md b/tests/FSharp.Literate.Tests/previous-next/fellowship.md
new file mode 100644
index 000000000..bfdcba378
--- /dev/null
+++ b/tests/FSharp.Literate.Tests/previous-next/fellowship.md
@@ -0,0 +1,9 @@
+---
+index: 1
+categoryindex: 1
+category: fiction
+---
+
+# The Fellowship of the ring
+
+[Next]({{fsdocs-next-page-link}})
\ No newline at end of file
diff --git a/tests/FSharp.Literate.Tests/previous-next/return.md b/tests/FSharp.Literate.Tests/previous-next/return.md
new file mode 100644
index 000000000..1a8838cd2
--- /dev/null
+++ b/tests/FSharp.Literate.Tests/previous-next/return.md
@@ -0,0 +1,9 @@
+---
+categoryindex: 1
+category: fiction
+index: 3
+---
+
+# The Return of the King
+
+[Previous]({{fsdocs-previous-page-link}})
\ No newline at end of file
diff --git a/tests/FSharp.Literate.Tests/previous-next/two-tower.fsx b/tests/FSharp.Literate.Tests/previous-next/two-tower.fsx
new file mode 100644
index 000000000..75fdef443
--- /dev/null
+++ b/tests/FSharp.Literate.Tests/previous-next/two-tower.fsx
@@ -0,0 +1,13 @@
+(**
+---
+category: fiction
+index: 2
+
+categoryindex: 1
+---
+
+# The Two Towers
+
+[Previous]({{fsdocs-previous-page-link}})
+[Next]({{fsdocs-next-page-link}})
+*)
\ No newline at end of file