blob: 9359d38fa28ef2351f76ba3cffb4a98d47f157a9 (
plain) (
blame)
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
{ depot, ... }:
let
inherit (depot.third_party.nixpkgs)
lib
;
inherit (depot.users.sterni.nix)
url
fun
string
;
inherit (depot.web.bubblegum)
pathInfo
scriptName
respond
absolutePath
;
# substituted using substituteAll in default.nix
blogdir = "@blogdir@";
# blogdir = toString ./posts; # for local testing
parseDate = post:
let
matched = builtins.match "/?([0-9]+)-([0-9]+)-([0-9]+)-.+" post;
in
if matched == null
then [ 0 0 0 ]
else builtins.map builtins.fromJSON matched;
parseTitle = post:
let
matched = builtins.match "/?[0-9]+-[0-9]+-[0-9]+-(.+).html" post;
in
if matched == null
then "no title"
else builtins.head matched;
dateAtLeast = a: b:
builtins.all fun.id
(lib.zipListsWith (partA: partB: partA >= partB) a b);
byPostDate = a: b:
dateAtLeast (parseDate a) (parseDate b);
posts = builtins.sort byPostDate
(builtins.attrNames
(lib.filterAttrs (_: v: v == "regular")
(builtins.readDir blogdir)));
generic = { title, inner, ... }: ''
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>${title}</title>
<style>a:link, a:visited { color: blue; }</style>
</head>
<body>
${inner}
</body>
</html>
'';
index = posts: ''
<main>
<h1>blog posts</h1>
<ul>
'' + lib.concatMapStrings (post: ''
<li>
<a href="${absolutePath (url.encode {} post)}">${parseTitle post}</a>
</li>
'') posts + ''
</ul>
</main>
'';
formatDate =
let
# Assume we never deal with years < 1000
formatDigit = d: string.fit {
char = "0"; width = 2;
} (toString d);
in lib.concatMapStringsSep "-" formatDigit;
post = title: post: ''
<main>
<h1>${title}</h1>
<div id="content">
${builtins.readFile (blogdir + "/" + post)}
</div>
</main>
<footer>
<p>Posted on ${formatDate (parseDate post)}</p>
<nav><a href="${scriptName}">index</a></nav>
</footer>
'';
validatePathInfo = pathInfo:
let
chars = string.toChars pathInfo;
in builtins.length chars > 1
&& !(builtins.elem "/" (builtins.tail chars));
response =
if pathInfo == "/"
then {
title = "blog";
status = 200;
inner = index posts;
}
else if !(validatePathInfo pathInfo)
then {
title = "Bad Request";
status = 400;
inner = "No slashes in post names 😡";
}
# CGI should already url.decode for us
else if builtins.pathExists (blogdir + "/" + pathInfo)
then rec {
title = parseTitle pathInfo;
status = 200;
inner = post title pathInfo;
} else {
title = "Not Found";
status = 404;
inner = "<h1>404 — not found</h1>";
};
in
respond response.status {
"Content-type" = "text/html";
} (generic response)
|