git.fiddlerwoaroof.com
Browse code

feat(st): st html generator

Edward Langley authored on 19/04/2022 09:31:07
Showing 5 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,830 @@
1
+(defpackage :fwoar.lisp-sandbox.format-st-snips
2
+  (:use :cl )
3
+  (:export ))
4
+(in-package :fwoar.lisp-sandbox.format-st-snips)
5
+
6
+(defparameter *counters-css*
7
+  "@counter-style objectio {
8
+  system: extends numeric;
9
+  prefix: \"arg.\";
10
+}
11
+
12
+@counter-style sedcontra {
13
+  system: extends numeric;
14
+  prefix: \"s.c. \";
15
+}
16
+
17
+@counter-style responsio {
18
+  system: extends numeric;
19
+  prefix: \"ad \";
20
+}
21
+")
22
+
23
+(defparameter *fonts-css*
24
+  "@font-face {
25
+font-family: century_supra_a;
26
+font-style: normal;
27
+font-weight: normal;
28
+font-stretch: normal;
29
+font-display: auto;
30
+src: url('./fonts/century_supra_a_regular.woff2') format('woff2');
31
+}
32
+
33
+@font-face {
34
+font-family: century_supra_a;
35
+font-style: italic;
36
+font-weight: normal;
37
+font-stretch: normal;
38
+font-display: auto;
39
+src: url('./fonts/century_supra_a_italic.woff2') format('woff2');
40
+}
41
+
42
+@font-face {
43
+font-family: century_supra_a;
44
+font-style: normal;
45
+font-weight: bold;
46
+font-stretch: normal;
47
+font-display: auto;
48
+src: url('./fonts/century_supra_a_bold.woff2') format('woff2');
49
+}
50
+
51
+@font-face {
52
+font-family: century_supra_a;
53
+font-style: italic;
54
+font-weight: bold;
55
+font-stretch: normal;
56
+font-display: auto;
57
+src: url('./fonts/century_supra_a_bold_italic.woff2') format('woff2');
58
+}
59
+
60
+@font-face {
61
+font-family: concourse_4;
62
+font-style: normal;
63
+font-weight: normal;
64
+font-stretch: normal;
65
+font-display: auto;
66
+src: url('./fonts/concourse_4_regular.woff2') format('woff2');
67
+}
68
+
69
+@font-face {
70
+font-family: concourse_4;
71
+font-style: italic;
72
+font-weight: normal;
73
+font-stretch: normal;
74
+font-display: auto;
75
+src: url('./fonts/concourse_4_italic.woff2') format('woff2');
76
+}
77
+
78
+@font-face {
79
+font-family: concourse_4;
80
+font-style: normal;
81
+font-weight: bold;
82
+font-stretch: normal;
83
+font-display: auto;
84
+src: url('./fonts/concourse_4_bold.woff2') format('woff2');
85
+}
86
+
87
+@font-face {
88
+font-family: concourse_4;
89
+font-style: italic;
90
+font-weight: bold;
91
+font-stretch: normal;
92
+font-display: auto;
93
+src: url('./fonts/concourse_4_bold_italic.woff2') format('woff2');
94
+}
95
+")
96
+
97
+(defun ht->vector (h-t)
98
+  (let* ((keys (alexandria:hash-table-keys h-t))
99
+         (base (reduce #'min keys :initial-value 100000))
100
+         (limit (reduce #'max keys :initial-value 0))
101
+         (result (make-array (1+ (- limit base))
102
+                             :initial-element nil)))
103
+    (prog1 result
104
+      (mapc (lambda (key)
105
+              (setf (aref result (- key base))
106
+                    (gethash key h-t)))
107
+            keys))))
108
+
109
+(defun path-hash-table (h-t path value)
110
+  (let ((most-specific (car (last path))))
111
+    (prog1 h-t
112
+      (loop with cur = h-t
113
+            for it in (butlast path)
114
+            do
115
+               (setf cur
116
+                     (alexandria:ensure-gethash it cur
117
+                                                (make-hash-table :test (hash-table-test h-t))))
118
+            finally
119
+               (setf (gethash most-specific cur) value)))))
120
+
121
+(defun cycle (&rest vs)
122
+  (let ((cur vs))
123
+    (lambda ()
124
+      (prog1 (car cur)
125
+        (if (cdr cur)
126
+            (setf cur (cdr cur))
127
+            (setf cur vs))))))
128
+
129
+(defun alternate (l1 l2)
130
+  (remove-if 'null
131
+             (mapcan 'identity
132
+                     (funcall (data-lens:zipping 'list :fill-value nil)
133
+                              l1 l2))))
134
+
135
+(defun collate-it (forms)
136
+  (let ((h-t (make-hash-table :test 'equal)))
137
+    (prog1 h-t
138
+      (mapc (lambda (it)
139
+              (path-hash-table h-t
140
+                               (cons (second it)
141
+                                     (mapcan 'decode-ref
142
+                                             (parse-ref (third it))))
143
+                               (list (cons (string (second it)) (parse-ref-o (third it)))
144
+                                     (cleanup (fourth it)))))
145
+            forms))))
146
+
147
+(defmacro regex-cond (it &body body)
148
+  it body (error "macrolet"))
149
+(defun cleanup (it)
150
+  (macrolet ((regex-cond-case (target regex (beg end) &body body)
151
+               `(multiple-value-bind (,beg ,end) (cl-ppcre:scan ,regex ,target)
152
+                  (declare (ignorable ,beg ,end))
153
+                  (when ,beg
154
+                    (return
155
+                      (progn ,@body)))))
156
+             (regex-cond (it &body cases)
157
+               (alexandria:once-only (it)
158
+                 `(block nil
159
+                    ,@(loop for case in cases
160
+                            if (equal t (car case))
161
+                              collect `(progn ,@(cdr case))
162
+                            else collect `(regex-cond-case ,it ,@case))))))
163
+    (with-simple-restart (skip "skip ~s" it)
164
+      (serapeum:trim-whitespace
165
+       (regex-cond it
166
+         ("arg[.] [0-9]+" (b e)
167
+                          (subseq it e))
168
+         ("s[.] c[.] [0-9]+" (b e)
169
+                             (subseq it e))
170
+         ("s[.] c[.]" (b e)
171
+                      (subseq it e))
172
+         ("co[.]" (b e)
173
+                  (subseq it e))
174
+         ("pr[.]" (b e)
175
+                  (subseq it e))
176
+         ("ad [0-9]+" (b e)
177
+                      (subseq it e))
178
+         ("ad arg[.]" (b e)
179
+                      (subseq it e))
180
+         (t (break)))))))
181
+
182
+(defun parse-ref-o (ref)
183
+  (let* ((book (funcall (data-lens:regex-match "^[I-]+") ref))
184
+         (ref (subseq ref (length book))))
185
+    (multiple-value-bind (a b)
186
+        (serapeum:with-collectors (w n)
187
+          (let ((cleaner (cycle #'w (data-lens:• #'n #'parse-integer))))
188
+            (mapcar (lambda (it)
189
+                      (funcall (funcall cleaner)
190
+                               (apply 'subseq ref it)))
191
+                    (loop for x from 0 below (length ref)
192
+                          for idx-f = (if (evenp x)
193
+                                          'position-if
194
+                                          'position-if-not)
195
+                          for start-idx = 0 then idx
196
+                          for idx = (funcall idx-f 'digit-char-p ref :start start-idx)
197
+                          collect (list start-idx idx)
198
+                          while idx))))
199
+      (list* book
200
+             (funcall (data-lens:• (data-lens:over
201
+                                    (data-lens:applying
202
+                                     (lambda (i j)
203
+                                       (if (= j 0)
204
+                                           (format nil "~a" i)
205
+                                           (format nil "~a ~a" i j)))))
206
+                                   (data-lens:zipping 'list :fill-value 0))
207
+                      a b)))))
208
+
209
+(defun parse-ref (ref)
210
+  (if (equal ref "pr.")
211
+      (list 1000)
212
+      (let* ((book (funcall (data-lens:regex-match "^[I-]+") ref))
213
+             (ref (subseq ref (length book))))
214
+        (multiple-value-bind (a b)
215
+            (serapeum:with-collectors (w n)
216
+              (let ((cleaner (cycle #'w (data-lens:• #'n #'parse-integer))))
217
+                (mapcar (lambda (it)
218
+                          (funcall (funcall cleaner)
219
+                                   (apply 'subseq ref it)))
220
+                        (loop for x from 0 below (length ref)
221
+                              for idx-f = (if (evenp x)
222
+                                              'position-if
223
+                                              'position-if-not)
224
+                              for start-idx = 0 then idx
225
+                              for idx = (funcall idx-f 'digit-char-p ref :start start-idx)
226
+                              collect (list start-idx idx)
227
+                              while idx))))
228
+          (let ((a (mapcar (lambda (it)
229
+                             (string-case:string-case (it)
230
+                               ("pr." 1)
231
+                               ("q." 2)
232
+                               ("a." 3)
233
+                               ("arg." 4)
234
+                               ("s.c." 5)
235
+                               ("co." 6)
236
+                               ("ad" 7)
237
+                               ("adarg." 8)))
238
+                           a)))
239
+            (list* (string-case:string-case (book)
240
+                     ("I" 1)
241
+                     ("I-II" 2)
242
+                     ("II-II" 3)
243
+                     ("III" 4))
244
+                   (funcall (data-lens:• (data-lens:over
245
+                                          (data-lens:applying
246
+                                           (lambda (i j)
247
+                                             (+ (* i 1000)
248
+                                                j))))
249
+                                         (data-lens:zipping 'list :fill-value 0))
250
+                            a b)))))))
251
+
252
+(defun format-text (forms)
253
+  (let ((old-s-o *standard-output*)
254
+        (dir (ensure-directories-exist "/Users/edwlan/summa-html/"))
255
+        (files '("prooemium.html")))
256
+    (unwind-protect
257
+         (progn (setf *standard-output* (open (merge-pathnames (car files)
258
+                                                               dir)
259
+                                              :direction :output
260
+                                              :if-exists :supersede))
261
+                (spinneret:with-html
262
+                  (:doctype)
263
+                  (:html
264
+                   (:meta :charset "utf-8")
265
+                   (:link :rel "stylesheet"
266
+                          :href "../style.css")
267
+                   (:body
268
+                    (:main
269
+                     (let ((counter 0)
270
+                           (question-counter -1)
271
+                           (state))
272
+                       (:nav
273
+                        (:a :href "index.html" "index") "–" (:a :href "q-1.html" "next"))
274
+                       (funcall (data-lens:over
275
+                                 (lambda (it)
276
+                                   (let ((id (format nil "~a~a"
277
+                                                     (second it)
278
+                                                     (remove #\. (third it)))))
279
+                                     (cond
280
+                                       ((cl-ppcre:scan "arg[.]" (fourth it))
281
+                                        (case state
282
+                                          ((:sc :ad)
283
+                                           (princ "</ol>" spinneret:*html*))
284
+                                          (:corp
285
+                                           (princ "</section>" spinneret:*html*)))
286
+                                        (when (cl-ppcre:scan "^.*arg[.].*Ad.*sic proceditur" (fourth it))
287
+                                          (when (> counter 0)
288
+                                            (princ "</div>" spinneret:*html*))
289
+                                          (:h2 (format nil "Articulus ~@R"
290
+                                                       (incf counter)))
291
+                                          (princ "<div>" spinneret:*html*))
292
+                                        (unless (eql state :arg)
293
+                                          (princ "<ol class=\"arg\">" spinneret:*html*)
294
+                                          (setf state :arg))
295
+                                        (:li.obj :id id
296
+                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 8) 8)))
297
+                                       ((cl-ppcre:scan "s[.] c[.]" (fourth it))
298
+                                        (case state
299
+                                          ((:arg :ad)
300
+                                           (princ "</ol>" spinneret:*html*))
301
+                                          (:corp
302
+                                           (princ "</section>" spinneret:*html*)))
303
+                                        (unless (eql state :sc)
304
+                                          (princ "<ol class=\"sc\">" spinneret:*html*)
305
+                                          (setf state :sc))
306
+                                        (:li.sc :id id
307
+                                                (elt (fwoar.string-utils:split " " (fourth it) :count 8) 8)))
308
+                                       ((cl-ppcre:scan "co[.]" (fourth it))
309
+                                        (case state
310
+                                          ((:arg :sc :ad)
311
+                                           (princ "</ol>" spinneret:*html*)))
312
+                                        (unless (eql state :corp)
313
+                                          (princ "<section class=\"resp\">" spinneret:*html*)
314
+                                          (setf state :corp))
315
+                                        (:div.co :id id
316
+                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 7) 7)))
317
+                                       ((cl-ppcre:scan "pr[.]" (fourth it))
318
+                                        (if (>= question-counter 0)
319
+                                            (let ((new-file (format nil "q-~d.html"
320
+                                                                    (incf question-counter))))
321
+                                              (push (list new-file
322
+                                                          (format nil "~a ~a"
323
+                                                                  (second it)
324
+                                                                  (third it)))
325
+                                                    files)
326
+                                              (close *standard-output*)
327
+                                              (setf *standard-output*
328
+                                                    (open (merge-pathnames new-file dir)
329
+                                                          :direction :output
330
+                                                          :if-exists :supersede))
331
+                                              (:doctype html)
332
+                                              (:link :rel "stylesheet"
333
+                                                     :href "../style.css")
334
+                                              (:meta :charset "utf-8")
335
+                                              (princ "<main>")
336
+                                              (:nav
337
+                                               (if (> question-counter 0)
338
+                                                   (progn
339
+                                                     (:a :href (format nil "./~a"
340
+                                                                       (if (= 1 question-counter)
341
+                                                                           "prooemium.html"
342
+                                                                           (format nil "q-~d.html"
343
+                                                                                   (1- question-counter))))
344
+                                                         "prev"))
345
+                                                   (progn (:a :href "index.html" "index")))
346
+                                               " – "
347
+                                               (format nil "~a ~a" (second it) (subseq (third it)
348
+                                                                                       0
349
+                                                                                       (- (length
350
+                                                                                           (third it))
351
+                                                                                          3)))
352
+                                               " – "
353
+                                               (:a :href (format nil "./~a"
354
+                                                                 (if (= 0 question-counter)
355
+                                                                     "index.html"
356
+                                                                     (format nil "q-~d.html"
357
+                                                                             (1+ question-counter))))
358
+                                                   (if (= 0 question-counter)
359
+                                                       "index"
360
+                                                       "next")))
361
+                                              (setf counter 0)
362
+                                              (:h1 (format nil "~a ~a"
363
+                                                           (second it)
364
+                                                           (subseq (third it)
365
+                                                                   0
366
+                                                                   (- (length (third it))
367
+                                                                      3)))))
368
+                                            (prog1 (:h1 "Prooemium")
369
+                                              (push (list (pop files)
370
+                                                          (format nil "~a ~a"
371
+                                                                  (second it)
372
+                                                                  (third it)))
373
+                                                    files)
374
+                                              (setf counter 0)
375
+                                              (incf question-counter)))
376
+                                        (:div.pr :id id
377
+                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 5) 5)))
378
+                                       ((cl-ppcre:scan "ad [0-9]" (fourth it))
379
+                                        (case state
380
+                                          ((:arg :sc)
381
+                                           (princ "</ol>" spinneret:*html*))
382
+                                          (:corp
383
+                                           (princ "</section>" spinneret:*html*)))
384
+                                        (unless (eql state :ad)
385
+                                          (princ "<ol class=\"ad\">" spinneret:*html*)
386
+                                          (setf state :ad))
387
+                                        (:li.adr :id id
388
+                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 8) 8)))
389
+                                       (t (fourth it))))))
390
+                                forms)
391
+                       (case state
392
+                         ((:arg :sc :ad)(princ "</ol>" spinneret:*html*))
393
+                         (:corp (princ "</section>" spinneret:*html*)))
394
+                       nil))))))
395
+      (close *standard-output*)
396
+      (setf *standard-output* old-s-o))
397
+    (let ((*standard-output* (open (merge-pathnames "index.html" dir)
398
+                                   :direction :output
399
+                                   :if-exists :supersede)))
400
+      (spinneret:with-html
401
+        (:doctype html)
402
+        (:link :rel "stylesheet"
403
+               :href "../style.css")
404
+        (:meta :charset "utf-8")
405
+        (:main
406
+         (:nav (:a :href ".." "totum"))
407
+         (:ul
408
+          (loop for (file cite) in (reverse files)
409
+                do (:li (:a :href file cite)))))))))
410
+
411
+(defun idify (ref)
412
+  (remove-if-not 'alphanumericp
413
+                 (format nil "~{~a~}" ref)))
414
+(defun fnify (ref)
415
+  (with-output-to-string (s)
416
+    (mapcar (lambda (it)
417
+              (princ (remove-if-not 'alphanumericp
418
+                                    (typecase it
419
+                                      (integer (format nil "~4,'0d" it))
420
+                                      (t (format nil "~a" it))))
421
+                     s))
422
+            ref)))
423
+
424
+(defun classify (context)
425
+  (:printv context)
426
+  "")
427
+(defun htmlify (h-t type number title &optional context)
428
+  (flet ((article-template (thing)
429
+           (spinneret:with-html
430
+             (let ((arg (gethash "arg" thing)))
431
+               (when arg
432
+                 (:h4 "Objectiones")
433
+                 (let ((arg-v (ht->vector arg)))
434
+                   (:ol.objectiones
435
+                    (map nil
436
+                         (lambda (it)
437
+                           (:li.objectio :id (idify (car it))
438
+                                         (cadr it)))
439
+                         arg-v)))))
440
+             (let ((sc (gethash "sc" thing)))
441
+               (when sc
442
+                 (:h4 "Sed Contra")
443
+                 (typecase sc
444
+                   (hash-table (let ((sc-v (ht->vector sc)))
445
+                                 (:ol.sedcontrae
446
+                                  (map nil
447
+                                       (lambda (it)
448
+                                         (:li.sedcontra :id (idify (car it))
449
+                                                        (cadr it)))
450
+                                       sc-v))))
451
+                   (cons
452
+                    (:ol.sedcontrae
453
+                     (:li.sedcontra
454
+                      :id (idify (car sc))
455
+                      (cadr sc)))))))
456
+             (let ((co (gethash "co" thing)))
457
+               (when co
458
+                 (:h4 "Respondeo")
459
+                 (:section.corpus :id (idify (car co))
460
+                                  (cadr co))))
461
+             (let ((responsiones (gethash "ad" thing)))
462
+               (when responsiones
463
+                 (:h4 "Ad Objectiones")
464
+                 (let ((responsiones-v (ht->vector responsiones)))
465
+                   (:ol.responsiones
466
+                    (map nil
467
+                         (lambda (it)
468
+                           (:li.responsio :id (idify (car it))
469
+                                          (cadr it)))
470
+                         responsiones-v)))))
471
+             (let ((adarg (gethash "adarg" thing)))
472
+               (when adarg
473
+                 (:h4 "Ad Objectiones")
474
+                 (:ol.responsiones
475
+                  (:li.responsi :id (idify (car adarg))
476
+                                (cadr adarg))))))))
477
+    (let ((thing (gethash number h-t)))
478
+      (string-case:string-case (type)
479
+        ("q"
480
+         (spinneret:with-html
481
+           (let ((next-context (list* number "q" context)))
482
+             (:section :id (idify (reverse next-context))
483
+                       :class "quaestio"
484
+                       (:h2 "Quaestio" (format nil "~@r" number) title
485
+                            (:a :href (format nil "#~a" (idify (reverse next-context)))
486
+                                "#"))
487
+                       (let ((maybe-pr (gethash "pr" thing)))
488
+                         (when maybe-pr
489
+                           (:section.pr
490
+                            (:h3 "Prooemium")
491
+                            (destructuring-bind (ref pr) maybe-pr
492
+                              (:div :id (idify ref)
493
+                                    pr)))))
494
+                       (let* ((article-ht (gethash "a" thing)))
495
+                         (if article-ht
496
+                             (:section.articuli
497
+                              (let ((articles (ht->vector article-ht)))
498
+                                (map nil
499
+                                     (lambda (idx)
500
+                                       (htmlify article-ht
501
+                                                "a"
502
+                                                idx
503
+                                                ""
504
+                                                next-context))
505
+                                     (alexandria:iota (length articles) :start 1))))
506
+                             (article-template thing)))
507
+                       ))))
508
+        ("a"
509
+         (let ((next-context (list* number "a" context)))
510
+           (spinneret:with-html
511
+             (:section.articulus
512
+              :id (idify (reverse next-context))
513
+              (:h3 "Articulus" number title
514
+                   (:a :href (format nil "#~a" (idify (reverse next-context)))
515
+                       "#"))
516
+              (article-template thing)
517
+              ))))))))
518
+
519
+(defun style.css ()
520
+  (lass:compile-and-write
521
+   '(:import (url "./counters.css"))
522
+   '(:import (url "./fonts.css"))
523
+
524
+   '((nav > a + a)
525
+     :margin-left 1em)
526
+
527
+   (let ((colors '(("zenburn-fg-plus-2" . "#FFFFEF")
528
+                   ("zenburn-fg-plus-1" . "#F5F5D6")
529
+                   ("zenburn-fg" . "#DCDCCC")
530
+                   ("zenburn-fg-1" . "#A6A689")
531
+                   ("zenburn-fg-2" . "#656555")
532
+                   ("zenburn-black" . "#000000")
533
+                   ("zenburn-bg-2" . "#000000")
534
+                   ("zenburn-bg-1" . "#111112")
535
+                   ("zenburn-bg-05" . "#383838")
536
+                   ("zenburn-bg" . "#2A2B2E")
537
+                   ("zenburn-bg-plus-05" . "#494949")
538
+                   ("zenburn-bg-plus-1" . "#4F4F4F")
539
+                   ("zenburn-bg-plus-2" . "#5F5F5F")
540
+                   ("zenburn-bg-plus-3" . "#6F6F6F")
541
+                   ("zenburn-red-plus-2" . "#ECB3B3")
542
+                   ("zenburn-red-plus-1" . "#DCA3A3")
543
+                   ("zenburn-red" . "#CC9393")
544
+                   ("zenburn-red-1" . "#BC8383")
545
+                   ("zenburn-red-2" . "#AC7373")
546
+                   ("zenburn-red-3" . "#9C6363")
547
+                   ("zenburn-red-4" . "#8C5353")
548
+                   ("zenburn-red-5" . "#7C4343")
549
+                   ("zenburn-red-6" . "#6C3333")
550
+                   ("zenburn-orange" . "#DFAF8F")
551
+                   ("zenburn-yellow" . "#F0DFAF")
552
+                   ("zenburn-yellow-1" . "#E0CF9F")
553
+                   ("zenburn-yellow-2" . "#D0BF8F")
554
+                   ("zenburn-green-5" . "#2F4F2F")
555
+                   ("zenburn-green-4" . "#3F5F3F")
556
+                   ("zenburn-green-3" . "#4F6F4F")
557
+                   ("zenburn-green-2" . "#5F7F5F")
558
+                   ("zenburn-green-1" . "#6F8F6F")
559
+                   ("zenburn-green" . "#7F9F7F")
560
+                   ("zenburn-green-plus-1" . "#8FB28F")
561
+                   ("zenburn-green-plus-2" . "#9FC59F")
562
+                   ("zenburn-green-plus-3" . "#AFD8AF")
563
+                   ("zenburn-green-plus-4" . "#BFEBBF")
564
+                   ("zenburn-cyan" . "#93E0E3")
565
+                   ("zenburn-blue-plus-3" . "#BDE0F3")
566
+                   ("zenburn-blue-plus-2" . "#ACE0E3")
567
+                   ("zenburn-blue-plus-1" . "#94BFF3")
568
+                   ("zenburn-blue" . "#8CD0D3")
569
+                   ("zenburn-blue-1" . "#7CB8BB")
570
+                   ("zenburn-blue-2" . "#6CA0A3")
571
+                   ("zenburn-blue-3" . "#5C888B")
572
+                   ("zenburn-blue-4" . "#4C7073")
573
+                   ("zenburn-blue-5" . "#366060")
574
+                   ("zenburn-magenta" . "#DC8CC3"))))
575
+     `(:root
576
+       ,@(loop for (name . color) in colors
577
+               appending (list (alexandria:make-keyword (format nil "--~a" name))
578
+                               (string-downcase color)))))
579
+   '(* :box-sizing border-box)
580
+   '((:or html body ol)
581
+     :margin 0
582
+     :padding 0)
583
+
584
+   '(body
585
+     :color (var --foreground)
586
+     :background-color (var --background))
587
+
588
+   '(body
589
+     :--background (var --zenburn-bg)
590
+     :--foreground (var --zenburn-fg)
591
+     :--foreground-diminish (var --zenburn-fg-1)
592
+     :--foreground-highlight (var --zenburn-fg-plus-1)
593
+     :--accent (var --zenburn-orange)
594
+     :--link (var --zenburn-blue)
595
+     :--link-visited (var --zenburn-magenta))
596
+
597
+   '(:media "(prefers-color-scheme: light)"
598
+     (body
599
+      :--background (var --zenburn-fg)
600
+      :--foreground (var --zenburn-bg)
601
+      :--foreground-diminish (var --zenburn-bg-1)
602
+      :--foreground-highlight (var --zenburn-bg-plus-1)
603
+      :--accent (var --zenburn-red-5)
604
+      :--link (var --zenburn-blue-4)
605
+      :--link-visited (var --zenburn-green-4)))
606
+
607
+
608
+   '(:media "print"
609
+     (body
610
+      :--background white
611
+      :--foreground black
612
+      :--foreground-diminish "#444"
613
+      :--foreground-highlight white
614
+      :--accent "#888"
615
+      :--link black
616
+      :--link-visited black)
617
+     (main
618
+      :text-align justify))
619
+
620
+   '(a
621
+     :color (var --link))
622
+   '((:and a :visited)
623
+     :color (var --link-visited))
624
+   '((:and a :hover)
625
+     :outline thin solid "currentColor")
626
+
627
+   '(main
628
+     :font-family sans-serif
629
+     :hyphens auto
630
+     :text-rendering optimizeLegibility
631
+     :font-feature-settings "'liga' on, 'onum' on"
632
+
633
+     :line-height 1.5
634
+     :letter-spacing 1px
635
+     :width 100vw
636
+     :padding-top 2rem
637
+     :padding-bottom 2rem
638
+     :padding-right (calc (- 100vw 40em))
639
+     :padding-left 5.5rem
640
+     :min-height 100vh)
641
+
642
+   '(label
643
+     :position relative
644
+     :padding 0.25em
645
+     :left 5.5rem
646
+     :top 0.5rem)
647
+   '((:and input (:= type "radio"))
648
+     :display none)
649
+
650
+   '(((:or
651
+       main
652
+       (:and label (:= for "century-supra"))) ~ main)
653
+     :font-family "century_supra_a")
654
+   '((:or
655
+      (:and label (:= for "concourse"))
656
+      ((:and :checked "#concourse") ~ main))
657
+     :font-family "concourse_4")
658
+
659
+   '((:or h1 h2 h3 h4 h5 h6)
660
+     :color (var --accent)
661
+     :font-family "century_supra_a"
662
+     :margin 0.25rem 0 0.5rem 0)
663
+   '((:or h4 h5 h6)
664
+     :display none)
665
+
666
+   `(:let ()
667
+      ,@(loop for tag in '(h1 h2 h3 h4 h5 h6)
668
+              for font-size = 2 then (/ (+ font-size 1) 2.0)
669
+              collect `(,tag :font-size ,(format nil "~arem" font-size))))
670
+
671
+   '(((:not (:or h1 h2 h3 h4 h5 h6)) + (:or h1 h2 h3 h4 h5 h6))
672
+     :margin-top 2.5rem
673
+     :margin-bottom 1rem)
674
+
675
+   '(u
676
+     :color (var --foreground-highlight)
677
+     :text-decoration underline 0.1px)
678
+
679
+   '((li + li)
680
+     :margin-top 0.25em)
681
+
682
+   '((:or .pr .articulus)
683
+     :padding-bottom 3em)
684
+   '((:or ol.objectiones ol.sedcontrae #|ol.responsiones|# section.corpus)
685
+     :padding-bottom 2.5em)
686
+   '(((:or ol.objectiones ol.sedcontrae ol.responsiones section.corpus) li + li)
687
+     :margin-top 1.5em)
688
+
689
+   '((:and li "::marker")
690
+     :color (var --foreground-diminish))
691
+
692
+   '(ol.objectiones
693
+     :list-style outside objectio)
694
+   '(ol.sedcontrae
695
+     :list-style outside sedcontra)
696
+   '(ol.responsiones
697
+     :list-style outside responsio)
698
+
699
+   '((:or .objectio .sedcontra .responsio .toc)
700
+     :margin 0
701
+     :padding 0)
702
+
703
+   '(:media (:and screen "(max-width: 980px)")
704
+     (body
705
+      :font-size 24px
706
+      :line-height 1.2em)
707
+     (main
708
+      :padding 0 1em)
709
+     ((:or ol.objectiones ol.sedcontrae ol.responsiones)
710
+      :list-style-position inside))))
711
+
712
+(defun question-template (questions number &optional context)
713
+  (let ((spinneret:*html-lang* "it")
714
+        (prooemium (fw.lu:dive (list number "pr") questions)))
715
+    (spinneret:with-html
716
+      (:doctype html)
717
+      (:html
718
+       (:meta :charset "utf-8")
719
+       (:meta :property "book:author" :content "Sancti Thomae de Aquino")
720
+       (:meta :property "book:tag" :content "theology")
721
+       (:meta :property "book:tag" :content "theologia")
722
+       (:meta :property "book:tag" :content "thomism")
723
+       (:meta :property "book:tag" :content "aquinas")
724
+       (when prooemium
725
+         (:meta :property "og:description"
726
+                :content (cadr prooemium)))
727
+       (:link :rel "stylesheet" :href "../style.css")
728
+       (:title ("Quaestio ~@r — Summa Theologiae" number))
729
+
730
+       (:input :type "radio" :name "font" :id "concourse" :checked t)
731
+       (:label :for "concourse" "A")
732
+       (:input :type "radio" :name "font" :id "century-supra")
733
+       (:label :for "century-supra" "A")
734
+       (:main
735
+        (:nav (:a :href "." "pars")
736
+              (:a :href ".." "totum"))
737
+        (htmlify questions "q" number "" context))))))
738
+
739
+(defun dump-questions (dir questions &optional context)
740
+  (let ((q-v (ht->vector questions))
741
+        (files ()))
742
+    (map nil
743
+         (lambda (idx)
744
+           (let ((ref (reverse (list* idx "q" context))))
745
+             (with-open-file (*standard-output* (merge-pathnames (make-pathname
746
+                                                                  :name (fnify ref)
747
+                                                                  :type "html")
748
+                                                                 dir)
749
+                                                :direction :output
750
+                                                :if-exists :supersede)
751
+               (question-template questions idx context))
752
+             (push ref files)))
753
+         (alexandria:iota (length q-v) :start 1))
754
+
755
+    (alexandria:with-output-to-file (*standard-output* (merge-pathnames "index.html" dir)
756
+                                                       :if-exists :supersede)
757
+      (spinneret:with-html
758
+        (:doctype html)
759
+        (:link :rel "stylesheet" :href "../style.css")
760
+        (:body
761
+         (:main
762
+          (:nav (:a :href ".." "totum"))
763
+          (:ul
764
+           (loop for file in (reverse files)
765
+                 do (:li
766
+                     (:a :href (format nil "./~a.html" (fnify file))
767
+                         (format nil "~{~a~^ ~}" file)))))))))))
768
+
769
+(defun make-toplevel-structure (dir)
770
+  (alexandria:with-output-to-file (s (merge-pathnames "style.css" dir)
771
+                                     :if-exists :supersede)
772
+    (princ (style.css) s)
773
+    (terpri s))
774
+
775
+  (alexandria:with-output-to-file (s (merge-pathnames "counters.css" dir)
776
+                                     :if-exists :supersede)
777
+    (princ *counters-css* s))
778
+
779
+  (alexandria:with-output-to-file (s (merge-pathnames "fonts.css" dir)
780
+                                     :if-exists :supersede)
781
+    (princ *fonts-css* s))
782
+
783
+  (alexandria:with-output-to-file (*standard-output* (merge-pathnames "index.html" dir)
784
+                                                     :if-exists :supersede)
785
+    (spinneret:with-html
786
+      (:doctype html)
787
+      (:html
788
+       (:head
789
+        (:meta :charset "utf-8")
790
+        (:title "Summa Theologiae")
791
+        (:link :rel "stylesheet" :href "./style.css"))
792
+       (:body
793
+        (:main
794
+         (:h1 "Summa Theologiae")
795
+         (:h2 "Sancti Thomae de Aquino")
796
+         (:ul
797
+          (:li (:a :href "./I/index.html" "Prima Pars"))
798
+          (:li "Secunda Pars"
799
+               (:ul
800
+                (:li (:a :href "./I-II/index.html" "Prima Pars Secundae Partis"))
801
+                (:li (:a :href "./II-II/index.html" "Secunda Pars Secundae Partis"))))
802
+          (:li (:a :href "./III/index.html" "Tertia Pars"))))))))
803
+  (values))
804
+
805
+(defun decode-ref (num &optional long)
806
+  (multiple-value-bind (m r) (floor num 1000)
807
+    (list* (ecase m
808
+             (0 (if long "liber" "l"))
809
+             (1 (if long "prooemium" "pr"))
810
+             (2 (if long "quaestio" "q"))
811
+             (3 (if long "articulus" "a"))
812
+             (4 "arg")
813
+             (5 "sc")
814
+             (6 "co")
815
+             (7 "ad")
816
+             (8 "adarg"))
817
+           (unless (= r 0)
818
+             (list r)))))
819
+
820
+(defun ref->id (ref)
821
+  (with-output-to-string (s)
822
+    (mapc (lambda (it)
823
+            (destructuring-bind (type &optional v) it
824
+              (string-case:string-case (type)
825
+                ("l" (format s "~@r" v))
826
+                (t (format s "~a~:[~;~:*~a~]" type v)))))
827
+          ref)))
828
+(defun write-question (s)
829
+  (lambda ()))
830
+;;("ST" "I" "q. 3" "a. 7" "s.c.")
0 831
new file mode 100644
... ...
@@ -0,0 +1 @@
1
+fonts
1 3
new file mode 100644
... ...
@@ -0,0 +1,20 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="UTF-8" />
5
+    <title>Summa Theologiae</title>
6
+    <link href="./style.css" rel="stylesheet" />
7
+  </head>
8
+  <body>
9
+    <main>
10
+      <h1>Summa Theologiae</h1>
11
+      <h2>Sancti Thomae de Aquino</h2>
12
+      <ul>
13
+        <li><a href="./I/index.html">Prima Pars</a></li>
14
+        <li><a href="./I-II/index.html">Prima Pars Secundae Partis</a></li>
15
+        <li><a href="./II-II/index.html">Secunda Pars Secundae Partis</a></li>
16
+        <li><a href="./III/index.html">Tertia Pars</a></li>
17
+      </ul>
18
+    </main>
19
+  </body>
20
+</html>
0 21
new file mode 100644
... ...
@@ -0,0 +1,284 @@
1
+:root {
2
+  --zenburn-fg-plus-2: #ffffef;
3
+  --zenburn-fg-plus-1: #f5f5d6;
4
+  --zenburn-fg: #dcdccc;
5
+  --zenburn-fg-1: #a6a689;
6
+  --zenburn-fg-2: #656555;
7
+  --zenburn-black: #000000;
8
+  --zenburn-bg-2: #000000;
9
+  --zenburn-bg-1: #111112;
10
+  --zenburn-bg-05: #383838;
11
+  --zenburn-bg: #2a2b2e;
12
+  --zenburn-bg-plus-05: #494949;
13
+  --zenburn-bg-plus-1: #4f4f4f;
14
+  --zenburn-bg-plus-2: #5f5f5f;
15
+  --zenburn-bg-plus-3: #6f6f6f;
16
+  --zenburn-red-plus-2: #ecb3b3;
17
+  --zenburn-red-plus-1: #dca3a3;
18
+  --zenburn-red: #cc9393;
19
+  --zenburn-red-1: #bc8383;
20
+  --zenburn-red-2: #ac7373;
21
+  --zenburn-red-3: #9c6363;
22
+  --zenburn-red-4: #8c5353;
23
+  --zenburn-red-5: #7c4343;
24
+  --zenburn-red-6: #6c3333;
25
+  --zenburn-orange: #dfaf8f;
26
+  --zenburn-yellow: #f0dfaf;
27
+  --zenburn-yellow-1: #e0cf9f;
28
+  --zenburn-yellow-2: #d0bf8f;
29
+  --zenburn-green-5: #2f4f2f;
30
+  --zenburn-green-4: #3f5f3f;
31
+  --zenburn-green-3: #4f6f4f;
32
+  --zenburn-green-2: #5f7f5f;
33
+  --zenburn-green-1: #6f8f6f;
34
+  --zenburn-green: #7f9f7f;
35
+  --zenburn-green-plus-1: #8fb28f;
36
+  --zenburn-green-plus-2: #9fc59f;
37
+  --zenburn-green-plus-3: #afd8af;
38
+  --zenburn-green-plus-4: #bfebbf;
39
+  --zenburn-cyan: #93e0e3;
40
+  --zenburn-blue-plus-3: #bde0f3;
41
+  --zenburn-blue-plus-2: #ace0e3;
42
+  --zenburn-blue-plus-1: #94bff3;
43
+  --zenburn-blue: #8cd0d3;
44
+  --zenburn-blue-1: #7cb8bb;
45
+  --zenburn-blue-2: #6ca0a3;
46
+  --zenburn-blue-3: #5c888b;
47
+  --zenburn-blue-4: #4c7073;
48
+  --zenburn-blue-5: #366060;
49
+  --zenburn-magenta: #dc8cc3;
50
+}
51
+html,
52
+body,
53
+ol {
54
+  margin: 0;
55
+  padding: 0;
56
+}
57
+* {
58
+  box-sizing: border-box;
59
+}
60
+
61
+body {
62
+  font-family: "Lato", sans-serif;
63
+  color: var(--foreground);
64
+  background-color: var(--background);
65
+}
66
+body {
67
+  --background: var(--zenburn-bg);
68
+  --foreground: var(--zenburn-fg);
69
+  --foreground-diminish: var(--zenburn-fg-1);
70
+  --foreground-highlight: var(--zenburn-fg-plus-1);
71
+  --accent: var(--zenburn-orange);
72
+  --link: var(--zenburn-blue);
73
+  --link-visited: var(--zenburn-magenta);
74
+}
75
+
76
+@media (prefers-color-scheme: light) {
77
+  body {
78
+    --background: var(--zenburn-fg);
79
+    --foreground: var(--zenburn-bg);
80
+    --foreground-diminish: var(--zenburn-bg-1);
81
+    --foreground-highlight: var(--zenburn-bg-plus-1);
82
+    --accent: var(--zenburn-red-5);
83
+    --link: var(--zenburn-blue-4);
84
+    --link-visited: var(--zenburn-green-4);
85
+  }
86
+}
87
+
88
+
89
+@media print {
90
+  body {
91
+    --background: white;
92
+    --foreground: black;
93
+    --foreground-diminish: #444;
94
+    --foreground-highlight: white;
95
+    --accent: #888;
96
+    --link: black;
97
+    --link-visited: black;
98
+  }
99
+  main {
100
+      text-align: justify;
101
+  }
102
+}
103
+
104
+a {
105
+  color: var(--link);
106
+}
107
+a:visited {
108
+  color: var(--link-visited);
109
+}
110
+a:hover {
111
+  outline: thin solid currentColor;
112
+}
113
+
114
+label[for="theme-selector"] {
115
+  position: fixed;
116
+  top: 1rem;
117
+  right: 1rem;
118
+}
119
+input[name="theme-selector"] {
120
+  display: none;
121
+}
122
+input[name="theme-selector"]:checked ~ label[for="theme-selector"] {
123
+  color: qvar(--zenburn-blue);
124
+  content: "light";
125
+}
126
+input[name="theme-selector"]:checked ~ label[for="theme-selector"] > *:before {
127
+  content: "light";
128
+}
129
+label[for="theme-selector"] {
130
+  color: var(--zenburn-blue-4);
131
+  content: "dark";
132
+  font-family: "Lato";
133
+}
134
+label[for="theme-selector"] > *:before {
135
+  content: "dark";
136
+}
137
+main {
138
+  line-height: 1.5;
139
+  letter-spacing: 1px;
140
+  width: 100%;
141
+  padding-top: 2rem;
142
+  padding-bottom: 2rem;
143
+  padding-right: calc(100vw - 40em);
144
+  padding-left: 5.5rem;
145
+  min-height: 100vh;
146
+}
147
+main > * {
148
+}
149
+
150
+h1,
151
+h2,
152
+h3,
153
+h4,
154
+h5,
155
+h6 {
156
+  color: var(--accent);
157
+  margin: 0.25rem 0 0.5rem 0;
158
+}
159
+h1 {
160
+  font-size: 2rem;
161
+}
162
+h2 {
163
+  font-size: 1.5rem;
164
+}
165
+h3 {
166
+  font-size: 1.25rem;
167
+}
168
+h4 {
169
+  font-size: 1.125rem;
170
+  display: none;
171
+}
172
+h5 {
173
+  font-size: 1.0625rem;
174
+}
175
+h6 {
176
+  font-size: 1.03125rem;
177
+}
178
+
179
+*:not(h1, h2, h3, h4, h5, h6) + h2,
180
+*:not(h1, h2, h3, h4, h5, h6) + h3,
181
+*:not(h1, h2, h3, h4, h5, h6) + h4,
182
+*:not(h1, h2, h3, h4, h5, h6) + h5,
183
+*:not(h1, h2, h3, h4, h5, h6) + h6 {
184
+  margin-top: 2.5rem;
185
+  margin-bottom: 1rem;
186
+}
187
+
188
+ol.sedcontrae li::before,
189
+ol.responsiones li::before,
190
+.resp::before {
191
+  color: var(--foreground-diminish);
192
+}
193
+
194
+u {
195
+  color: var(--foreground-highlight);
196
+  text-decoration: underline 0.1px;
197
+}
198
+
199
+li + li {
200
+  margin-top: 0.25em;
201
+}
202
+.objectio,
203
+.sedcontra,
204
+.responsio,
205
+.toc {
206
+  margin: 0;
207
+  padding: 0;
208
+}
209
+.pr,.articulus {
210
+    padding-bottom: 3em;
211
+}
212
+ol.objectiones,
213
+ol.sedcontrae,
214
+section.corpus {
215
+  padding-bottom: 2.5em;
216
+}
217
+ol.objectiones li + li,
218
+ol.sedcontrae li + li,
219
+ol.responsiones li + li,
220
+section.corpus li + li {
221
+  margin-top: 1.5em;
222
+}
223
+
224
+li::marker {
225
+  color: var(--foreground-diminish);
226
+}
227
+
228
+@counter-style objectio {
229
+  system: extends numeric;
230
+  prefix: "arg.";
231
+}
232
+ol.objectiones {
233
+  position: relative;
234
+  list-style: outside objectio;
235
+}
236
+
237
+@counter-style sedcontra {
238
+  system: extends numeric;
239
+  prefix: "s.c. ";
240
+}
241
+ol.sedcontrae {
242
+  position: relative;
243
+  list-style: outside sedcontra;
244
+}
245
+
246
+@counter-style responsio {
247
+  system: extends numeric;
248
+  prefix: "ad ";
249
+}
250
+ol.responsiones {
251
+  position: relative;
252
+  list-style: outside responsio;
253
+}
254
+
255
+.resp {
256
+  position: relative;
257
+}
258
+
259
+.resp::before {
260
+  content: "co. ";
261
+}
262
+
263
+@media screen and (max-width: 980px) {
264
+  body {
265
+    font-size: 24px;
266
+    line-height: 1.2em;
267
+  }
268
+  main {
269
+    padding: 0 1em;
270
+  }
271
+  ol.objectiones,
272
+  ol.sedcontrae,
273
+  ol.responsiones {
274
+    list-style-position: inside;
275
+  }
276
+}
277
+
278
+* + .minor,
279
+* + .major,
280
+* + .conclusion,
281
+.corpus > * + *,
282
+ol.responsiones > li > div + div {
283
+  margin-top: 0.75em;
284
+}