Skip to content

Commit

Permalink
Merge pull request #846 from nojaf/previous-next-page
Browse files Browse the repository at this point in the history
Add previous and next page url substitutions
  • Loading branch information
nojaf authored Sep 15, 2023
2 parents 676a00f + 2b01ba2 commit 2c8e690
Show file tree
Hide file tree
Showing 14 changed files with 351 additions and 82 deletions.
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

0 comments on commit 2c8e690

Please sign in to comment.