Commit
+172 -130 +/-8 browse
1 | diff --git a/build.py b/build.py |
2 | index a15b4c8..e424c55 100755 |
3 | --- a/build.py |
4 | +++ b/build.py |
5 | @@ -13,26 +13,39 @@ def _nth(n): |
6 | |
7 | |
8 | def _render_rule(w: ninja_syntax.Writer, name, link): |
9 | - w.rule(f"render_{name}", " ".join([ |
10 | - "./render.py", |
11 | - "-stylesheet", |
12 | - "gen/main.css", |
13 | - "-template", |
14 | - _nth(1), |
15 | - "-sitemap", |
16 | - "sitemap.yaml", |
17 | - "-link", link, |
18 | - "-content", _nth(2) + "| htmlmin > $out", |
19 | - ])) |
20 | + w.rule( |
21 | + f"render_{name}", |
22 | + " ".join( |
23 | + [ |
24 | + "./render.py", |
25 | + "-stylesheet", |
26 | + "gen/main.css", |
27 | + "-template", |
28 | + _nth(1), |
29 | + "-sitemap", |
30 | + "sitemap.yaml", |
31 | + "-link", |
32 | + link, |
33 | + "-content", |
34 | + _nth(2) + "| htmlmin > $out", |
35 | + ] |
36 | + ), |
37 | + ) |
38 | return f"render_{name}" |
39 | |
40 | |
41 | + def _render_rss_rule(w: ninja_syntax.Writer): |
42 | + w.rule(f"render_rss", " ".join(["./render.py", "-rss", "> $out"])) |
43 | + return f"render_rss" |
44 | + |
45 | + |
46 | def _compile_sm(w: ninja_syntax.Writer, entries: list, path: pathlib.Path): |
47 | - entries = list(filter( |
48 | - lambda entry: "enabled" not in entry or entry["enabled"], entries)) |
49 | + entries = list( |
50 | + filter(lambda entry: "enabled" not in entry or entry["enabled"], entries) |
51 | + ) |
52 | for entry in entries: |
53 | next_path = path.joinpath(entry["name"]) |
54 | - if "others" in entry and entry["others"]: # its a dir |
55 | + if "others" in entry and entry["others"]: # its a dir |
56 | w.build(str(next_path), "mkdir") |
57 | _compile_sm(w, entry["others"], next_path) |
58 | else: |
59 | @@ -47,8 +60,11 @@ def _compile_sm(w: ninja_syntax.Writer, entries: list, path: pathlib.Path): |
60 | target = target.replace("README.md", "index.html", 1) |
61 | target = "gen/" + target |
62 | cmd = _render_rule(w, source.replace("/", "_", -1), link) |
63 | - w.build(target, cmd, inputs=[ |
64 | - "index.jinja", source, "sitemap.yaml", "render.py"]) |
65 | + w.build( |
66 | + target, |
67 | + cmd, |
68 | + inputs=["index.html.jinja", source, "sitemap.yaml", "render.py"], |
69 | + ) |
70 | |
71 | |
72 | if __name__ == "__main__": |
73 | @@ -62,7 +78,7 @@ if __name__ == "__main__": |
74 | w.rule("compile_sass", command="sassc $sass_flags $in $out") |
75 | w.rule("make_qr_code", command="cat $in | qrencode -o $out") |
76 | w.rule("copy", command="cp $in $out") |
77 | - w.build("index.jinja", "phony") |
78 | + w.build("index.html.jinja", "phony") |
79 | w.build("render.py", "phony") |
80 | w.build("sitemap.yaml", "phony") |
81 | w.build("gen", "mkdir") |
82 | @@ -71,8 +87,18 @@ if __name__ == "__main__": |
83 | w.build("gen/favicon.ico", "copy", inputs=["assets/favicon.ico"]) |
84 | w.build("gen/main.css", "compile_sass", inputs=["main.scss"]) |
85 | cmd = _render_rule(w, "index", "/") |
86 | - w.build("gen/index.html", cmd, inputs=[ |
87 | - "index.jinja", "content/index.md", "sitemap.yaml", "render.py"]) |
88 | + w.build( |
89 | + "gen/index.html", |
90 | + cmd, |
91 | + inputs=[ |
92 | + "index.html.jinja", |
93 | + "content/index.md", |
94 | + "sitemap.yaml", |
95 | + "render.py", |
96 | + ], |
97 | + ) |
98 | + cmd = _render_rss_rule(w) |
99 | + w.build("gen/index.xml", cmd) |
100 | if sitemap["enabled"]: |
101 | _compile_sm(w, sitemap["entries"], pathlib.Path("./content")) |
102 | w.close() |
103 | diff --git a/content/blog/building-this-website/README.md b/content/blog/building-this-website/README.md |
104 | index 55af1a0..25cf874 100644 |
105 | --- a/content/blog/building-this-website/README.md |
106 | +++ b/content/blog/building-this-website/README.md |
107 | @@ -30,27 +30,6 @@ annotated flags. |
108 | gen/blog/building-this-website/index.html |
109 | ``` |
110 | |
111 | - ### Builtin Preview Server |
112 | - |
113 | - In "production" we'll serve all of the static content of an Nginx server but |
114 | - locally we want a way to browse the current state. This will be really simple, |
115 | - just add a Make target that starts up Python's builtin |
116 | - [http.server](https://docs.python.org/3/library/http.server.html#module-http.server). |
117 | - |
118 | - ``` |
119 | - BIND_ARGS := 127.0.0.1 9000 |
120 | - |
121 | - serve: |
122 | - python -m http.server --bind ${BIND_ARGS} --directory gen |
123 | - ``` |
124 | - |
125 | - ### Templating |
126 | - |
127 | - Each page and it's content will be written as a markdown file that can include |
128 | - Jinja syntax. |
129 | - |
130 | - Python + Markdown + Jinja |
131 | - |
132 | ### Hierarchical Sitemap |
133 | |
134 | I want to generate a "tree" sitemap similar to a file browser like NERDTree. |
135 | @@ -128,12 +107,9 @@ and they also look very cool. The idea for this was inspired by <a href="https:/ |
136 | ### Spell & Grammar Checking |
137 | |
138 | I can implement some basic spell checking against the website with [aspell](http://aspell.net/). |
139 | - If I was really ambitious (I'm not) it could be integrated into some kind of |
140 | - automated testing. For now a simple Make target should suffice. |
141 | |
142 | - ``` |
143 | - spell: |
144 | - find content -name '*.md' -exec aspell --mode=markdown check {} \; |
145 | + ```sh |
146 | + find content -name '*.md' -exec aspell --mode=markdown check {} \; |
147 | ``` |
148 | |
149 | ### Filesystem "Watch" Support |
150 | diff --git a/content/index.md b/content/index.md |
151 | index 7e6a726..543ca64 100644 |
152 | --- a/content/index.md |
153 | +++ b/content/index.md |
154 | @@ -13,3 +13,6 @@ Check out some of my past and present projects [here](https://ayllu-forge.org/br |
155 | Contact info (same as above, but a QR code): |
156 | |
157 | <img alt="qr code" class="contact" src="/contact.png"/> |
158 | + |
159 | + ### Subscribe |
160 | + [RSS](/index.xml) |
161 | diff --git a/index.html.jinja b/index.html.jinja |
162 | new file mode 100644 |
163 | index 0000000..3ae4697 |
164 | --- /dev/null |
165 | +++ b/index.html.jinja |
166 | @@ -0,0 +1,34 @@ |
167 | + <!DOCTYPE html> |
168 | + <html lang="en"> |
169 | + <head> |
170 | + <title>kevinschoon-dot-com</title> |
171 | + <meta charset="UTF-8"> |
172 | + <meta name="viewport" content="width=device-width, initial-scale=1"> |
173 | + <link rel="stylesheet" href="/main.css"/> |
174 | + </head> |
175 | + <body> |
176 | + <section> |
177 | + <div class="wrapper"> |
178 | + <header class="page-header"> |
179 | + {% if not link == "/" %}<a href="/">Home</a>{% endif %} |
180 | + </header> |
181 | + <main class="page-body"> |
182 | + {{ content }} |
183 | + </main> |
184 | + <footer class="page-footer"></footer> |
185 | + </div> |
186 | + </section> |
187 | + </body> |
188 | + {% if devmode %} |
189 | + <script> |
190 | + const socket = new WebSocket("ws://localhost:8080/ws") |
191 | + socket.addEventListener("open", (event) => { |
192 | + console.log("connected to server", event); |
193 | + }); |
194 | + socket.addEventListener("message", (event) => { |
195 | + console.log("reloading page", event); |
196 | + window.location.reload(); |
197 | + }) |
198 | + </script> |
199 | + {% endif %} |
200 | + </html> |
201 | diff --git a/index.jinja b/index.jinja |
202 | deleted file mode 100644 |
203 | index 3ae4697..0000000 |
204 | --- a/index.jinja |
205 | +++ /dev/null |
206 | @@ -1,34 +0,0 @@ |
207 | - <!DOCTYPE html> |
208 | - <html lang="en"> |
209 | - <head> |
210 | - <title>kevinschoon-dot-com</title> |
211 | - <meta charset="UTF-8"> |
212 | - <meta name="viewport" content="width=device-width, initial-scale=1"> |
213 | - <link rel="stylesheet" href="/main.css"/> |
214 | - </head> |
215 | - <body> |
216 | - <section> |
217 | - <div class="wrapper"> |
218 | - <header class="page-header"> |
219 | - {% if not link == "/" %}<a href="/">Home</a>{% endif %} |
220 | - </header> |
221 | - <main class="page-body"> |
222 | - {{ content }} |
223 | - </main> |
224 | - <footer class="page-footer"></footer> |
225 | - </div> |
226 | - </section> |
227 | - </body> |
228 | - {% if devmode %} |
229 | - <script> |
230 | - const socket = new WebSocket("ws://localhost:8080/ws") |
231 | - socket.addEventListener("open", (event) => { |
232 | - console.log("connected to server", event); |
233 | - }); |
234 | - socket.addEventListener("message", (event) => { |
235 | - console.log("reloading page", event); |
236 | - window.location.reload(); |
237 | - }) |
238 | - </script> |
239 | - {% endif %} |
240 | - </html> |
241 | diff --git a/index.xml.jinja b/index.xml.jinja |
242 | new file mode 100644 |
243 | index 0000000..b92f652 |
244 | --- /dev/null |
245 | +++ b/index.xml.jinja |
246 | @@ -0,0 +1 @@ |
247 | + {{content}} |
248 | diff --git a/render.py b/render.py |
249 | index e746f49..216798e 100755 |
250 | --- a/render.py |
251 | +++ b/render.py |
252 | @@ -3,11 +3,13 @@ import argparse |
253 | import os |
254 | import sys |
255 | import xml.etree.ElementTree as ET |
256 | - |
257 | + from io import StringIO |
258 | from datetime import datetime |
259 | |
260 | import markdown |
261 | import yaml |
262 | + |
263 | + from bs4 import BeautifulSoup |
264 | from jinja2 import Environment, FunctionLoader |
265 | |
266 | |
267 | @@ -22,8 +24,11 @@ def _load_sitemap(path, current_path): |
268 | |
269 | def _filter_entries(entries): |
270 | entries = list( |
271 | - filter(lambda entry: "enabled" |
272 | - not in entry or entry["enabled"] is True, entries)) |
273 | + filter( |
274 | + lambda entry: "enabled" not in entry or entry["enabled"] is True, |
275 | + entries, |
276 | + ) |
277 | + ) |
278 | for entry in entries: |
279 | if "others" in entry: |
280 | entry["others"] = _filter_entries(entry["others"]) |
281 | @@ -90,65 +95,95 @@ def _make_tree(links): |
282 | return ET.tostring(root).decode() |
283 | |
284 | |
285 | + def load_content(path): |
286 | + with open(path) as fp: |
287 | + text = fp.read() |
288 | + content = markdown.markdown( |
289 | + text, |
290 | + extensions=["fenced_code", "tables"], |
291 | + ) |
292 | + return content |
293 | + |
294 | + |
295 | def generate(template, content_path, stylesheet, link, sitemap, devmode=False): |
296 | - sm, tree = None, None |
297 | - if sitemap: |
298 | - sm = _load_sitemap(sitemap, current_path=link) |
299 | - if sm: |
300 | - tree = _make_tree(sm["links"]) |
301 | + tree = _make_tree(sitemap["links"]) |
302 | with open(template, "r") as fp: |
303 | template = fp.read() |
304 | env = Environment(loader=FunctionLoader(lambda name: template)) |
305 | base = env.get_template(template) |
306 | with open(stylesheet) as fp: |
307 | style = fp.read() |
308 | - with open(content_path) as fp: |
309 | - text = fp.read() |
310 | - content = markdown.markdown( |
311 | - text, |
312 | - extensions=["fenced_code", "tables"], |
313 | - ) |
314 | + content = load_content(content_path) |
315 | env_content = Environment(loader=FunctionLoader(lambda name: content)) |
316 | - out = base.render({ |
317 | - "content": env_content.get_template("").render({ |
318 | - "sitemap": sm, |
319 | + out = base.render( |
320 | + { |
321 | + "content": env_content.get_template("").render( |
322 | + { |
323 | + "sitemap": sitemap, |
324 | + "tree": tree, |
325 | + } |
326 | + ), |
327 | + "is_index": False, |
328 | + "link": link, |
329 | + "style": style, |
330 | + "sitemap": sitemap, |
331 | "tree": tree, |
332 | - }), |
333 | - "is_index": False, |
334 | - "link": link, |
335 | - "style": style, |
336 | - "sitemap": sm, |
337 | - "tree": tree, |
338 | - "devmode": devmode, |
339 | - "current_time": datetime.now(), |
340 | - }) |
341 | - print(out) |
342 | + "devmode": devmode, |
343 | + "current_time": datetime.now(), |
344 | + } |
345 | + ) |
346 | + sys.stdout.write(out) |
347 | + |
348 | + |
349 | + def generate_rss(sitemap): |
350 | + sitemap = _load_sitemap(sitemap, "/") |
351 | + |
352 | + rss = ET.Element("rss", attrib={"version": "2.0", "encoding": "UTF-8"}) |
353 | + channel = ET.SubElement(rss, "channel") |
354 | + |
355 | + title = ET.SubElement(channel, "title") |
356 | + title.text = "Blog of Kevin Schoon" |
357 | + link = ET.SubElement(channel, "link") |
358 | + link.attrib["href"] = "https://kevinschoon.com" |
359 | + description = ET.SubElement(channel, "description") |
360 | + description.text = "Blog of Kevin Schoon" |
361 | + |
362 | + for entry in sitemap["by_name"]["/blog"]["others"]: |
363 | + item = ET.SubElement(channel, "item") |
364 | + title_item = ET.SubElement(item, "title") |
365 | + link_item = ET.SubElement(item, "link") |
366 | + description_item = ET.SubElement(item, "description") |
367 | + title_item.text = entry["name"] |
368 | + link_item.attrib["href"] = "https://kevinschoon.com/blog/" + entry["name"] |
369 | + description_item.text = entry["tagline"] |
370 | + |
371 | + xml_str = ET.tostring(rss, xml_declaration=True, encoding="utf-8", method="xml") |
372 | + sys.stdout.buffer.write(xml_str) |
373 | |
374 | |
375 | if __name__ == "__main__": |
376 | parser = argparse.ArgumentParser(description="static renderer") |
377 | - parser.add_argument("-content", |
378 | - required=True, |
379 | - help="markdown file with content") |
380 | - parser.add_argument("-template", |
381 | - default="index.jinja", |
382 | - help="jinja template file") |
383 | - parser.add_argument("-stylesheet", |
384 | - default="./assets/main.css", |
385 | - help="style sheet") |
386 | - parser.add_argument("-sitemap", |
387 | - help="sitemap") |
388 | - parser.add_argument("-link", |
389 | - default="/", |
390 | - help="current url") |
391 | - parser.add_argument("-devmode", |
392 | - action="store_true", |
393 | - help="developer mode (websocket page refresh)") |
394 | + parser.add_argument("-rss", help="if rendering the rss feed", action="store_true") |
395 | + parser.add_argument("-content", help="markdown file with content") |
396 | + parser.add_argument( |
397 | + "-template", default="index.html.jinja", help="jinja template file" |
398 | + ) |
399 | + parser.add_argument("-stylesheet", default="./assets/main.css", help="style sheet") |
400 | + parser.add_argument("-sitemap", default="sitemap.yaml", help="sitemap") |
401 | + parser.add_argument("-link", default="/", help="current url") |
402 | + parser.add_argument( |
403 | + "-devmode", action="store_true", help="developer mode (websocket page refresh)" |
404 | + ) |
405 | args = parser.parse_args() |
406 | - generate( |
407 | - template=args.template, |
408 | - content_path=args.content, |
409 | - stylesheet=args.stylesheet, |
410 | - link=args.link, |
411 | - sitemap=args.sitemap, |
412 | - devmode=args.devmode) |
413 | + sitemap = _load_sitemap(args.sitemap, args.link) |
414 | + if args.rss: |
415 | + generate_rss(args.sitemap) |
416 | + else: |
417 | + generate( |
418 | + template=args.template, |
419 | + content_path=args.content, |
420 | + stylesheet=args.stylesheet, |
421 | + link=args.link, |
422 | + sitemap=sitemap, |
423 | + devmode=args.devmode, |
424 | + ) |
425 | diff --git a/sitemap.yaml b/sitemap.yaml |
426 | index e772827..07d9bdc 100644 |
427 | --- a/sitemap.yaml |
428 | +++ b/sitemap.yaml |
429 | @@ -4,6 +4,7 @@ entries: |
430 | enabled: true |
431 | others: |
432 | - name: building-this-website |
433 | + tagline: There is no deficiency of static website generators these days and so I had to write my own. |
434 | date: 2023-04-04 |
435 | - name: projects |
436 | enabled: true |