Skip to content

Commit

Permalink
Add {{fsdocs-meta-tags}} substitution.
Browse files Browse the repository at this point in the history
  • Loading branch information
nojaf committed Nov 30, 2023
1 parent 3014d1b commit ec9d162
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="{{root}}">
<meta name="twitter:title" content="{{fsdocs-page-title}}">
{{fsdocs-meta-tags}}
<title>{{fsdocs-page-title}} | {{fsdocs-collection-name}}</title>
<link href="https://fonts.googleapis.com" rel="preconnect">
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect">
Expand Down
9 changes: 8 additions & 1 deletion docs/content.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Any file or directory beginning with `.` is ignored.
## Front matter
Each content file can have optional frontmatter. This determines the navigation bar title, categorization and ordering.
Each content file can have optional frontmatter. This determines the navigation bar title, categorization ordering and meta tags.
For markdown, the format is:
```
Expand All @@ -79,6 +79,8 @@ title: Some Title
category: Some Category
categoryindex: 2
index: 3
description: Some description
keywords: tag1, tag2, tag3
---
```
For F# scripts the frontmatter is in this form:
Expand All @@ -89,13 +91,17 @@ For F# scripts the frontmatter is in this form:
category: Examples
categoryindex: 2
index: 1
description: Some description
keywords: tag1, tag2, tag3
---
*)

All entries are optional.
The `categoryindex` determines the ordering of categories.
The `index` determines the ordering of within each category.
The `title` is used in the navigation bar instead of any title inferred from the document.
The `description` is used in `<meta name="description"` as part of the `{{fsdocs-meta-tags}}` substitution.
The `keywords` are also used in a meta tag as part of `{{fsdocs-meta-tags}}`.

## Link Translation for Inputs

Expand Down Expand Up @@ -151,6 +157,7 @@ See [Styling](styling.html) for information about template parameters and stylin
| `fsdocs-head-extra` | Additional html content loaded from the `_head.html` file if present in the `--input` folder |
| `fsdocs-body-extra` | Additional html content loaded from the `_body.html` file if present in the `--input` folder |
| `fsdocs-body-class` | A css class value to help distinguish between `content` and `api-docs` |
| `fsdocs-meta-tags` | A set of additional HTML meta tags, present when description and/or keywords are present in the frontmatter |

The following substitutions are extracted from your project files and may or may not be used by the default
template:
Expand Down
5 changes: 5 additions & 0 deletions src/FSharp.Formatting.Common/Templating.fs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ module ParamKeys =
/// This helps to differentiate styles between API docs and custom content.
let ``fsdocs-body-class`` = ParamKey "fsdocs-body-class"

/// A parameter key known to FSharp.Formatting, it is HTML composed from additional frontmatter information.
/// Such as tags and description
/// This can be empty when both properties are not provided for the current page.
let ``fsdocs-meta-tags`` = ParamKey "fsdocs-meta-tags"

module internal SimpleTemplating =

#if NETSTANDARD2_0
Expand Down
18 changes: 18 additions & 0 deletions src/FSharp.Formatting.Literate/Formatting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ module internal Formatting =
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...
Expand Down Expand Up @@ -229,10 +231,26 @@ module internal Formatting =

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 ]
Expand Down
49 changes: 48 additions & 1 deletion tests/FSharp.Literate.Tests/DocContentTests.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module FSharp.Literate.Tests.DocContent

open System.IO
open FSharp.Formatting.Templating
open fsdocs
open NUnit.Framework
open FsUnitTyped
Expand Down Expand Up @@ -255,6 +256,52 @@ let ``Parses frontmatter correctly `` () =
twoTowersHtml |> shouldContainText "<a href=\"return.html\">Next</a>"
returnHtml |> shouldContainText "<a href=\"two-tower.html\">Previous</a>"

[<Test>]
let ``Parses description and keywords from frontmatter `` () =
let rootOutputFolderAsGiven = __SOURCE_DIRECTORY__ </> "output1"

let relativeInputFolderAsGiven =
Path.GetRelativePath(System.Environment.CurrentDirectory, __SOURCE_DIRECTORY__ </> "files")

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://github.com",
crefResolver = (fun _ -> None),
onError = failwith
)

let docModels = content.Convert(relativeInputFolderAsGiven, None, [])

let seoPageDocModel =
docModels
|> List.pick (fun (docInfo, _substitutions) ->
match docInfo with
| Some(_, _, docModel) when docModel.Title = "StringAnalyzer" -> Some(docModel)
| _ -> None)

let globals = []

for _thing, action in docModels do
action globals

let meta =
seoPageDocModel.Substitutions
|> List.find (fst >> ((=) ParamKeys.``fsdocs-meta-tags``))
|> snd

StringAssert.Contains("<meta name=\"description\"", meta)
StringAssert.Contains("Great description about StringAnalyzer!", meta)
StringAssert.Contains("fsharp, analyzers, tooling", meta)

(* Cannot get this test to evaluate the notebook
[<Test>]
Expand Down Expand Up @@ -283,7 +330,7 @@ let ``ipynb notebook evaluates`` () =
let globals = []
for (_thing, action) in docModels do
action globals
action globals
let ipynbOut = rootOutputFolderAsGiven </> "eval.html" |> File.ReadAllText
Expand Down
1 change: 1 addition & 0 deletions tests/FSharp.Literate.Tests/FSharp.Literate.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<None Include="files/simple2.md" />
<None Include="files/template.html" />
<None Include="files/docpage.html" />
<None Include="files\seo-page.md" />
<Compile Include="..\Common\MarkdownUnit.fs">
<Link>Common\MarkdownUnit.fs</Link>
</Compile>
Expand Down
34 changes: 34 additions & 0 deletions tests/FSharp.Literate.Tests/files/seo-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: StringAnalyzer
category: analyzers
categoryindex: 1
index: 3
description: Great description about StringAnalyzer!
keywords: fsharp, analyzers, tooling
---

# StringAnalyzer

There are multiple analyzers for various string comparison functions:

- String.EndsWith Analyzer
- String.StartsWith Analyzer
- String.IndexOf Analyzer

## Problem

The [recommendations for string usage](https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings#recommendations-for-string-usage) mention calling the overloads using `StringComparison` to increase performance.

```fsharp
"foo".EndsWith("p")
```

## Fix

Signal your intention explicitly by calling an overload.

```fsharp
open System
"foo".EndsWith("p", StringComparison.Ordinal)
```

0 comments on commit ec9d162

Please sign in to comment.