Skip to content

Instantly share code, notes, and snippets.

@jeffesp
Created June 19, 2019 18:50
Show Gist options
  • Save jeffesp/e298b8638bba365b9d73c212385c7207 to your computer and use it in GitHub Desktop.
Save jeffesp/e298b8638bba365b9d73c212385c7207 to your computer and use it in GitHub Desktop.
open System
open System.IO
open System.Text.RegularExpressions
open System.Linq
open MarkdownDeep
module Seq =
let sortByDesc f s = System.Linq.Enumerable.OrderByDescending(s, new System.Func<'a, 'b>(f))
type Header = {date:DateTime; author:string; title:string; published:bool; layout:string; permalink:string }
type Post = {meta:Header; content:string}
let splitLine line =
let items = (line:string).Split(':')
match items with
| i when i.Length = 2 -> Some((items.[0].Trim(), items.[1].Trim()))
| i -> None
// this feels hacky
let getItem items (key:string) def =
let i = items |> Seq.find(fun i ->
match i with
| Some(i) ->
let (k:string), _ = i
k.ToLower() = key.ToLower()
| None -> false)
match i with
| Some(t) ->
let _, v = t
v
| None -> def
let itemsToHeader (items:seq<(string * string) option>) =
//TODO: the first 4 should fail instead of using bad defaults, the last two are optional
let findItem = getItem items
let date = findItem "date" "2000-01-01" |> DateTime.Parse
let author = findItem "author" "Unknown"
let title = findItem "title" "A Post"
let pub = findItem "published" "false" |> Boolean.Parse
let layout = findItem "layout" ""
let permalink = findItem "permalink" ""
{date = date; author = author; title = title; published = pub; layout = layout; permalink = permalink}
let parseValues heading =
let lines = (heading:string).Split([| "\n"; "\r"; "\r\n"; |], StringSplitOptions.RemoveEmptyEntries) |> Seq.map splitLine
let result = itemsToHeader lines
result
let extractHeader (content:string) =
let headStart = content.IndexOf "---"
let headEnd = content.IndexOf ("---", headStart + 1)
match headStart, headEnd with
| (s,e) when s >= 0 && e > 0 && e > s -> content.Substring(headStart, headEnd - headStart) |> parseValues |> Some
| (s,e) -> None
let extractContent (content:string) =
let bodyStart = content.LastIndexOf("---")
match bodyStart with
| -1 -> None
| _ -> Some(content.Substring(bodyStart + 3))
let splitContent content =
let header = extractHeader content
let body = extractContent content
(header, body)
let createPost = function
| (Some(h), Some(c)) -> { meta = h; content = c}
| (_, _) -> failwith "Header or body not found in post."
let parsePost p =
printfn "Processing post in file: %s" p
use s = File.OpenText p
s.ReadToEnd() |> splitContent |> createPost
let applyLayout rootPath outputType part =
File.ReadAllText(Path.Combine(rootPath, String.Format("{0}.{1}", part, outputType)))
let outputPath (rootPath) (p:Post) =
let transformTitle (title:string) =
title.Replace(" ", "_")
let filep =
match p.meta.permalink with
| path when path = String.Empty ->
p.meta.date.ToString(@".\\yyyy\\MM\\dd\\") + (transformTitle p.meta.title) + ".html"
| _ ->
p.meta.permalink
Path.Combine(rootPath, filep).ToLower()
let getPosts baseDir =
let isPostFile fileName =
File.Exists(fileName)
&& Path.GetExtension(fileName) = ".md"
&& Regex.IsMatch(Path.GetFileNameWithoutExtension(fileName), @"\d\d\d\d-\d\d-\d\d-[A-Za-z0-9%_]")
if not(Directory.Exists(baseDir)) then failwithf "%s does not exist" baseDir
Directory.GetFiles(baseDir) |> Seq.filter isPostFile
let writeContent rootPath posts =
let writePost (rootPath) (p:Post) =
let applyHtml = applyLayout rootPath "html"
let md = new MarkdownDeep.Markdown()
let path = outputPath rootPath p
let head = String.Format((applyHtml "head"), p.meta.title)
let body = String.Format((applyHtml "body"), p.content)
let tail = applyHtml "tail"
match Directory.Exists(path) with
| false -> Directory.CreateDirectory(Path.GetDirectoryName(path)) |> ignore
| true -> ()
use w = File.CreateText(path)
w.Write(head)
w.Write(body)
w.Write(tail)
let writePostToPath = writePost rootPath
posts |> Seq.iter(fun f -> writePostToPath f)
posts
let writeRss rootPath posts =
let applyRss = applyLayout rootPath "rss"
let writeRssItem stream post =
()
use rss = File.CreateText(Path.Combine(rootPath, "rss.xml"))
posts |> Seq.sortByDesc(fun p -> p.meta.date) |> Seq.take(10) |> Seq.iter(fun p -> writeRssItem rss p)
posts
[<EntryPoint>]
let main argv =
match argv with
| [| "compile"; dir |] ->
getPosts dir |> Seq.map(fun f -> parsePost f) |> writeContent dir |> writeRss dir |> ignore
| _ -> printfn "Usage: Thursday.exe compile [path to input files]"
0 // return an integer exit code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment