// thinkbeforecoding

Full F# Blog

2018-12-07T08:04:00 / jeremie chassaing

this post is part of the F# Advent Calendar 2018

Updated 2020-09-23 to use official FSharp.Formatting release

Christmas arrived early this year: my pull request to update FSharp.Formatting to netstandard2.0 was accepted on Oct 22 .

This, combined with a discussion at Open FSharp with Alfonso - famous for creating Fable - led me to use F#, and only F# to publish my blog.

Why not just using wordpress ?

This blog was previously hosted by Gandi on dotclear, and it was ok for years.

Pros:

  • Free
  • Managed
  • No ads

Cons:

  • Hard to style
  • No extensibility
  • Low ownership
  • Painful editing

The editing was really painful in the browser, especialy since Live Writer was not supported anymore. I managed to use FSharp.Formatting for some posts. But when pasting the code in the editor, most of the formatting was rearranged due to platform limitations. I took infinite time to gets the tips working.

But it was free.

I could have found another blogging platform, but most of them are free because ads. Or I could pay. But those plateforms are not necessary better...

And there was anyway an extra challenge: I didn't want to break existing links.

I have some traffic on my blog. External posts, Stack Overflow answers, tweets pointing to my posts. It would be a waste to lose all this.

So it took some time and I finally decided to host it myself.

My stack

The constraint of not breaking existing structure forced me to actually develop my own solution. And since I'm quite fluent in F#... everything would eventually be in F#.

  • F# scripts for building
    • FSharp.Literate to convert md and fsx files to HTML.
    • Fable.React server side rendering for a static full F# template engine
    • FSharp.Data for RSS generation
  • F# script for testing
    • Giraffe to host a local web server to view the result
  • F# script for publishing

The other constraint was price. And since previous solution was free, I took it has a chanlenge to try to make it as cheap as possible. There are lots of free options, but never with custom domain (needed to not break links), and never with https (mandatory since google is showing HTTP sites as unsecure).

I choosed Azure Functions, but with no code. I get:

  • Custom domain for free
  • Free SSL certificate thanks to letsencrypt
  • KeyVault for secret management
  • Staging for deployment
  • Full ownership
  • Almost free (currently around 0.01€/month)

I'll detail the F# part in this post. The azure hosting will be for part 2, and letsencrypt for part 3.

Fake 5

Fake 5 is the tool to write build scripts or simply scripts in F# (thx forki).

To install it, simply type in a command line

dotnet tool install fake-cli -g

and you're done.

The strength of Fake is that it can reference and load nuget packages using paket (thx again forki) directly in the fsx script:

#r "paket: 
source https://api.nuget.org/v3/index.json
nuget FSharp.Data //"

Fake will then dowload and reference specified packages.

To help with completion at design time you can reference an autogenerated fsx like this:

#load "../.fake/blog.fsx/intellisense.fsx" 

here I use .. because this blog post is in a subfolder in my project

Packages can then be used:

open FSharp.Data

FSharp.Literate

FSharp.Formatting is an awsome project to convert F# and MarkDown to HTML.

Conversion to netstandard has been stuck for some time due to its dependency on Razor for HTML templating.

Razor has changed a lot in AspNetCore, and porting existing code was a real nightmare.

To speed up things, I proposed to only port FSharp.Literate and the rest of the project but to get rid of formatting and this dependency on Razor. There is now a beta nuget package deployed on appveyor at https://ci.appveyor.com/nuget/fsharp-formatting : so for my build script I use the following references:

#r "paket:
source https://api.nuget.org/v3/index.json

nuget Fake.IO.FileSystem
nuget Fake.Core.Trace
nuget FSharp.Data
nuget Fable.React
nuget FSharp.Formatting //" 

#load "../.fake/blog.fsx/intellisense.fsx" 
open FSharp.Formatting.Literate

Markdown

The simplest usage of FSharp.Literate is for posts with no code. In this case, I write it as MarkDown file and convert them using the TransformHtml function:

let md = """# Markdown is cool
especially with *FSharp.Formatting* ! """
            |> FSharp.Formatting.Markdown.Markdown.ToHtml

which returns:

"<h1>Markdown is cool</h1>
<p>especially with <em>FSharp.Formatting</em> !</p>
"

Fsx

We can also take a snipet of F# and convert it to HTML:

open FSharp.Formatting.Literate.Evaluation
let snipet =
    """
    (** # *F# literate* in action *)
    printfn "Hello"
    """
let parse source =
    let fsharpCoreDir = "-I:" + __SOURCE_DIRECTORY__ + @"\..\packages\full\FSharp.Core\lib\netstandard2.0\"
    let fcsDir = "-I:" + __SOURCE_DIRECTORY__ + @"\..\packages\full\FSharp.Compiler.Service\lib\netstandard2.0\"
    let fcs = "-r:" + __SOURCE_DIRECTORY__ + @"\..\packages\full\FSharp.Compiler.Service\lib\netstandard2.0\FSharp.Compiler.Service.dll"

    Literate.ParseScriptString(
                source, 
                fscOptions = String.concat " " [ fsharpCoreDir; fcsDir; fcs ],
                fsiEvaluator = FsiEvaluator([|fsharpCoreDir; fcsDir; fcs|]))
    
let fs =
    let doc = 
        snipet 
        |> parse
    Literate.ToHtml(doc, "", true, true)
    

The fsharpCoreDir and the -I options are necessary to help FSharp.Literate resolve the path to FSharp.Core. System.Runtime must also be referenced to get tooltips working fine with netstandard assemblies. FSharp interactive is not totally ready for production due to this problem, but with some helps, it works for our need.

Running this code we get:

"<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
<span class="l">2: </span>
<span class="l">3: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp">    <span class="c">(** # *F# literate* in action *)</span>
    <span onmouseout="hideTip(event, '1', 1)" onmouseover="showTip(event, '1', 1)" class="fn">printfn</span> <span class="s">&quot;Hello&quot;</span>
    
</code></pre></td>
</tr>
</table>
<div class="fsdocs-tip" id="1">val printfn: format: Printf.TextWriterFormat&lt;&#39;T&gt; -&gt; &#39;T<br /><em>&lt;summary&gt;Print to &lt;c&gt;stdout&lt;/c&gt; using the given format, and add a newline.&lt;/summary&gt;<br />&lt;param name=&quot;format&quot;&gt;The formatter.&lt;/param&gt;<br />&lt;returns&gt;The formatted result.&lt;/returns&gt;</em></div>

"

As you can see, the code contains a reference to a javascript functions. You can find an implementation on github. It displays type information tool tips generated by the compiler. All the type information is generated during parsing phase:

let tips =
    let doc = parse snipet
    doc.FormattedTips
""

this way readers get full type inference information in the browser !

But it's even better than that. You can also get the value of some bindings in your ouput:

let values  = 
    """
(** # code execution *)
let square x = x * x
let v = square 3
(** the value is: *)
(*** include-value: v ***)"""
    |> parse
    |> Literate.ToHtml

and the result is:

"<h1>code execution</h1>
<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
<span class="l">2: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp"><span class="k">let</span> <span onmouseout="hideTip(event, 'fs1', 1)" onmouseover="showTip(event, 'fs1', 1)" class="fn">square</span> <span onmouseout="hideTip(event, 'fs2', 2)" onmouseover="showTip(event, 'fs2', 2)" class="fn">x</span> <span class="o">=</span> <span onmouseout="hideTip(event, 'fs2', 3)" onmouseover="showTip(event, 'fs2', 3)" class="fn">x</span> <span class="o">*</span> <span onmouseout="hideTip(event, 'fs2', 4)" onmouseover="showTip(event, 'fs2', 4)" class="fn">x</span>
<span class="k">let</span> <span onmouseout="hideTip(event, 'fs3', 5)" onmouseover="showTip(event, 'fs3', 5)" class="id">v</span> <span class="o">=</span> <span onmouseout="hideTip(event, 'fs1', 6)" onmouseover="showTip(event, 'fs1', 6)" class="fn">square</span> <span class="n">3</span>
</code></pre></td>
</tr>
</table>
<p>the value is:</p>
<table class="pre"><tr><td><pre><code>9</code></pre></td></tr></table>
<div class="fsdocs-tip" id="fs1">val square: x: int -&gt; int</div>
<div class="fsdocs-tip" id="fs2">val x: int</div>
<div class="fsdocs-tip" id="fs3">val v: int</div>

"

You can see that the value of v - 9 - has been computed in the output HTML !

As you can gess, I just used this feature to print the HTML output! Inception !

It also works for printf:

let output  = 
    """
(** # printing *)
let square x = x * x
(*** define-output: result ***)
printfn "result: %d" (square 3)
(** the value is: *)
(*** include-output: result ***)"""
    |> parse
    |> Literate.ToHtml

Notice the presence of the printf output on the last line:

"<h1>printing</h1>
<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp"><span class="k">let</span> <span onmouseout="hideTip(event, 'fs1', 1)" onmouseover="showTip(event, 'fs1', 1)" class="fn">square</span> <span onmouseout="hideTip(event, 'fs2', 2)" onmouseover="showTip(event, 'fs2', 2)" class="fn">x</span> <span class="o">=</span> <span onmouseout="hideTip(event, 'fs2', 3)" onmouseover="showTip(event, 'fs2', 3)" class="fn">x</span> <span class="o">*</span> <span onmouseout="hideTip(event, 'fs2', 4)" onmouseover="showTip(event, 'fs2', 4)" class="fn">x</span>
</code></pre></td>
</tr>
</table>
<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp"><span onmouseout="hideTip(event, 'fs3', 5)" onmouseover="showTip(event, 'fs3', 5)" class="fn">printfn</span> <span class="s">&quot;result: </span><span class="pf">%d</span><span class="s">&quot;</span> <span class="pn">(</span><span onmouseout="hideTip(event, 'fs1', 6)" onmouseover="showTip(event, 'fs1', 6)" class="fn">square</span> <span class="n">3</span><span class="pn">)</span>
</code></pre></td>
</tr>
</table>
<p>the value is:</p>
<table class="pre"><tr><td><pre><code>result: 9</code></pre></td></tr></table>
<div class="fsdocs-tip" id="fs1">val square: x: int -&gt; int</div>
<div class="fsdocs-tip" id="fs2">val x: int</div>
<div class="fsdocs-tip" id="fs3">val printfn: format: Printf.TextWriterFormat&lt;&#39;T&gt; -&gt; &#39;T<br /><em>&lt;summary&gt;Print to &lt;c&gt;stdout&lt;/c&gt; using the given format, and add a newline.&lt;/summary&gt;<br />&lt;param name=&quot;format&quot;&gt;The formatter.&lt;/param&gt;<br />&lt;returns&gt;The formatted result.&lt;/returns&gt;</em></div>

"

Templating

Now that we can convert the content to HTML, we need to add the surrounding layout.

I use Fable.React for this, but just the server side rendering. So there is no need for the JS tools, only the .net nuget.

After adding the nuget Fable.React in the paket includes, we can open it and start a HTML template:

open Fable.React
open Fable.React.Props
open FSharp.Formatting.Markdown

type Post = {
    title: string
    content: string
}

let template post = 
    html [Lang "en"] [
        head [] [
            title [] [ str ("My blog / " + post.title) ]
        ]
        body [] [
            RawText post.content
        ]
    ]

to convert it too string, we simply add the doctype to make it HTML5 compatible and use renderToString

let render html =
  fragment [] [ 
    RawText "<!doctype html>"
    RawText "\n" 
    html ]
  |> Fable.ReactServer.renderToString 

let's use it :

let myblog =
    { title = "super post"
      content = Markdown.ToHtml "# **interesting** things" }
    |> template
    |> render 

now we get the final page:

"<!doctype html>
<html data-reactroot="" lang="en"><head><title>My blog / super post</title></head><body><h1><strong>interesting</strong> things</h1>
</body></html>"

RSS

Rss has lost attraction lately, but I still have requests every day on the atom feed.

Using Fsharp data, generating the RSS feed is straight forward:

#I @"..\packages\full\FSharp.Data\lib\netstandard2.0\"
#r "System.Xml.Linq"
#r "FSharp.Data"
open System
open FSharp.Data
open System.Security.Cryptography

[<Literal>]
let feedXml = """<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xml:lang="en">
  
  <title type="html">Think Before Coding</title>
  <link href="http://thinkbeforecoding.com:82/feed/atom" rel="self" type="application/atom+xml"/>
  <link href="http://thinkbeforecoding.com/" rel="alternate" type="text/html"
  title=""/>
  <updated>2017-12-09T01:20:21+01:00</updated>
  <author>
    <name>Jérémie Chassaing</name>
  </author>
  <id>urn:md5:18477</id>
  <generator uri="http://www.dotclear.net/">Dotclear</generator>
  <entry>
    <title>fck: Fake Construction Kit</title>
    <link href="http://thinkbeforecoding.com/post/2016/12/04/fck%3A-Fake-Construction-Kit" rel="alternate" type="text/html"
    title="fck: Fake Construction Kit" />
    <id>urn:md5:d78962772329a428a89ca9d77ae1a56b</id>
    <updated>2016-12-04T10:34:00+01:00</updated>
    <author><name>Jérémie Chassaing</name></author>
        <dc:subject>f</dc:subject><dc:subject>FsAdvent</dc:subject>    
    <content type="html">    &lt;p&gt;Yeah it's christmas time again, and santa's elves are quite busy.&lt;/p&gt;
ll name: Microsoft.FSharp.Core.Operators.not&lt;/div&gt;</content>
      </entry>
  <entry>
    <title>Ukulele Fun for XMas !</title>
    <link href="http://thinkbeforecoding.com/post/2015/12/17/Ukulele-Fun-for-XMas-%21" rel="alternate" type="text/html"
    title="Ukulele Fun for XMas !" />
    <id>urn:md5:5919e73c387df2af043bd531ea6edf47</id>
    <updated>2015-12-17T10:44:00+01:00</updated>
    <author><name>Jérémie Chassaing</name></author>
        <dc:subject>F#</dc:subject>
    <content type="html">    &lt;div style=&quot;margin-top:30px&quot; class=&quot;container row&quot;&gt;
lt;/div&gt;</content>
      </entry>
</feed>"""

type Rss = XmlProvider<feedXml>
let links: Rss.Link[] = [|
    Rss.Link("https://thinkbeforecoding.com/feed/atom","self", "application/atom+xml", null)
    Rss.Link("https://thinkbeforecoding.com/","alternate", "text/html", "thinkbeforecoding")
    |]

let entry title link date content = 
    let md5Csp = MD5.Create()
    let md5 =
        md5Csp.ComputeHash(Text.Encoding.UTF8.GetBytes(content: string))
        |> Array.map (sprintf "%2x")
        |> String.concat ""
        |> (+) "urn:md5:"

    Rss.Entry(
        title,
        Rss.Link2(link, "alternate", "text/html", title),
        md5,
        DateTimeOffset.op_Implicit date,
        Rss.Author2("Jérémie Chassaing"),
        [||],
        Rss.Content("html", content)
        )
let feed entries =
    Rss.Feed("en", 
        Rss.Title("html","thinkbeforecoding"), 
        links,DateTimeOffset.UtcNow, 
        Rss.Author("Jérémie Chassaing"),
        "urn:md5:18477",
        Rss.Generator("https://fsharp.org","F# script"),
        List.toArray entries
         )

just pass all posts to the feed function, and you get a full RSS feed.

Migration

To migrate from my previous blog, I exported all data to csv, and used the CSV type provider to parse it.

I extracted the HTML and put it in files, and generated a fsx file containing a list of posts with metadata:

  • title
  • date
  • url
  • category

Once done, I just have to map the post list using conversion and templates, and I have my new blog.

Wrapping it up

Using F# tools, I get easily a full control on my blog. And all this in my favorite language!

See you in next part about hosting in Azure.

Happy Christmas!

Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
namespace FSharp.Formatting
namespace FSharp.Formatting.Literate
val md: string
namespace FSharp.Formatting.Markdown
type Markdown = static member Parse: text: string * ?newline: string * ?parseOptions: MarkdownParseOptions -> MarkdownDocument static member ToFsx: doc: MarkdownDocument * ?newline: string * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string static member ToHtml: doc: MarkdownDocument * ?newline: string * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string + 1 overload static member ToLatex: doc: MarkdownDocument * ?newline: string * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) * ?lineNumbers: bool -> string + 1 overload static member ToMd: doc: MarkdownDocument * ?newline: string * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string static member ToPynb: doc: MarkdownDocument * ?newline: string * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string static member WriteHtml: doc: MarkdownDocument * writer: TextWriter * ?newline: string * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> unit + 1 overload static member WriteLatex: doc: MarkdownDocument * writer: TextWriter * ?newline: string * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) * ?lineNumbers: bool -> unit + 1 overload
<summary> Static class that provides methods for formatting and transforming Markdown documents. </summary>
static member FSharp.Formatting.Markdown.Markdown.ToHtml: markdownText: string * ?newline: string * ?substitutions: (FSharp.Formatting.Templating.ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string
static member FSharp.Formatting.Markdown.Markdown.ToHtml: doc: FSharp.Formatting.Markdown.MarkdownDocument * ?newline: string * ?substitutions: (FSharp.Formatting.Templating.ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string
namespace FSharp.Formatting.Literate.Evaluation
val snipet: string
val parse: source: string -> LiterateDocument
val source: string
val fsharpCoreDir: string
val fcsDir: string
val fcs: string
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) -> 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) -> 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 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 ToLatex: doc: LiterateDocument * ?prefix: string * ?lineNumbers: bool * ?generateAnchors: bool * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string static member ToPynb: doc: LiterateDocument * ?substitutions: (ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string ...
<summary> This type provides three simple methods for calling the literate programming tool. The <c>ConvertMarkdownFile</c> and <c>ConvertScriptFile</c> methods process a single Markdown document and F# script, respectively. The <c>ConvertDirectory</c> method handles an entire directory tree (looking for <c>*.fsx</c> and <c>*.md</c> files). </summary>
<namespacedoc><summary>Functionality to support literate programming for F# scripts</summary></namespacedoc>
static member Literate.ParseScriptString: content: string * ?path: string * ?fscOptions: string * ?definedSymbols: string list * ?references: bool * ?fsiEvaluator: IFsiEvaluator * ?parseOptions: FSharp.Formatting.Markdown.MarkdownParseOptions * ?rootInputFolder: string * ?onError: (string -> unit) -> LiterateDocument
module String from Microsoft.FSharp.Core
<summary>Functional programming operators for string processing. Further string operations are available via the member functions on strings and other functionality in <a href="http://msdn2.microsoft.com/en-us/library/system.string.aspx">System.String</a> and <a href="http://msdn2.microsoft.com/library/system.text.regularexpressions.aspx">System.Text.RegularExpressions</a> types. </summary>
<category>Strings and Text</category>
val concat: sep: string -> strings: seq<string> -> string
<summary>Returns a new string made by concatenating the given strings with separator <c>sep</c>, that is <c>a1 + sep + ... + sep + aN</c>.</summary>
<param name="sep">The separator string to be inserted between the strings of the input sequence.</param>
<param name="strings">The sequence of strings to be concatenated.</param>
<returns>A new string consisting of the concatenated strings separated by the separation string.</returns>
<exception cref="T:System.ArgumentNullException">Thrown when <c>strings</c> is null.</exception>
Multiple items
type FsiEvaluator = interface IFsiEvaluator new: ?options: string[] * ?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>
<summary> A wrapper for F# interactive service that is used to evaluate inline snippets </summary>

--------------------
new: ?options: string[] * ?fsiObj: obj * ?addHtmlPrinter: bool * ?discardStdOut: bool * ?disableFsiObj: bool * ?onError: (string -> unit) -> FsiEvaluator
val fs: string
val doc: 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 tips: string
property LiterateDocument.FormattedTips: string with get
<summary> Formatted tool tips </summary>
val values: string
val output: string
namespace Fable
namespace Fable.React
module Props from Fable.React
type Post = { title: string content: string }
Post.title: string
Multiple items
val string: value: 'T -> string
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>


--------------------
type string = System.String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
Post.content: string
val template: post: Post -> ReactElement
val post: Post
val html: props: seq<IHTMLProp> -> children: seq<ReactElement> -> ReactElement
union case HTMLAttr.Lang: string -> HTMLAttr
val head: props: seq<IHTMLProp> -> children: seq<ReactElement> -> ReactElement
val title: props: seq<IHTMLProp> -> children: seq<ReactElement> -> ReactElement
val str: s: string -> ReactElement
<summary> Alias of `ofString` </summary>
val body: props: seq<IHTMLProp> -> children: seq<ReactElement> -> ReactElement
union case HTMLNode.RawText: string -> HTMLNode
val render: html: ReactElement -> string
val html: ReactElement
val fragment: props: seq<IFragmentProp> -> children: seq<ReactElement> -> ReactElement
<summary> Instantiate a React fragment </summary>
module ReactServer from Fable
val renderToString: htmlNode: ReactElement -> string
val myblog: string
static member Markdown.ToHtml: markdownText: string * ?newline: string * ?substitutions: (FSharp.Formatting.Templating.ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string
static member Markdown.ToHtml: doc: MarkdownDocument * ?newline: string * ?substitutions: (FSharp.Formatting.Templating.ParamKey * string) list * ?crefResolver: (string -> (string * string) option) * ?mdlinkResolver: (string -> string option) -> string
namespace System
namespace System.Security
namespace System.Security.Cryptography
Multiple items
union case MarkdownSpan.Literal: text: string * range: MarkdownRange option -> MarkdownSpan

--------------------
type LiteralAttribute = inherit Attribute new: unit -> LiteralAttribute
<summary>Adding this attribute to a value causes it to be compiled as a CLI constant literal.</summary>
<category>Attributes</category>


--------------------
new: unit -> LiteralAttribute
[<Literal>] val feedXml: string = "<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xml:lang="en"> <title type="html">Think Before Coding</title> <link href="http://thinkbeforecoding.com:82/feed/atom" rel="self" type="application/atom+xml"/> <link href="http://thinkbeforecoding.com/" rel="alternate" type="text/html" title=""/> <updated>2017-12-09T01:20:21+01:00</updated> <author> <name>Jérémie Chassaing</name> </author> <id>urn:md5:18477</id> <generator uri="http://www.dotclear.net/">Dotclear</generator> <entry> <title>fck: Fake Construction Kit</title> <link href="http://thinkbeforecoding.com/post/2016/12/04/fck%3A-Fake-Construction-Kit" rel="alternate" type="text/html" title="fck: Fake Construction Kit" /> <id>urn:md5:d78962772329a428a89ca9d77ae1a56b</id> <updated>2016-12-04T10:34:00+01:00</updated> <author><name>Jérémie Chassaing</name></author> <dc:subject>f</dc:subject><dc:subject>FsAdvent</dc:subject> <content type="html"> &lt;p&gt;Yeah it's christmas time again, and santa's elves are quite busy.&lt;/p&gt; ll name: Microsoft.FSharp.Core.Operators.not&lt;/div&gt;</content> </entry> <entry> <title>Ukulele Fun for XMas !</title> <link href="http://thinkbeforecoding.com/post/2015/12/17/Ukulele-Fun-for-XMas-%21" rel="alternate" type="text/html" title="Ukulele Fun for XMas !" /> <id>urn:md5:5919e73c387df2af043bd531ea6edf47</id> <updated>2015-12-17T10:44:00+01:00</updated> <author><name>Jérémie Chassaing</name></author> <dc:subject>F#</dc:subject> <content type="html"> &lt;div style=&quot;margin-top:30px&quot; class=&quot;container row&quot;&gt; lt;/div&gt;</content> </entry> </feed>"
type Rss = XmlProvider<...>
type XmlProvider
<summary>Typed representation of a XML file.</summary> <param name='Sample'>Location of a XML sample file or a string containing a sample XML document.</param> <param name='SampleIsList'>If true, the children of the root in the sample document represent individual samples for the inference.</param> <param name='Global'>If true, the inference unifies all XML elements with the same name.</param> <param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param> <param name='Encoding'>The encoding used to read the sample. You can specify either the character set name or the codepage number. Defaults to UTF8 for files, and to ISO-8859-1 the for HTTP requests, unless <c>charset</c> is specified in the <c>Content-Type</c> response header.</param> <param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param> <param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource (e.g. 'MyCompany.MyAssembly, resource_name.xml'). This is useful when exposing types generated by the type provider.</param> <param name='InferTypesFromValues'>If true, turns on additional type inference from values. (e.g. type inference infers string values such as "123" as ints and values constrained to 0 and 1 as booleans. The XmlProvider also infers string values as JSON.)</param> <param name='Schema'>Location of a schema file or a string containing xsd.</param>
val links: XmlProvider<...>.Link[]
val entry: title: string -> link: string -> date: DateTime -> content: string -> XmlProvider<...>.Entry
val title: string
val link: string
val date: DateTime
val content: string
val md5Csp: MD5
type MD5 = inherit HashAlgorithm static member Create: unit -> MD5 + 1 overload static member HashData: source: byte[] -> byte[] + 2 overloads static member TryHashData: source: ReadOnlySpan<byte> * destination: Span<byte> * bytesWritten: byref<int> -> bool
<summary>Represents the abstract class from which all implementations of the <see cref="T:System.Security.Cryptography.MD5" /> hash algorithm inherit.</summary>
MD5.Create() : MD5
MD5.Create(algName: string) : MD5
val md5: string
HashAlgorithm.ComputeHash(inputStream: IO.Stream) : byte[]
HashAlgorithm.ComputeHash(buffer: byte[]) : byte[]
HashAlgorithm.ComputeHash(buffer: byte[], offset: int, count: int) : byte[]
Multiple items
union case HttpResponseBody.Text: string -> HttpResponseBody

--------------------
namespace System.Text
type Encoding = interface ICloneable member Clone: unit -> obj member Equals: value: obj -> bool member GetByteCount: chars: nativeptr<char> * count: int -> int + 5 overloads member GetBytes: chars: nativeptr<char> * charCount: int * bytes: nativeptr<byte> * byteCount: int -> int + 7 overloads member GetCharCount: bytes: nativeptr<byte> * count: int -> int + 3 overloads member GetChars: bytes: nativeptr<byte> * byteCount: int * chars: nativeptr<char> * charCount: int -> int + 4 overloads member GetDecoder: unit -> Decoder member GetEncoder: unit -> Encoder member GetHashCode: unit -> int ...
<summary>Represents a character encoding.</summary>
property Text.Encoding.UTF8: Text.Encoding with get
<summary>Gets an encoding for the UTF-8 format.</summary>
<returns>An encoding for the UTF-8 format.</returns>
Text.Encoding.GetBytes(s: string) : byte[]
Text.Encoding.GetBytes(chars: char[]) : byte[]
Text.Encoding.GetBytes(chars: ReadOnlySpan<char>, bytes: Span<byte>) : int
Text.Encoding.GetBytes(s: string, index: int, count: int) : byte[]
Text.Encoding.GetBytes(chars: char[], index: int, count: int) : byte[]
Text.Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
Text.Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte[], byteIndex: int) : int
Text.Encoding.GetBytes(chars: char[], charIndex: int, charCount: int, bytes: byte[], byteIndex: int) : int
Multiple items
val string: value: 'T -> string
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>


--------------------
type string = String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
type Array = interface ICollection interface IEnumerable interface IList interface IStructuralComparable interface IStructuralEquatable interface ICloneable member Clone: unit -> obj member CopyTo: array: Array * index: int -> unit + 1 overload member GetEnumerator: unit -> IEnumerator member GetLength: dimension: int -> int ...
<summary>Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the base class for all arrays in the common language runtime.</summary>
val map: mapping: ('T -> 'U) -> array: 'T[] -> 'U[]
<summary>Builds a new array whose elements are the results of applying the given function to each of the elements of the array.</summary>
<param name="mapping">The function to transform elements of the array.</param>
<param name="array">The input array.</param>
<returns>The array of transformed elements.</returns>
<exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
val sprintf: format: Printf.StringFormat<'T> -> 'T
<summary>Print to a string using the given format.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
Multiple items
type String = interface IEnumerable<char> interface IEnumerable interface ICloneable interface IComparable interface IComparable<string> interface IConvertible interface IEquatable<string> new: value: nativeptr<char> -> unit + 8 overloads member Clone: unit -> obj member CompareTo: value: obj -> int + 1 overload ...
<summary>Represents text as a sequence of UTF-16 code units.</summary>

--------------------
String(value: nativeptr<char>) : String
String(value: char[]) : String
String(value: ReadOnlySpan<char>) : String
String(value: nativeptr<sbyte>) : String
String(c: char, count: int) : String
String(value: nativeptr<char>, startIndex: int, length: int) : String
String(value: char[], startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : String
Multiple items
[<Struct>] type DateTimeOffset = new: dateTime: DateTime -> unit + 5 overloads member Add: timeSpan: TimeSpan -> DateTimeOffset member AddDays: days: float -> DateTimeOffset member AddHours: hours: float -> DateTimeOffset member AddMilliseconds: milliseconds: float -> DateTimeOffset member AddMinutes: minutes: float -> DateTimeOffset member AddMonths: months: int -> DateTimeOffset member AddSeconds: seconds: float -> DateTimeOffset member AddTicks: ticks: int64 -> DateTimeOffset member AddYears: years: int -> DateTimeOffset ...
<summary>Represents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).</summary>

--------------------
DateTimeOffset ()
DateTimeOffset(dateTime: DateTime) : DateTimeOffset
DateTimeOffset(dateTime: DateTime, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(ticks: int64, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, calendar: Globalization.Calendar, offset: TimeSpan) : DateTimeOffset
DateTimeOffset.op_Implicit(dateTime: DateTime) : DateTimeOffset
val feed: entries: XmlProvider<...>.Entry list -> XmlProvider<...>.Feed
val entries: XmlProvider<...>.Entry list
property DateTimeOffset.UtcNow: DateTimeOffset with get
<summary>Gets a <see cref="T:System.DateTimeOffset" /> object whose date and time are set to the current Coordinated Universal Time (UTC) date and time and whose offset is <see cref="F:System.TimeSpan.Zero" />.</summary>
<returns>An object whose date and time is the current Coordinated Universal Time (UTC) and whose offset is <see cref="F:System.TimeSpan.Zero" />.</returns>
Multiple items
union case HTMLAttr.List: string -> HTMLAttr

--------------------
module List from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.list`1" />.</summary>
<namespacedoc><summary>Operations for collections such as lists, arrays, sets, maps and sequences. See also <a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/fsharp-collection-types">F# Collection Types</a> in the F# Language Guide. </summary></namespacedoc>


--------------------
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 ...
<summary>The type of immutable singly-linked lists.</summary>
<remarks>Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or the notation <c>[1;2;3]</c>. Use the values in the <c>List</c> module to manipulate values of this type, or pattern match against the values directly. </remarks>
<exclude />
val toArray: list: 'T list -> 'T[]
<summary>Builds an array from the given list.</summary>
<param name="list">The input list.</param>
<returns>The array containing the elements of the list.</returns>