Logo FSharp.Formatting

Script

Embedding script output

For literate F# scripts, you may embed the result of running the script as part of the literate output. This is a feature of the functions discussed in literate programming and it is implemented using the F# Compiler service.

Including Console Output

To include the Console output use include-output:

let test = 40 + 2

printf "A result is: %d" test
(*** include-output ***)

The script defines a variable test and then prints it. The console output is included in the output.

To include the a formatted value use include-it:

[ 0 .. 99 ]

(*** include-it ***)

To include the meta output of F# Interactive processing such as type signatures use (*** include-fsi-output ***):

let test = 40 + 3

(*** include-fsi-output ***)

To include both console output and F# Interactive output blended use (*** include-fsi-merged-output ***).

let test = 40 + 4
(*** include-fsi-merged-output ***)

You can use the same commands with a named snippet:

(*** include-it: test ***)
(*** include-fsi-output: test ***)
(*** include-output: test ***)

You can use the include-value command to format a specific value:

let value1 = [ 0 .. 50 ]
let value2 = [ 51 .. 100 ]
(*** include-value: value1 ***)

Using AddPrinter and AddHtmlPrinter

You can use fsi.AddPrinter, fsi.AddPrintTransformer and fsi.AddHtmlPrinter to extend the formatting of objects.

fsi.AddHtmlPrinter lets you register a function that renders values of a particular type as raw HTML. The function receives the value and returns a sequence of CSS/JS resource pairs and an HTML string. Registered printers are invoked automatically when a value of the matching type is included via (*** include-it ***) or (*** include-it-raw ***).

A common use-case is embedding chart or plot images. For example, if your charting library produces values of type MyChart, you can register a printer that converts them to an <img> tag:

fsi.AddHtmlPrinter(fun (chart: MyChart) ->
    // Convert the chart to a PNG byte array
    let bytes = chart.ToPngBytes()
    let b64 = System.Convert.ToBase64String(bytes)
    // Return (no extra resources, HTML string)
    Seq.empty, sprintf """<img src="data:image/png;base64,%s" />""" b64)

let myChart = MyChart.Create(data)
(*** include-it ***)

With this printer registered, (*** include-it ***) will emit the chart as an inline Base64 image in the HTML output.

If you don't have a custom type but still want to embed an image produced by your script, you can use a plain helper function together with (*** include-it-raw ***):

let inlinePng (fileName: string) =
    let bytes = System.IO.File.ReadAllBytes(fileName)
    let b64 = System.Convert.ToBase64String(bytes)
    sprintf """<img src="data:image/png;base64,%s" />""" b64

// Generate the image, then embed it:
// myChart.SavePng("chart.png")
(*** hide ***)
// inlinePng "chart.png"
(*** include-it-raw ***)

The (*** hide ***) command suppresses the source code of the expression so that only the rendered image appears in the output. See Embedding Images in the command-line reference for more details, including the --saveimages flag for downloading remote images.

Emitting Raw Text

To emit raw text in F# literate scripts use the following:

(**
	(*** raw ***)
	Some raw text.
*)

which would emit

Some raw text.

directly into the document.

F# Formatting as a Library: Specifying the Evaluator and Formatting

If using F# Formatting as a library the embedding of F# output requires specifying an additional parameter to the parsing functions discussed in literate programming documentation. Assuming you have all the references in place, you can now create an instance of FsiEvaluator that represents a wrapper for F# interactive and pass it to all the functions that parse script files or process script files:

open FSharp.Formatting.Literate
open FSharp.Formatting.Literate.Evaluation
open FSharp.Formatting.Markdown

// Sample literate content
let content =
    """
let a = 10
(*** include-value:a ***)"""

// Create evaluator and parse script
let fsi = new FsiEvaluator()

let doc = Literate.ParseScriptString(content, fsiEvaluator = fsi)

Literate.ToHtml(doc)

When the fsiEvaluator parameter is specified, the script is evaluated and so you can use additional commands such as include-value. When the evaluator is not specified, it is not created automatically, so the functionality is not available (this way, you won't accidentally run unexpected code!)

If you specify the fsiEvaluator parameter, but don't want a specific snippet to be evaluated (because it might throw an exception, for example), you can use the (*** do-not-eval ***) command.

The constructor of FsiEvaluator takes command line parameters for fsi.exe that can be used to specify, for example, defined symbols and other attributes for F# Interactive.

You can also subscribe to the EvaluationFailed event which is fired whenever the evaluation of an expression fails. You can use that to do tests that verify that all of the code in your documentation executes without errors.

F# Formatting as a Library: Custom formatting functions

As mentioned earlier, values are formatted using a simple "%A" formatter by default. However, you can specify a formatting function that provides nicer formatting for values of certain types. For example, let's say that we would want to format F# lists such as [1; 2; 3] as HTML ordered lists <ol>.

This can be done by calling FsiEvaluator.RegisterTransformation on the FsiEvaluator instance:

// Create evaluator & register simple formatter for lists
let fsiEvaluator = new FsiEvaluator()

fsiEvaluator.RegisterTransformation(fun (o, ty, _executionCount) ->
    // If the type of value is an F# list, format it nicely
    if ty.IsGenericType
       && ty.GetGenericTypeDefinition() = typedefof<list<_>> then
        let items =
            // Get items as objects and create a paragraph for each item
            [ for it in Seq.cast<obj> (unbox o) ->
                  [ Paragraph([ Literal(it.ToString(), MarkdownRange.zero) ], MarkdownRange.zero) ] ]
        // Return option value (success) with ordered list
        Some [ ListBlock(MarkdownListKind.Ordered, items, MarkdownRange.zero) ]
    else
        None)

The function is called with two arguments - o is the value to be formatted, and ty is the static type of the value (as inferred by the F# compiler). The sample checks that the type of the value is a list (containing values of any type), and then it casts all values in the list to obj (for simplicity). Then, we generate Markdown blocks representing an ordered list. This means that the code will work for both LaTeX and HTML formatting - but if you only need one, you can simply produce HTML and embed it in InlineHtmlBlock.

To use the new FsiEvaluator, we can use the same style as earlier. This time, we format a simple list containing strings:

let listy =
    """
### Formatting demo
let test = ["one";"two";"three"]
(*** include-value:test ***)"""

let docOl = Literate.ParseScriptString(listy, fsiEvaluator = fsiEvaluator)

Literate.ToHtml(docOl)

The resulting HTML formatting of the document contains the snippet that defines test, followed by a nicely formatted ordered list:

Formatting demo

let test = ["one";"two";"three"]
  1. one

  2. two

  3. three

val test: int
val printf: format: Printf.TextWriterFormat<'T> -> 'T
val value1: int list
val value2: int list
val fsi: FSharp.Compiler.Interactive.InteractiveSession
namespace System
Converts a base data type to another base data type.
type Convert =
  static member ChangeType: value: obj * conversionType: Type -> obj + 3 overloads
  static member FromBase64CharArray:
      inArray: char array *
      offset: int *
      length: int ->
          byte array
  static member FromBase64String: s: string -> byte array
  static member FromHexString: utf8Source: ReadOnlySpan<byte> -> byte array + 5 overloads
  static member GetTypeCode: value: obj -> TypeCode
  static member IsDBNull: value: obj -> bool
  static member ToBase64CharArray: inArray: byte array * offsetIn: int * length: int * outArray: char array * offsetOut: int -> int + 1 overload
  static member ToBase64String: inArray: byte array -> string + 4 overloads
  static member ToBoolean: value: bool -> bool + 17 overloads
  static member ToByte: value: bool -> byte + 18 overloads
  ...
System.Convert.ToBase64String(inArray: byte array) : string
System.Convert.ToBase64String(bytes: System.ReadOnlySpan<byte>, ?options: System.Base64FormattingOptions) : string
System.Convert.ToBase64String(inArray: byte array, options: System.Base64FormattingOptions) : string
System.Convert.ToBase64String(inArray: byte array, offset: int, length: int) : string
System.Convert.ToBase64String(inArray: byte array, offset: int, length: int, options: System.Base64FormattingOptions) : string
module Seq
from Microsoft.FSharp.Collections
val empty<'T> : 'T seq
val sprintf: format: Printf.StringFormat<'T> -> 'T
val myChart: obj
val inlinePng: fileName: string -> string
val fileName: string
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
val bytes: byte array
namespace System.IO
Provides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of FileStream objects.
type File =
  static member AppendAllBytes: path: string * bytes: byte array -> unit + 1 overload
  static member AppendAllBytesAsync: path: string * bytes: byte array * ?cancellationToken: CancellationToken -> Task + 1 overload
  static member AppendAllLines: path: string * contents: string seq -> unit + 1 overload
  static member AppendAllLinesAsync: path: string * contents: string seq * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 1 overload
  static member AppendAllText: path: string * contents: ReadOnlySpan<char> -> unit + 3 overloads
  static member AppendAllTextAsync: path: string * contents: ReadOnlyMemory<char> * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 3 overloads
  static member AppendText: path: string -> StreamWriter
  static member Copy: sourceFileName: string * destFileName: string -> unit + 1 overload
  static member Create: path: string -> FileStream + 2 overloads
  static member CreateSymbolicLink:
      path: string *
      pathToTarget: string ->
          FileSystemInfo
  ...
System.IO.File.ReadAllBytes(path: string) : byte array
val b64: string
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.Formatting
namespace FSharp.Formatting.Literate
namespace FSharp.Formatting.Literate.Evaluation
namespace FSharp.Formatting.Markdown
val content: string
val fsi: FsiEvaluator
Multiple items
A wrapper for F# interactive service that is used to evaluate inline snippets
type FsiEvaluator =
  interface IDisposable
  interface IFsiEvaluator
  new:
      ?options: string array *
      ?fsiObj: obj *
      ?addHtmlPrinter: bool *
      ?discardStdOut: bool *
      ?disableFsiObj: bool *
      ?onError: (string -> unit) ->
          FsiEvaluator
  member RegisterTransformation:
      f: (obj * Type * int -> MarkdownParagraph list option) ->
          unit
  member EvaluationFailed: IEvent<FsiEvaluationFailedInfo>

--------------------
new:
    ?options: string array *
    ?fsiObj: obj *
    ?addHtmlPrinter: bool *
    ?discardStdOut: bool *
    ?disableFsiObj: bool *
    ?onError: (string -> unit) ->
        FsiEvaluator
val doc: LiterateDocument
 This type provides three simple methods for calling the literate programming tool.
 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
  ...
static member Literate.ParseScriptString:
    content: string *
    ?path: string *
    ?fscOptions: string *
    ?definedSymbols: string list *
    ?references: bool *
    ?fsiEvaluator: IFsiEvaluator *
    ?parseOptions: MarkdownParseOptions *
    ?rootInputFolder: string *
    ?onError: (string -> unit) ->
        LiterateDocument
static member Literate.ToHtml:
    doc: LiterateDocument *
    ?prefix: string *
    ?lineNumbers: bool *
    ?generateAnchors: bool *
    ?substitutions: (FSharp.Formatting.Templating.ParamKey * string) list *
    ?crefResolver: (string -> (string * string) option) *
    ?mdlinkResolver: (string -> string option) *
    ?tokenKindToCss: (FSharp.Formatting.CodeFormat.TokenKind -> string) ->
        string
val fsiEvaluator: FsiEvaluator
member FsiEvaluator.RegisterTransformation:
    f: (obj * System.Type * int -> MarkdownParagraph list option) ->
        unit
val o: obj
val ty: System.Type
val _executionCount: int
property System.Type.IsGenericType: bool with get
System.Type.GetGenericTypeDefinition() : System.Type
val typedefof<'T> : System.Type
type 'T list = List<'T>
val items: MarkdownParagraph list list
val it: obj
val cast: source: System.Collections.IEnumerable -> 'T seq
type obj = System.Object
val unbox: value: objnull -> 'T
union case MarkdownParagraph.Paragraph:
    body: MarkdownSpans *
    range: MarkdownRange ->
        MarkdownParagraph
Multiple items
union case MarkdownSpan.Literal:
    text: string *
    range: MarkdownRange ->
        MarkdownSpan

--------------------
type LiteralAttribute =
  inherit Attribute
  new: unit -> LiteralAttribute

--------------------
new: unit -> LiteralAttribute
System.Object.ToString() : string
Multiple items
Helper functions for working with MarkdownRange values
module MarkdownRange
from FSharp.Formatting.Markdown

--------------------
Represents a source range in a Markdown document, identified by start and end line/column positions.
Used to track where each parsed element originated in the source text.

type MarkdownRange =
  {
    StartLine: int
    StartColumn: int
    EndLine: int
    EndColumn: int
  }
A zero range used as a default/placeholder when no real range is available
val zero: MarkdownRange
union case Option.Some: Value: 'T -> Option<'T>
A Markdown List block
union case MarkdownParagraph.ListBlock:
    kind: MarkdownListKind *
    items: MarkdownParagraphs list *
    range: MarkdownRange ->
        MarkdownParagraph
A list kind can be Ordered or Unordered corresponding to <ol> and <ul> elements
type MarkdownListKind =
  | Ordered
  | Unordered
union case MarkdownListKind.Ordered: MarkdownListKind
union case Option.None: Option<'T>
val listy: string
val docOl: LiterateDocument

Type something to start searching.