Index

joshstock.in / db8cde9

Source for serving and static templating/compiling of https://joshstock.in.

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
20405 Sep 2024 14:05b2bc5e5commit updatesJosh Stockin101G

Blob @ joshstock.in / site / targets.py

application/x-python8087 bytesdownload raw
1# targets.py / Template engine for my website
2# Joshua Stockin / josh@joshstock.in / https://joshstock.in
3
4# Python standard lib
5import os
6import html
7from datetime import datetime, timezone, timedelta
8from xml.dom import minidom
9
10# External libraries
11import markdown2
12import htmlgenerator as hg
13import readtime
14import sass
15from feedgen.feed import FeedGenerator
16
17# Local imports
18from _utils import dotdict as namespace, current_dir, load_generators, list_files
19
20# Site generation metadata
21CONTENT_DIRECTORY = os.path.join(current_dir(), "content")
22SASS_DIRECTORY = os.path.join(current_dir(), "style")
23STATIC_DIRECTORY = os.path.join(current_dir(), "static")
24
25blog_description = "Barely coherent ramblings about engineering projects, software, hardware, and other things."
26
27# Fetch generator functions
28GENERATORS_MODULE = "generators"
29GENERATORS = [
30 "head.head",
31 "topbar",
32 "footer",
33 "blog.article",
34 "blog.listing",
35]
36generate = load_generators(GENERATORS_MODULE, GENERATORS)
37
38def render_basic_page(page_data, *contents):
39 # construct page
40 page_generator = hg.HTML(
41 generate("head.head", page_data),
42 hg.BODY(
43 *generate("topbar", page_data),
44 hg.DIV(
45 hg.DIV(
46 hg.mark_safe(contents[0]), _class="content-body"
47 ),
48 hg.DIV(_class="vfill"),
49 generate("footer"),
50 _class="content-container",
51 ),
52 onscroll="scroll()",
53 ),
54 )
55 return hg.render(page_generator, {}).encode("utf-8")
56
57
58# Site template implementation; returns dict {filename: data}
59def template() -> {str: str}:
60 files = {}
61
62 # sitemap.xml
63 sitemap_root = minidom.Document()
64 sitemap_urlset = sitemap_root.createElementNS("http://www.sitemap.org/schemas/sitemap/0.9", "urlset")
65 sitemap_urlset.setAttribute("xmlns", sitemap_urlset.namespaceURI)
66 sitemap_root.appendChild(sitemap_urlset)
67 def add_sitemap_url(url, priority=1.0):
68 # <url>
69 url_obj = sitemap_root.createElement("url")
70 # <loc>
71 loc_obj = sitemap_root.createElement("loc")
72 loc_obj.appendChild(sitemap_root.createTextNode(url))
73 url_obj.appendChild(loc_obj)
74 # </loc>
75 # <priority>
76 priority_obj = sitemap_root.createElement("priority")
77 priority_obj.appendChild(sitemap_root.createTextNode(str(priority)))
78 url_obj.appendChild(priority_obj)
79 # </priority>
80 sitemap_urlset.appendChild(url_obj)
81 # </url>
82
83 # Atom and RSS feeds for blog
84 articles_list = []
85 fg = FeedGenerator()
86 fg.id("https://joshstock.in/blog")
87 fg.title("Blog - Josh Stockin")
88 fg.author({"name": "Josh Stockin", "email": "josh@joshstock.in", "uri": "https://joshstock.in"})
89 fg.link(href="https://joshstock.in/blog", rel="alternate")
90 fg.subtitle(blog_description)
91 fg.language("en")
92
93 # Setup for string templating
94 website_pages = []
95 class template_string_dict(dict):
96 def __missing__(self, key):
97 return "{" + key + "}"
98 template_strings = template_string_dict()
99
100 # Iterate over content directory for markdown files
101 for content_file in list_files(CONTENT_DIRECTORY, ".md"):
102 f = open(content_file, "r")
103 data = f.read()
104 f.close()
105
106 # Compile markdown as markdown2 object with HTML, metadata
107 content_html = markdown2.markdown(
108 data,
109 safe_mode=False,
110 extras=[
111 "code-friendly",
112 "cuddled-lists",
113 "fenced-code-blocks",
114 "footnotes",
115 "header-ids",
116 "metadata",
117 "strike",
118 "tables",
119 "wiki-tables",
120 "tag-friendly",
121 "target-blank-links",
122 ],
123 )
124
125 # Normalize content metadata
126 page_data = namespace(content_html.metadata)
127 page_data.link = page_data.link or ""
128 page_data.banner_image = page_data.banner_image or ""
129 page_data.thumbnail = page_data.thumbnail or page_data.banner_image
130
131 # type=="website"
132 if page_data.type == "website":
133 # save for templating later
134 website_pages.append((content_html, page_data))
135 # type=="website"
136
137 # type=="article"
138 elif page_data.type == "article":
139 # Blog article page metadata
140 page_data.readtime = readtime.of_html(content_html, wpm=150)
141 page_data.link = "/blog/" + page_data.identifier
142 page_data.links = page_data.links or {}
143 page_data.content = content_html
144 articles_list += [page_data]
145
146 rendered = render_basic_page(page_data, hg.render(hg.DIV(*generate("blog.article", page_data)), {}))
147
148 # render, export, add to sitemap
149 files["blog/" + page_data.identifier + ".html"] = rendered
150 add_sitemap_url("/blog/" + page_data.identifier, priority=0.6)
151 # type=="article"
152
153 # type=="project"
154 elif page_data.type == "project":
155 pass
156 add_sitemap_url("/projects/" + page_data.identifier, priority=0.6)
157 # type=="project"
158
159 # end of content md files
160
161 # Template strings
162 articles_list = sorted(articles_list, key=lambda x: x.datestring, reverse=True)
163 template_strings["articles_list"] = hg.render(hg.DIV(*[generate("blog.listing", x) for x in articles_list]), {})
164 template_strings["projects_list"] = '<p>Under construction; check <a rel="noopener" target="_blank" href="https://git.joshstock.in">Git repositories</a> instead</p>'
165
166 # Apply templates
167 for website_page in website_pages:
168 content_html = website_page[0]
169 page_data = website_page[1]
170
171 templated = content_html.format_map(template_strings)
172 rendered = render_basic_page(page_data, templated)
173
174 files[page_data.index] = rendered
175 if page_data.index != "index.html":
176 add_sitemap_url("/" + page_data.index.rsplit(".html")[0], priority=0.8)
177 else:
178 add_sitemap_url("/", priority=1.0)
179
180 # Create article entries for feed generator
181 for page_data in articles_list:
182 fe = fg.add_entry()
183 fe.id("https://joshstock.in/blog/" + page_data.identifier)
184 fe.author({"name": "Josh Stockin", "email": "josh@joshstock.in", "uri": "https://joshstock.in"})
185 fe.title(page_data.title)
186 fe.summary(page_data.description + " / https://joshstock.in/blog/" + page_data.identifier)
187 datetime_pub = datetime.strptime(page_data.datestring, "%Y-%m-%d").replace(tzinfo=timezone(-timedelta(hours=6)))
188 fe.published(datetime_pub)
189 fe.updated(datetime_pub)
190 fe.link(href="https://joshstock.in/blog/" + page_data.identifier)
191
192 # Generate Atom and RSS fees for blog
193 fg.link(href="https://joshstock.in/atom", rel="self")
194 files["atom.xml"] = fg.atom_str(pretty=True)
195 fg.link(href="https://joshstock.in/rss", rel="self", replace=True)
196 files["rss.xml"] = fg.rss_str(pretty=True)
197
198 # Compile Sass stylesheets
199 for stylesheet_file in list_files(SASS_DIRECTORY, ".scss"):
200 if os.path.basename(stylesheet_file)[0] != "_":
201 files[
202 os.path.join(
203 "static",
204 "style",
205 os.path.splitext(os.path.relpath(stylesheet_file, SASS_DIRECTORY))[
206 0
207 ]
208 + ".css",
209 )
210 ] = sass.compile(filename=stylesheet_file, output_style="compressed").encode("utf-8")
211
212 # Copy content from static files
213 for static_file in list_files(STATIC_DIRECTORY):
214 f = open(static_file, "rb")
215 data = f.read()
216 f.close()
217
218 files[
219 os.path.join("static", os.path.relpath(static_file, STATIC_DIRECTORY))
220 ] = data
221
222 # Compile XML, export sitemap
223 files["sitemap.xml"] = sitemap_root.toprettyxml(indent="\t").encode("utf-8")
224
225 return files
226