git.fiddlerwoaroof.com
Raw Blame History
(defpackage :fwoar.lisp-sandbox.format-st-snips
  (:use :cl )
  (:export ))
(in-package :fwoar.lisp-sandbox.format-st-snips)

(defparameter *counters-css*
  "@counter-style objectio {
  system: extends numeric;
  prefix: \"arg.\";
}

@counter-style sedcontra {
  system: extends numeric;
  prefix: \"s.c. \";
}

@counter-style responsio {
  system: extends numeric;
  prefix: \"ad \";
}
")

(defparameter *fonts-css*
  "@font-face {
font-family: century_supra_a;
font-style: normal;
font-weight: normal;
font-stretch: normal;
font-display: auto;
src: url('./fonts/century_supra_a_regular.woff2') format('woff2');
}

@font-face {
font-family: century_supra_a;
font-style: italic;
font-weight: normal;
font-stretch: normal;
font-display: auto;
src: url('./fonts/century_supra_a_italic.woff2') format('woff2');
}

@font-face {
font-family: century_supra_a;
font-style: normal;
font-weight: bold;
font-stretch: normal;
font-display: auto;
src: url('./fonts/century_supra_a_bold.woff2') format('woff2');
}

@font-face {
font-family: century_supra_a;
font-style: italic;
font-weight: bold;
font-stretch: normal;
font-display: auto;
src: url('./fonts/century_supra_a_bold_italic.woff2') format('woff2');
}

@font-face {
font-family: concourse_4;
font-style: normal;
font-weight: normal;
font-stretch: normal;
font-display: auto;
src: url('./fonts/concourse_4_regular.woff2') format('woff2');
}

@font-face {
font-family: concourse_4;
font-style: italic;
font-weight: normal;
font-stretch: normal;
font-display: auto;
src: url('./fonts/concourse_4_italic.woff2') format('woff2');
}

@font-face {
font-family: concourse_4;
font-style: normal;
font-weight: bold;
font-stretch: normal;
font-display: auto;
src: url('./fonts/concourse_4_bold.woff2') format('woff2');
}

@font-face {
font-family: concourse_4;
font-style: italic;
font-weight: bold;
font-stretch: normal;
font-display: auto;
src: url('./fonts/concourse_4_bold_italic.woff2') format('woff2');
}
")

(defun ht->vector (h-t)
  (let* ((keys (alexandria:hash-table-keys h-t))
         (base (reduce #'min keys :initial-value 100000))
         (limit (reduce #'max keys :initial-value 0))
         (result (make-array (1+ (- limit base))
                             :initial-element nil)))
    (prog1 result
      (mapc (lambda (key)
              (setf (aref result (- key base))
                    (gethash key h-t)))
            keys))))

(defun path-hash-table (h-t path value)
  (let ((most-specific (car (last path))))
    (prog1 h-t
      (loop with cur = h-t
            for it in (butlast path)
            do
               (setf cur
                     (alexandria:ensure-gethash it cur
                                                (make-hash-table :test (hash-table-test h-t))))
            finally
               (setf (gethash most-specific cur) value)))))

(defun cycle (&rest vs)
  (let ((cur vs))
    (lambda ()
      (prog1 (car cur)
        (if (cdr cur)
            (setf cur (cdr cur))
            (setf cur vs))))))

(defun alternate (l1 l2)
  (remove-if 'null
             (mapcan 'identity
                     (funcall (data-lens:zipping 'list :fill-value nil)
                              l1 l2))))

(defun collate-it (forms)
  (let ((h-t (make-hash-table :test 'equal)))
    (prog1 h-t
      (mapc (lambda (it)
              (path-hash-table h-t
                               (cons (second it)
                                     (mapcan 'decode-ref
                                             (parse-ref (third it))))
                               (list (cons (string (second it)) (parse-ref-o (third it)))
                                     (cleanup (fourth it)))))
            forms))))

(defmacro regex-cond (it &body body)
  it body (error "macrolet"))
(defun cleanup (it)
  (macrolet ((regex-cond-case (target regex (beg end) &body body)
               `(multiple-value-bind (,beg ,end) (cl-ppcre:scan ,regex ,target)
                  (declare (ignorable ,beg ,end))
                  (when ,beg
                    (return
                      (progn ,@body)))))
             (regex-cond (it &body cases)
               (alexandria:once-only (it)
                 `(block nil
                    ,@(loop for case in cases
                            if (equal t (car case))
                              collect `(progn ,@(cdr case))
                            else collect `(regex-cond-case ,it ,@case))))))
    (with-simple-restart (skip "skip ~s" it)
      (serapeum:trim-whitespace
       (regex-cond it
         ("arg[.] [0-9]+" (b e)
                          (subseq it e))
         ("s[.] c[.] [0-9]+" (b e)
                             (subseq it e))
         ("s[.] c[.]" (b e)
                      (subseq it e))
         ("co[.]" (b e)
                  (subseq it e))
         ("pr[.]" (b e)
                  (subseq it e))
         ("ad [0-9]+" (b e)
                      (subseq it e))
         ("ad arg[.]" (b e)
                      (subseq it e))
         (t (break)))))))

(defun parse-ref-o (ref)
  (let* ((book (funcall (data-lens:regex-match "^[I-]+") ref))
         (ref (subseq ref (length book))))
    (multiple-value-bind (a b)
        (serapeum:with-collectors (w n)
          (let ((cleaner (cycle #'w (data-lens:• #'n #'parse-integer))))
            (mapcar (lambda (it)
                      (funcall (funcall cleaner)
                               (apply 'subseq ref it)))
                    (loop for x from 0 below (length ref)
                          for idx-f = (if (evenp x)
                                          'position-if
                                          'position-if-not)
                          for start-idx = 0 then idx
                          for idx = (funcall idx-f 'digit-char-p ref :start start-idx)
                          collect (list start-idx idx)
                          while idx))))
      (list* book
             (funcall (data-lens:• (data-lens:over
                                    (data-lens:applying
                                     (lambda (i j)
                                       (if (= j 0)
                                           (format nil "~a" i)
                                           (format nil "~a ~a" i j)))))
                                   (data-lens:zipping 'list :fill-value 0))
                      a b)))))

(defun parse-ref (ref)
  (if (equal ref "pr.")
      (list 1000)
      (let* ((book (funcall (data-lens:regex-match "^[I-]+") ref))
             (ref (subseq ref (length book))))
        (multiple-value-bind (a b)
            (serapeum:with-collectors (w n)
              (let ((cleaner (cycle #'w (data-lens:• #'n #'parse-integer))))
                (mapcar (lambda (it)
                          (funcall (funcall cleaner)
                                   (apply 'subseq ref it)))
                        (loop for x from 0 below (length ref)
                              for idx-f = (if (evenp x)
                                              'position-if
                                              'position-if-not)
                              for start-idx = 0 then idx
                              for idx = (funcall idx-f 'digit-char-p ref :start start-idx)
                              collect (list start-idx idx)
                              while idx))))
          (let ((a (mapcar (lambda (it)
                             (string-case:string-case (it)
                               ("pr." 1)
                               ("q." 2)
                               ("a." 3)
                               ("arg." 4)
                               ("s.c." 5)
                               ("co." 6)
                               ("ad" 7)
                               ("adarg." 8)))
                           a)))
            (list* (string-case:string-case (book)
                     ("I" 1)
                     ("I-II" 2)
                     ("II-II" 3)
                     ("III" 4))
                   (funcall (data-lens:• (data-lens:over
                                          (data-lens:applying
                                           (lambda (i j)
                                             (+ (* i 1000)
                                                j))))
                                         (data-lens:zipping 'list :fill-value 0))
                            a b)))))))

(defun format-text (forms)
  (let ((old-s-o *standard-output*)
        (dir (ensure-directories-exist "/Users/edwlan/summa-html/"))
        (files '("prooemium.html")))
    (unwind-protect
         (progn (setf *standard-output* (open (merge-pathnames (car files)
                                                               dir)
                                              :direction :output
                                              :if-exists :supersede))
                (spinneret:with-html
                  (:doctype)
                  (:html
                   (:meta :charset "utf-8")
                   (:link :rel "stylesheet"
                          :href "../style.css")
                   (:body
                    (:main
                     (let ((counter 0)
                           (question-counter -1)
                           (state))
                       (:nav
                        (:a :href "index.html" "index") "–" (:a :href "q-1.html" "next"))
                       (funcall (data-lens:over
                                 (lambda (it)
                                   (let ((id (format nil "~a~a"
                                                     (second it)
                                                     (remove #\. (third it)))))
                                     (cond
                                       ((cl-ppcre:scan "arg[.]" (fourth it))
                                        (case state
                                          ((:sc :ad)
                                           (princ "</ol>" spinneret:*html*))
                                          (:corp
                                           (princ "</section>" spinneret:*html*)))
                                        (when (cl-ppcre:scan "^.*arg[.].*Ad.*sic proceditur" (fourth it))
                                          (when (> counter 0)
                                            (princ "</div>" spinneret:*html*))
                                          (:h2 (format nil "Articulus ~@R"
                                                       (incf counter)))
                                          (princ "<div>" spinneret:*html*))
                                        (unless (eql state :arg)
                                          (princ "<ol class=\"arg\">" spinneret:*html*)
                                          (setf state :arg))
                                        (:li.obj :id id
                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 8) 8)))
                                       ((cl-ppcre:scan "s[.] c[.]" (fourth it))
                                        (case state
                                          ((:arg :ad)
                                           (princ "</ol>" spinneret:*html*))
                                          (:corp
                                           (princ "</section>" spinneret:*html*)))
                                        (unless (eql state :sc)
                                          (princ "<ol class=\"sc\">" spinneret:*html*)
                                          (setf state :sc))
                                        (:li.sc :id id
                                                (elt (fwoar.string-utils:split " " (fourth it) :count 8) 8)))
                                       ((cl-ppcre:scan "co[.]" (fourth it))
                                        (case state
                                          ((:arg :sc :ad)
                                           (princ "</ol>" spinneret:*html*)))
                                        (unless (eql state :corp)
                                          (princ "<section class=\"resp\">" spinneret:*html*)
                                          (setf state :corp))
                                        (:div.co :id id
                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 7) 7)))
                                       ((cl-ppcre:scan "pr[.]" (fourth it))
                                        (if (>= question-counter 0)
                                            (let ((new-file (format nil "q-~d.html"
                                                                    (incf question-counter))))
                                              (push (list new-file
                                                          (format nil "~a ~a"
                                                                  (second it)
                                                                  (third it)))
                                                    files)
                                              (close *standard-output*)
                                              (setf *standard-output*
                                                    (open (merge-pathnames new-file dir)
                                                          :direction :output
                                                          :if-exists :supersede))
                                              (:doctype html)
                                              (:link :rel "stylesheet"
                                                     :href "../style.css")
                                              (:meta :charset "utf-8")
                                              (princ "<main>")
                                              (:nav
                                               (if (> question-counter 0)
                                                   (progn
                                                     (:a :href (format nil "./~a"
                                                                       (if (= 1 question-counter)
                                                                           "prooemium.html"
                                                                           (format nil "q-~d.html"
                                                                                   (1- question-counter))))
                                                         "prev"))
                                                   (progn (:a :href "index.html" "index")))
                                               " – "
                                               (format nil "~a ~a" (second it) (subseq (third it)
                                                                                       0
                                                                                       (- (length
                                                                                           (third it))
                                                                                          3)))
                                               " – "
                                               (:a :href (format nil "./~a"
                                                                 (if (= 0 question-counter)
                                                                     "index.html"
                                                                     (format nil "q-~d.html"
                                                                             (1+ question-counter))))
                                                   (if (= 0 question-counter)
                                                       "index"
                                                       "next")))
                                              (setf counter 0)
                                              (:h1 (format nil "~a ~a"
                                                           (second it)
                                                           (subseq (third it)
                                                                   0
                                                                   (- (length (third it))
                                                                      3)))))
                                            (prog1 (:h1 "Prooemium")
                                              (push (list (pop files)
                                                          (format nil "~a ~a"
                                                                  (second it)
                                                                  (third it)))
                                                    files)
                                              (setf counter 0)
                                              (incf question-counter)))
                                        (:div.pr :id id
                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 5) 5)))
                                       ((cl-ppcre:scan "ad [0-9]" (fourth it))
                                        (case state
                                          ((:arg :sc)
                                           (princ "</ol>" spinneret:*html*))
                                          (:corp
                                           (princ "</section>" spinneret:*html*)))
                                        (unless (eql state :ad)
                                          (princ "<ol class=\"ad\">" spinneret:*html*)
                                          (setf state :ad))
                                        (:li.adr :id id
                                                 (elt (fwoar.string-utils:split " " (fourth it) :count 8) 8)))
                                       (t (fourth it))))))
                                forms)
                       (case state
                         ((:arg :sc :ad)(princ "</ol>" spinneret:*html*))
                         (:corp (princ "</section>" spinneret:*html*)))
                       nil))))))
      (close *standard-output*)
      (setf *standard-output* old-s-o))
    (let ((*standard-output* (open (merge-pathnames "index.html" dir)
                                   :direction :output
                                   :if-exists :supersede)))
      (spinneret:with-html
        (:doctype html)
        (:link :rel "stylesheet"
               :href "../style.css")
        (:meta :charset "utf-8")
        (:main
         (:nav (:a :href ".." "totum"))
         (:ul
          (loop for (file cite) in (reverse files)
                do (:li (:a :href file cite)))))))))

(defun idify (ref)
  (remove-if-not 'alphanumericp
                 (format nil "~{~a~}" ref)))
(defun fnify (ref)
  (with-output-to-string (s)
    (mapcar (lambda (it)
              (princ (remove-if-not 'alphanumericp
                                    (typecase it
                                      (integer (format nil "~4,'0d" it))
                                      (t (format nil "~a" it))))
                     s))
            ref)))

(defun classify (context)
  (:printv context)
  "")
(defun htmlify (h-t type number title &optional context)
  (flet ((article-template (thing)
           (spinneret:with-html
             (let ((arg (gethash "arg" thing)))
               (when arg
                 (:h4 "Objectiones")
                 (let ((arg-v (ht->vector arg)))
                   (:ol.objectiones
                    (map nil
                         (lambda (it)
                           (:li.objectio :id (idify (car it))
                                         (cadr it)))
                         arg-v)))))
             (let ((sc (gethash "sc" thing)))
               (when sc
                 (:h4 "Sed Contra")
                 (typecase sc
                   (hash-table (let ((sc-v (ht->vector sc)))
                                 (:ol.sedcontrae
                                  (map nil
                                       (lambda (it)
                                         (:li.sedcontra :id (idify (car it))
                                                        (cadr it)))
                                       sc-v))))
                   (cons
                    (:ol.sedcontrae
                     (:li.sedcontra
                      :id (idify (car sc))
                      (cadr sc)))))))
             (let ((co (gethash "co" thing)))
               (when co
                 (:h4 "Respondeo")
                 (:section.corpus :id (idify (car co))
                                  (cadr co))))
             (let ((responsiones (gethash "ad" thing)))
               (when responsiones
                 (:h4 "Ad Objectiones")
                 (let ((responsiones-v (ht->vector responsiones)))
                   (:ol.responsiones
                    (map nil
                         (lambda (it)
                           (:li.responsio :id (idify (car it))
                                          (cadr it)))
                         responsiones-v)))))
             (let ((adarg (gethash "adarg" thing)))
               (when adarg
                 (:h4 "Ad Objectiones")
                 (:ol.responsiones
                  (:li.responsi :id (idify (car adarg))
                                (cadr adarg))))))))
    (let ((thing (gethash number h-t)))
      (string-case:string-case (type)
        ("q"
         (spinneret:with-html
           (let ((next-context (list* number "q" context)))
             (:section :id (idify (reverse next-context))
                       :class "quaestio"
                       (:h2 "Quaestio" (format nil "~@r" number) title
                            (:a :href (format nil "#~a" (idify (reverse next-context)))
                                "#"))
                       (let ((maybe-pr (gethash "pr" thing)))
                         (when maybe-pr
                           (:section.pr
                            (:h3 "Prooemium")
                            (destructuring-bind (ref pr) maybe-pr
                              (:div :id (idify ref)
                                    pr)))))
                       (let* ((article-ht (gethash "a" thing)))
                         (if article-ht
                             (:section.articuli
                              (let ((articles (ht->vector article-ht)))
                                (map nil
                                     (lambda (idx)
                                       (htmlify article-ht
                                                "a"
                                                idx
                                                ""
                                                next-context))
                                     (alexandria:iota (length articles) :start 1))))
                             (article-template thing)))
                       ))))
        ("a"
         (let ((next-context (list* number "a" context)))
           (spinneret:with-html
             (:section.articulus
              :id (idify (reverse next-context))
              (:h3 "Articulus" number title
                   (:a :href (format nil "#~a" (idify (reverse next-context)))
                       "#"))
              (article-template thing)
              ))))))))

(defun style.css ()
  (lass:compile-and-write
   '(:import (url "./counters.css"))
   '(:import (url "./fonts.css"))

   '((nav > a + a)
     :margin-left 1em)

   (let ((colors '(("zenburn-fg-plus-2" . "#FFFFEF")
                   ("zenburn-fg-plus-1" . "#F5F5D6")
                   ("zenburn-fg" . "#DCDCCC")
                   ("zenburn-fg-1" . "#A6A689")
                   ("zenburn-fg-2" . "#656555")
                   ("zenburn-black" . "#000000")
                   ("zenburn-bg-2" . "#000000")
                   ("zenburn-bg-1" . "#111112")
                   ("zenburn-bg-05" . "#383838")
                   ("zenburn-bg" . "#2A2B2E")
                   ("zenburn-bg-plus-05" . "#494949")
                   ("zenburn-bg-plus-1" . "#4F4F4F")
                   ("zenburn-bg-plus-2" . "#5F5F5F")
                   ("zenburn-bg-plus-3" . "#6F6F6F")
                   ("zenburn-red-plus-2" . "#ECB3B3")
                   ("zenburn-red-plus-1" . "#DCA3A3")
                   ("zenburn-red" . "#CC9393")
                   ("zenburn-red-1" . "#BC8383")
                   ("zenburn-red-2" . "#AC7373")
                   ("zenburn-red-3" . "#9C6363")
                   ("zenburn-red-4" . "#8C5353")
                   ("zenburn-red-5" . "#7C4343")
                   ("zenburn-red-6" . "#6C3333")
                   ("zenburn-orange" . "#DFAF8F")
                   ("zenburn-yellow" . "#F0DFAF")
                   ("zenburn-yellow-1" . "#E0CF9F")
                   ("zenburn-yellow-2" . "#D0BF8F")
                   ("zenburn-green-5" . "#2F4F2F")
                   ("zenburn-green-4" . "#3F5F3F")
                   ("zenburn-green-3" . "#4F6F4F")
                   ("zenburn-green-2" . "#5F7F5F")
                   ("zenburn-green-1" . "#6F8F6F")
                   ("zenburn-green" . "#7F9F7F")
                   ("zenburn-green-plus-1" . "#8FB28F")
                   ("zenburn-green-plus-2" . "#9FC59F")
                   ("zenburn-green-plus-3" . "#AFD8AF")
                   ("zenburn-green-plus-4" . "#BFEBBF")
                   ("zenburn-cyan" . "#93E0E3")
                   ("zenburn-blue-plus-3" . "#BDE0F3")
                   ("zenburn-blue-plus-2" . "#ACE0E3")
                   ("zenburn-blue-plus-1" . "#94BFF3")
                   ("zenburn-blue" . "#8CD0D3")
                   ("zenburn-blue-1" . "#7CB8BB")
                   ("zenburn-blue-2" . "#6CA0A3")
                   ("zenburn-blue-3" . "#5C888B")
                   ("zenburn-blue-4" . "#4C7073")
                   ("zenburn-blue-5" . "#366060")
                   ("zenburn-magenta" . "#DC8CC3"))))
     `(:root
       ,@(loop for (name . color) in colors
               appending (list (alexandria:make-keyword (format nil "--~a" name))
                               (string-downcase color)))))
   '(* :box-sizing border-box)
   '((:or html body ol)
     :margin 0
     :padding 0)

   '(body
     :color (var --foreground)
     :background-color (var --background))

   '(body
     :--background (var --zenburn-bg)
     :--foreground (var --zenburn-fg)
     :--foreground-diminish (var --zenburn-fg-1)
     :--foreground-highlight (var --zenburn-fg-plus-1)
     :--accent (var --zenburn-orange)
     :--link (var --zenburn-blue)
     :--link-visited (var --zenburn-magenta))

   '(:media "(prefers-color-scheme: light)"
     (body
      :--background (var --zenburn-fg)
      :--foreground (var --zenburn-bg)
      :--foreground-diminish (var --zenburn-bg-1)
      :--foreground-highlight (var --zenburn-bg-plus-1)
      :--accent (var --zenburn-red-5)
      :--link (var --zenburn-blue-4)
      :--link-visited (var --zenburn-green-4)))


   '(:media "print"
     (body
      :--background white
      :--foreground black
      :--foreground-diminish "#444"
      :--foreground-highlight white
      :--accent "#888"
      :--link black
      :--link-visited black)
     (main
      :text-align justify))

   '(a
     :color (var --link))
   '((:and a :visited)
     :color (var --link-visited))
   '((:and a :hover)
     :outline thin solid "currentColor")

   '(main
     :font-family sans-serif
     :hyphens auto
     :text-rendering optimizeLegibility
     :font-feature-settings "'liga' on, 'onum' on"

     :line-height 1.5
     :letter-spacing 1px
     :width 100vw
     :padding-top 2rem
     :padding-bottom 2rem
     :padding-right (calc (- 100vw 40em))
     :padding-left 5.5rem
     :min-height 100vh)

   '(label
     :position relative
     :padding 0.25em
     :left 5.5rem
     :top 0.5rem)
   '((:and input (:= type "radio"))
     :display none)

   '(((:or
       main
       (:and label (:= for "century-supra"))) ~ main)
     :font-family "century_supra_a")
   '((:or
      (:and label (:= for "concourse"))
      ((:and :checked "#concourse") ~ main))
     :font-family "concourse_4")

   '((:or h1 h2 h3 h4 h5 h6)
     :color (var --accent)
     :font-family "century_supra_a"
     :margin 0.25rem 0 0.5rem 0)
   '((:or h4 h5 h6)
     :display none)

   `(:let ()
      ,@(loop for tag in '(h1 h2 h3 h4 h5 h6)
              for font-size = 2 then (/ (+ font-size 1) 2.0)
              collect `(,tag :font-size ,(format nil "~arem" font-size))))

   '(((:not (:or h1 h2 h3 h4 h5 h6)) + (:or h1 h2 h3 h4 h5 h6))
     :margin-top 2.5rem
     :margin-bottom 1rem)

   '(u
     :color (var --foreground-highlight)
     :text-decoration underline 0.1px)

   '((li + li)
     :margin-top 0.25em)

   '((:or .pr .articulus)
     :padding-bottom 3em)
   '((:or ol.objectiones ol.sedcontrae #|ol.responsiones|# section.corpus)
     :padding-bottom 2.5em)
   '(((:or ol.objectiones ol.sedcontrae ol.responsiones section.corpus) li + li)
     :margin-top 1.5em)

   '((:and li "::marker")
     :color (var --foreground-diminish))

   '(ol.objectiones
     :list-style outside objectio)
   '(ol.sedcontrae
     :list-style outside sedcontra)
   '(ol.responsiones
     :list-style outside responsio)

   '((:or .objectio .sedcontra .responsio .toc)
     :margin 0
     :padding 0)

   '(:media (:and screen "(max-width: 980px)")
     (body
      :font-size 24px
      :line-height 1.2em)
     (main
      :padding 0 1em)
     ((:or ol.objectiones ol.sedcontrae ol.responsiones)
      :list-style-position inside))))

(defun question-template (questions number &optional context)
  (let ((spinneret:*html-lang* "it")
        (prooemium (fw.lu:dive (list number "pr") questions)))
    (spinneret:with-html
      (:doctype html)
      (:html
       (:meta :charset "utf-8")
       (:meta :property "book:author" :content "Sancti Thomae de Aquino")
       (:meta :property "book:tag" :content "theology")
       (:meta :property "book:tag" :content "theologia")
       (:meta :property "book:tag" :content "thomism")
       (:meta :property "book:tag" :content "aquinas")
       (when prooemium
         (:meta :property "og:description"
                :content (cadr prooemium)))
       (:link :rel "stylesheet" :href "../style.css")
       (:title ("Quaestio ~@r — Summa Theologiae" number))

       (:input :type "radio" :name "font" :id "concourse" :checked t)
       (:label :for "concourse" "A")
       (:input :type "radio" :name "font" :id "century-supra")
       (:label :for "century-supra" "A")
       (:main
        (:nav (:a :href "." "pars")
              (:a :href ".." "totum"))
        (htmlify questions "q" number "" context))))))

(defun dump-questions (dir questions &optional context)
  (let ((q-v (ht->vector questions))
        (files ()))
    (map nil
         (lambda (idx)
           (let ((ref (reverse (list* idx "q" context))))
             (with-open-file (*standard-output* (merge-pathnames (make-pathname
                                                                  :name (fnify ref)
                                                                  :type "html")
                                                                 dir)
                                                :direction :output
                                                :if-exists :supersede)
               (question-template questions idx context))
             (push ref files)))
         (alexandria:iota (length q-v) :start 1))

    (alexandria:with-output-to-file (*standard-output* (merge-pathnames "index.html" dir)
                                                       :if-exists :supersede)
      (spinneret:with-html
        (:doctype html)
        (:link :rel "stylesheet" :href "../style.css")
        (:body
         (:main
          (:nav (:a :href ".." "totum"))
          (:ul
           (loop for file in (reverse files)
                 do (:li
                     (:a :href (format nil "./~a.html" (fnify file))
                         (format nil "~{~a~^ ~}" file)))))))))))

(defun make-toplevel-structure (dir)
  (alexandria:with-output-to-file (s (merge-pathnames "style.css" dir)
                                     :if-exists :supersede)
    (princ (style.css) s)
    (terpri s))

  (alexandria:with-output-to-file (s (merge-pathnames "counters.css" dir)
                                     :if-exists :supersede)
    (princ *counters-css* s))

  (alexandria:with-output-to-file (s (merge-pathnames "fonts.css" dir)
                                     :if-exists :supersede)
    (princ *fonts-css* s))

  (alexandria:with-output-to-file (*standard-output* (merge-pathnames "index.html" dir)
                                                     :if-exists :supersede)
    (spinneret:with-html
      (:doctype html)
      (:html
       (:head
        (:meta :charset "utf-8")
        (:title "Summa Theologiae")
        (:link :rel "stylesheet" :href "./style.css"))
       (:body
        (:main
         (:h1 "Summa Theologiae")
         (:h2 "Sancti Thomae de Aquino")
         (:ul
          (:li (:a :href "./I/index.html" "Prima Pars"))
          (:li "Secunda Pars"
               (:ul
                (:li (:a :href "./I-II/index.html" "Prima Pars Secundae Partis"))
                (:li (:a :href "./II-II/index.html" "Secunda Pars Secundae Partis"))))
          (:li (:a :href "./III/index.html" "Tertia Pars"))))))))
  (values))

(defun decode-ref (num &optional long)
  (multiple-value-bind (m r) (floor num 1000)
    (list* (ecase m
             (0 (if long "liber" "l"))
             (1 (if long "prooemium" "pr"))
             (2 (if long "quaestio" "q"))
             (3 (if long "articulus" "a"))
             (4 "arg")
             (5 "sc")
             (6 "co")
             (7 "ad")
             (8 "adarg"))
           (unless (= r 0)
             (list r)))))

(defun ref->id (ref)
  (with-output-to-string (s)
    (mapc (lambda (it)
            (destructuring-bind (type &optional v) it
              (string-case:string-case (type)
                ("l" (format s "~@r" v))
                (t (format s "~a~:[~;~:*~a~]" type v)))))
          ref)))
(defun write-question (s)
  (lambda ()))
;;("ST" "I" "q. 3" "a. 7" "s.c.")