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 cl-who
post showed, how to escape values properly
to protect your site from JavaScript Injection attacks. With 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 raw
mode:
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>"
With cl-who
you might misuse str
and 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 cl-who
post:
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 cl-who:htm
and cl-who:esc
here.
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 Zenekindarl
and
CL-WHO
. Probably that is because it conses more memory?
@ruricolist, do you have an idea why does Spinneret
3 times slower than
CL-WHO
?
Paul suggested to turn off pretty printing for Spinneret
. And with this
setting it outperforms CL-WHO
. Added both results to the chart.