| 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)
|