Or see the list of project sponsors.
Spinneret is a sexp based templating engine similar to cl-who, reviewed in post number #0075. Today we'll reimplement the snippets from the
cl-who post and I'll show you a few features I'm especially like in Spinneret.
The first example is very simple. It is almost identical to
cl-who, but more concise:
POFTHEDAY> (spinneret:with-html-string (:body (:p "Hello world!"))) "<body> <p>Hello world! </body>"
Next example in the
Spinneret, you don't need this, because it always escapes the values.
But if you really need to inject the HTML or JS into the page, then you have to use
POFTHEDAY> (defclass user () ((name :initarg :name :reader get-name))) POFTHEDAY> (let ((user (make-instance 'user :name "Bob <script>alert('You are hacked')</script>"))) (spinneret:with-html-string (:div :class "comment" ;; Here Spinneret protects you: (:div :class "username" (get-name user)) ;; This way you can force RAW mode. ;; DON'T do this unless the value is from the ;; trusted source! (:div :class "raw-user" (:raw (get-name user)))))) "<div class=comment> <div class=username> Bob <script>alert('You are hacked')</script> </div> <div class=raw-user>Bob <script>alert('You are hacked')</script> </div> </div>"
cl-who you might misuse
esc functions. But with
Spinneret there is less probability for such a mistake.
Another cool Spinneret's feature is its code walker. It allows mixing usual Common Lisp forms with HTML sexps. Compare this code snippet with the corresponding part from
POFTHEDAY> (let ((list (list 1 2 3 4 5))) (spinneret:with-html-string (:ul (loop for item in list do (:li (format nil "Item number ~A" item)))))) "<ul> <li>Item number 1 <li>Item number 2 <li>Item number 3 <li>Item number 4 <li>Item number 5 </ul>"
We don't have to use wrappers like
Finally, let's compare Spinneret's performance with
Zenekindarl, reviewed yesterday:
POFTHEDAY> (declaim (optimize (debug 1) (speed 3))) POFTHEDAY> (defun render (title items) (spinneret:with-html-string (:h1 title (:ul (loop for item in items do (:li item)))))) POFTHEDAY> (time (loop repeat 1000000 do (render "Foo Bar" '("One" "Two" "Three")))) Evaluation took: 4.939 seconds of real time 4.950155 seconds of total run time (4.891959 user, 0.058196 system) [ Run times consist of 0.078 seconds GC time, and 4.873 seconds non-GC time. ] 100.22% CPU 10,905,720,340 processor cycles 991,997,936 bytes consed POFTHEDAY> (time (loop with *print-pretty* = nil repeat 1000000 do (render "Foo Bar" '("One" "Two" "Three")))) Evaluation took: 1.079 seconds of real time 1.081015 seconds of total run time (1.072325 user, 0.008690 system) [ Run times consist of 0.043 seconds GC time, and 1.039 seconds non-GC time. ] 100.19% CPU 2,381,893,880 processor cycles 368,001,648 bytes consed
Sadly, but in this test
Spinneret 3 times slower than
CL-WHO. Probably that is because it conses more memory?
@ruricolist, do you have an idea why does
Spinneret 3 times slower than
Paul suggested to turn off pretty printing for
Spinneret. And with this setting it outperforms
CL-WHO. Added both results to the chart.