diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/wikme.scm | 276 |
1 files changed, 185 insertions, 91 deletions
diff --git a/src/wikme.scm b/src/wikme.scm index 187864e..2d476ca 100644 --- a/src/wikme.scm +++ b/src/wikme.scm | |||
@@ -1,69 +1,150 @@ | |||
1 | ;;; wikme --- build a static wiki out of a folder of markdown files | 1 | ;;;; wikme --- build a static wiki out of a folder of markdown files |
2 | 2 | ||
3 | (import (cmark) | 3 | (import (cmark) |
4 | (srfi-152) | 4 | (srfi-152) |
5 | (utf8) | 5 | (utf8) |
6 | (chicken file) | ||
7 | (chicken file posix) | ||
6 | (chicken irregex) | 8 | (chicken irregex) |
9 | (chicken pathname) | ||
7 | (chicken port) | 10 | (chicken port) |
8 | (chicken string)) | 11 | (chicken process) |
12 | (chicken process-context) | ||
13 | (chicken string) | ||
14 | (chicken time posix)) | ||
9 | 15 | ||
10 | 16 | ||
11 | ;;; Configuration | 17 | ;;; Wiki |
12 | 18 | ||
13 | (define site-config | 19 | (define-record-type <wiki> |
14 | (make-parameter `((base-url . "https://www.example.com") | 20 | ;;; A wiki is a collection of pages and assorted metadata. |
15 | ;; These default directories aren't .. great. | 21 | (make-wiki base-url ; base URL for links |
16 | (source-dir . "src") | 22 | origin-dir ; origin directory |
17 | (output-dir . "out") | 23 | destination-dir ; destination directory |
18 | (transformers . ,(list commonmark->html | 24 | page-template ; template for pages |
19 | wikify-links)) | 25 | file-transformers ; list of filename transformers |
20 | (filename-transform | 26 | transformers ; list of source transformer functions |
21 | . (lambda (fname) | 27 | pages ; list of <page>s |
22 | (md->index-html fname))) | 28 | ) |
23 | (page-environment | 29 | wiki? |
24 | . ((title | 30 | (base-url wiki-base-url (setter wiki-base-url)) |
25 | . ,(lambda (page) | 31 | (origin-dir wiki-origin-dir (setter wiki-origin-dir)) |
26 | (cdr (assq 'title (page-meta page))))) | 32 | (destination-dir wiki-destination-dir (setter wiki-destination-dir)) |
27 | (body | 33 | (page-template wiki-page-template (setter wiki-page-template)) |
28 | . ,(lambda (page) | 34 | (file-transformers wiki-file-transformers (setter wiki-file-transformers)) |
29 | (page-body page))) | 35 | (transformers wiki-transformers (setter wiki-transformers)) |
30 | (last_updated | 36 | (pages wiki-pages (setter wiki-pages))) |
31 | . ,(lambda (page) | 37 | |
32 | (cdr (assq 'last-updated (page-meta page)))))))))) | 38 | (define (directory->wiki directory |
33 | 39 | #!key | |
34 | (define (config-get x) | 40 | (extension "md") |
35 | (if (assq x (site-config)) | 41 | (base-url "https://www.example.com") |
36 | (cdr (assq x (site-config))) | 42 | (destination-directory (make-pathname directory "out")) |
37 | #f)) | 43 | (page-template (make-pathname directory "template.html")) |
44 | (file-transformers (list indexify)) | ||
45 | (transformers (list cmark->html wikify-links))) | ||
46 | ;;; Build a <wiki> out of the markdown files in DIRECTORY. | ||
47 | ;; The given keyword arguments will fill out the other fields in the result. | ||
48 | |||
49 | (define wiki | ||
50 | (make-wiki base-url | ||
51 | directory | ||
52 | destination-directory | ||
53 | file-transformers | ||
54 | transformers | ||
55 | '())) | ||
56 | |||
57 | (set! wiki-pages (map (lambda (f) (file->page f wiki)) | ||
58 | (glob (make-pathname directory "*.md")))) | ||
59 | |||
60 | wiki) | ||
38 | 61 | ||
39 | 62 | ||
40 | ;;; Templates | 63 | ;;; Pages |
41 | 64 | ||
42 | (define (render template env) | 65 | (define-record-type <page> |
43 | ;;; Render TEMPLATE using ENV. | 66 | ;;; A wiki page is a mapping between source and body content, and between the |
44 | ;; TEMPLATE is a string with {{placeholders}}; ENV is an alist of key-value | 67 | ;;; page's origin and its destination files, wrapped together with some |
45 | ;; pairs to insert into the TEMPLATE's placeholders. | 68 | ;;; metadata. |
46 | (string-substitute* template (env->replacements env))) | 69 | (make-page source ; source markup |
70 | body ; rendered page body | ||
71 | origin ; file containing the markup | ||
72 | destination ; destination file | ||
73 | wiki ; the <wiki> this page is a part of | ||
74 | meta ; alist of metadata tags | ||
75 | ) | ||
76 | page? | ||
77 | (source page-source (setter page-source)) | ||
78 | (body page-body (setter page-source)) | ||
79 | (origin page-origin (setter page-origin)) | ||
80 | (destination page-destination (setter page-destination)) | ||
81 | (wiki page-wiki (setter page-wiki)) | ||
82 | (meta page-meta (setter page-meta))) | ||
47 | 83 | ||
48 | (define (env->replacements env) | 84 | (define (page-meta-ref page key) |
49 | ;;; Convert an ENV alist of the form `((X . Y) ...) to '(("{{X}}" . "Y") ...). | 85 | ;;; Get metadata KEY from PAGE. |
50 | ;; X's are template variables and Y's are the values of those variables. In | 86 | (cdr (assq key (page-meta page)))) |
51 | ;; the template, both "{{X}}" and "{{ X }}" will be replaced. | 87 | |
52 | ;; If Y is a thunk, call it. | 88 | (define (page-meta-set! page key value) |
53 | (let loop ((env env) | 89 | ;;; Set KEY in PAGE's metadata to VALUE. |
54 | (res '())) | 90 | (set! (page-meta page) (cons (cons key value) |
55 | (if (null? env) | 91 | (page-meta page)))) |
56 | res | 92 | |
57 | (let* ((this (car env)) | 93 | (define (basename file) |
58 | (rest (cdr env)) | 94 | (let-values (((_ base _) (decompose-pathname file))) |
59 | (key (->string (car this))) | 95 | base)) |
60 | (val (if (procedure? (cdr this)) | 96 | |
61 | ((cdr this)) | 97 | (define (indexify fname outdir) |
62 | (->string (cdr this))))) | 98 | ;;; Transform a FILENAME of the form ..dir/name.md to outdir/name/index.html. |
63 | (loop (cdr env) | 99 | (make-pathname (list outdir (basename fname)) "index" "html")) |
64 | (append (list (cons (string-append "{{" key "}}") val) | 100 | |
65 | (cons (string-append "{{ " key " }}") val)) | 101 | (define (guess-title page) |
66 | env)))))) | 102 | ;;; Guess the title from PAGE. |
103 | ;; If the first line is a Markdown H1 ("# ..."), use that as the title. | ||
104 | ;; Otherwise, unslugify the basename of the PAGE file to use as the title. | ||
105 | (let* ((str (page-body page)) | ||
106 | (m (irregex-match '(: "#" (* whitespace) (submatch (nonl)) (* any)) | ||
107 | str))) | ||
108 | (if (irregex-match-data? m) | ||
109 | (irregex-match-substring m 1) | ||
110 | (unslugify (basename (page-origin page)))))) | ||
111 | |||
112 | (define (guess-last-updated page) | ||
113 | ;;; Guess when PAGE was last edited. | ||
114 | ;; Tries to use git, but falls back to mtime. | ||
115 | (let ((f (page-origin page))) | ||
116 | (time->string | ||
117 | (seconds->local-time | ||
118 | (or (string->number | ||
119 | (string-trim-both | ||
120 | (with-input-from-pipe | ||
121 | (string-join '("git" "log" "-1" "--format=%ct" "--date=unix" | ||
122 | "-C" (wiki-origin-dir (page-wiki page)) | ||
123 | f) | ||
124 | " ") | ||
125 | read-string))) | ||
126 | (file-modification-time f)))))) | ||
127 | |||
128 | (define (file->page file wiki) | ||
129 | ;;; Create a <page> from FILE in WIKI. | ||
130 | ;; Wraps make-page for easier use. | ||
131 | (define source | ||
132 | (with-input-from-file file read-string)) | ||
133 | (define page | ||
134 | (make-page source | ||
135 | (apply transform source (wiki-transformers wiki)) | ||
136 | file | ||
137 | (apply file-transform | ||
138 | file | ||
139 | (wiki-destination-dir wiki) | ||
140 | (wiki-file-transformers wiki)) | ||
141 | wiki | ||
142 | '())) | ||
143 | |||
144 | (page-meta-set! page 'title (guess-title page)) | ||
145 | (page-meta-set! page 'last-updated (guess-last-updated page)) | ||
146 | |||
147 | page) | ||
67 | 148 | ||
68 | 149 | ||
69 | ;;; Wiki links | 150 | ;;; Wiki links |
@@ -130,48 +211,61 @@ | |||
130 | (loop (cdr transformers) | 211 | (loop (cdr transformers) |
131 | ((car transformers) output))))) | 212 | ((car transformers) output))))) |
132 | 213 | ||
133 | (define (md->index-html filename) | 214 | (define (file-transform origin destination-directory . transformers) |
134 | ;;; Transform a FILENAME of the form dir/name.md to dir/name/index.html. | 215 | ;;; Transform ORIGIN to a DESTINATION filename using TRANSFORMERS. |
135 | ;; Uses source | 216 | ;; Each TRANSFORMER will be called with two arguments: the ORIGIN filaname and |
136 | ) | 217 | ;; the DESTINATION-DIRECTORY. It should return the transformed filename. |
218 | (let loop ((transformers transformers) | ||
219 | (destination origin)) | ||
220 | (if (null? transformers) | ||
221 | destination | ||
222 | (loop (cdr transformers) | ||
223 | ((car transformers) origin destination-directory))))) | ||
137 | 224 | ||
138 | 225 | ||
139 | ;;; Pages | 226 | ;;; Templates |
140 | 227 | ||
141 | (define-record-type <page> | 228 | (define (render template env) |
142 | ;;; A wiki page is a mapping between source and body content, and between the | 229 | ;;; Render TEMPLATE using ENV. |
143 | ;;; page's origin and its destination files, wrapped together with some | 230 | ;; TEMPLATE is a string with {{placeholders}}; ENV is an alist of key-value |
144 | ;;; metadata. | 231 | ;; pairs to insert into the TEMPLATE's placeholders. |
145 | (make-page source body origin destination meta) | 232 | (string-substitute* template (env->replacements env))) |
146 | page? | ||
147 | (source page-source ; source markup | ||
148 | (setter page-source)) | ||
149 | (body page-body ; rendered page body | ||
150 | (setter page-source)) | ||
151 | (origin page-origin ; file containing the markup | ||
152 | (setter page-origin)) | ||
153 | (destination page-destination ; destination file | ||
154 | (setter page-destination)) | ||
155 | (meta page-meta ; alist of metadata tags | ||
156 | (setter page-meta))) | ||
157 | |||
158 | (define (page-meta-ref key page) | ||
159 | ;;; Get metadata KEY from PAGE. | ||
160 | (cdr (assq key (page-meta page)))) | ||
161 | 233 | ||
162 | (define (file->page file | 234 | (define (env->replacements env) |
163 | #!key | 235 | ;;; Convert an ENV alist of the form `((X . Y) ...) to '(("{{X}}" . "Y") ...). |
164 | (transformers (config-get 'transformers)) | 236 | ;; X's are template variables and Y's are the values of those variables. In |
165 | (destination )) | 237 | ;; the template, both "{{X}}" and "{{ X }}" will be replaced. |
166 | ;;; Create a <page> from FILE. | 238 | ;; If Y is a thunk, call it. |
167 | ;; Wraps make-page for easier use. | 239 | (let loop ((env env) |
240 | (res '())) | ||
241 | (if (null? env) | ||
242 | res | ||
243 | (let* ((this (car env)) | ||
244 | (rest (cdr env)) | ||
245 | (key (->string (car this))) | ||
246 | (val (if (procedure? (cdr this)) | ||
247 | ((cdr this)) | ||
248 | (->string (cdr this))))) | ||
249 | (loop (cdr env) | ||
250 | (append (list (cons (string-append "{{" key "}}") val) | ||
251 | (cons (string-append "{{ " key " }}") val)) | ||
252 | env)))))) | ||
168 | 253 | ||
169 | ) | 254 | (define (render-page template page) |
255 | ;;; Render PAGE with its metadata using TEMPLATE. | ||
256 | (render template `((title . ,(page-meta-ref 'title page)) | ||
257 | (body . ,(page-body page)) | ||
258 | (last_updated ,(page-meta-ref 'last-updated page)) | ||
259 | ;; TODO: backlinks and what-not | ||
260 | ))) | ||
170 | 261 | ||
171 | 262 | ||
172 | ;;; Writing files | 263 | ;;; Wikify |
173 | 264 | ||
174 | (define (publish file config) | 265 | (define (render-wiki wiki) |
175 | ;;; Publish FILE, using CONFIG. | 266 | ;;; Render the files in WIKI to disk. |
176 | ;; CONFIG should be a configuration alist, which see above. | 267 | (for-each (lambda (page) |
177 | #f) | 268 | (with-output-to-file (page-destination page) |
269 | (lambda () | ||
270 | (write-string (render-page (wiki-page-template wiki) page))))) | ||
271 | (wiki-pages wiki))) | ||