;;;; cl-scopes/web/dom - "Data Output Model" = simple and dedicated HTML generator (defpackage :scopes/web/dom (:use :common-lisp) (:local-nicknames (:util :scopes/util) (:alx :alexandria)) (:export #:render #:text #:dl #:link)) (in-package :scopes/web/dom) ;;;; basic / common stuff (defvar *output* (make-string-output-stream)) (defmacro render (&body body) `(let ((*output* (make-string-output-stream))) ,@body (get-output-stream-string *output*))) (defmacro put-string (s) `(write-string ,s *output*)) (defmacro put-char (c) `(write-char ,c *output*)) (defmacro element (tag attrs &body body) `(progn (start ,tag ,attrs) ,@body (end ,tag))) (defun put-attrs (plist) (loop for (key val . r) on plist by #'cddr do (put-char #\Space) (when val (put-string (string-downcase key)) (when (not (eq val t)) (put-string "=\"") (put-string (attr-str key val)) (put-char #\"))))) (defun attr-str (key val) (case key ((:id :class) (util:to-string val)) (t (string val)))) (defun start (tag &optional attrs) (put-char #\<) (put-string tag) (put-attrs attrs) (put-char #\>)) (defun end (tag) (put-string ")) (defun text (s) (put-string (string s))) (defun newline () (put-char #\Newline)) ;;;; tag-specific renderers (defun dl (attrs plist) (element "dl" attrs (loop for (key val . r) on plist by #'cddr do (element "dt" nil (put-string (string-downcase key))) (dd nil val)))) (defun dd (attrs v) (if (atom v) (setf v (list v))) (dolist (el v) (element "dd" attrs (text el)))) (defmacro link (attrs &body body) `(element "a" ,attrs ,@body))