This is the framework that this blog uses. It is (very creatively) named blog.lua. It was written in Lua and is dependant on Kepler.
This framework provides database-free support for an arbitrary number of blogs - a blog being defined for these purposes as a collection of pages - or articles - of content. Each blog has an archive page: a collection of all of the pages in that blog with an optional short description for each page. All of the blogs on a site are linked together using tags. Support for chronological previous/next navigation between the pages of a blog is built in. Each blog may optionally have comments for its pages. Comments have rudimentary cross-site scripting prevention as well as even more rudimentary CAPTCHA1 support. Look around this site for examples of these features.
Good things:
- Simple
- Database-free content generation
- Support for tags and comments
- Pretty urls
Bad things:
- Too simple?
- Not well tested, so probably less than safe
- If you really like databases, there are none
Usage
A site using blog.lua is made up of three components: First is a Lua file that I will call the dispatch file. The dispatch file contains the structure of the site - what blogs and other pages exist - and gets called whenever a page is requested. Second is the collection of LuaPages that the content of the site is rendered into. LuaPages are html pages with embedded snippets of Lua code. They dictate the actual layout of your site. More about them can be found here. The last component is the content. The content is the stuff that you want to be rendered into the pages of your site. The content is kept in files that you create, one file for every page.
Dispatch
The dispatch file gets called by cgi.lua with the requested url as an argument. See my tutorial on configuring Kepler for more details on how to set this up. To discuss the way the dispatch file works, I'll step through the dispatch file of this site which is available in full.
local root = "/home/alexcharlton/alex-charlton.com/" package.path = package.path..";"..root.."hg/repos/blog/?.lua" require "blog"
This first section sets the path that blog.lua can be found in, and calls it as a requirement.
rootDir = root.."hg/repos/site/"
pageDir = rootDir.."pages/"
commentDir = root.."comments/"
reservedNames = {"Alex Charlton"}
Four global variables must be declared in the dispatch file so that blog.lua knows how to configure things. The four global variables needed are:
rootDir: The directory in which the files for the site's content is kept.pageDir: The directory in which the site's LuaPages are kept.commentDir: The directory in which comments will be stored.reservedNames: A list of names (if any) that are not allowed to be used in the comment system (ie. That are reserved for the site owner).
As I mentioned before, blogs are collections of pages of content that relate to each other and are organized to reflect this. This site is composed of three blogs: news, library, and downloads. Each has its own collection of files that are kept in a folder named after them in the pageDir. Each of them can be configured to behave in different ways.
local b = {}
b.blogs = { news = {page = "site", nav = true, comments = true, displayDate = true}
, downloads = {page = "site", longDescription = true}
, library = {page = "site", longDescription = true} }
A table is defined to house the structure of the site. The blogs element of that table houses the list of blogs as well as their options.
page: The LuaPage that is to be called for that blog. Required.nav: Should the blog have support for chronological navigation (eg. the news section of this site)?comments: Support for comments.displayDate: When rendering a page of that site, should the date that the page was last modified be associated with it?longDescription: Descriptions in the archive for that blog.
b.default = "news"
default defines which blog to choose from for the home page of the site. Currently chooses the most recent article in the specified blog.
b.tags = {page = "site"}
The tags element of the table specifies which LuaPage with which to render the tags section of the site.
b.other = { about = "about", ["rss"] = "rss"}
function b.about()
local f = io.open(rootDir.."about")
local e = {}
e.title = "about"
e.content = f:read("*a")
Blog.render("site", e)
f:close()
end
function b.rss()
local e = {}
e.items = {}
local articles = Content.articleOrder("news")
for i = 1, 10 do
local item = {}
item.page = articles[i]
if not item.page then break end
local path = files.."news/"..item.page
local f = io.open(path)
item.taglist = f:read()
item.text = f:read("*a")
f:close()
item.title = Format.titlify(page)
table.insert(e.items, item)
end
Blog.render("rss", e)
end
other is a list of other urls and the names of the functions that are to be called when those urls are requested. Those functions are defined in the same master-table.
function b.fourOfour()
local e = {}
e.title = "404"
e.content = [[You've come across a page that doesn't exist.
If you feel as though there should be a page here, don't hesitate to email Alex.
Tell him the url you are trying to get to and why there should be a page here.]]
Blog.render("site", e)
end
The fourOfour element is the function that gets called when a url that isn't defined elsewhere is requested.
Blog.makeBlog(b)
Finally, the function Blog.makeBlog() is called with the table we just created, thus kicking off the process of rendering the content requested into the appropriate LuaPages
LuaPages
When a page is requested, there are six different situations that can result from that request. If the url is one that is listed in the other section of the site, the rendering is left up to the function specified - ie. alex-charlton.com/about - the function about() is called. If the url is the name of a blog - ie. alex-charlton.com/news - then the archive for the news section is rendered. If the url is the name of a blog followed by the name of an article - ie. alex-charlton.com/downloads/Blog - then that article is rendered. tags ie. alex-charlton.com/tags results in a list of all files in the site, organized by tag and blog to be rendered, while tags/ATag limits this to a specific tag. Every other url causes the function fourOfour to be called.
The thing that all of these scenarios have in common is that the function Blog.render() must be called at the end of things (something to keep in mind when writing a custom function for a page) for anything to be rendered. This function calls a given LuaPage with a table containing a number of variables that will then become available in the LuaPage being rendered.
The actual variables available will vary depending on what page is being requested. For each article in a blog the following variables will be available, along with all of the options you declared the configuration for that blog:
article: The raw name of the article suitable for urlsblogName: The name of the blog the article resides intitle: The properly formatted name of the articlecontent: The actual content of the articletaglist: The list of tags to be associated with the article
date: The timestamp of the last modification of the articlemessage: A message (if any) resulting from an attempt (successful or otherwise) to post a commentbackandforward: The raw name of the chronological previous and next articles in the same bloglongDescription: A description of the article
There are two variables available for the archive section of a blog: title - the name of that blog, and archives. archives is a list of all the articles in the blog with the article, title, date, taglist, and (optional) longDescription for each.
The tags section of the site provides title ("Tags") and a list, tags, that contains a list of all the tags in the site, tag, which in turn contain lists of the different blogs, blog, which hold the names of the articles in that blog, that have that tag. The tags section that is called with a specific tag has the same variables as well as tag which holds the name of the tag called.
Content
In order for all of these this functionality to work with no database in place the files of the site must be organized in a somewhat specific fashion. This layout was touched upon in the dispatch section: Namely the directories specified in the global variables rootDir, pageDir, and commentDir. pageDir is easy: it is simply a directory that must exist with your LuaPages in it. The other two are trickier.
In the rootDir of your site, blog.lua expects to see folders with the same names as each of your blogs. In these folders are the files that hold the content of the site. Each file is given the desired name of the article, with underscores representing spaced in the name and tildes representing apostrophes. The first line of each file is reserved for a space separated list of the tags for that article - formatted in a similar way to the name of the file. If the article is in a blog with longDescriptions then the second line of the file is reserved for that description. The rest of file is the actual content of that article. Remember, these are just flat text files, so any formatting you want to show up in your site will have to be done with html in body of your content.
For every blog that has comments enabled, there must be a correspondingly named folder in the commentDir. In this folder there will be a folder generated named after every article with a comment. In these folders there are a series of files numbered from 1 on, which represent the comments for the article. The first line of this file is reserved for the name of the commenter. The rest of the file is the comment. Comments are automatically formatted with <p> tags and dangerous characters are escaped.
One thing to note about the comment system is that if you add your name to the reservedNames list, you will not be able to use the web interface to comment. Rather, you will have to create an appropriately numbered file in the folder corresponding to the article being commented on. Don't forget that your name goes in the first line.
Functions
The following are functions that you'll be able to call in your dispatch page and LuaPages that will come in handy. Each function call must be prefaced by the group that that function belongs to. For example the function makeBlog() would be called as Blog.makeBlog().
Blog
makeBlog(): This function is called with a table as the argument and causes the site to be rendered in the fashion dictated by that table.
render(): Takes two arguments: the name of the LuaPages to render to (sans the .lp extension), and optionally a table that contains the variables that are to be made available in that LuaPage.
Comments
getComments(): This function takes the name of a blog and the name of an article and returns a iterator of all the comments for that article. Variables supplied are, in order: name - the name of the commenter, comment - the body of the comment, and date - the timestamp of the comment.
Format
titlify(): Takes a raw title as described in the content section and transforms underscores into spaces and tildes into apostrophes.
firstCap(): Takes a string and returns that string with the first character capitalized.
titleCase(): Takes a string and returns that string in title-case.
longDate(): Takes a timestamp and returns a date in the form "March 25th, 2009".
shortDate(): Takes a timestamp and returns a date in the form "25/03/09".
dateTime(): Takes a timestamp and returns a time in the form "06:12PM, 25/03/09 ".
trim(): Trims the leading and trailing whitespace off of a string.
Additional Resources
For additional information on what needs to be done to make a website using blog.lua, feel free to browse through the Mercurial repository for the content of this site. Particularity, the content of the pages/ directory should be useful for seeing how LuaPages are used.
For a closer look at what's going on behind the sceens, check out blog.lua's Mercurial repository.
If you're not comfortable with Lua syntax, I highly recommend reading Programming in Lua. Extra help can be found in Lua Reference Manual and in Lua User's Wiki. Info on Kepler can be found at their site or in my tutorial on how to set it up.
