Index

joshstock.in / 0fe4f18

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

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
18818 Mar 2023 13:434febf83Use placeholder for projects listJosh Stockin111G

Blob @ joshstock.in / site / targets.py

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