1 | |
(load "telml.scm")
|
2 | |
(load "libs.scm")
|
| 1 |
(include "types.scm")
|
| 2 |
(include "telml.scm")
|
| 3 |
(include "libs.scm")
|
| 4 |
(include "templates.scm")
|
| 5 |
|
3 | 6 |
(use-modules (srfi srfi-1))
|
4 | 7 |
(use-modules (ice-9 match))
|
5 | 8 |
(use-modules (ice-9 ftw))
|
6 | 9 |
|
7 | |
;; template-ey things and style things
|
8 | 10 |
|
9 | |
(define (atom-element title url date content)
|
10 | |
`(entry
|
11 | |
(title ,title)
|
12 | |
(link (@ (href ,url)))
|
13 | |
(id ,url)
|
14 | |
(updated ,date)
|
15 | |
(content (@ (type "html"))
|
16 | |
,content)))
|
17 | 11 |
|
18 | |
(define (atom-feed posts)
|
19 | |
`(feed (@ (xmlns "htpt://www.w3.org/2005/Atom"))
|
20 | |
(title "what happens when computer")
|
21 | |
(link (@ (href "http://what.happens.when.computer/feed/")
|
22 | |
(rel "self")))
|
23 | |
(link (@ (href "http://what.happens.when.computer/")))
|
24 | |
(id "http://what.happens.when.computer/")
|
25 | |
,(map (lambda (post) (apply atom-element post)) posts)))
|
| 12 |
;; Create a folder if it doesn't exist. (There does not
|
| 13 |
;; appear to be a Guile binding to mkdir -p.)
|
| 14 |
(define (ensure-folder f)
|
| 15 |
(if (not (file-exists? f))
|
| 16 |
(mkdir f)))
|
26 | 17 |
|
27 | |
(define stylesheet
|
28 | |
'((body
|
29 | |
(font-family "Palatino, \"Palatino Linotype\", \"Palatino LT STD\", \"Book Antiqua\", Georgia, serif")
|
30 | |
(font-size 15pt)
|
31 | |
(background-color "#eeeeee")
|
32 | |
(counter-reset sidenote-counter))
|
| 18 |
;; create an output filename from a set of path components,
|
| 19 |
;; creating the necessary folders if needed, as well.
|
| 20 |
(define (outfile chunks)
|
| 21 |
(define (go rest so-far)
|
| 22 |
(ensure-folder so-far)
|
| 23 |
(if (null? rest)
|
| 24 |
(++ so-far "/index.html")
|
| 25 |
(go (cdr rest)
|
| 26 |
(++ so-far "/" (as-string (car rest))))))
|
| 27 |
(go chunks "output"))
|
33 | 28 |
|
34 | |
((= class all)
|
35 | |
(width 800px)
|
36 | |
(margin-left auto)
|
37 | |
(margin-right auto)
|
38 | |
(text-align center))
|
| 29 |
;; create a URL from a set of path components
|
| 30 |
(define (url chunks)
|
| 31 |
(define (go rest so-far)
|
| 32 |
(match rest
|
| 33 |
(() so-far)
|
| 34 |
((x . xs) (go xs (++ so-far "/" (as-string x))))))
|
| 35 |
(go chunks "/"))
|
39 | 36 |
|
40 | |
((= class menu)
|
41 | |
(color "#0f0f0f"))
|
42 | |
|
43 | |
((= class main)
|
44 | |
(padding-right 25%)
|
45 | |
(text-align justify)
|
46 | |
(line-height 150%))
|
47 | |
|
48 | |
((= class menu-index:before)
|
49 | |
(font-family "fira, \"Arial\", \"Helvetica\", sans-serif")
|
50 | |
(content "\"/ \""))
|
51 | |
((= class menu-archive:before)
|
52 | |
(font-family "fira, \"Arial\", \"Helvetica\", sans-serif")
|
53 | |
(content "\"^ \""))
|
54 | |
((= class menu-tags:before)
|
55 | |
(font-family "fira, \"Arial\", \"Helvetica\", sans-serif")
|
56 | |
(content "\"# \""))
|
57 | |
((= class menu-about:before)
|
58 | |
(font-family "fira, \"Arial\", \"Helvetica\", sans-serif")
|
59 | |
(content "\"@ \""))
|
60 | |
|
61 | |
((// (= class navlist) li)
|
62 | |
(display inline)
|
63 | |
(list-style-type none)
|
64 | |
(padding-left 10px)
|
65 | |
(padding-right 20px))
|
66 | |
|
67 | |
((// (= class navlist) ul)
|
68 | |
(padding-left 100px)
|
69 | |
(padding-right 100px))
|
70 | |
|
71 | |
((// (= class archive-list) ul)
|
72 | |
(padding-left 100px)
|
73 | |
(padding-right 100px))
|
74 | |
|
75 | |
((// (= class archive-list) li)
|
76 | |
(list-style-type none))
|
77 | |
|
78 | |
((= class tag-entry:before)
|
79 | |
(content "\"#\""))
|
80 | |
|
81 | |
((// (= class tags) ul)
|
82 | |
(display inline))
|
83 | |
|
84 | |
((= class tag:before)
|
85 | |
(content "\"#\""))
|
86 | |
|
87 | |
((= class tag)
|
88 | |
(font-style italic))
|
89 | |
|
90 | |
((= class sidenote)
|
91 | |
(float right)
|
92 | |
(clear right)
|
93 | |
(margin-right -60%)
|
94 | |
(font-size 12pt)
|
95 | |
(line-height 130%)
|
96 | |
(width 50%))
|
97 | |
|
98 | |
((= class sidenote-number)
|
99 | |
(counter-increment sidenote-counter))
|
100 | |
|
101 | |
((&& (= class sidenote-number:after) (= class sidenote:before))
|
102 | |
(content "counter(sidenote-counter) \" \"")
|
103 | |
(position relative)
|
104 | |
(color "#ff0000"))
|
105 | |
|
106 | |
((= class sidenote-number:after)
|
107 | |
(content "counter(sidenote-counter) \" \"")
|
108 | |
(top -0.5rem)
|
109 | |
(left -0.1rem)
|
110 | |
(vertical-align super)
|
111 | |
(font-size 70%)
|
112 | |
(color "#ff0000")
|
113 | |
(font-size: 0.9rem))
|
114 | |
|
115 | |
((= class sidenote:before)
|
116 | |
(content "counter(sidenote-counter) \". \"")
|
117 | |
(position absolute)
|
118 | |
(-webkit-transform "translateX(-100%) translateX(-0.25rem)")
|
119 | |
(-ms-transform "translateX(-100%) translateX(-0.25rem)")
|
120 | |
(transform "translateX(-100%) translateX(-0.25rem)"))))
|
121 | |
|
122 | |
;; the SXML chunk representing the navigation menu
|
123 | |
(define menu
|
124 | |
(let ((menu-item
|
125 | |
(lambda (name url)
|
126 | |
`(li (@ (class ,(string-append "menu-" name)))
|
127 | |
(a (@ (href ,url)) ,name)))))
|
128 | |
`(ul (@ (class navlist))
|
129 | |
,(menu-item "index" "/")
|
130 | |
,(menu-item "archive" "/archive/")
|
131 | |
,(menu-item "tags" "/tags/")
|
132 | |
,(menu-item "about" "/about/"))))
|
133 | |
|
134 | |
;; The SXML chunk representing a page on the site
|
135 | |
(define (page title content)
|
136 | |
`(html
|
137 | |
(head
|
138 | |
(meta (@ (http-equiv "Content-Type")
|
139 | |
(content "application/xhtml+xml; charset=utf-8;")))
|
140 | |
(style (@ (type "text/css")) ,(scss->css stylesheet))
|
141 | |
(script (@ (type "text/javascript")
|
142 | |
(src "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML")) "")
|
143 | |
(title ,(string-append "what happens when computer: " title)))
|
144 | |
(div (@ (class "all"))
|
145 | |
(div (@ (class "header"))
|
146 | |
(h1 "what happens when computer"))
|
147 | |
(div (@ (class "nav")) ,menu)
|
148 | |
(div (@ (class "main")) ,content)
|
149 | |
(div (@ (class "footer")) "© 2015 getty ritter"))))
|
150 | |
|
151 | |
(define (add-tags chunk tags)
|
152 | |
(define (tag->link tag)
|
153 | |
`(a (@ (href ,(format #f "/tag/~a/" tag))
|
154 | |
(class tag)) ,tag))
|
155 | |
(if (null? tags)
|
156 | |
chunk
|
157 | |
(append chunk
|
158 | |
`((div (@ (class "tags"))
|
159 | |
(ul ,(map tag->link tags)))))))
|
160 | |
|
| 37 |
;; Grab all the posts and their metadata from the posts folder,
|
| 38 |
;; sorted by creation time. This does unnecessary work right
|
| 39 |
;; now; parsing all of them just to get the first metadata tag
|
| 40 |
;; out of them.
|
161 | 41 |
(define (get-all-posts)
|
162 | 42 |
(let* ((files (cddr (file-system-tree "posts")))
|
163 | 43 |
(files-sorted (sort files (lambda (x y)
|
164 | 44 |
(> (stat:mtime (cadr x))
|
165 | 45 |
(stat:mtime (cadr y)))))))
|
166 | 46 |
(map (lambda (file-entry)
|
167 | |
(let ((filename (string-append "posts/" (car file-entry))))
|
168 | |
(cons filename
|
169 | |
(cons (localtime (stat:mtime (cadr file-entry)))
|
170 | |
(car (translate-file filename))))))
|
| 47 |
(let* ((filename (string-append "posts/" (car file-entry)))
|
| 48 |
(metadata (telml-meta (translate-file filename))))
|
| 49 |
(make-post filename
|
| 50 |
(localtime (stat:mtime (cadr file-entry)))
|
| 51 |
(meta-slug metadata)
|
| 52 |
(meta-title metadata)
|
| 53 |
(meta-tags metadata))))
|
171 | 54 |
files-sorted)))
|
172 | 55 |
|
173 | |
(define post-file car)
|
174 | |
(define post-time cadr)
|
175 | |
(define post-slug caddr)
|
176 | |
(define post-title cadddr)
|
177 | |
(define (post-tags l)
|
178 | |
(list-ref l 4))
|
179 | |
|
180 | |
(define meta-slug car)
|
181 | |
(define meta-title cadr)
|
182 | |
(define meta-tags caddr)
|
183 | |
|
184 | |
(define (date->string date)
|
185 | |
(strftime "%Y-%m-%d" date))
|
186 | |
|
| 56 |
;; Turn a post into an <li> element with appropriate
|
| 57 |
;; link and date.
|
187 | 58 |
(define (post->list-elem post)
|
188 | 59 |
(let* ((date (date->string (post-time post)))
|
189 | |
(url (format #f "/~a/~a/" date (post-slug post)))
|
| 60 |
(href (post-url post))
|
190 | 61 |
(title (post-title post)))
|
191 | |
`(li ,(++ date ": ") (a (@ (href ,url)) ,title))))
|
| 62 |
`(li ,(++ date ": ") (a (@ (href ,href)) ,title))))
|
192 | 63 |
|
| 64 |
;; Turn a tag into an <li> element with the appropriate link
|
193 | 65 |
(define (tag->list-elem tag)
|
194 | |
(let ((url (format #f "/tag/~a/" tag)))
|
| 66 |
(let ((url (url (list 'tag tag))))
|
195 | 67 |
`(li (@ (class tag-entry))
|
196 | 68 |
(a (@ (href ,url)) ,tag))))
|
197 | 69 |
|
| 70 |
;; Generate the archive page
|
198 | 71 |
(define (archive)
|
199 | 72 |
(let* ((posts (get-all-posts))
|
200 | 73 |
(list `(div (@ (class archive-list))
|
201 | 74 |
(ul
|
202 | 75 |
,(map post->list-elem posts)))))
|
203 | |
(with-output-to-file "output/archive/index.html"
|
| 76 |
(with-output-to-file (outfile '(archive))
|
204 | 77 |
(lambda ()
|
205 | 78 |
(display (sxml->html5 (page "archive" list)))))))
|
206 | 79 |
|
| 80 |
;; Flatten a list
|
207 | 81 |
(define (concat list)
|
208 | 82 |
(apply append list))
|
209 | 83 |
|
| 84 |
;; Get the list of all tags
|
210 | 85 |
(define (get-tag-list posts)
|
211 | 86 |
(let ((c (concat (map post-tags posts))))
|
212 | 87 |
(delete-duplicates (sort c string<?))))
|
213 | 88 |
|
| 89 |
;; Get the list of all posts that are tagged with a given tag
|
214 | 90 |
(define (get-posts-with-tag tag posts)
|
215 | 91 |
(filter (lambda (post) (member tag (post-tags post))) posts))
|
216 | 92 |
|
| 93 |
;; Generate all tag-related pages
|
217 | 94 |
(define (tags)
|
218 | |
|
219 | 95 |
(let* ((posts (get-all-posts))
|
220 | 96 |
(tags (get-tag-list posts))
|
221 | 97 |
(tag-list `(div (@ (class archive-list))
|
|
224 | 100 |
|
225 | 101 |
(define (mk-tag-page tag)
|
226 | 102 |
(let* ((ps (get-posts-with-tag tag posts))
|
227 | |
(list `(div (@ (class archive-list))
|
228 | |
"pages tagged "
|
229 | |
(span (@ (class tag)) ,tag)
|
230 | |
":"
|
231 | |
(ul
|
232 | |
,(map post->list-elem ps)))))
|
233 | |
(ensure-folder (format #f "output/tag/~a" tag))
|
234 | |
(with-output-to-file (format #f "output/tag/~a/index.html" tag)
|
| 103 |
(content `(div (@ (class archive-list))
|
| 104 |
"pages tagged "
|
| 105 |
(span (@ (class tag)) ,tag)
|
| 106 |
":"
|
| 107 |
(ul
|
| 108 |
,(map post->list-elem ps)))))
|
| 109 |
(with-output-to-file (outfile (list 'tag tag))
|
235 | 110 |
(lambda ()
|
236 | |
(display (sxml->html5 (page tag list)))))))
|
| 111 |
(display (sxml->html5 (page tag content)))))))
|
237 | 112 |
|
238 | |
(with-output-to-file (format #f "output/tags/index.html")
|
| 113 |
(with-output-to-file (outfile '(tags))
|
239 | 114 |
(lambda ()
|
240 | 115 |
(display (sxml->html5 (page "tags" tag-list)))))
|
241 | 116 |
|
242 | 117 |
(map mk-tag-page tags)))
|
243 | 118 |
|
244 | |
(define (ensure-folder f)
|
245 | |
(if (not (file-exists? f))
|
246 | |
(mkdir f)))
|
247 | |
|
248 | |
;; actually load and generate the relevant files
|
| 119 |
;; Load a telml page and turn it into an html page
|
249 | 120 |
(define (main pg file out)
|
250 | 121 |
(let* ((page-source (translate-file file))
|
251 | |
(telml (cdr page-source))
|
252 | |
(meta (car page-source))
|
| 122 |
(telml (telml-content page-source))
|
| 123 |
(meta (telml-meta page-source))
|
253 | 124 |
(date (localtime (stat:mtime (stat file))))
|
254 | 125 |
(ndate (date->string date))
|
255 | |
(title (if (not (null? meta)) (meta-title meta) pg))
|
256 | |
(outfile (if out out
|
257 | |
(format #f "output/~a/~a/index.html" ndate (meta-slug meta))))
|
| 126 |
(title (if meta (meta-title meta) pg))
|
| 127 |
(target (if out out
|
| 128 |
(outfile (list ndate (meta-slug meta)))))
|
258 | 129 |
(display (sxml->html5 (page title (add-tags telml (meta-tags meta))))))
|
259 | |
(if (not out)
|
260 | |
(begin
|
261 | |
(ensure-folder (format #f "output/~a" ndate))
|
262 | |
(ensure-folder (format #f "output/~a/~a" ndate (car meta)))))
|
263 | |
(format #t "printing to ~a\n" outfile)
|
264 | |
(with-output-to-file outfile
|
| 130 |
(format #t "printing to ~a\n" target)
|
| 131 |
(with-output-to-file target
|
265 | 132 |
(lambda () (format #t "~a\n" display)))))
|
266 | 133 |
|
| 134 |
;; Figure out which page is supposed to be generated
|
267 | 135 |
(define (dispatch pg files)
|
268 | |
(cond ((equal? pg "index")
|
269 | |
(main "index" (car files) "output/index.html"))
|
270 | |
((equal? pg "post")
|
271 | |
(map
|
272 | |
(lambda (f) (main "post" f #f))
|
273 | |
files))
|
274 | |
((equal? pg "archive")
|
275 | |
(archive))
|
276 | |
((equal? pg "tags")
|
277 | |
(tags))
|
278 | |
((equal? pg "about")
|
279 | |
(main "about" "pages/about.telml" "output/about/index.html"))))
|
| 136 |
(match pg
|
| 137 |
("index" (main "index" (car files) (outfile '())))
|
| 138 |
("post" (map (lambda (f) (main "post" f #f)) files))
|
| 139 |
("archive" (archive))
|
| 140 |
("tags" (tags))
|
| 141 |
("about" (main "about" "pages/about.telml" (outfile '(about))))))
|
280 | 142 |
|
281 | 143 |
(let ((args (cdr (command-line))))
|
282 | 144 |
(cond ((= (length args) 0)
|