diff -r b40ca010c49c -r 74deddff1202 build.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/build.lua Mon May 18 15:20:52 2009 +0100 @@ -0,0 +1,196 @@ +require "LuaTemplates.templates" +require "LuaTemplates.parsers" +require "lfs" + +-- Config -- + +dofile(arg[1] or ".blogrc") + +posts_dir = posts_dir or "_posts/" +layouts_dir = layouts_dir or "_layouts/" +-- - -- - -- + +-- Logging -- +function make_msg(prefix, name) return function (fmt, ...) print(prefix, string.format(fmt, ...)); end end +msg_info, msg_warn, msg_error = make_msg("II", "info"), make_msg("WW", "warn"), make_msg("EE", "error"); +-- -- - -- -- + +-- Append '/' to end of posts_dir if necessary +posts_dir = posts_dir:gsub("([^/])$", "%1/") + +-- Append '/' to end of base_url if necessary +base_url = base_url:gsub("([^/])$", "%1/") + +posts = {}; + + +-- Read posts, interpret +for post_filename in lfs.dir(posts_dir:gsub("/$", "")) do + -- Ignore dotfiles + if not post_filename:match("^%.") then + msg_info("Processing %s", post_filename); + local post_file, err = io.open(posts_dir..post_filename); + if post_file then + local post_format = post_filename:match("%.(%w+)") or "text"; + local post = { filename = post_filename, path = posts_dir..post_filename, format = post_format }; + if not parsers[post_format] then + msg_warn("Post format '%s' not supported", post_format); + else + local inside_header, line_num = nil, 0; + for line in post_file:lines() do + line_num = line_num + 1; + if line:match("^%-%-%-") then + if not inside_header then + inside_header = true; + else + inside_header = false; + break; + end + elseif not inside_header then + break; + else + local k,v = line:match("^(%w+):%s?(.*)$"); + if k and v then + post[k] = v; + else + msg_warn("Couldn't parse header at line %d", line_num); + end + end + end + end + + local post_data, err = post_file:read("*a"); + + -- Parse post_data according to extension, add to post list + --msg_info("Parsing %s...", post_filename) --:gsub("%.[^%.]+$", ""):match("[^/]+")); + post.content = templates.load(parsers[post_format](post_data)):render{ page = post }; + post.shorttitle = post.title:gsub("%W+", "-"):lower():sub(1,45) + if #post.title:gsub("%W+", "-") > 45 then + post.shorttitle = post.shorttitle:gsub("%-%w+$", ""); + end + post.url = base_url..post.shorttitle.."/" + post.post = post + post.updated = post.updated or os.date("!%Y-%m-%dT%H:%M:%SZ", lfs.attributes(post.path).modification); + if post.published then + if not post.published:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d%:%d%dZ$") then + msg_error("Post has invalid published date"); + end + end + table.insert(posts, post); + else + msg_error("Failed to open %s, %s", post_filename, err); + end + end +end + +local layouts = setmetatable({ default = templates.init(layouts_dir.."default.html"); }, + { __index = function (t, k) rawset(t, k, templates.init(layouts_dir..k..".html")); return rawget(t, k); end }); + +table.sort(posts, function (a, b) + do return (a.updated or "") < (b.updated or ""); end + if not a.updated then return true; end + if not b.updated then return false; end + local a_year, a_month, a_day = a.updated:match("(%d%d%d%d)%-(%d%d)%-(%d%d)"); + local b_year, b_month, b_day = b.updated:match("(%d%d%d%d)%-(%d%d)%-(%d%d)"); + if b_year >= a_year then return true; end + if b_month >= a_month then return true; end + if b_day >= a_day then return true; end + return false; + end); + +do + local shorttitles = {}; + for _, post in ipairs(posts) do + while shorttitles[post.shorttitle] do + post.shorttitle = post.shorttitle:gsub("%~%d+$", "").."~"..((tonumber(post.shorttitle:match("%~(%d*)$")) or 0) + 1) + post.url = base_url..post.shorttitle.."/" + end + shorttitles[post.shorttitle] = post; + end +end + +msg_info("%s post%s processed", #posts, #posts ~= 1 and "s" or ""); + +msg_info(" - - - "); + +local unpublished = 0; +for n, post in ipairs(posts) do + if post.content and (not post.layout or layouts[post.layout]) then + lfs.mkdir(post.shorttitle); + local outfile, err = io.open(post.shorttitle.."/index.html", "w+"); + if outfile then + local layout = layouts[post.layout or "default"]; + + -- Link to the previous and next published posts + local i = 1; + repeat + post.next = posts[n+i]; + i = i + 1; + until (not post.next) or post.next.published; + i = 1; + repeat + post.previous = posts[n-i]; + i = i + 1; + until (not post.previous) or post.previous.published; + + post.id = n; + outfile:write(layout:render(post)); + outfile:close(); + if not post.published then + -- Not published yet, hide it + --msg_info("Hiding unpublished post"); + unpublished = unpublished + 1; + posts[n] = nil; + end + else + msg_error("Failed to write HTML: %s", err); + end + if post.published then + msg_info("Published: %s", post.shorttitle); + else + msg_info("Generated: %s", post.shorttitle); + end + end +end + +-- Do main page +local main_tpl = templates.init(layouts_dir.."/main.html"); + +main_tpl:saverender("index.html", { title = blog_title, posts = posts }); + +msg_info("%s post%s published, %d post%s unpublished", #posts, #posts ~= 1 and "s" or "", unpublished, unpublished ~= 1 and "s" or ""); + +msg_info(" - - - "); + +-- Do feed +local atom = io.open("feed/atom.xml.new", "w+"); +atom:write[[ + +]] +atom:write("\t", base_url, "\n"); +atom:write("\t", blog_title, "\n"); +atom:write("\t", blog_subtitle, "\n"); +atom:write("\t\n"); +atom:write("\t\n"); + +atom:write("\t", os.date "!%Y-%m-%dT%H:%M:%SZ", "\n"); + +for n, post in ipairs(posts) do + if n > 10 then break; end + if post.published then + atom:write("\n"); + atom:write("\t\n\t\t", post.author or default_author or "Unknown Author", "\n\t\n"); + atom:write("\t", post.url, "\n"); + atom:write("\t", post.title, "\n"); + atom:write("\t", post.published, "\n"); + atom:write("\t", post.updated, "\n"); + atom:write("\t\n"); + atom:write("\t\n", post.content:gsub("&", "&"):gsub("<", "<"), "\t\n"); + atom:write("\n"); + end +end +atom:write(""); +atom:close(); +os.rename("feed/atom.xml.new", "feed/atom.xml"); + +msg_info("ATOM feed written to feed/atom.xml");