Skip to content

Commit 755d45d

Browse files
committed
Primitive Paket + F# Formatting runner to replace Jekyll
1 parent cac7815 commit 755d45d

7 files changed

+258
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.paket/paket.exe
2+
packages/
3+
paket-files/
14
_site/
25
*~
36
.DS_Store

.paket/paket.bootstrapper.exe

13 KB
Binary file not shown.

build.cmd

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@echo off
2+
cls
3+
4+
.paket\paket.bootstrapper.exe
5+
if errorlevel 1 (
6+
exit /b %errorlevel%
7+
)
8+
9+
.paket\paket.exe restore
10+
if errorlevel 1 (
11+
exit /b %errorlevel%
12+
)
13+
14+
packages\FAKE\tools\FAKE.exe build.fsx %*

build.fsx

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// --------------------------------------------------------------------------------------
2+
// FAKE build script that builds the web site & runs it in a local HTTP server
3+
// --------------------------------------------------------------------------------------
4+
5+
#r "packages/FAKE/tools/FakeLib.dll"
6+
#I "packages/FSharp.Compiler.Service/lib/net40"
7+
#I "packages/FSharp.Formatting/lib/net40"
8+
#r "FSharp.Compiler.Service.dll"
9+
#r "FSharp.Literate.dll"
10+
#r "FSharp.CodeFormat.dll"
11+
#load "paket-files/www.fssnip.net/raw/pj/HttpServer.fs"
12+
open System
13+
open System.IO
14+
open System.Collections.Generic
15+
open System.Text.RegularExpressions
16+
open Fake
17+
open FSharp.Literate
18+
open FSharp.Http
19+
20+
// --------------------------------------------------------------------------------------
21+
// Configuration
22+
// --------------------------------------------------------------------------------------
23+
24+
/// Output directory for the generated site
25+
let site = "_site"
26+
27+
/// Port for the HTTP server to start
28+
let port = 8080
29+
30+
/// Directories that should be copied to the output directory
31+
let copy = [ "css"; "img"; "js"; "about/files" ]
32+
33+
/// Directories and files that should be skipped (when looking
34+
/// for source files)
35+
let exclusions = [ "_layouts"; "packages"; "README.md"; "_site" ]
36+
37+
// --------------------------------------------------------------------------------------
38+
// Implementation
39+
// --------------------------------------------------------------------------------------
40+
41+
let (-/-) a b = Path.Combine(a, b)
42+
let root = __SOURCE_DIRECTORY__
43+
Environment.CurrentDirectory <- root
44+
let siteDir = root -/- site
45+
let exclusionSet =
46+
set [ for n in exclusions -> Path.Combine(root, n) ]
47+
48+
/// Get a sequence of all *.md and *.html files that are not excluded
49+
let rec enumerateSiteFiles current = seq {
50+
let files =
51+
Seq.concat [ Directory.GetFiles(current, "*.md")
52+
Directory.GetFiles(current, "*.html") ]
53+
for file in files do
54+
if not (exclusionSet.Contains(file)) then yield file
55+
for dir in Directory.GetDirectories(current) do
56+
if not (exclusionSet.Contains(dir)) then
57+
yield! enumerateSiteFiles dir }
58+
59+
/// Search for Jekyll header - file can start with "---" delimited block
60+
/// that contains "key: value" pairs. Returns dictionary with key-value
61+
/// pairs, together with the body
62+
let parseHeader (lines:string[]) =
63+
if lines.[0] = "---" then
64+
let endIndex = lines |> Seq.skip 1 |> Seq.findIndex ((=) "---")
65+
let header = lines.[1 .. endIndex]
66+
let body = lines.[endIndex + 2 .. lines.Length - 1]
67+
let header =
68+
[ for line in header do
69+
// A line can continue with more things if they are indented.. we ignore this
70+
if line.TrimStart() <> line then printfn "Ignoring: %A" line
71+
else
72+
let split = line.IndexOf(':')
73+
let key = line.Substring(0, split).Trim()
74+
let value = line.Substring(split + 1, line.Length - split - 1).Trim()
75+
yield key, value ] |> dict
76+
Some(header), body |> String.concat "\n"
77+
else
78+
None, lines |> String.concat "\n"
79+
80+
/// Replace "{{ page.<key> }}" in the template with the values specified by 'headers'
81+
/// Additionally, replaces "{{ content }}" with the specified body.
82+
let replaceKeys template body (header:IDictionary<string, string>) =
83+
Regex.Replace(template, "\{\{\\s*(?<key>[a-z\-\.]*)\\s*\}\}", fun (m:Match) ->
84+
let key = m.Groups.["key"].Value
85+
if key = "content" then body
86+
elif key.StartsWith("page.") then
87+
match header.TryGetValue(key.Substring("page.".Length)) with
88+
| true, value -> value
89+
| _ -> ""
90+
else "")
91+
92+
/// Takes html & headers (result of 'parseHeader') and applies
93+
/// the template specified by the 'layout' key.
94+
let rec applyTemplates html (header:IDictionary<string, string>) =
95+
let templateFile = root -/- "_layouts" -/- (header.["layout"] + ".html")
96+
let subheader, template = parseHeader(File.ReadAllLines(templateFile))
97+
let body = replaceKeys template html header
98+
match subheader with
99+
| None -> body
100+
| Some subheader ->
101+
let header =
102+
[ for (KeyValue(k, v)) in header do yield k, v
103+
for (KeyValue(k, v)) in subheader do yield k, v ] |> dict
104+
applyTemplates body header
105+
106+
107+
let generate () =
108+
for file in enumerateSiteFiles root do
109+
// Get name for the output file
110+
printfn "Parsing: %s" file
111+
let output = file.Replace(root, siteDir)
112+
113+
// Process Markdown or HTML
114+
let header, html, output =
115+
if file.EndsWith(".md") then
116+
let header, body = parseHeader(File.ReadAllLines(file))
117+
118+
// Process the file in a temp directory & read the HTML
119+
let temp = Path.GetTempFileName()
120+
File.WriteAllText(temp, body)
121+
Literate.ProcessMarkdown(temp, output=temp + ".html")
122+
let html = File.ReadAllText(temp + ".html")
123+
File.Delete(temp)
124+
File.Delete(temp + ".html")
125+
126+
// Return HTML & new output file name (.md -> .html)
127+
let output = output.Substring(0, output.Length - 3) + ".html"
128+
header, html, output
129+
else
130+
let header, html = parseHeader(File.ReadAllLines(file))
131+
header, html, output
132+
133+
// Apply template (if any) and write to the output
134+
let outputHtml =
135+
match header with
136+
| None -> html
137+
| Some header -> applyTemplates html header
138+
139+
printfn "Writing: %s" output
140+
ensureDirectory (Path.GetDirectoryName(output))
141+
File.WriteAllText(output, outputHtml)
142+
143+
let copy() =
144+
for subdir in copy do
145+
ensureDirectory (root -/- site -/- subdir)
146+
CopyRecursive (root -/- subdir) (root -/- site -/- subdir) true |> ignore
147+
148+
// --------------------------------------------------------------------------------------
149+
// Generate assembly info files with the right version & up-to-date information
150+
151+
152+
// Delete the '_site' folder & re-create it
153+
Target "Clean" (fun _ ->
154+
CleanDirs [site]
155+
Directory.CreateDirectory(siteDir) |> ignore
156+
)
157+
158+
// Copy all static files to the output folder
159+
Target "Copy" (fun _ ->
160+
copy()
161+
)
162+
163+
// Process the site & generate the output HTML
164+
Target "Generate" (fun _ ->
165+
generate()
166+
)
167+
168+
// Start HTTP server with the web site
169+
Target "Run" (fun _ ->
170+
let url = sprintf "http://localhost:%d/" port
171+
let server = HttpServer.Start(url, root -/- site)
172+
printfn "Starting web server at %s" url
173+
System.Diagnostics.Process.Start(url) |> ignore
174+
System.Console.ReadLine() |> ignore
175+
server.Stop()
176+
)
177+
178+
Target "All" DoNothing
179+
180+
"Clean"
181+
==> "Copy"
182+
==> "Generate"
183+
==> "Run"
184+
==> "All"
185+
186+
Run <| getBuildParamOrDefault "target" "All"
187+
188+
// To run things in F# Interactive:
189+
// * copy ()
190+
// * generate ()

build.sh

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
if test "$OS" = "Windows_NT"
3+
then
4+
# use .Net
5+
6+
.paket/paket.bootstrapper.exe
7+
exit_code=$?
8+
if [ $exit_code -ne 0 ]; then
9+
exit $exit_code
10+
fi
11+
12+
.paket/paket.exe restore
13+
exit_code=$?
14+
if [ $exit_code -ne 0 ]; then
15+
exit $exit_code
16+
fi
17+
18+
packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx
19+
else
20+
# use mono
21+
mono .paket/paket.bootstrapper.exe
22+
exit_code=$?
23+
if [ $exit_code -ne 0 ]; then
24+
exit $exit_code
25+
fi
26+
27+
mono .paket/paket.exe restore
28+
exit_code=$?
29+
if [ $exit_code -ne 0 ]; then
30+
exit $exit_code
31+
fi
32+
33+
mono packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx
34+
fi

paket.dependencies

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source https://nuget.org/api/v2
2+
3+
nuget FSharp.Formatting
4+
nuget FAKE
5+
6+
http http://www.fssnip.net/raw/pj HttpServer.fs

paket.lock

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
NUGET
2+
remote: https://nuget.org/api/v2
3+
specs:
4+
FAKE (3.14.7)
5+
FSharp.Compiler.Service (0.0.82)
6+
FSharp.Formatting (2.6.3)
7+
FSharp.Compiler.Service (>= 0.0.81)
8+
HTTP
9+
remote: http://www.fssnip.net/raw/pj
10+
specs:
11+
HttpServer.fs

0 commit comments

Comments
 (0)