Common Lisp interface

We're getting down to details now. I'll document in this section how to interface with Common Lisp, that is, define your own primitive functions, define custom template handlers etc.

Sytes doesn't implement its own data structures; instead it uses ordinary Common Lisp values, which makes it easy to interface with CL. Even a lambda, compiles to an ordinary CL function, which means you can pass functions defined in Sytes to Common Lisp, i.e.:

{[defun list-item (text) { <li>{\text}</li> }]}
{(mapcar list-item '(1 2 3))}

 <li>1</li>  <li>2</li>  <li>3</li> 

[mapcar is the standard Common Lisp function, not implemented in Sytes].

Defining your own primitive functions

You might want to look in template/compiler.lisp and sytes.lisp from the Sytes source code to see many examples.

Primitives can be made globally available (to all Sytes running in the same Lisp image) or can be per-syte. Back to our first example from the Hello World page, it looked like this:

;; in file: /tmp/syte.foobar/syte.foobar.lisp
(defclass syte.foobar (syte)
   :names '("" "" "foobar.local")
   :root (merge-pathnames "root/"
                           (asdf:find-system :syte.foobar)))))

;; create the Syte instance
(defparameter *site* (make-instance 'syte.foobar))

;; register it with Sytes
(register-syte *site*)

Now for example in order to define the primitive that we referenced in the dhandlers example we can do:

(sytes:def-syte-primitive *syte* "fetch-blog-entry"
  (lambda (path)
    ;; do your thing here
    (let ((entry ...))
      ;; to return it to Sytes, it's best to return a hash table
      ;; or an association list, so it can reference properties
      ;; using the dot notation, i.e.:
      `(("title" . ,(blog-entry-title entry))
        ("body" . ,(blog-entry-body entry))))))

Defining custom URL handlers

Another way to interface with CL which might be convenient sometimes is to intercept whole calls to an URL and perhaps decide which template to execute (rather than delegate this responsibility to Sytes), or pass additional variables to the template. This would work as well (maybe even better) as a dhandler for the blog example, it's just another way to do it.

(sytes:def-url-handler (*syte* "^/blog/([0-9]+)/([0-9]+)/([0-9]+)/(.*)$"
                        year month date title)
  ;; as you can see, it's regexp-based
  ;; when a blog URL is encountered, it'll get here,
  ;; and we also conveniently get the parts of the URL
  ;; that we're interested in (year, month, date and title)
  (let ((entry ...fetch DB entry...))
    `(:template "blog/index.syt"
      :variables ("title" ,(blog-entry-title entry)
                  "body" ,(blog-entry-body entry)))))

Notice an important thing above: the variables that we pass to the template needs to be a plist, and in particular, variable names should be strings rather than CL symbols. They can also be Sytes symbols, i.e. you can pass (intern "title" :sytes.%runtime%), with the advantage that you can evaluate that at read-time, but it's uglier to type.

A custom handler may return the keyword :continue in order to decline and let Sytes continue handling the request normally. Otherwise the return value should be a list of keyword-values (that's passed to (destructuring-bind)) and it supports the following keywords (some are exclusive):

Fork me on Github