diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2023-05-28 01:37:52 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2023-05-28 01:37:52 -0700 |
commit | 87b0002faa80f358938bd2dabd8ac4bdd5bd9a50 (patch) | |
tree | f4da01f2a83bdc9ffc15b407e17b05432758e9fe | |
parent | 07dafe9b351c32326ce20b5804e798f10d4f273d (diff) | |
download | tl-who-87b0002faa80f358938bd2dabd8ac4bdd5bd9a50.tar.gz tl-who-87b0002faa80f358938bd2dabd8ac4bdd5bd9a50.tar.bz2 tl-who-87b0002faa80f358938bd2dabd8ac4bdd5bd9a50.zip |
First cut at TXR Lisp translation.
This relies on an improvement in TXR Lisp that will
be released in TXR 287: parameter macros like :key being
expanded inside nested macro parameter lists.
-rw-r--r-- | packages.tl (renamed from packages.lisp) | 71 | ||||
-rw-r--r-- | specials.lisp | 132 | ||||
-rw-r--r-- | specials.tl | 117 | ||||
-rw-r--r-- | tl-who.tl | 4 | ||||
-rw-r--r-- | util.lisp | 252 | ||||
-rw-r--r-- | util.tl | 56 | ||||
-rw-r--r-- | who.lisp | 331 | ||||
-rw-r--r-- | who.tl | 251 |
8 files changed, 455 insertions, 759 deletions
diff --git a/packages.lisp b/packages.tl index c52da45..eb62f6a 100644 --- a/packages.lisp +++ b/packages.tl @@ -1,20 +1,18 @@ -;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*- -;;; $Header: /usr/local/cvsrep/cl-who/packages.lisp,v 1.21 2009/01/26 11:10:49 edi Exp $ - ;;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved. - +;;; Copyright (c) 2023, Kaz Kylheku. +;;; ;;; Redistribution and use in source and binary forms, with or without ;;; modification, are permitted provided that the following conditions ;;; are met: - +;;; ;;; * Redistributions of source code must retain the above copyright ;;; notice, this list of conditions and the following disclaimer. - +;;; ;;; * Redistributions in binary form must reproduce the above ;;; copyright notice, this list of conditions and the following ;;; disclaimer in the documentation and/or other materials ;;; provided with the distribution. - +;;; ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -27,40 +25,25 @@ ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -(in-package :cl-user) - -(defpackage :cl-who - (:use :cl) - (:nicknames :who) - #+(or :clasp :sbcl) (:shadow #:defconstant) - #+:sb-package-locks (:lock t) - (:export #:*attribute-quote-char* - #:*empty-attribute-syntax* - #:*escape-char-p* - #:*prologue* - #:*downcase-tokens-p* - #:*html-no-indent-tags* - #:*html-empty-tags* - #:*html-empty-tag-aware-p* - #:conc - #:convert-attributes - #:convert-tag-to-string-list - #:esc - #:escape-char - #:escape-char-all - #:escape-char-iso-8859-1 - #:escape-char-minimal - #:escape-char-minimal-plus-quotes - #:escape-string - #:escape-string-all - #:escape-string-iso-8859-1 - #:escape-string-minimal - #:escape-string-minimal-plus-quotes - #:fmt - #:htm - #:html-mode - #:str - #:with-html-output - #:with-html-output-to-string)) - -(pushnew :cl-who *features*) +(defpackage "tl-who" + (:fallback "usr") + (:local "*attribute-quote-char*" + "*empty-attribute-syntax*" + "*prologue*" + "*upcase-tokens-p*" + "*html-no-indent-tags*" + "*html-empty-tags*" + "*html-empty-tag-aware-p*" + "conc" + "convert-attributes" + "convert-tag-to-string-list" + "esc" + "fmt" + "htm" + "html-mode" + "str" + "with-html-output" + "with-html-output-to-string")) + +(defpackage "tl-who-priv" + (:fallback "tl-who" "usr")) diff --git a/specials.lisp b/specials.lisp deleted file mode 100644 index cb0401c..0000000 --- a/specials.lisp +++ /dev/null @@ -1,132 +0,0 @@ -;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package:CL-WHO; Base: 10 -*- -;;; $Header: /usr/local/cvsrep/cl-who/specials.lisp,v 1.6 2009/01/26 11:10:49 edi Exp $ - -;;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved. - -;;; Redistribution and use in source and binary forms, with or without -;;; modification, are permitted provided that the following conditions -;;; are met: - -;;; * Redistributions of source code must retain the above copyright -;;; notice, this list of conditions and the following disclaimer. - -;;; * Redistributions in binary form must reproduce the above -;;; copyright notice, this list of conditions and the following -;;; disclaimer in the documentation and/or other materials -;;; provided with the distribution. - -;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED -;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(in-package :cl-who) - -#+(or :clasp :sbcl) -(defmacro defconstant (name value &optional doc) - "Make sure VALUE is evaluated only once \(to appease SBCL & clasp)." - `(cl:defconstant ,name (if (boundp ',name) (symbol-value ',name) ,value) - ,@(when doc (list doc)))) - -(defvar *prologue* - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" - "This is the first line that'll be printed if the :PROLOGUE keyword -argument is T") - -(defvar *escape-char-p* - (lambda (char) - (or (find char "<>&'\"") - (> (char-code char) 127))) - "Used by ESCAPE-STRING to test whether a character should be escaped.") - -(defvar *indent* nil - "Whether to insert line breaks and indent. Also controls amount of -indentation dynamically.") - -(defvar *html-mode* :xml - ":SGML for \(SGML-)HTML, :XML \(default) for XHTML, :HTML5 for HTML5.") - -(defvar *empty-attribute-syntax* nil - "Set this to t to enable attribute minimization (also called -'boolean attributes', or 'empty attribute syntax' according to the w3 -html standard). In XHTML attribute minimization is forbidden, and all -attributes must have a value. Thus in XHTML boolean attributes must be -defined as <input disabled='disabled' />. In HTML5 boolean attributes -can be defined as <input disabled>") - -(defvar *downcase-tokens-p* t - "If NIL, a keyword symbol representing a tag or attribute name will -not be automatically converted to lowercase. If T, the tag and -attribute name will be converted to lowercase only if it is in the -same case. This is useful when one needs to output case sensitive -XML.") - -(defvar *attribute-quote-char* #\' - "Quote character for attributes.") - -(defvar *empty-tag-end* " />" - "End of an empty tag. Default is XML style.") - -(defvar *html-no-indent-tags* - '(:pre :textarea) - "The list of HTML tags that should disable indentation inside them. The initial -value is a list containing only :PRE and :TEXTAREA.") - -(defvar *html-empty-tags* - '(:area - :atop - :audioscope - :base - :basefont - :br - :choose - :col - :command - :embed - :frame - :hr - :img - :input - :isindex - :keygen - :left - :limittext - :link - :meta - :nextid - :of - :over - :param - :range - :right - :source - :spacer - :spot - :tab - :track - :wbr) - "The list of HTML tags that should be output as empty tags. -See *HTML-EMPTY-TAG-AWARE-P*.") - -(defvar *html-empty-tag-aware-p* t - "Set this to NIL to if you want to use CL-WHO as a strict XML -generator. Otherwise, CL-WHO will only write empty tags listed -in *HTML-EMPTY-TAGS* as <tag/> \(XHTML mode) or <tag> \(SGML -mode and HTML5 mode). For all other tags, it will always generate -<tag></tag>.") - -(defconstant +newline+ (make-string 1 :initial-element #\Newline) - "Used for indentation.") - -(defconstant +spaces+ (make-string 2000 - :initial-element #\Space - :element-type 'base-char) - "Used for indentation.") - diff --git a/specials.tl b/specials.tl new file mode 100644 index 0000000..e743e1b --- /dev/null +++ b/specials.tl @@ -0,0 +1,117 @@ +;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved. +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions +;; are met: +;; +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following +;; disclaimer in the documentation and/or other materials +;; provided with the distribution. +;; +;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED +;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(in-package "tl-who-priv") + +;; Kind of simulate Common Lisp defconstant. +(defmacro defconstant (name value) + ^(unless (boundp ',name) + (defsymacro ,name (macro-time ,value)))) + + +;; This is the first line that'll be printed if the :prologue keyword +;; argument is true. +(defvar *prologue* + "<!DOCTYPE html PUBLIC\ \ + \"-//W3C//DTD XHTML 1.0 Strict//EN\"\ \ + \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">") + +;; Whether to insert line breaks and indent. Also controls amount of +;; indentation dynamically. +(defvar *indent* nil) + +;; :sgml for (SGML-)HTML, :xml (default) for xhtml, :html5 for html5.") +(defvar *html-mode* :xml) + +;; Set this to t to enable attribute minimization (also called +;; 'boolean attributes', or 'empty attribute syntax' according to the w3 +;; html standard). In XHTML attribute minimization is forbidden, and all +;; attributes must have a value. Thus in XHTML boolean attributes must be +;; defined as <input disabled='disabled' />. In HTML5 boolean attributes +;; can be defined as <input disabled>. +(defvar *empty-attribute-syntax* nil) + + +;; If true, tag and attribute names will be converted to upper case only +;; if they contain no upper case letters. This is useful when one needs to +;; output case sensitive XML. +(defvar *upcase-tokens-p* nil) + +;; Quote character for attributes. +(defvar *attribute-quote-char* #\') + +;; End of an empty tag. Default is XML style. +(defvar *empty-tag-end* " />") + +;; The list of HTML tags that should disable indentation inside them. +;; The initial value is a list containing only :pre and :textarea. +(defvar *html-no-indent-tags* '(:pre :textarea)) + +;; The list of HTML tags that should be output as empty tags. +;; See *html-empty-tag-aware-p*. +(defvar *html-empty-tags* + '(:area + :atop + :audioscope + :base + :basefont + :br + :choose + :col + :command + :embed + :frame + :hr + :img + :input + :isindex + :keygen + :left + :limittext + :link + :meta + :nextid + :of + :over + :param + :range + :right + :source + :spacer + :spot + :tab + :track + :wbr)) + +;; Set this to NIL to if you want to use CL-WHO as a strict XML +;; generator. Otherwise, CL-WHO will only write empty tags listed +;; in *HTML-EMPTY-TAGS* as <tag/> (XHTML mode) or <tag> (SGML +;; mode and HTML5 mode). For all other tags, it will always generate +;; <tag></tag> +(defvar *html-empty-tag-aware-p* t) + +;; Used for indentation. +(defconstant +newline+ "\n") diff --git a/tl-who.tl b/tl-who.tl new file mode 100644 index 0000000..5b22d6e --- /dev/null +++ b/tl-who.tl @@ -0,0 +1,4 @@ +(load "packages") +(load "specials") +(load "util") +(load "who") diff --git a/util.lisp b/util.lisp deleted file mode 100644 index 17743fd..0000000 --- a/util.lisp +++ /dev/null @@ -1,252 +0,0 @@ -;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package:CL-WHO; Base: 10 -*- -;;; $Header: /usr/local/cvsrep/cl-who/util.lisp,v 1.4 2009/01/26 11:10:49 edi Exp $ - -;;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved. - -;;; Redistribution and use in source and binary forms, with or without -;;; modification, are permitted provided that the following conditions -;;; are met: - -;;; * Redistributions of source code must retain the above copyright -;;; notice, this list of conditions and the following disclaimer. - -;;; * Redistributions in binary form must reproduce the above -;;; copyright notice, this list of conditions and the following -;;; disclaimer in the documentation and/or other materials -;;; provided with the distribution. - -;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED -;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(in-package :cl-who) - -#+:lispworks -(eval-when (:compile-toplevel :load-toplevel :execute) - (import 'lw:with-unique-names)) - -#-:lispworks -(defmacro with-unique-names ((&rest bindings) &body body) - "Syntax: WITH-UNIQUE-NAMES ( { var | (var x) }* ) declaration* form* - -Executes a series of forms with each VAR bound to a fresh, -uninterned symbol. The uninterned symbol is as if returned by a call -to GENSYM with the string denoted by X - or, if X is not supplied, the -string denoted by VAR - as argument. - -The variable bindings created are lexical unless special declarations -are specified. The scopes of the name bindings and declarations do not -include the Xs. - -The forms are evaluated in order, and the values of all but the last -are discarded \(that is, the body is an implicit PROGN)." - ;; reference implementation posted to comp.lang.lisp as - ;; <cy3bshuf30f.fsf@ljosa.com> by Vebjorn Ljosa - see also - ;; <http://www.cliki.net/Common%20Lisp%20Utilities> - `(let ,(mapcar #'(lambda (binding) - (check-type binding (or cons symbol)) - (if (consp binding) - (destructuring-bind (var x) binding - (check-type var symbol) - `(,var (gensym ,(etypecase x - (symbol (symbol-name x)) - (character (string x)) - (string x))))) - `(,binding (gensym ,(symbol-name binding))))) - bindings) - ,@body)) - -#+:lispworks -(eval-when (:compile-toplevel :load-toplevel :execute) - (setf (macro-function 'with-rebinding) - (macro-function 'lw:rebinding))) - -#-:lispworks -(defmacro with-rebinding (bindings &body body) - "WITH-REBINDING ( { var | (var prefix) }* ) form* - -Evaluates a series of forms in the lexical environment that is -formed by adding the binding of each VAR to a fresh, uninterned -symbol, and the binding of that fresh, uninterned symbol to VAR's -original value, i.e., its value in the current lexical environment. - -The uninterned symbol is created as if by a call to GENSYM with the -string denoted by PREFIX - or, if PREFIX is not supplied, the string -denoted by VAR - as argument. - -The forms are evaluated in order, and the values of all but the last -are discarded \(that is, the body is an implicit PROGN)." - ;; reference implementation posted to comp.lang.lisp as - ;; <cy3wv0fya0p.fsf@ljosa.com> by Vebjorn Ljosa - see also - ;; <http://www.cliki.net/Common%20Lisp%20Utilities> - (loop for binding in bindings - for var = (if (consp binding) (car binding) binding) - for name = (gensym) - collect `(,name ,var) into renames - collect ``(,,var ,,name) into temps - finally (return `(let ,renames - (with-unique-names ,bindings - `(let (,,@temps) - ,,@body)))))) - -;; TODO... -#+(or) -(defun apply-to-tree (function test tree) - (declare (optimize speed space)) - (declare (type function function test)) - "Applies FUNCTION recursively to all elements of the tree TREE \(not -only leaves) which pass TEST." - (cond - ((funcall test tree) - (funcall function tree)) - ((consp tree) - (cons - (apply-to-tree function test (car tree)) - (apply-to-tree function test (cdr tree)))) - (t tree))) - -(defmacro n-spaces (n) - "A string with N spaces - used by indentation." - `(make-array ,n - :element-type 'base-char - :displaced-to +spaces+ - :displaced-index-offset 0)) - -(declaim (inline escape-char)) -(defun escape-char (char &key (test *escape-char-p*)) - (declare (optimize speed)) - "Returns an escaped version of the character CHAR if CHAR satisfies -the predicate TEST. Always returns a string." - (if (funcall test char) - (case char - (#\< "<") - (#\> ">") - (#\& "&") - (#\' "'") - (#\" """) - (t (format nil (if (eq *html-mode* :xml) "&#x~x;" "&#~d;") - (char-code char)))) - (make-string 1 :initial-element char))) - -(defun escape-string (string &key (test *escape-char-p*)) - (declare (optimize speed)) - "Escape all characters in STRING which pass TEST. This function is -not guaranteed to return a fresh string. Note that you can pass NIL -for STRING which'll just be returned." - (let ((first-pos (position-if test string)) - (format-string (if (eq *html-mode* :xml) "&#x~x;" "&#~d;"))) - (if (not first-pos) - ;; nothing to do, just return STRING - string - (with-output-to-string (s) - (loop with len = (length string) - for old-pos = 0 then (1+ pos) - for pos = first-pos - then (position-if test string :start old-pos) - ;; now the characters from OLD-POS to (excluding) POS - ;; don't have to be escaped while the next character has to - for char = (and pos (char string pos)) - while pos - do (write-sequence string s :start old-pos :end pos) - (case char - ((#\<) - (write-sequence "<" s)) - ((#\>) - (write-sequence ">" s)) - ((#\&) - (write-sequence "&" s)) - ((#\') - (write-sequence "'" s)) - ((#\") - (write-sequence """ s)) - (otherwise - (format s format-string (char-code char)))) - while (< (1+ pos) len) - finally (unless pos - (write-sequence string s :start old-pos))))))) - -(defun minimal-escape-char-p (char) - "Helper function for the ESCAPE-FOO-MINIMAL functions to determine -whether CHAR must be escaped." - (find char "<>&")) - -(defun escape-char-minimal (char) - "Escapes only #\<, #\>, and #\& characters." - (escape-char char :test #'minimal-escape-char-p)) - -(defun escape-string-minimal (string) - "Escapes only #\<, #\>, and #\& in STRING." - (escape-string string :test #'minimal-escape-char-p)) - -(defun minimal-plus-quotes-escape-char-p (char) - "Helper function for the ESCAPE-FOO-MINIMAL-PLUS-QUOTES functions to -determine whether CHAR must be escaped." - (find char "<>&'\"")) - -(defun escape-char-minimal-plus-quotes (char) - "Like ESCAPE-CHAR-MINIMAL but also escapes quotes." - (escape-char char :test #'minimal-plus-quotes-escape-char-p)) - -(defun escape-string-minimal-plus-quotes (string) - "Like ESCAPE-STRING-MINIMAL but also escapes quotes." - (escape-string string :test #'minimal-plus-quotes-escape-char-p)) - -(defun iso-8859-1-escape-char-p (char) - "Helper function for the ESCAPE-FOO-ISO-8859-1 functions to -determine whether CHAR must be escaped." - (or (find char "<>&'\"") - (> (char-code char) 255))) - -(defun escape-char-iso-8859-1 (char) - "Escapes characters that aren't defined in ISO-8859-9." - (escape-char char :test #'iso-8859-1-escape-char-p)) - -(defun escape-string-iso-8859-1 (string) - "Escapes all characters in STRING which aren't defined in ISO-8859-1." - (escape-string string :test #'iso-8859-1-escape-char-p)) - -(defun non-7bit-ascii-escape-char-p (char) - "Helper function for the ESCAPE-FOO-ISO-8859-1 functions to -determine whether CHAR must be escaped." - (or (find char "<>&'\"") - (> (char-code char) 127))) - -(defun escape-char-all (char) - "Escapes characters which aren't in the 7-bit ASCII character set." - (escape-char char :test #'non-7bit-ascii-escape-char-p)) - -(defun escape-string-all (string) - "Escapes all characters in STRING which aren't in the 7-bit ASCII -character set." - (escape-string string :test #'non-7bit-ascii-escape-char-p)) - -(defun extract-declarations (forms) - "Given a FORM, the declarations - if any - will be extracted - from the head of the FORM, and will return two values the declarations, - and the remaining of FORM" - (loop with declarations - for forms on forms - for form = (first forms) - while (and (consp form) - (eql (first form) 'cl:declare)) - do (push form declarations) - finally (return (values (nreverse declarations) forms)))) - -(defun same-case-p (string) - "Test if all characters of a string are in the same case." - (or (every #'(lambda (c) (or (not (alpha-char-p c)) (lower-case-p c))) string) - (every #'(lambda (c) (or (not (alpha-char-p c)) (upper-case-p c))) string))) - -(defun maybe-downcase (symbol) - (let ((string (string symbol))) - (if (and *downcase-tokens-p* (same-case-p string)) - (string-downcase string) - string))) @@ -0,0 +1,56 @@ +;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved. +;; Copyright (c) 2023, Kaz Kylheku. +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions +;; are met: +;; +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following +;; disclaimer in the documentation and/or other materials +;; provided with the distribution. +;; +;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED +;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(in-package :tl-who-priv) + +;; A string with N spaces - used by indentation. +(defmacro n-spaces (n) ^(usr:str ,n)) + +;; If the input is a symbol, convert it to a string by taking its +;; name. If it is a string, take it as is. +;; Then, if the *upcase-tokens-p* variable is true, convert the +;; string to upper case, unless it contains at least one upper case +;; letter already. Finally, return the string. +(defun maybe-upcase (symbol) + (let ((string (if (symbolp symbol) + (symbol-name symbol) + symbol))) + (if (and *upcase-tokens-p* (not [find-if chr-isupper string])) + (upcase-str string) + string))) + +;; Implement to ANSI CL's with-output-to-string (minus the element-type +;; keyword argument) argument using TXR Lisp's with-out-string-stream. +(defmacro with-output-to-string ((var : string-form) . body) + (if (null string-form) + ^(with-out-string-stream (,var) ,*body) + (with-gensyms (str res) + ^(let ((,str ,string-form) ,res) + (string-extend ,str (with-out-string-stream (,var) + (set ,res (progn ,*body))) + t) + ,res)))) diff --git a/who.lisp b/who.lisp deleted file mode 100644 index a1064f6..0000000 --- a/who.lisp +++ /dev/null @@ -1,331 +0,0 @@ -;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package:CL-WHO; Base: 10 -*- -;;; $Header: /usr/local/cvsrep/cl-who/who.lisp,v 1.42 2009/01/26 11:10:49 edi Exp $ - -;;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved. - -;;; Redistribution and use in source and binary forms, with or without -;;; modification, are permitted provided that the following conditions -;;; are met: - -;;; * Redistributions of source code must retain the above copyright -;;; notice, this list of conditions and the following disclaimer. - -;;; * Redistributions in binary form must reproduce the above -;;; copyright notice, this list of conditions and the following -;;; disclaimer in the documentation and/or other materials -;;; provided with the distribution. - -;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED -;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(in-package :cl-who) - -(defun html-mode () - "Returns the current HTML mode. :SGML for \(SGML-)HTML, :XML for -XHTML and :HTML5 for HTML5 (HTML syntax)." - *html-mode*) - -(defun (setf html-mode) (mode) - "Sets the output mode to XHTML or \(SGML-)HTML. MODE can be -:SGML for HTML, :XML for XHTML or :HTML5 for HTML5 (HTML syntax)." - (ecase mode - ((:sgml) - (setf *html-mode* :sgml - *empty-attribute-syntax* t - *empty-tag-end* ">" - *prologue* "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">")) - ((:xml) - (setf *html-mode* :xml - *empty-attribute-syntax* nil - *empty-tag-end* " />" - *prologue* "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")) - ((:html5) - (setf *html-mode* :html5 - *empty-attribute-syntax* t - *empty-tag-end* ">" - *prologue* "<!DOCTYPE html>")))) - -(defun process-tag (sexp body-fn) - (declare (optimize speed space)) - "Returns a string list corresponding to the `HTML' \(in CL-WHO -syntax) in SEXP. Uses the generic function CONVERT-TO-STRING-LIST -internally. Utility function used by TREE-TO-TEMPLATE." - (let (tag attr-list body) - (cond - ((keywordp sexp) - (setq tag sexp)) - ((atom (first sexp)) - (setq tag (first sexp)) - ;; collect attribute/value pairs into ATTR-LIST and tag body (if - ;; any) into BODY - (loop for rest on (cdr sexp) by #'cddr - if (keywordp (first rest)) - collect (cons (first rest) (second rest)) into attr - else - do (progn (setq attr-list attr) - (setq body rest) - (return)) - finally (setq attr-list attr))) - ((listp (first sexp)) - (setq tag (first (first sexp))) - (loop for rest on (cdr (first sexp)) by #'cddr - if (keywordp (first rest)) - collect (cons (first rest) (second rest)) into attr - finally (setq attr-list attr)) - (setq body (cdr sexp)))) - (convert-tag-to-string-list tag attr-list body body-fn))) - -(defun convert-attributes (attr-list) - "Helper function for CONVERT-TAG-TO-STRING-LIST which converts the -alist ATTR-LIST of attributes into a list of strings and/or Lisp -forms." - (declare (optimize speed space)) - (loop with =var= = (gensym) - for (orig-attr . val) in attr-list - for attr = (maybe-downcase orig-attr) - unless (null val) ;; no attribute at all if VAL is NIL - if (constantp val) - if (and *empty-attribute-syntax* (eq val t)) ; special case for SGML and HTML5 - nconc (list " " attr) - else - nconc (list " " - ;; name of attribute - attr - (format nil "=~C" *attribute-quote-char*) - ;; value of attribute - (cond ((eq val t) - ;; VAL is T, use attribute's name - attr) - (t - ;; constant form, PRINC it - - ;; EVAL is OK here because of CONSTANTP - (format nil "~A" (eval val)))) - (string *attribute-quote-char*)) - end - else - ;; do the same things as above but at runtime - nconc (list `(let ((,=var= ,val)) - (cond ((null ,=var=)) - ((eq ,=var= t) - ,(if *empty-attribute-syntax* - `(fmt " ~A" ,attr) - `(fmt " ~A=~C~A~C" - ,attr - *attribute-quote-char* - ,attr - *attribute-quote-char*))) - (t - (fmt " ~A=~C~A~C" - ,attr - *attribute-quote-char* - ,=var= - *attribute-quote-char*))))))) - -(defgeneric convert-tag-to-string-list (tag attr-list body body-fn) - (:documentation "Used by PROCESS-TAG to convert `HTML' into a list -of strings. TAG is a keyword symbol naming the outer tag, ATTR-LIST -is an alist of its attributes \(the car is the attribute's name as a -keyword, the cdr is its value), BODY is the tag's body, and BODY-FN is -a function which should be applied to BODY. The function must return -a list of strings or Lisp forms.")) - -(defmethod convert-tag-to-string-list (tag attr-list body body-fn) - "The standard method which is not specialized. The idea is that you -can use EQL specializers on the first argument." - (declare (optimize speed space)) - (let ((tag (maybe-downcase tag)) - (body-indent - ;; increase *INDENT* by 2 for body -- or disable it - (when (and *indent* (not (member tag *html-no-indent-tags* :test #'string-equal))) - (+ 2 *indent*)))) - (nconc - (if *indent* - ;; indent by *INDENT* spaces - (list +newline+ (n-spaces *indent*))) - ;; tag name - (list "<" tag) - ;; attributes - (convert-attributes attr-list) - ;; body - (if body - (append - (list ">") - ;; now hand over the tag's body to TREE-TO-TEMPLATE - (let ((*indent* body-indent)) - (funcall body-fn body)) - (when body-indent - ;; indentation - (list +newline+ (n-spaces *indent*))) - ;; closing tag - (list "</" tag ">")) - ;; no body, so no closing tag unless defined in *HTML-EMPTY-TAGS* - (if (or (not *html-empty-tag-aware-p*) - (member tag *html-empty-tags* :test #'string-equal)) - (list *empty-tag-end*) - (list ">" "</" tag ">")))))) - -(defun tree-to-template (tree) - "Transforms an HTML tree into an intermediate format - mainly a -flattened list of strings. Utility function used by TREE-TO-COMMANDS-AUX." - (loop for element in tree - if (or (keywordp element) - (and (listp element) - (keywordp (first element))) - (and (listp element) - (listp (first element)) - (keywordp (first (first element))))) - ;; the syntax for a tag - process it - nconc (process-tag element #'tree-to-template) - ;; list - insert as sexp - else if (consp element) - collect `(let ((*indent* ,*indent*)) - nil ;; If the element is (declare ...) it - ;; won't be interpreted as a declaration and an - ;; appropriate error could be signaled - ,element) - ;; something else - insert verbatim - else - collect element)) - -(defun string-list-to-string (string-list) - (declare (optimize speed space)) - "Concatenates a list of strings to one string." - ;; note that we can't use APPLY with CONCATENATE here because of - ;; CALL-ARGUMENTS-LIMIT - (let ((total-size 0)) - (dolist (string string-list) - (incf total-size (length string))) - (let ((result-string (make-string total-size - #+:lispworks #+:lispworks - :element-type 'lw:simple-char)) - (curr-pos 0)) - (dolist (string string-list) - (replace result-string string :start1 curr-pos) - (incf curr-pos (length string))) - result-string))) - -(defun conc (&rest string-list) - "Concatenates all arguments which should be string into one string." - (funcall #'string-list-to-string string-list)) - -(defun tree-to-commands (tree stream &key prologue ((:indent *indent*) *indent*)) - (declare (optimize speed space)) - (when (and *indent* - (not (integerp *indent*))) - (setq *indent* 0)) - (let ((in-string-p t) - collector - string-collector - (template (tree-to-template tree))) - (when prologue - (push +newline+ template) - (when (eq prologue t) - (setq prologue *prologue*)) - (push prologue template)) - (flet ((emit-string-collector () - "Generate a WRITE-STRING statement for what is currently -in STRING-COLLECTOR." - (list 'write-string - (string-list-to-string (nreverse string-collector)) - stream))) - (dolist (element template) - (cond ((and in-string-p (stringp element)) - ;; this element is a string and the last one - ;; also was (or this is the first element) - - ;; collect into STRING-COLLECTOR - (push element string-collector)) - ((stringp element) - ;; the last one wasn't a string so we start - ;; with an empty STRING-COLLECTOR - (setq string-collector (list element) - in-string-p t)) - (string-collector - ;; not a string but STRING-COLLECTOR isn't - ;; empty so we have to emit the collected - ;; strings first - (push (emit-string-collector) collector) - (setq in-string-p nil - string-collector '()) - (push element collector)) - (t - ;; not a string and empty STRING-COLLECTOR - (push element collector)))) - (if string-collector - ;; finally empty STRING-COLLECTOR if - ;; there's something in it - (nreverse (cons (emit-string-collector) - collector)) - (nreverse collector))))) - -(defmacro with-html-output ((var &optional stream - &rest rest - &key prologue indent) - &body body) - "Transform the enclosed BODY consisting of HTML as s-expressions -into Lisp code to write the corresponding HTML as strings to VAR - -which should either hold a stream or which'll be bound to STREAM if -supplied." - (declare (ignore prologue)) - (multiple-value-bind (declarations forms) (extract-declarations body) - `(let ((,var ,(or stream var))) - ,@declarations - (check-type ,var stream) - (macrolet ((htm (&body body) - `(with-html-output (,',var nil :prologue nil :indent ,,indent) - ,@body)) - (fmt (&rest args) - `(format ,',var ,@args)) - (esc (thing) - (with-unique-names (result) - `(let ((,result ,thing)) - (when ,result (write-string (escape-string ,result) ,',var))))) - (str (thing) - (with-unique-names (result) - `(let ((,result ,thing)) - (when ,result (princ ,result ,',var)))))) - ,@(apply 'tree-to-commands forms var rest))))) - -(defmacro with-html-output-to-string ((var &optional string-form - &key #-(or :ecl :cmu :sbcl) - (element-type #-:lispworks ''character - #+:lispworks ''lw:simple-char) - prologue - indent) - &body body) - "Transform the enclosed BODY consisting of HTML as s-expressions -into Lisp code which creates the corresponding HTML as a string." - (multiple-value-bind (declarations forms) (extract-declarations body) - `(with-output-to-string (,var ,string-form - #-(or :ecl :cmu :sbcl) :element-type - #-(or :ecl :cmu :sbcl) ,element-type) - ,@declarations - (with-html-output (,var nil :prologue ,prologue :indent ,indent) - ,@forms)))) - -;; stuff for Nikodemus Siivola's HYPERDOC -;; see <http://common-lisp.net/project/hyperdoc/> -;; and <http://www.cliki.net/hyperdoc> -;; also used by LW-ADD-ONS - -(defvar *hyperdoc-base-uri* "http://weitz.de/cl-who/") - -(let ((exported-symbols-alist - (loop for symbol being the external-symbols of :cl-who - collect (cons symbol - (concatenate 'string - "#" - (string-downcase symbol)))))) - (defun hyperdoc-lookup (symbol type) - (declare (ignore type)) - (cdr (assoc symbol - exported-symbols-alist - :test #'eq)))) @@ -0,0 +1,251 @@ +;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved. +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions +;; are met: +;; +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following +;; disclaimer in the documentation and/or other materials +;; provided with the distribution. +;; +;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED +;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(in-package :tl-who-priv) + +;; Returns the current HTML mode. :sgml for (SGML-HTML, :xml for +;; XHTML and :html5 for HTML5 (HTML syntax). +(defun html-mode () + *html-mode*) + +;; Sets the output mode to XHTML or (SGML-)HTML. mode can be +;; :sgml for (SGML-)HTML, :xml for XHTML or :html5 for HTML5 (HTML syntax). +(defun set-html-mode (mode) + (ecaseq mode + ((:sgml) + (set *html-mode* :sgml + *empty-attribute-syntax* t + *empty-tag-end* ">" + *prologue* "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">")) + ((:xml) + (set *html-mode* :xml + *empty-attribute-syntax* nil + *empty-tag-end* " />" + *prologue* "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")) + ((:html5) + (set *html-mode* :html5 + *empty-attribute-syntax* t + *empty-tag-end* ">" + *prologue* "<!DOCTYPE html>")))) + +(defset html-mode set-html-mode) + +;; Returns a string list corresponding to the `HTML' (in CL-WHO +;; syntax) in sexp. Uses the function convert-tag-to-string-list +;; internally. Utility function used by tree-to-template. +(defun process-tag (sexp body-fn) + (let ((head (if (consp sexp) (first sexp) sexp)) + tag attr-list body) + (cond + ((keywordp sexp) + (set tag sexp)) + ((atom head) + (set tag head) + ;; collect attribute/value pairs into attr-list and tag body (if + ;; any) into body + (let (attr) + (for ((rest (rest sexp))) (rest) ((set rest (cddr rest))) + (cond + ((keywordp (first rest)) + (push (cons (first rest) (second rest)) attr)) + (t + (set attr-list (nreverse attr) + body rest + rest nil)))) + (set attr-list (nreverse attr)))) + ((listp head) + (set tag (first head)) + (let (attr) + (for ((rest (rest head))) (rest) ((set rest (cddr rest))) + (if (keywordp (first rest)) + (push (cons (first rest) (second rest)) attr))) + (set body (rest sexp))))) + (convert-tag-to-string-list tag attr-list body body-fn))) + +;; Helper function for convert-tag-to-string-list which converts the +;; alist attr-list of attributes into a list of strings and/or Lisp +;; forms. +(defun convert-attributes (attr-list) + (with-gensyms (=var=) + (let ((aqc *attribute-quote-char*)) + (keep-matches (^(,orig-attr . ,val) attr-list) + (let ((attr (maybe-upcase orig-attr))) + (if val + (if (constantp val) + ;; Handle constant attribute value at macro time + (if (and *empty-attribute-syntax* (eq val t)) + ` @attr` + ` @attr=@aqc@(if (eq val t) + attr + (tostringp (eval val)))@aqc`) + ;; For non-constant, do the same things as above but at runtime + ^(let ((,=var= ,val)) + (cond + ((null ,=var=)) + ((eq ,=var= t) + ,(if *empty-attribute-syntax* + ` @attr` + ` @attr=@aqc@attr@aqc`)) + (t + ` @,attr=@,aqc@{,=var=}@,aqc`)))))))))) + + +;; Used by process-tag to convert `HTML' into a list +;; of strings. tag is a keyword symbol naming the outer tag, attr-list +;; is an alist of its attributes (the car is the attribute's name as a +;; ;; keyword, the cdr is its value), body is the tag's body, and body-fn is +;; a function which should be applied to body. The function must return +;; a list of strings or Lisp forms. +;; This is an ordinary function in TXR Lisp. +;; In CL-WHO, this is a generic function where the idea is that +;; you can use EQL specializers on the first argument to create +;; custom handling for different tags. +(defun convert-tag-to-string-list (tag attr-list body body-fn) + (let ((tag (maybe-upcase tag)) + (body-indent + ;; increase *indent* by 2 for body -- or disable it + (when (and *indent* (not (member tag *html-no-indent-tags*))) + (ssucc *indent*)))) + (nconc + (if *indent* + ;; indent by *indent* spaces + (list +newline+ (n-spaces *indent*))) + ;; tag name + (list "<" tag) + ;; attributes + (convert-attributes attr-list) + ;; body + (if body + (append + (list ">") + ;; now hand over the tag's body to tree-to-template + (let ((*indent* body-indent)) + [body-fn body]) + (when body-indent + ;; indentation + (list +newline+ (n-spaces *indent*))) + ;; closing tag + (list "</" tag ">")) + ;; no body, so no closing tag unless defined in *html-empty-tags* + (if (or (not *html-empty-tag-aware-p*) + (member tag *html-empty-tags*)) + (list *empty-tag-end*) + (list ">" "</" tag ">")))))) + +;; Transforms an HTML tree into an intermediate format - mainly a +;; flattened list of strings. Utility function used by tree-to-commands. +(defun tree-to-template (tree) + (build + (each ((element tree)) + (match-case element + (@(or @(keywordp) + (@(keywordp) . @nil) + ((@(keywordp) .@nil) . @nil)) + ;; the syntax for a tag - process it + (ncon [process-tag element tree-to-template])) + (@(consp) + ;; list - insert as sexp + (add ^(let ((*indent* , *indent*)) ,element))) + ;; something else - insert verbatim + (@else (add else)))))) + +(defun tree-to-commands (:key tree stream -- prologue (indent *indent*)) + (let ((*indent* indent)) + (when (and *indent* + (not (integerp *indent*))) + (set *indent* 0)) + (let ((in-string-p t) + collector + string-collector + (template (tree-to-template tree))) + (when prologue + (push +newline+ template) + (when (eq prologue t) + (set prologue *prologue*)) + (push prologue template)) + (flet ((emit-string-collector () + ;; Generate a put-string form for what is currently + ;; in string-collector. + (list 'put-string + (cat-str (nreverse string-collector)) + stream))) + (each ((element template)) + (cond ((and in-string-p (stringp element)) + ;; this element is a string and the last one + ;; also was (or this is the first element) - + ;; collect into string-collector + (push element string-collector)) + ((stringp element) + ;; the last one wasn't a string so we start + ;; with an empty string-collector + (set string-collector (list element) + in-string-p t)) + (string-collector + ;; not a string but string-collector isn't + ;; empty so we have to emit the collected + ;; strings first + (push (emit-string-collector) collector) + (set in-string-p nil + string-collector '()) + (push element collector)) + (t + ;; not a string and empty string-collector + (push element collector)))) + (if string-collector + ;; finally empty string-collector if + ;; there's something in it + (nreverse (cons (emit-string-collector) + collector)) + (nreverse collector)))))) + +;; Transform the enclosed body consisting of HTML as s-expressions +;; into Lisp code to write the corresponding HTML as strings to var - +;; which should either hold a stream or which'll be bound to stream if +;; supplied. +(defmacro with-html-output ((:key var : stream -- prologue indent . rest) + . body) + ^(let ((,var ,(or stream var))) + (macrolet ((htm (. body) + ^(with-html-output (,',var nil :prologue nil :indent ,,indent) + ,*body)) + (fmt (. args) + ^(format ,',var ,*args)) + (esc (thing) + (with-gensyms (result) + ^(whenlet ((,result ,thing)) + (put-string (html-encode ,result) ,',var)))) + (str (thing) + (with-gensyms (result) + ^(whenlet ((,result ,thing)) + (pprint ,result ,',var))))) + ,*[apply tree-to-commands body var rest]))) + +(defmacro with-html-output-to-string ((:key var : string-form + -- prologue indent) + . body) + ^(with-output-to-string (,var ,string-form) + (with-html-output (,var nil :prologue ,prologue :indent ,indent) + ,*body))) |