This tutorial will show how a blog can easily be implemented in Common Lisp, using a few frameworks. Installing these frameworks is not covered, and neither are details on getting Common Lisp implementation up and running.
Part 1 shows how data can be stored persistently, and how HTML pages can be generated and served to the end-user. For your convenience, the source code of the files created in this tutorial can be downloaded.
The frameworks I'll be using are:
First, let's define the package that will hold our code and load the packages that we require.
in-package comes our package content. First, we
make sure we have the necessary libraries loaded, by
require. Next, we use the
use-package to load the symbols exported by
Elephant into our local namespace. Without this, we would have to
prefix use of those symbols with
elephant:, as we
will see be done with the other packages.
Next, we define our model. To keep this simple, we will just
define a class for our blog posts, and then put them all in a
list. Alternativly, we could have defined a
class that would be a container for our blog posts.
(defpclass blog-post ()|
((title :initarg :title
(body :initarg :body
(timestamp :initarg :timestamp
Here we define a new class,
blog-post, which has
timestamp. Notice that we're using
defpclass macro, instead
defclass which is the normal way of declaring
defpclass makes sure that the class uses
persistent-metaclass metaclass, which adds
persistence to our class. Alternativly, we could have written
defclass and setting the metaclass ourselves.
(defclass blog-post ()|
((title :initarg :title
(body :initarg :body
(timestamp :initarg :timestamp
We're almost ready to start creating blog posts, but we will first need to tell Elephant where to store our data. In this example I will use CLSQL and SQLite, but there are several other alternatives.
; Open the store where our data is stored|
(defvar *elephant-store* (open-store '(:clsql (:sqlite3 "/tmp/blog.db"))))
; Container for all our blog posts
(defvar *blog-posts* (or (get-from-root "blog-posts")
(let ((blog-posts (make-pset)))
(add-to-root "blog-posts" blog-posts)
In the first line here, the function
called, which tells Elephant to open a new persistent store where
our data is to be stored. The parameters tell it to use the CLSQL
package, and SQLite3. The store object is put into a variable in
case we need it later.
The next line defines a variable that will contain all our blog
posts. It is either retrieved from the Elephant store
get-from-root, but if it's not found then a
pset is created and added to the store.
The reason for using
pset instead of a normal Common
Lisp collection such as a list, is that operations which change
these do not have persistent side effects. In other words; if we
want collections that are persistent, we have to use classes
provided by Elephant. Two classes are provided:
which provides a persistent and unordered collection of objects,
btree which provides persistent collection of
Let's create a blog post and see if things work.
CL-USER> (in-package :my-blog) #<PACKAGE "MY-BLOG"> MY-BLOG> (setq first-post (make-instance 'blog-post :title "Hello blog world" :body "First post!")) #<BLOG-POST oid:5> MY-BLOG> (insert-item first-post *blog-posts*) #<BLOG-POST oid:5> MY-BLOG>
Ok, so far so good. Let's stop the Lisp instance and see if the blog post was stored.
CL-USER> (in-package :my-blog) #<PACKAGE "MY-BLOG"> MY-BLOG> *blog-posts* #<DEFAULT-PSET oid:4> MY-BLOG> (pset-list *blog-posts*) (#<BLOG-POST oid:5>) MY-BLOG> (title (first (pset-list *blog-posts*))) "Hello blog world" MY-BLOG>
When we load our package again, we can see that
*blog-posts* is defined, as we would expect, and
when we generate a list of the contents of the
pset-list, we can see that it contains the blog
posts that we created.
Let's create another blog post for good measure.
MY-BLOG> (insert-item (make-instance 'blog-post :title "This is fun" :body "Common Lisp is easy!") *blog-posts*) #<BLOG-POST oid:6> MY-BLOG>
Now that we have the possibility of storing and fetching blog posts, we need to somehow generate a web page containing the blog posts. We will do this using the html-template framework. We will create a single template, which simply displays all blog posts on a single page.
<!DOCTYPE html> <html> <head> <title>My Blog</title> </head> <body> <h1>My Blog</h1> <!-- tmpl_loop blog-posts --> <div> <h2><!-- tmpl_var title --></h2> <div> <!-- tmpl_var body --> </div> </div> <!-- /tmpl_loop --> </body> </html>
The format of html-template is based on the Perl module
tmpl_loop will loop
over a collection, and
tmpl_var will print the value
of the supplied parameters.
Next, we create a function that will use this template and generate the final page.
(defun generate-index-page ()|
"Generate the index page showing all the blog posts."
(loop for blog-post in (pset-list *blog-posts*)
collect (list :title (title blog-post) :body (body blog-post))))
Let's go through this step by step. First,
html-template::fill-and-print-template needs to
print the resulting HTML to a stream, we use the
with-output-to-string to create a stream that
can be written to and that will return the result as a string.
Next, we call the function
which does exactly that. Since it's in the
html-template, we prefix it with the correct
namespace. The required parameters to this function are the
template that is to be rendered and parameters that are to be used
in the template. We also tell the function to print to our
character stream. The
loop goes through the list of
blog posts and creates property lists for each post, containing
the title and body. To show how what the result of this loop , we
can run it separatly.
MY-BLOG> (loop for blog-post in (pset-list *blog-posts*) collect (list :title (title blog-post) :body (body blog-post))) ((:TITLE "This is fun" :BODY "Common Lisp is easy!") (:TITLE "Hello blog world" :BODY "First post!")) MY-BLOG>
To make sure
fill-and-print finds the correct file, we can set
*default-template-pathname* to point to
where our templates are.
|(setq html-template:*default-template-pathname* #P"/Users/vetler/Documents/devel/blog/source/")|
Let's try it out.
MY-BLOG> (generate-index-page) "<!DOCTYPE html> <html> <head> <title>My Blog</title> </head> <body> <h1>My Blog</h1> <div> <h2>Hello blog world</h2> <div> First post! </div> </div> </body> </html>" MY-BLOG>
Finally, we will need to set up a web server to serve our web page. We will do this using Hunchentoot, a web server implemented in Common Lisp.
Since we already have everything we need for generating the web page, it's just a matter of telling the web server to call the function for generating the web page and starting it.
(list (hunchentoot:create-prefix-dispatcher "/" 'generate-index-page)))
(defvar *ht-server* (hunchentoot:start-server :port 8080))
The web server uses the
*dispatch-table* to map URLs
to functions. In this example we simply create a mapping from the
root to our function, but it's also possible to create dispatchers
with regular expressions,
Lastly the web server is started, at port 8080. That's it! Now
it's just a matter of pointing a browser
and looking at our beautiful blog.
The basics of how to store data persistently and how to generate and serve web pages should now be clear. Of course, there is some essential functionality missing from our current implementation. In the next part, it will be shown how show blog posts on separate pages, edit content, and how to query the Elephant store.
Until then, good luck with Common Lisp!