Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add previous and next page url substitutions #846

Merged
merged 3 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ tests/FSharp.Literate.Tests/output1/
.vscode/
.DS_Store
tests/FSharp.Literate.Tests/output2/
tests/FSharp.Literate.Tests/previous-next-output/
2 changes: 2 additions & 0 deletions docs/content.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
48 changes: 48 additions & 0 deletions src/FSharp.Formatting.Common/Templating.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

/// <summary>
/// Defines the parameter keys known to FSharp.Formatting processing code
/// </summary>
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/FSharp.Formatting.Literate/Contexts.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 71 additions & 9 deletions src/FSharp.Formatting.Literate/Formatting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
27 changes: 17 additions & 10 deletions src/FSharp.Formatting.Literate/Literate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

/// <summary>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -424,7 +423,8 @@ type Literate private () =
crefResolver,
mdlinkResolver,
parseOptions,
onError
onError,
filesWithFrontMatter: FrontMatterFile array
) =

let parseOptions =
Expand Down Expand Up @@ -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
Expand All @@ -477,7 +477,8 @@ type Literate private () =
rootInputFolder,
crefResolver,
mdlinkResolver,
onError
onError,
filesWithFrontMatter: FrontMatterFile array
) =

let parseOptions =
Expand Down Expand Up @@ -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
Expand All @@ -529,14 +530,16 @@ type Literate private () =
?rootInputFolder,
?crefResolver,
?mdlinkResolver,
?onError
?onError,
?filesWithFrontMatter
) =

let outputKind = defaultArg outputKind OutputKind.Html
let output = defaultOutput output input outputKind
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(
Expand All @@ -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)
Expand Down Expand Up @@ -582,14 +586,16 @@ type Literate private () =
?rootInputFolder,
?crefResolver,
?mdlinkResolver,
?onError
?onError,
?filesWithFrontMatter
) =

let outputKind = defaultArg outputKind OutputKind.Html
let output = defaultOutput output input outputKind
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(
Expand All @@ -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)
Expand Down
53 changes: 53 additions & 0 deletions src/FSharp.Formatting.Literate/ParseScript.fs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Loading
Loading