FSharp.Formatting


F# Formatting: Output embedding

A nice feature of the literate programming package (FSharp.Literate.dll in F# Formatting) is that it lets you 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.

Embedding literate script output

The functionality is currently available (and tested) for F# script files (*.fsx) that contain special comments to embed the Markdown text. To embed output of a script file, a couple of additional special comments are added.

The following snippet (F# Script file) demonstates the functionality:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
(** 
### Evaluation demo 
The following is a simple calculation: *)
let test = 40 + 2

(** We can print it as follows: *)
(*** define-output:test ***)
printf "Result is: %d" test

(** The result of the previous snippet is: *)
(*** include-output:test ***)

(** And the variable `test` has the following value: *)
(*** include-value: test ***)

The script defines a variable test and then prints it. The comment (*** define-output:test ***) is used to capture the result of the printing (using the key test) so that it can be embedded later in the script. We refer to it using another special comment written as (*** include-output:test ***). In addition, it is also possible to include the value of any variable using the comment (*** include-value: test ***). By default, this is formatted using the "%A" formatter in F# (but the next section shows how to override this behavior). The formatted result of the above snippet looks as follows:

Evaluation demo

The following is a simple calculation:

1: 
let test = 40 + 2

We can print it as follows:

1: 
printf "Result is: %d" test

The result of the previous snippet is:

Result is: 42

And the variable test has the following value:

42

In addition to the commands demonstrated in the above sample, you can also use (*** include-it: test ***) to include the it value that was produced by a snippet named test using the (*** define-output: test ***) command.

Specifying the evaluator and formatting

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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
open FSharp.Literate
open FSharp.Markdown

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

// Create evaluator and parse script
let fsi = FsiEvaluator()
let doc = Literate.ParseScriptString(content, fsiEvaluator = fsi)
Literate.WriteHtml(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 and 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 off the code in your documentation executes without errors.

Emitting Raw Text

When writing documents, it is sometimes required to emit completely unaltered text. Up to this point all of the commands have decorated the code or text with some formatting, for example a pre element. When working with layout or content generation engines such as Jeykll, we sometimes need to emit plain text as declarations to said engines. This is where the raw command is useful.

1: 
2: 
3: 
4: 
(**
	(*** raw ***)
	Some raw text.
*)

which would emit

Some raw text.

directly into the document.

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 a 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 RegisterTransformation on the FsiEvaluator instance:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
// Create evaluator & register simple formatter for lists
let fsiOl = FSharp.Literate.FsiEvaluator()
fsiOl.RegisterTransformation(fun (o, ty) ->
  // 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 paragraph for each item
      [ for it in Seq.cast<obj> (unbox o) -> 
          [ Paragraph([Literal(it.ToString(), None)], None) ] ]
    // Return option value (success) with ordered list
    Some [ ListBlock(MarkdownListKind.Ordered, items, None) ]
  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 InlineBlock.

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

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let listy = """
### Formatting demo
let test = ["one";"two";"three"]
(*** include-value:test ***)"""

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

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

Formatting demo

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

  2. two

  3. three

val test : int

Full name: evaluation.test
val printf : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.Literate
namespace FSharp.Markdown
val content : string

Full name: Evaluation.content
val fsi : FsiEvaluator

Full name: Evaluation.fsi
Multiple items
type FsiEvaluator =
  interface IFsiEvaluator
  new : ?options:string [] * ?fsiObj:obj -> FsiEvaluator
  member RegisterTransformation : f:(obj * Type -> MarkdownParagraph list option) -> unit
  member EvaluationFailed : IEvent<FsiEvaluationFailedInfo>

Full name: FSharp.Literate.FsiEvaluator

--------------------
new : ?options:string [] * ?fsiObj:obj -> FsiEvaluator
val doc : LiterateDocument

Full name: Evaluation.doc
type Literate =
  private new : unit -> Literate
  static member FormatLiterateNodes : doc:LiterateDocument * ?format:OutputKind * ?prefix:string * ?lineNumbers:bool * ?generateAnchors:bool -> LiterateDocument
  static member ParseMarkdownFile : path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ParseMarkdownString : content:string * ?path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ParseScriptFile : path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ParseScriptString : content:string * ?path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ProcessDirectory : inputDirectory:string * ?templateFile:string * ?outputDirectory:string * ?format:OutputKind * ?formatAgent:CodeFormatAgent * ?prefix:string * ?compilerOptions:string * ?lineNumbers:bool * ?references:bool * ?fsiEvaluator:IFsiEvaluator * ?replacements:(string * string) list * ?includeSource:bool * ?layoutRoots:string list * ?generateAnchors:bool * ?assemblyReferences:string list * ?processRecursive:bool * ?customizeDocument:(ProcessingContext -> LiterateDocument -> LiterateDocument) -> unit
  static member ProcessDocument : doc:LiterateDocument * output:string * ?templateFile:string * ?format:OutputKind * ?prefix:string * ?lineNumbers:bool * ?includeSource:bool * ?generateAnchors:bool * ?replacements:(string * string) list * ?layoutRoots:string list * ?assemblyReferences:string list -> unit
  static member ProcessMarkdown : input:string * ?templateFile:string * ?output:string * ?format:OutputKind * ?formatAgent:CodeFormatAgent * ?prefix:string * ?compilerOptions:string * ?lineNumbers:bool * ?references:bool * ?replacements:(string * string) list * ?includeSource:bool * ?layoutRoots:string list * ?generateAnchors:bool * ?assemblyReferences:string list * ?customizeDocument:(ProcessingContext -> LiterateDocument -> LiterateDocument) -> unit
  static member ProcessScriptFile : input:string * ?templateFile:string * ?output:string * ?format:OutputKind * ?formatAgent:CodeFormatAgent * ?prefix:string * ?compilerOptions:string * ?lineNumbers:bool * ?references:bool * ?fsiEvaluator:IFsiEvaluator * ?replacements:(string * string) list * ?includeSource:bool * ?layoutRoots:string list * ?generateAnchors:bool * ?assemblyReferences:string list * ?customizeDocument:(ProcessingContext -> LiterateDocument -> LiterateDocument) -> unit
  ...

Full name: FSharp.Literate.Literate
static member Literate.ParseScriptString : content:string * ?path:string * ?formatAgent:FSharp.CodeFormat.CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
static member Literate.WriteHtml : doc:LiterateDocument * ?prefix:string * ?lineNumbers:bool * ?generateAnchors:bool -> string
static member Literate.WriteHtml : doc:LiterateDocument * writer:System.IO.TextWriter * ?prefix:string * ?lineNumbers:bool * ?generateAnchors:bool -> unit
val fsiOl : FsiEvaluator

Full name: Evaluation.fsiOl
member FsiEvaluator.RegisterTransformation : f:(obj * System.Type -> MarkdownParagraph list option) -> unit
val o : obj
val ty : System.Type
property System.Type.IsGenericType: bool
System.Type.GetGenericTypeDefinition() : System.Type
val typedefof<'T> : System.Type

Full name: Microsoft.FSharp.Core.Operators.typedefof
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val items : MarkdownParagraph list list
val it : obj
module Seq

from Microsoft.FSharp.Collections
val cast : source:System.Collections.IEnumerable -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.cast
type obj = System.Object

Full name: Microsoft.FSharp.Core.obj
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
union case MarkdownParagraph.Paragraph: body: MarkdownSpans * range: FSharp.Formatting.Common.MarkdownRange option -> MarkdownParagraph
Multiple items
union case MarkdownSpan.Literal: text: string * range: FSharp.Formatting.Common.MarkdownRange option -> MarkdownSpan

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

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
System.Object.ToString() : string
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
union case MarkdownParagraph.ListBlock: kind: MarkdownListKind * items: MarkdownParagraphs list * range: FSharp.Formatting.Common.MarkdownRange option -> MarkdownParagraph
type MarkdownListKind =
  | Ordered
  | Unordered

Full name: FSharp.Markdown.MarkdownListKind
union case MarkdownListKind.Ordered: MarkdownListKind
val listy : string

Full name: Evaluation.listy
val docOl : LiterateDocument

Full name: Evaluation.docOl
Fork me on GitHub