-*- mode: lisp -*- You can get the latest version from http://common-lisp.net/~mchan/lsp-latest.tar.gz or check out the source from the contrib directory svn checkout svn://common-lisp.net/project/tbnl/svn http://common-lisp.net/websvn/listing.php?repname=tbnl&path=%2Fcontrib%2Flsp%2F It depends on Edi's hunchentoot, cl-ppcre and cl-fads (for directory listing) ;; Sample Usage (asdf:oos 'asdf:load-op :hunchentoot-test) (hunchentoot:start-server :port 8080) ;; verify that hunchentoot is running ;; http://localhost:8080/hunchentoot/test/ (asdf:oos 'asdf:load-op :lsp) (in-package :lsp) ;; set debug on so we can examine the generated code (set-lsp-debug t) ;; use cl-user package for all unqualified symbols (set-lsp-package :cl-user) ;; a sample lsp page (defparameter *sample-lsp* (merge-pathnames "test/test.lsp" *this-dir*)) ;; compile the test file (get-lsp-function *sample-lsp*) ;; take a look at the generated code (show-lsp-form *sample-lsp*) ;; print the output to REPL (do-lsp-request *sample-lsp*) ;; install the dispatcher function (push (create-lsp-folder-dispatcher-and-handler ;; uri "/hunchentoot/lsp/" ;; directory *this-dir* ;; valid lsp page suffixes :lsp-file-types '("lsp" "asp") ;; inc file types that should not be rendered alone :inc-file-types '("inc") ;; show directory listing :folder-index-page t ;; default content-type for non lsp files :content-type "text/plain") hunchentoot:*dispatch-table*) ;; try it out (click refresh a few times to verify that it is _dynamic_) ;; http://localhost:8080/hunchentoot/lsp/test/test.lsp ;; take a look at the source code too ;; http://localhost:8080/hunchentoot/lsp/lsp.lisp ;; make sure users can't access the `include' files ;; http://localhost:8080/hunchentoot/lsp/test/header.inc ;; directory listing ;; http://localhost:8080/hunchentoot/lsp/ ;; alternatively, you can also create separate handler for each lsp file (push (hunchentoot:create-prefix-dispatcher "/hunchentoot/lsp/test/test.lsp" (get-lsp-function *sample-lsp*)) hunchentoot:*dispatch-table*) ;; finally, you can also reuse lsp function within a custom dispatch handler (hunchentoot:define-easy-handler (lsp-demo :uri "/hunchentoot/test/lsp-demo.html") () (do-lsp-request *sample-lsp*)) ;; http://localhost:8080/hunchentoot/test/lsp-demo.html ;; By default, create-lsp-folder-dispatcher-and-handler will return ;; +http-not-found+ to the browser if file is not found. ;; you can override this by setting :http-not-found-handler to NIL. ;; In this case create-lsp-folder-dispatcher-and-handler will not ;; handle it and hunchentoot will ask the next handler in the ;; in the *dispatch-table* chain to handle it (setq hunchentoot:*dispatch-table* (list (create-lsp-folder-dispatcher-and-handler "/hunchentoot/lsp/" *this-dir* :http-not-found-handler nil) #'hunchentoot:*default-handler*)) ;; or you can supply your own 404 handler (setq hunchentoot:*dispatch-table* (list (create-lsp-folder-dispatcher-and-handler "/hunchentoot/lsp/" *this-dir* :http-not-found-handler #'(lambda (&optional pathname) (with-html (:html (:body (:p "The file" (str pathname) "is not found.")))))))) ;; You can also supply a function before-handler which will be called ;; before create-lsp-folder-dispatcher-and-handler dispatch each request. ;; If before-handler returns non-NIL value, it will be sent to the browser instead. ;; But most often you can use this to change user credential, etc (setq hunchentoot:*dispatch-table* (list (create-lsp-folder-dispatcher-and-handler "/hunchentoot/lsp/" *this-dir* :before-handler ;; password protect all pdf files #'(lambda (&optional pathname) (when (equal "pdf" (pathname-type pathname)) (multiple-value-bind (user password) (hunchentoot:authorization) (unless (and (equal user "admin") (equal password "admin")) (require-authorization)))))))) LAQ * Why another template module? There are indeed a lot of template libraries (see cliki.net/web). But some of the libraries are obstinate in that they require you to construct and pass the `environment' variable in order to generate the dynamic content. Secondly, I found that the embedded mini language (if then else, etc) are often too limited in expressive power. You are programming lisp in the backend, but all of a sudden you need to drop to a very dumb language when you need to deal with the template code, which is kind of frustrating. John Wiseman's code is very close to what I need, but it is designed to work with Franz's allegoserver. So I port it to work with my preferred web server (hunchentoot) instead. * So should I use this for all my dynamic content generation? No. Absolutely not. If you take a look at the very simple test.lsp, it screams like spaghetti code! In the ideal world, if the graphics designer can deliver properly formatted html in cl-who sexp, I'm all set. But that's not gonna happen. So for pages that I have total control, I prefer to write it using sexp. However, there are times where the designer need to frequently update a messy html template and I cannot afford to sync up with it given the time constraint, using lsp is a good compromise. * Any tips & tricks? Lisp is very hard to write (and even harder to read) without a good editor (like emacs) to help indenting your code and provide structured editing. So unlike other dumber languages (php, perl...), intermixed lisp code with content is extremely hard to maintain and read. In this regard, I would recommend you to use Franz's if-star package ( http://www.franz.com/~jkf/ifstar.txt ) in your template file because it helps you to match the open and close parenthesis for if-then-else conditional expression. Also, unlike other dumber languages where if you have a syntax error in the template file, the compiler can point you to the exact line where it occurs. With lsp, the line number is lost when we use the built-in lisp reader to read the form. However you can use `show-lsp-form' in the REPL to examine the code when you encounter an error.