about summary refs log tree commit diff stats
path: root/src/wikme.scm
diff options
context:
space:
mode:
authorCase Duckworth2023-03-29 23:18:06 -0500
committerCase Duckworth2023-03-29 23:18:06 -0500
commit246b99687121b3863a931945aa2f3b259adb905a (patch)
tree773a760aae90b2a2fdd8c38ffec9e26e7d5949e1 /src/wikme.scm
parentInitial commit (oops) (diff)
downloadwikme-246b99687121b3863a931945aa2f3b259adb905a.tar.gz
wikme-246b99687121b3863a931945aa2f3b259adb905a.zip
Begin on executable
Diffstat (limited to 'src/wikme.scm')
-rw-r--r--src/wikme.scm276
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)))