build.lua

changeset 1
74deddff1202
child 2
80eb1f7784c4
equal deleted inserted replaced
0:b40ca010c49c 1:74deddff1202
1 require "LuaTemplates.templates"
2 require "LuaTemplates.parsers"
3 require "lfs"
4
5 -- Config --
6
7 dofile(arg[1] or ".blogrc")
8
9 posts_dir = posts_dir or "_posts/"
10 layouts_dir = layouts_dir or "_layouts/"
11 -- - -- - --
12
13 -- Logging --
14 function make_msg(prefix, name) return function (fmt, ...) print(prefix, string.format(fmt, ...)); end end
15 msg_info, msg_warn, msg_error = make_msg("II", "info"), make_msg("WW", "warn"), make_msg("EE", "error");
16 -- -- - -- --
17
18 -- Append '/' to end of posts_dir if necessary
19 posts_dir = posts_dir:gsub("([^/])$", "%1/")
20
21 -- Append '/' to end of base_url if necessary
22 base_url = base_url:gsub("([^/])$", "%1/")
23
24 posts = {};
25
26
27 -- Read posts, interpret
28 for post_filename in lfs.dir(posts_dir:gsub("/$", "")) do
29 -- Ignore dotfiles
30 if not post_filename:match("^%.") then
31 msg_info("Processing %s", post_filename);
32 local post_file, err = io.open(posts_dir..post_filename);
33 if post_file then
34 local post_format = post_filename:match("%.(%w+)") or "text";
35 local post = { filename = post_filename, path = posts_dir..post_filename, format = post_format };
36 if not parsers[post_format] then
37 msg_warn("Post format '%s' not supported", post_format);
38 else
39 local inside_header, line_num = nil, 0;
40 for line in post_file:lines() do
41 line_num = line_num + 1;
42 if line:match("^%-%-%-") then
43 if not inside_header then
44 inside_header = true;
45 else
46 inside_header = false;
47 break;
48 end
49 elseif not inside_header then
50 break;
51 else
52 local k,v = line:match("^(%w+):%s?(.*)$");
53 if k and v then
54 post[k] = v;
55 else
56 msg_warn("Couldn't parse header at line %d", line_num);
57 end
58 end
59 end
60 end
61
62 local post_data, err = post_file:read("*a");
63
64 -- Parse post_data according to extension, add to post list
65 --msg_info("Parsing %s...", post_filename) --:gsub("%.[^%.]+$", ""):match("[^/]+"));
66 post.content = templates.load(parsers[post_format](post_data)):render{ page = post };
67 post.shorttitle = post.title:gsub("%W+", "-"):lower():sub(1,45)
68 if #post.title:gsub("%W+", "-") > 45 then
69 post.shorttitle = post.shorttitle:gsub("%-%w+$", "");
70 end
71 post.url = base_url..post.shorttitle.."/"
72 post.post = post
73 post.updated = post.updated or os.date("!%Y-%m-%dT%H:%M:%SZ", lfs.attributes(post.path).modification);
74 if post.published then
75 if not post.published:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d%:%d%dZ$") then
76 msg_error("Post has invalid published date");
77 end
78 end
79 table.insert(posts, post);
80 else
81 msg_error("Failed to open %s, %s", post_filename, err);
82 end
83 end
84 end
85
86 local layouts = setmetatable({ default = templates.init(layouts_dir.."default.html"); },
87 { __index = function (t, k) rawset(t, k, templates.init(layouts_dir..k..".html")); return rawget(t, k); end });
88
89 table.sort(posts, function (a, b)
90 do return (a.updated or "") < (b.updated or ""); end
91 if not a.updated then return true; end
92 if not b.updated then return false; end
93 local a_year, a_month, a_day = a.updated:match("(%d%d%d%d)%-(%d%d)%-(%d%d)");
94 local b_year, b_month, b_day = b.updated:match("(%d%d%d%d)%-(%d%d)%-(%d%d)");
95 if b_year >= a_year then return true; end
96 if b_month >= a_month then return true; end
97 if b_day >= a_day then return true; end
98 return false;
99 end);
100
101 do
102 local shorttitles = {};
103 for _, post in ipairs(posts) do
104 while shorttitles[post.shorttitle] do
105 post.shorttitle = post.shorttitle:gsub("%~%d+$", "").."~"..((tonumber(post.shorttitle:match("%~(%d*)$")) or 0) + 1)
106 post.url = base_url..post.shorttitle.."/"
107 end
108 shorttitles[post.shorttitle] = post;
109 end
110 end
111
112 msg_info("%s post%s processed", #posts, #posts ~= 1 and "s" or "");
113
114 msg_info(" - - - ");
115
116 local unpublished = 0;
117 for n, post in ipairs(posts) do
118 if post.content and (not post.layout or layouts[post.layout]) then
119 lfs.mkdir(post.shorttitle);
120 local outfile, err = io.open(post.shorttitle.."/index.html", "w+");
121 if outfile then
122 local layout = layouts[post.layout or "default"];
123
124 -- Link to the previous and next published posts
125 local i = 1;
126 repeat
127 post.next = posts[n+i];
128 i = i + 1;
129 until (not post.next) or post.next.published;
130 i = 1;
131 repeat
132 post.previous = posts[n-i];
133 i = i + 1;
134 until (not post.previous) or post.previous.published;
135
136 post.id = n;
137 outfile:write(layout:render(post));
138 outfile:close();
139 if not post.published then
140 -- Not published yet, hide it
141 --msg_info("Hiding unpublished post");
142 unpublished = unpublished + 1;
143 posts[n] = nil;
144 end
145 else
146 msg_error("Failed to write HTML: %s", err);
147 end
148 if post.published then
149 msg_info("Published: %s", post.shorttitle);
150 else
151 msg_info("Generated: %s", post.shorttitle);
152 end
153 end
154 end
155
156 -- Do main page
157 local main_tpl = templates.init(layouts_dir.."/main.html");
158
159 main_tpl:saverender("index.html", { title = blog_title, posts = posts });
160
161 msg_info("%s post%s published, %d post%s unpublished", #posts, #posts ~= 1 and "s" or "", unpublished, unpublished ~= 1 and "s" or "");
162
163 msg_info(" - - - ");
164
165 -- Do feed
166 local atom = io.open("feed/atom.xml.new", "w+");
167 atom:write[[<?xml version="1.0" encoding="UTF-8"?>
168 <feed xmlns="http://www.w3.org/2005/Atom">
169 ]]
170 atom:write("\t<id>", base_url, "</id>\n");
171 atom:write("\t<title>", blog_title, "</title>\n");
172 atom:write("\t<subtitle>", blog_subtitle, "</subtitle>\n");
173 atom:write("\t<link href='", base_url, "feed/atom.xml' rel='self' />\n");
174 atom:write("\t<link href='", base_url, "' />\n");
175
176 atom:write("\t<updated>", os.date "!%Y-%m-%dT%H:%M:%SZ", "</updated>\n");
177
178 for n, post in ipairs(posts) do
179 if n > 10 then break; end
180 if post.published then
181 atom:write("<entry>\n");
182 atom:write("\t<author>\n\t\t<name>", post.author or default_author or "Unknown Author", "</name>\n\t</author>\n");
183 atom:write("\t<id>", post.url, "</id>\n");
184 atom:write("\t<title>", post.title, "</title>\n");
185 atom:write("\t<published>", post.published, "</published>\n");
186 atom:write("\t<updated>", post.updated, "</updated>\n");
187 atom:write("\t<link href='", post.url, "' />\n");
188 atom:write("\t<content type='html'>\n", post.content:gsub("&", "&amp;"):gsub("<", "&lt;"), "\t</content>\n");
189 atom:write("</entry>\n");
190 end
191 end
192 atom:write("</feed>");
193 atom:close();
194 os.rename("feed/atom.xml.new", "feed/atom.xml");
195
196 msg_info("ATOM feed written to feed/atom.xml");

mercurial