1 ;; -*- geiser-scheme-implementation: guile -*-
3 (use-modules (srfi srfi-1
) ; list stuff
4 (srfi srfi-11
) ; let-values
5 (srfi srfi-19
) ; date functions
7 (ice-9 ftw
) ; file system
8 (ice-9 match
) ; match-lambda
11 (haunt reader commonmark
)
14 (haunt post
) ;post-file-name
16 (haunt html
) ;sxml->html
17 (haunt utils
) ;absolute-file-name
23 (make-skribe-reader #:modules
'((haunt skribe utils
)
28 (define (photo-snippet post
)
29 ;; TODO: derive alt-text from "photo" metadata
30 (let ((meta (post-ref post
'photo
)))
32 `(img (@ (class "stretch full")
33 (src ,(string-append "/images/posts/" meta
))))
37 (define (license-snippet post
)
38 (let ((meta (post-ref post
'license
)))
40 `(div (@ (class "fineprint"))
41 (div (@ (class "license"))
43 (href "http://creativecommons.org/licenses/by-sa/3.0/"))
44 (img (@ (alt "Creative Commons License")
45 (style "border-width:0")
46 (src "http://i.creativecommons.org/l/by-sa/3.0/80x15.png"))))
47 (span (@ (xmlns:dct
"http://purl.org/dc/terms/")
48 (href "http://purl.org/dc/dcmitype/StillImage")
49 (property "dct:title")
51 ,(post-ref post
'title
))
53 (a (@ (xmlns:cc
"http://creativecommons.org/ns#")
54 (href "http://elephly.net")
55 (property "cc:attributionName")
56 (rel "cc:attributionURL"))
58 " is licensed under a "
60 (href "http://creativecommons.org/licenses/by-sa/3.0/"))
61 "Creative Commons Attribution-ShareAlike 3.0 Unported License")))
66 (define (drop-extension file-name
)
68 (drop-right (string-split file-name
#\.
) 1) ""))
71 (define (post/file-base-name post
)
72 (drop-extension (basename (post-file-name post
))))
75 (define* (read-page reader file-name layout target
)
76 "Read a page object from FILE-NAME using READER and wrap it in LAYOUT."
77 (let-values (((metadata sxml
) ((reader-proc reader
) file-name
)))
79 (layout #f
(assoc-ref metadata
'title
) sxml
) ; site is #f
83 (define* (wrap-pages directory dest layout readers
)
84 "Read all files in DIRECTORY, wrap them with the given LAYOUT and
85 place them in the directory DEST."
86 (define enter?
(const #t
))
88 ;; remove "directory" from the front of "file-name", prepend "dest"
89 (define (leaf file-name stat memo
)
90 (let* ((reader (find (cut reader-match?
<> file-name
) readers
))
91 (base-length (length (file-name-components directory
)))
92 (dest* (file-name-components dest
))
93 (file-name* (file-name-components file-name
))
94 (target (join-file-name-components
95 (append dest
* (drop file-name
* base-length
))))
96 (target-name (string-append (drop-extension target
) ".html")))
98 (cons (read-page reader file-name default-layout target-name
) memo
)
99 (error "no reader available for page: " file-name
))))
101 (define (noop file-name stat memo
) memo
)
103 (define (err file-name stat errno memo
)
104 (error "layout processing failed with errno: " file-name errno
))
106 (file-system-fold enter? leaf noop noop noop err
'() directory
))
109 (define* (latest-blog-post #:key theme
)
110 "Return a builder procedure that copies the latest blog post to posts/latest.html."
112 (make-page "posts/latest.html"
113 ((@@ (haunt builder blog
) render-post
)
116 (first (posts/reverse-chronological posts
)))
119 (define* (pin-blog-post file-name pinned-name
#:key theme
)
120 "Return a builder procedure that copies FILE-NAME as PINNED-NAME."
122 (make-page pinned-name
123 ((@@ (haunt builder blog
) render-post
) theme site
125 (equal?
(post-file-name post
) file-name
))
129 (define* (tag-pages #:key
130 (theme elephly-theme
)
132 (filter posts
/reverse-chronological
))
133 "Return a builder procedure that renders a list page for every tag
134 used in a post. All arguments are optional:
136 PREFIX: The directory in which to write the posts
137 FILTER: The procedure called to manipulate the posts list before rendering"
139 (define (tag-list tag posts all-posts
)
140 (define (render-list title posts prefix
)
141 (let ((body ((theme-collection-template theme
)
142 site title posts prefix all-posts tag
)))
143 ((theme-layout theme
) site title body
)))
144 (make-page (string-append "tags/" tag
".html")
145 (render-list (string-append "Posts tagged ‘" tag
"’")
149 (let ((tag-groups (posts/group-by-tag posts
)))
151 ((tag . tagged-posts
) (tag-list tag tagged-posts posts
)))
155 (define (tag-links posts
)
156 "Generate an alphabetically sorted list of links to tagged posts.
157 The link text consists of the tag name and the number of tagged posts
159 `(ul (@ (class "tags"))
162 `(li (a (@ (href ,(string-append "/tags/" tag
".html")))
163 ,(string-append tag
" (" (number->string
(length posts
)) ")")))))
165 (sort (posts/group-by-tag posts
)
166 (lambda (a b
) (string<?
(car a
) (car b
)))))))
168 (define (date->string
* date
)
169 "Convert DATE to human readable string."
170 (date->string date
"~B ~e, ~Y"))
174 (define (default-layout site title body
)
177 (meta (@ (http-equiv "Content-Type") (content "text/html; charset=UTF-8")))
178 (meta (@ (http-equiv "Content-Language") (content "en")))
179 (meta (@ (name "author") (content "Ricardo Wurmus")))
180 (meta (@ (name "viewport") (content "width=device-width")))
181 (title ,(or title
(if site
(site-title site
) "Rekado")))
182 (link (@ (rel "openid2.provider")
183 (href "https://openid.stackexchange.com/openid/provider")))
184 (link (@ (rel "openid2.local_id")
185 (href "https://openid.stackexchange.com/user/0f8f09d4-a2f3-429f-8752-b53a328c255c")))
186 (link (@ (rel "stylesheet")
189 (href "/css/reset.css")))
190 (link (@ (rel "stylesheet")
193 (href "/css/screen.css")))
194 (link (@ (rel "shortcut icon")
195 (href "https://elephly.net/favicon.ico")))
196 (script (@ (type "text/javascript") (src "/js/deobfuscate.js")))
197 (script (@ (type "text/javascript") (src "/js/hyphenator/Hyphenator.js")))
198 (script (@ (type "text/javascript")) "Hyphenator.run();"))
200 (div (@ (id "index"))
201 (a (@ (href "/") (title "show index"))
203 (src "/images/logo.png")))))
204 (div (@ (id "page") (class "hyphenate"))
208 (define elephly-theme
209 (theme #:name
"Elephly"
210 #:layout default-layout
211 #:post-template
; TODO: should also take "site" for "site-post-slug"
213 ;; TODO: similar version below for collection-template
214 (define (post-uri post
)
215 (string-append "/posts/" (%make-slug post
) ".html"))
216 `((h1 (@ (class "donthyphenate")) ,(post-ref post
'title
))
217 (div (@ (class "time"))
218 (a (@ (href ,(post-uri post
)))
219 ,(date->string
* (post-date post
))))
220 ,(photo-snippet post
)
222 ,(license-snippet post
)
223 (p (@ (class "back"))
224 (a (@ (href "/posts"))
226 (div (@ (id "comments"))
227 (p "Comments? Then send me an email! Interesting comments may be published here."))))
228 #:collection-template
229 (lambda* (site title posts prefix
#:optional all-posts tag
)
230 (define (post-uri post
)
231 (string-append "/" (or prefix
"") (site-post-slug site post
) ".html"))
232 `((h1 (@ (class "donthyphenate"))
235 `(a (@ (href ,(string-append "/feeds/tags/" tag
".xml")))
236 (img (@ (class "feed-icon")
237 (src "/images/feed.png")
238 (alt "subscribe to atom feed"))))
240 (ul (@ (class "archive"))
241 ,@(map (lambda (post)
243 (a (@ (href ,(post-uri post
)))
244 ,(post-ref post
'title
))))
247 ;; TODO: I really want this to be computed only once for
249 ,(tag-links (or all-posts posts
))
251 '(a (@ (href "/posts"))
256 ;; needed for post template, because the site is not passed to the
258 (define %make-slug post
/file-base-name
)
260 (site #:title
"Rekado"
261 #:domain
"http://elephly.net"
263 '((author .
"Ricardo Wurmus")
264 (email .
"rekado+web@elephly.net"))
265 #:make-slug %make-slug
266 #:readers
(list commonmark-reader skribe-reader html-reader
)
267 #:builders
(list (lambda (args . rest
)
268 (wrap-pages "non-posts" "." default-layout
269 (list skribe-reader html-reader
)))
271 (directory-assets "static" (const #t
) "."))
272 (blog #:theme elephly-theme
274 (tag-pages #:theme elephly-theme
276 (latest-blog-post #:theme elephly-theme
)
277 (pin-blog-post "posts/2010-03-28-elephly.skr"
279 #:theme elephly-theme
)
280 (pin-blog-post "posts/2010-03-23-fur-man.skr"
282 #:theme elephly-theme
)
283 (atom-feed #:blog-prefix
"/posts")
284 (atom-feeds-by-tag #:blog-prefix
"/posts")))