June 2011

Getting started with Hunchentoot and Talcl websites

This is a short guide to setting up a lisp-powered website with Hunchentoot and Talcl/Buildnode.  Hunchentoot is a web server, Talcl is a templating system, and Buildnode is a CXML helper library Talcl uses.  These are from notes I made while writing an app to help my wife record attendance and student progress for dance classes.

My high-level approach on my hobby projects is to write the user interfaces using mostly pure HTML/Javascript/CSS and jQuery, and then make a RESTful (mostly) API with Hunchentoot’s Easy Handlers that the Javascript front-end calls to perform some operations.  For some reason things like parenscript and cl-who never felt right to me.  Anyhoo, let’s get started.

Foundation

I’m calling this project “Alice”, so time to make the foundation:

(quickproject:make-project "~/lisp/alice/" :depends-on '(:iterate :alexandria :talcl :hunchentoot :buildnode))

I generally always include iterate and alexandria, and we’ll want a few things from buildnode directly so we’re depending on that separately from talcl.  Quickproject makes all my files, and I’m good to start.

Here’s the basic goal:

 

I want to have my templates stored in .tal files, and hunchentoot will need a place to look for static files, so we start with a few new directories: “www” for hunchentoot and “templates” for tal.  To easily get paths to these, I added a helper function:

(defun resource-path (path)
  "looks up path relative to whereever this asdf system is installed.  Returns a truename"
  (truename (asdf:system-relative-pathname :alice path)))

Hunchentoot

Now the fun begins. Next is a function to start the hunchentoot acceptor (which will handle listening on a port and dispatching requests) AND configure the static file handling I wanted.

(defvar *acceptor* nil "the hunchentoot acceptor")
(defun start-server (&optional (port 8888))
  (setf *acceptor* (make-instance 'hunchentoot:acceptor :port port))
  ;; make a folder dispatcher the last item of the dispatch table
  ;; if a request doesn't match anything else, try it from the filesystem
  (setf (alexandria:last-elt hunchentoot:*dispatch-table*)
	(hunchentoot:create-folder-dispatcher-and-handler "/" (resource-path "www")))
  (hunchentoot:start *acceptor*))

By having the folder-dispatcher-and-handler as the last item in hunchentoot’s *dispatch-table*, it will only bail to the filesystem if no other handlers match. Hunchentoot has a *default-handler* mechanism, but it is limited; default-handlers do not have access to any request information.

Now I toss a stub style.css into my www/ directory, call start-server, then can load http://localhost:8080/style.css in my browser.  Great, the right half of my desired flowchart is done.  Now the Talcl part.

Talcl

Talcl reads template files, compiles them into lisp functions that accept a tal enviroment.  The tal environment is a set of key/value pairs that will fill in the templates.  Talcl has a bunch of features to handle writing to streams, but for now I’ll just generate strings and pass them to hunchentoot.

(defvar *tal-generator*
  (make-instance 'talcl:caching-file-system-generator
		 :root-directories (list (resource-path "templates"))))

The tal generator maps template names to template files, compiling the templates if needed. There are a few different classes that can be used here, but this one checks file dates and recompiles only if the file is newer.

(defun render-page (template &optional tal-env)
  "renders the given template"
  (talcl:document-to-string
   (buildnode:with-html-document
     (talcl:tal-processing-instruction *tal-generator* template tal-env))))

This helper function takes the template name and the optional tal environment, and returns a string of the final output. Talcl deals in XML, but HTML is not XML so I use the buildnode:with-html-document macro to resolve the mismatches (eg: <script src=…></script> instead of <script/>). According to Talcl examples, there are several ways to get your tal content into an XML document, and tal-processing-instruction is the fastest.

(hunchentoot:define-easy-handler (home :uri "/") ()
  (render-page "home.tal"
	       (talcl:tal-env 'course (current-course))))

This adds a handler to hunchentoot’s table, and should get us going down the left branch of my flowchart.  The tal-env call is creating the tal environment where the compile template function will look for substitutions.  I think of these like keyword arguments for the template.  In this case, I’m pulling some course information and passing it to home.tal.

Tal Templates

The last complicated bit is the tal templates themselves. There are some good examples in the talcl repository.   I want one tal file to be the main site template, a frame around whatever content I’m trying to show with all the html,head,body tags.  Then I’ll have one tal file for each major UI element.

The overall site template will be in templates/template.tal:

<html lang="en"
 xmlns:tal="http://common-lisp.net/project/bese/tal/core"
 tal:in-package=":alice">
 <head>
 <meta charset="utf8"/>
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"/>
 <script src="/script/alice.js"/>
 <link rel="stylesheet" href="/css/style.css" type="text/css" />
 </head>
 <body>
 <span id="body">$body</span>
 </body>
</html>

Since this is XML, we need some xmlns noise at the top, but we can use XMLisms like “<script/>”.  The key things to note here:

  • tal:in-package=":alice" – need to tell Tal where it should be evaluating
  • $body – this is one way to substitute values into the template. Talcl will look for a symbol 'alice::body in it’s tal environment

So that’s our main template file, now for the home.tal file:

<tal:tal xmlns:tal="http://common-lisp.net/project/bese/tal/core"
 xmlns:param="http://common-lisp.net/project/bese/tal/params"
 tal:in-package=":alice">
 <tal:include tal:name="template.tal">
  <param:body>
   <button>Start Jam Class</button><br/>
   <tal:loop tal:var="c" tal:list="(classes course)">
    <a href="class?name=$(name c)">Start $(name c) Class</a>
   </tal:loop>
  </param:body>
 </tal:include>
</tal:tal>

I have the same xmlns noise, but have a new one namespace, param.  This is the xml namespace tal uses to pass information from one template to another. The top level XML node is a “tal:tal” node, which does not render any output.  I include template.tal to get our main template, passing it the UI for this page in a param:body.  This adds 'alice::body to the tal environment, with the XML contents as the value, then template.tal is called.  I use some fancier tal statements to loop over all the dance classes in the given course and make a link to each one.

Performance

Happy hacking!

lisp

Comments (3)

Permalink

Title cards for videos with Common Lisp

Xach posted recently about Fighting blog spam with Common Lisp as a short example of using lisp to solve everyday programming problems.  Here’s one I made last weekend.

The Problem:

My wife belly dances, and we frequently do some light video editing before posting things to youtube.  One of the annoying chores is making title cards, usually white text on a black background that we put at the beginning and end of each video to state time, place, etc, and then fade in/out.  An example:

Text should be large enough to fill the screen, centered, and all the same size.  For awhile I made these in Gimp, then made them in HTML and took screenshots, and finally said screw it and wrote a small helper program.  I don’t know how to use Gimp effectively, and jiggling font sizes in HTML is a pain.  I’m sure there are better solutions, but it was faster (and more fun) for me to write some lisp.

The Solution:

To make title cards, I wrote a little program called “Titler”.

Like Xach’s project, I started with (quickproject:make-project "~/src/lisp/titler/" :depends-on '(vecto iterate))

From there I banged away at it for a little while, found some .ttf files on my system and got the basic generation done using vecto’s string drawing functions. The only tricky bit was to determine the optimal font size. Vecto provides a string-bounding-box function that will give you pixel dimensions for a given string at a given font size. I made a function that uses newton’s method to iteratively try different font size values until we get one that fits on the title card and takes up more than 75% of the width. I’m almost positive there are corner cases where my implementation won’t converge on a solution, but it works pretty well for now.

For the next video I can make easy titles:

(make-title "www.ShamblingShimmies.com

May 26, 2011
Sun Center
Gainesville, FL" 640 480)

Code is at https://github.com/ryepup/titler, and you can see the results (and my wife fire dancing) at http://youtu.be/Wicy0B7Ol5Q.

lisp

Comments (3)

Permalink