Sat, 10 Nov 2012 04:02:30 +0000
parsers.markdown: Make module callable, to allow parsing text as a module
require "lfs" require "jorvick.templates" require "jorvick.parsers" require "jorvick.util" -- 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"); -- -- - -- -- 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 = make_short_title(post.title); 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, removed_posts = 0, {}; for n, post in ipairs(posts) do if post.content and (not post.layout or layouts[post.layout]) then lfs.mkdir(output_dir..post.shorttitle); local outfile, err = io.open(output_dir..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; table.insert(removed_posts, n); 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 local removed = 0; for _, n in ipairs(removed_posts) do table.remove(posts, n-removed); removed = removed + 1; end -- Do main page local main_tpl = templates.init(layouts_dir.."/main.html"); main_tpl:saverender(output_dir.."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(output_dir.."feed/atom.xml.new", "w+"); atom:write[[<?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> ]] atom:write("\t<id>", base_url, "</id>\n"); atom:write("\t<title>", blog_title, "</title>\n"); atom:write("\t<subtitle>", blog_subtitle, "</subtitle>\n"); atom:write("\t<link href='", base_url, "feed/atom.xml' rel='self' />\n"); atom:write("\t<link href='", base_url, "' />\n"); atom:write("\t<updated>", os.date "!%Y-%m-%dT%H:%M:%SZ", "</updated>\n"); for n = #posts,#posts-10,-1 do local post = posts[n]; if post.published then atom:write("<entry>\n"); atom:write("\t<author>\n\t\t<name>", post.author or default_author or "Unknown Author", "</name>\n\t</author>\n"); atom:write("\t<id>", post.url, "</id>\n"); atom:write("\t<title>", post.title, "</title>\n"); atom:write("\t<published>", post.published, "</published>\n"); atom:write("\t<updated>", post.updated, "</updated>\n"); atom:write("\t<link href='", post.url, "' />\n"); atom:write("\t<content type='html'>\n", post.content:gsub("&", "&"):gsub("<", "<"), "\t</content>\n"); atom:write("</entry>\n"); end end atom:write("</feed>"); atom:close(); os.rename("feed/atom.xml.new", "feed/atom.xml"); msg_info("ATOM feed written to feed/atom.xml");