// thinkbeforecoding

Full F# Blog

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

this post is part of the F# Advent Calendar 2018

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

1: 
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:

1: 
2: 
3: 
#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:

1: 
#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:

1: 
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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
#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.Literate //" 

#load "../.fake/blog.fsx/intellisense.fsx" 
1: 
open FSharp.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:

1: 
2: 
3: 
let md = """# Markdown is cool
especially with *FSharp.Formatting* ! """
            |> FSharp.Markdown.Markdown.TransformHtml

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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
let snipet  =
    """
    (** # *F# literate* in action *)
    printfn "Hello"
    """
let parse source =
    let doc = 
      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"
 
      let systemRuntime = "-r:System.Runtime"
      Literate.ParseScriptString(
                  source, 
                  compilerOptions = String.concat " " [systemRuntime; fsharpCoreDir; fcsDir; fcs],
                  fsiEvaluator = FSharp.Literate.FsiEvaluator([|fsharpCoreDir; fcsDir; fcs|]))
    FSharp.Literate.Literate.FormatLiterateNodes(doc, OutputKind.Html, "", true, true)
let format (doc: LiterateDocument) =
    Formatting.format doc.MarkdownDocument true OutputKind.Html
let fs =
    snipet 
    |> parse
    |> format

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 class="id">printfn</span> <span class="s">&quot;Hello&quot;</span>
    
</code></pre></td>
</tr>
</table>
"

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:

1: 
2: 
3: 
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:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let values  = 
    """
(** # code execution *)
let square x = x * x
let v = square 3
(** the value is: *)
(*** include-value: v ***)"""
    |> parse
    |> format

and the result is:

"<h1><a name="code-execution" class="anchor" href="#code-execution">code execution</a></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, '1', 1)" onmouseover="showTip(event, '1', 1)" class="fn">square</span> <span onmouseout="hideTip(event, '2', 2)" onmouseover="showTip(event, '2', 2)" class="id">x</span> <span class="o">=</span> <span onmouseout="hideTip(event, '2', 3)" onmouseover="showTip(event, '2', 3)" class="id">x</span> <span class="pn">*</span> <span onmouseout="hideTip(event, '2', 4)" onmouseover="showTip(event, '2', 4)" class="id">x</span>
<span class="k">let</span> <span onmouseout="hideTip(event, '3', 5)" onmouseover="showTip(event, '3', 5)" class="id">v</span> <span class="o">=</span> <span onmouseout="hideTip(event, '1', 6)" onmouseover="showTip(event, '1', 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>
"

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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let output  = 
    """
(** # printing *)
let square x = x * x
(*** define-output: result ***)
printfn "result: %d" (square 3)
(** the value is: *)
(*** include-output: result ***)"""
    |> parse
    |> format

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

"<h1><a name="printing" class="anchor" href="#printing">printing</a></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, '1', 1)" onmouseover="showTip(event, '1', 1)" class="fn">square</span> <span onmouseout="hideTip(event, '2', 2)" onmouseover="showTip(event, '2', 2)" class="id">x</span> <span class="o">=</span> <span onmouseout="hideTip(event, '2', 3)" onmouseover="showTip(event, '2', 3)" class="id">x</span> <span class="pn">*</span> <span onmouseout="hideTip(event, '2', 4)" onmouseover="showTip(event, '2', 4)" class="id">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 class="id">printfn</span> <span class="s">&quot;result: %d&quot;</span> <span class="pn">(</span><span onmouseout="hideTip(event, '1', 5)" onmouseover="showTip(event, '1', 5)" class="id">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>
"

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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
open Fable.React
open Fable.React.Props
open FSharp.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

1: 
2: 
3: 
4: 
5: 
6: 
let render html =
  fragment [] [ 
    RawText "<!doctype html>"
    RawText "\n" 
    html ]
  |> Fable.ReactServer.renderToString 

let's use it :

1: 
2: 
3: 
4: 
5: 
let myblog =
    { title = "super post"
      content = Markdown.TransformHtml "# **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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
#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 = MD5CryptoServiceProvider.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!

namespace FSharp
namespace FSharp.Data
namespace FSharp.Literate
val md : obj
namespace FSharp.Markdown
val snipet : string
val parse : source:'a -> 'b
val source : 'a
val doc : obj
val fsharpCoreDir : string
val fcsDir : string
val fcs : string
val systemRuntime : string
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>

--------------------
new : ?options:string Microsoft.FSharp.Core.[] * ?fsiObj:obj -> FsiEvaluator
type OutputKind =
  | Html
  | Latex
union case OutputKind.Html: OutputKind
val format : doc:LiterateDocument -> string
val doc : LiterateDocument
Multiple items
type LiterateDocument =
  new : paragraphs:MarkdownParagraphs * formattedTips:string * links:IDictionary<string,(string * string option)> * source:LiterateSource * sourceFile:string * errors:seq<SourceError> -> LiterateDocument
  override Equals : other:obj -> bool
  override GetHashCode : unit -> int
  member With : ?paragraphs:MarkdownParagraphs * ?formattedTips:string * ?definedLinks:IDictionary<string,(string * string option)> * ?source:LiterateSource * ?sourceFile:string * ?errors:seq<SourceError> -> LiterateDocument
  member DefinedLinks : IDictionary<string,(string * string option)>
  member Errors : seq<SourceError>
  member FormattedTips : string
  member MarkdownDocument : MarkdownDocument
  member Paragraphs : MarkdownParagraphs
  member Source : LiterateSource
  ...

--------------------
new : paragraphs:FSharp.Markdown.MarkdownParagraphs * formattedTips:string * links:System.Collections.Generic.IDictionary<string,(string * string option)> * source:LiterateSource * sourceFile:string * errors:Microsoft.FSharp.Collections.seq<FSharp.CodeFormat.SourceError> -> LiterateDocument
module Formatting

from FSharp.Literate
val format : doc:FSharp.Markdown.MarkdownDocument -> generateAnchors:bool -> outputKind:OutputKind -> string
property LiterateDocument.MarkdownDocument: FSharp.Markdown.MarkdownDocument
val fs : obj
val tips : 'a
val doc : 'a
val values : obj
val output : obj
namespace Fable
namespace Fable.React
module Props

from Fable.React
type Post =
  {title: obj;
   content: obj;}
Post.title: obj
Post.content: obj
val template : post:Post -> ReactElement
val post : Post
val html : props:Microsoft.FSharp.Collections.seq<IHTMLProp> -> children:Microsoft.FSharp.Collections.seq<ReactElement> -> ReactElement
union case HTMLAttr.Lang: string -> HTMLAttr
val head : props:Microsoft.FSharp.Collections.seq<IHTMLProp> -> children:Microsoft.FSharp.Collections.seq<ReactElement> -> ReactElement
val title : props:Microsoft.FSharp.Collections.seq<IHTMLProp> -> children:Microsoft.FSharp.Collections.seq<ReactElement> -> ReactElement
val str : s:string -> ReactElement
val body : props:Microsoft.FSharp.Collections.seq<IHTMLProp> -> children:Microsoft.FSharp.Collections.seq<ReactElement> -> ReactElement
union case HTMLNode.RawText: string -> HTMLNode
val html : 'a (requires 'a :> ReactElement)
val fragment : props:Microsoft.FSharp.Collections.seq<IFragmentProp> -> children:Microsoft.FSharp.Collections.seq<ReactElement> -> ReactElement
module ReactServer

from Fable
val renderToString : htmlNode:ReactElement -> string
val myblog : obj
namespace System
namespace System.Security
namespace System.Security.Cryptography
val feedXml : string
type Rss = obj
val links : 'a Microsoft.FSharp.Core.[]
val entry : title:'a -> link:'b -> date:'c -> content:'d -> 'e
val title : 'a
val link : 'a
val date : 'a
val content : 'a
val md5Csp : 'a
Multiple items
type MD5CryptoServiceProvider =
  inherit MD5
  new : unit -> MD5CryptoServiceProvider
  member Initialize : unit -> unit

--------------------
MD5CryptoServiceProvider() : MD5CryptoServiceProvider
val md5 : obj
Multiple items
union case HTMLNode.Text: string -> HTMLNode

--------------------
namespace System.Text
type Encoding =
  member BodyName : string
  member Clone : unit -> obj
  member CodePage : int
  member DecoderFallback : DecoderFallback with get, set
  member EncoderFallback : EncoderFallback with get, set
  member EncodingName : string
  member Equals : value:obj -> bool
  member GetByteCount : chars:char[] -> int + 5 overloads
  member GetBytes : chars:char[] -> byte[] + 7 overloads
  member GetCharCount : bytes:byte[] -> int + 3 overloads
  ...
property Text.Encoding.UTF8: Text.Encoding
Text.Encoding.GetBytes(s: string) : byte Microsoft.FSharp.Core.[]
Text.Encoding.GetBytes(chars: char Microsoft.FSharp.Core.[]) : byte Microsoft.FSharp.Core.[]
Text.Encoding.GetBytes(chars: ReadOnlySpan<char>, bytes: Span<byte>) : int
Text.Encoding.GetBytes(s: string, index: int, count: int) : byte Microsoft.FSharp.Core.[]
Text.Encoding.GetBytes(chars: char Microsoft.FSharp.Core.[], index: int, count: int) : byte Microsoft.FSharp.Core.[]
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 Microsoft.FSharp.Core.[], byteIndex: int) : int
Text.Encoding.GetBytes(chars: char Microsoft.FSharp.Core.[], charIndex: int, charCount: int, bytes: byte Microsoft.FSharp.Core.[], byteIndex: int) : int
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...
Multiple items
type String =
  new : value:char[] -> string + 8 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool + 3 overloads
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 3 overloads
  member EnumerateRunes : unit -> StringRuneEnumerator
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  ...

--------------------
String(value: char Microsoft.FSharp.Core.[]) : String
String(value: nativeptr<char>) : String
String(value: nativeptr<sbyte>) : String
String(value: ReadOnlySpan<char>) : String
String(c: char, count: int) : String
String(value: char Microsoft.FSharp.Core.[], startIndex: int, length: int) : String
String(value: nativeptr<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
type DateTimeOffset =
  struct
    new : dateTime:DateTime -> DateTimeOffset + 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
    ...
  end

--------------------
DateTimeOffset ()
DateTimeOffset(dateTime: DateTime) : DateTimeOffset
DateTimeOffset(ticks: int64, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(dateTime: DateTime, 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:'a -> 'b
val entries : 'a
property DateTimeOffset.UtcNow: DateTimeOffset
union case HTMLAttr.List: string -> HTMLAttr