Literate Scripts
The following example shows most of the features that can be used in a literate
F# script file with .fsx extension. Most of the features should be quite self-explanatory:
(**
# First-level heading
Some more documentation using `Markdown`.
*)
let helloWorld() = printfn "Hello world!"
(**
## Second-level heading
With some more documentation
*)
let numbers = [ 0 .. 99 ]
(*** include-value: numbers ***)
List.sum numbers
(*** include-it ***)
The F# script files are processed as follows:
-
A multi-line comment starting with
(**and ending with*)is turned into text and is processed using the F# Markdown processor (which supports standard Markdown commands). -
A single-line comment starting with
(***and ending with***)is treated as a special command. The command can consist ofkey,key: valueorkey=valuepairs.
Literate Command |
Description |
|---|---|
|
Markdown |
|
Utilise a code snippet when analyzing for tooltips or executing for outputs |
|
Include a code snippet when making a .ipynb notebook |
|
Include a code snippet when making a .tex output |
|
Include a code snippet when making HTML output |
|
Hide the subsequent snippet |
|
The subsequent code is treated as raw text |
Naming and including snippets
The command define defines a named snippet (such as final-sample) and removes the command together with
the following F# code block from the main document. The snippet can then
be referred to in 'include'. This makes it
possible to write documents without the ordering requirements of the
F# language.
Literate Command |
Description |
|---|---|
|
Define a named snippet |
|
Include the code of the named snippet |
Naming and including outputs
Literate Command |
Description |
|---|---|
|
Define a name for the outputs of the preceding snippet |
|
The console output of the preceding snippet |
|
The console output of the snippet (named with define-output) |
|
The F# Interactive output of the preceding snippet |
|
The F# Interactive output of the snippet (named with define-output) |
|
The merge of console output and F# Interactive output of the preceding snippet |
|
The merge of console output and F# Interactive output of the snippet (named with define-output) |
|
The formatted result of the preceding snippet |
|
The formatted result of the snippet (named with define-output) |
|
The unformatted result of the preceding snippet |
|
The unformatted result of the snippet (named with define-output) |
|
The formatted value, an F# identifier name |
Hiding code snippets
The command hide specifies that the following F# code block (until the next comment or command) should be
omitted from the output.
Evaluating and formatting results
The commands to evaluate and format results are explained in evaluation.
You must build your documentation with evaluation turned on using --eval.
Substitutions
Substitutions are applied to content, see content.
Literate Markdown Documents
For files with .md extension, the entire file is a Markdown document, which may
contain F# code snippets (but also other code snippets). As usual, snippets are
indented with four spaces. In addition, the snippets can be annotated with special
commands. Some of them are demonstrated in the following example:
# First-level heading
[hide]
let print s = printfn "%s" s
Some more documentation using `Markdown`.
[module=Hello]
let helloWorld() = print "Hello world!"
## Second-level heading
With some more documentation
[lang=csharp]
Console.WriteLine("Hello world!");
When processing the document, all F# snippets are copied to a separate file that
is type-checked using the F# compiler (to obtain colours and tooltips).
The commands are written on the first line of the named snippet, wrapped in [...]:
-
The
hidecommand specifies that the F# snippet should not be included in the final document. This can be used to include code that is needed to type-check the code, but is not visible to the reader.
-
The
module=Foocommand can be used to specify F#modulewhere the snippet is placed. Use this command if you need multiple versions of the same snippet or if you need to separate code from different snippets.
-
The
lang=foocommand specifies the language of the named snippet. If the language is other thanfsharp, the snippet is copied to the output as<pre>HTML tag without any processing.
LaTeX in Literate Scripts and Markdown Documents
Literate Scripts may contain LaTeX sections in Markdown using these forms:
Single line latex starting with
$$.A block delimited by
\begin{equation}...\end{equation}or\begin{align}...\end{align}.-
An indented paragraph starting with
$$$. This is F#-literate-specific and corresponds to\begin{equation}...\end{equation}.
For example
$$\frac{x}{y}$$
\begin{equation}
\frac{d}{dx} \left. \left( x \left( \left. \frac{d}{dy} x y \; \right|_{y=3} \right) \right) \right|_{x=2}
\end{equation}
Becomes
\[\frac{x}{y}\]
\[ \frac{d}{dx} \left. \left( x \left( \left. \frac{d}{dy} x y \; \right|_{y=3} \right) \right) \right|_{x=2}\]
The LaTeX will also be used in HTML and iPython notebook outputs.
Making literate scripts work for different outputs
Literate scripts and markdown can be turned into LaTex, Python Notebooks and F# scripts.
A header may be needed to get the code to load, a typical example is this:
(*** condition: prepare ***)
#nowarn "211"
#I "../src/FSharp.Formatting/bin/Release/netstandard2.1"
#r "FSharp.Formatting.Common.dll"
#r "FSharp.Formatting.Markdown.dll"
#r "FSharp.Formatting.CodeFormat.dll"
#r "FSharp.Formatting.Literate.dll"
(*** condition: fsx ***)
#if FSX
#r "nuget: FSharp.Formatting,1.0.0"
#endif // FSX
(*** condition: ipynb ***)
#if IPYNB
#r "nuget: FSharp.Formatting,1.0.0"
#endif // IPYNB
Processing literate files programmatically
To process files use the two static methods to turn single documents into HTML as follows using functionality from the Literate type:
open System.IO
open FSharp.Formatting.Literate
let source = __SOURCE_DIRECTORY__
let template = Path.Combine(source, "template.html")
let script = Path.Combine(source, "../docs/script.fsx")
Literate.ConvertScriptFile(script, template)
let doc = Path.Combine(source, "../docs/document.md")
Literate.ConvertMarkdownFile(doc, template)
The following sample also uses the optional parameter parameters to specify additional
keywords that will be replaced in the template file (this matches the template-project.html
file which is included as a sample in the package):
// Load the template & specify project information
let projTemplate = source + "template-project.html"
let projInfo =
[ "fsdocs-authors", "Tomas Petricek"
"fsdocs-source-link", "https://github.com/fsprojects/FSharp.Formatting"
"fsdocs-collection-name", "F# Formatting" ]
The methods used above (Literate.ConvertScriptFile, Literate.ConvertMarkdownFile) produce HTML output by default, but they can be also used to produce LaTeX output. This is done by setting the output kind. The following example shows how to call the methods to generate LaTeX documents:
let templateTex = Path.Combine(source, "template.tex")
let scriptTex = Path.Combine(source, "../docs/script.fsx")
Literate.ConvertScriptFile(scriptTex, templateTex, outputKind = OutputKind.Latex)
let docTex = Path.Combine(source, "../docs/document.md")
Literate.ConvertMarkdownFile(docTex, templateTex, outputKind = OutputKind.Latex)
The methods used above (ConvertScriptFile, ConvertMarkdownFile)
can also produce iPython Notebook output. This is done
by setting the named parameter format to OutputKind.Pynb:
// Process script file, Markdown document and a directory
let scriptPynb = Path.Combine(source, "../docs/script.fsx")
Literate.ConvertScriptFile(scriptPynb, outputKind = OutputKind.Pynb)
let docPynb = Path.Combine(source, "../docs/document.md")
Literate.ConvertMarkdownFile(docPynb, outputKind = OutputKind.Pynb)
All of the three methods discussed in the previous two sections take a number of optional parameters that can be used to tweak how the formatting works
Using Literate scripts as doc tests
Because Literate scripts execute embedded F# code via F# Interactive when the eval option
(or fsdocs build --eval / fsdocs watch --eval) is enabled, they double as a lightweight
form of "doc testing": the code samples in your documentation are run for real, and any
exceptions raised during evaluation cause the build to fail (or, with the (*** include-output ***)
directive, the actual output of the snippet is captured and included verbatim in the
generated page). This means documentation examples can't silently drift out of sync with
the API they describe — if a sample stops compiling or throws, you'll find out the next
time the docs are built.
This is not a full doc-test framework (there's no built-in support for asserting expected
output against actual output), but combined with (*** include-output ***) and
(*** include-value ***) it gives a useful low-effort way to keep documentation examples
honest.
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| op_Nil
| op_ColonColon of Head: 'T * Tail: 'T list
interface IReadOnlyList<'T>
interface IReadOnlyCollection<'T>
interface IEnumerable
interface IEnumerable<'T>
member GetReverseIndex: rank: int * offset: int -> int
member GetSlice: startIndex: int option * endIndex: int option -> 'T list
static member Cons: head: 'T * tail: 'T list -> 'T list
member Head: 'T
member IsEmpty: bool
member Item: index: int -> 'T with get
...
namespace FSharp
--------------------
namespace Microsoft.FSharp
type Path =
static member ChangeExtension: path: string * extension: string -> string
static member Combine: path1: string * path2: string -> string + 4 overloads
static member EndsInDirectorySeparator: path: ReadOnlySpan<char> -> bool + 1 overload
static member Exists: path: string -> bool
static member GetDirectoryName: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetExtension: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetFileName: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetFileNameWithoutExtension: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetFullPath: path: string -> string + 1 overload
static member GetInvalidFileNameChars: unit -> char array
...
Path.Combine( paths: string array) : string
Path.Combine(path1: string, path2: string) : string
Path.Combine(path1: string, path2: string, path3: string) : string
Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
The ConvertMarkdownFile and ConvertScriptFile methods process a single Markdown document
and F# script, respectively. The ConvertDirectory method handles an entire directory tree
(looking for *.fsx and *.md files).
Functionality to support literate programming for F# scripts
type Literate =
static member ConvertMarkdownFile:
input: string *
?template: string *
?output: string *
?outputKind: OutputKind *
?prefix: string *
?fscOptions: string *
?lineNumbers: bool *
?references: bool *
?substitutions: (ParamKey * string) list *
?generateAnchors: bool *
?imageSaver: (string -> string) *
?rootInputFolder: string *
?crefResolver: (string -> (string * string) option) *
?mdlinkResolver: (string -> string option) *
?onError: (string -> unit) *
?filesWithFrontMatter: FrontMatterFile array ->
unit
static member ConvertPynbFile:
input: string *
?template: string *
?output: string *
?outputKind: OutputKind *
?prefix: string *
?fscOptions: string *
?lineNumbers: bool *
?references: bool *
?substitutions: (ParamKey * string) list *
?generateAnchors: bool *
?imageSaver: (string -> string) *
?rootInputFolder: string *
?crefResolver: (string -> (string * string) option) *
?mdlinkResolver: (string -> string option) *
?onError: (string -> unit) *
?filesWithFrontMatter: FrontMatterFile array ->
unit
static member ConvertScriptFile:
input: string *
?template: string *
?output: string *
?outputKind: OutputKind *
?prefix: string *
?fscOptions: string *
?lineNumbers: bool *
?references: bool *
?fsiEvaluator: IFsiEvaluator *
?substitutions: (ParamKey * string) list *
?generateAnchors: bool *
?imageSaver: (string -> string) *
?rootInputFolder: string *
?crefResolver: (string -> (string * string) option) *
?mdlinkResolver: (string -> string option) *
?onError: (string -> unit) *
?filesWithFrontMatter: FrontMatterFile array ->
unit
static member ParseAndCheckScriptFile:
path: string *
?fscOptions: string *
?definedSymbols: string list *
?references: bool *
?fsiEvaluator: IFsiEvaluator *
?parseOptions: MarkdownParseOptions *
?rootInputFolder: string *
?onError: (string -> unit) ->
LiterateDocument
static member ParseMarkdownFile:
path: string *
?fscOptions: string *
?definedSymbols: string list *
?references: bool *
?fsiEvaluator: IFsiEvaluator *
?parseOptions: MarkdownParseOptions *
?rootInputFolder: string *
?onError: (string -> unit) ->
LiterateDocument
static member ParseMarkdownString:
content: string *
?path: string *
?fscOptions: string *
?definedSymbols: string list *
?references: bool *
?fsiEvaluator: IFsiEvaluator *
?parseOptions: MarkdownParseOptions *
?rootInputFolder: string *
?onError: (string -> unit) ->
LiterateDocument
static member ParsePynbString:
content: string *
?path: string *
?definedSymbols: string list *
?references: bool *
?parseOptions: MarkdownParseOptions *
?rootInputFolder: string *
?onError: (string -> unit) ->
LiterateDocument
static member ParseScriptString:
content: string *
?path: string *
?fscOptions: string *
?definedSymbols: string list *
?references: bool *
?fsiEvaluator: IFsiEvaluator *
?parseOptions: MarkdownParseOptions *
?rootInputFolder: string *
?onError: (string -> unit) ->
LiterateDocument
static member ToFsx:
doc: LiterateDocument *
?substitutions: (ParamKey * string) list *
?crefResolver: (string -> (string * string) option) *
?mdlinkResolver: (string -> string option) ->
string
static member ToHtml:
doc: LiterateDocument *
?prefix: string *
?lineNumbers: bool *
?generateAnchors: bool *
?substitutions: (ParamKey * string) list *
?crefResolver: (string -> (string * string) option) *
?mdlinkResolver: (string -> string option) *
?tokenKindToCss: (TokenKind -> string) ->
string
...
input: string *
?template: string *
?output: string *
?outputKind: OutputKind *
?prefix: string *
?fscOptions: string *
?lineNumbers: bool *
?references: bool *
?fsiEvaluator: Evaluation.IFsiEvaluator *
?substitutions: (FSharp.Formatting.Templating.ParamKey * string) list *
?generateAnchors: bool *
?imageSaver: (string -> string) *
?rootInputFolder: string *
?crefResolver: (string -> (string * string) option) *
?mdlinkResolver: (string -> string option) *
?onError: (string -> unit) *
?filesWithFrontMatter: FSharp.Formatting.Templating.FrontMatterFile array ->
unit
input: string *
?template: string *
?output: string *
?outputKind: OutputKind *
?prefix: string *
?fscOptions: string *
?lineNumbers: bool *
?references: bool *
?substitutions: (FSharp.Formatting.Templating.ParamKey * string) list *
?generateAnchors: bool *
?imageSaver: (string -> string) *
?rootInputFolder: string *
?crefResolver: (string -> (string * string) option) *
?mdlinkResolver: (string -> string option) *
?onError: (string -> unit) *
?filesWithFrontMatter: FSharp.Formatting.Templating.FrontMatterFile array ->
unit
type OutputKind =
| Html
| Latex
| Pynb
| Fsx
| Markdown
member Extension: string
union case OutputKind.Latex: OutputKind
union case OutputKind.Pynb: OutputKind
FSharp.Formatting