git.fiddlerwoaroof.com
Raw Blame History
(in-package :increvi)

(defvar *document*)

(defmacro with-document (&body ops)
  `(let ((*document* (plump:make-root)))
     ,@ops
     *document*))

(defgeneric node (node-designator)
  (:method ((node-designator plump:node))
    node-designator)
  (:method ((node-designator function))
    (node (funcall node-designator *document*)))
  (:method ((node-designator symbol))
    (node (string-downcase node-designator)))
  (:method ((node-designator string))
    (let ((pos 0)
          tag id classes)
      (labels ((parse ()
                 (read-tag)
                 (when pos
                   (read-attrs)))
               (separator-p (c)
                 (member c '(#\. #\#)))
               (update-pos (new-pos)
                 (setf pos new-pos))
               (read-tag ()
                 (let ((tag-end (position-if #'separator-p
                                             node-designator
                                             :start pos)))
                   (setf tag
                         (subseq node-designator 0 tag-end))
                   (update-pos tag-end)))
               (read-attrs ()
                 (loop while (and pos (< pos (length node-designator)))
                       do (ecase (elt node-designator pos)
                            (#\.
                             (update-pos (1+ pos))
                             (read-class))
                            (#\#
                             (update-pos (1+ pos))
                             (read-id)))))
               (read-class ()
                 (let ((class-end (position-if #'separator-p
                                               node-designator
                                               :start pos)))
                   (pushnew (subseq node-designator pos class-end)
                            classes
                            :test #'equal)
                   (update-pos class-end)))
               (read-id ()
                 (let ((id-end (position-if #'separator-p
                                            node-designator
                                            :start pos)))
                   (if id
                       (cerror "suppress new id" 'error)
                       (setf id
                             (subseq node-designator pos id-end)))
                   (update-pos id-end))))
        (parse)
        (let ((attrs (plump:make-attribute-map)))
          (when id
            (setf (gethash "id" attrs) id))
          (when classes
            (setf (gethash "class" attrs)
                  (with-output-to-string (s)
                    (format s "~{~a~^ ~}"
                            (nreverse classes)))))
          (plump:make-element *document* tag
                              :attributes attrs))))))

(defun {} (text)
  (etypecase text
    (string (plump:make-text-node *document* text))
    (function (plump:make-text-node *document* (funcall text)))))

(defun + (&rest nodes)
  (mapcar 'node nodes))

(defun * (node count)
  (typecase node
    (function (loop repeat count
                    for x from 0
                    for hydrated = (node (funcall node x))
                    do (plump:remove-child hydrated)
                    collect (plump:append-child *document*
                                                hydrated)))
    (t (let ((hydrated (node node)))
         (plump:remove-child hydrated)
         (loop repeat count
               collect (plump:append-child *document*
                                           (plump:clone-node hydrated t)))))))

(defun > (parent child)
  (let ((parent (node parent)))
    (prog1 parent
      (typecase child
        ((and sequence (not string))
         (map nil
              (lambda (it)
                (> parent it))
              child))
        (t (let ((child (node child)))
             (plump:remove-child child)
             (plump:append-child parent child)))))))

(defun @ (node &rest attrs)
    (let ((hydrated (node node)))
      (prog1 hydrated
        (loop for (k v) on attrs by #'cddr
              do (plump:set-attribute hydrated
                                      (etypecase k
                                        (symbol (string-downcase k))
                                        (string k))
                                      v)))))

(defun link (rel href &rest attrs)
  (apply #'@ "link"
             :rel rel
             :href href
             attrs))