build.py
Python script, Unicode text, UTF-8 text executable
1from Renderers import RenderTemplate, RenderMarkdown 2from sys import argv 3from shutil import rmtree as DeleteDirectory 4from os import mkdir as CreateDirectory, listdir as ListDirectory, unlink as DeleteFile 5from os.path import isfile as IsFile, exists as PathExists 6from distutils.dir_util import copy_tree as CopyDirectory 7from datetime import datetime 8from json import dump as DumpJSON 9from yaml import safe_load as LoadYML 10from re import sub as RegReplace 11from typing import Literal 12 13GITHUB_BUILD_DIR = "build" # Separate because this site is built with an action that won't work if they aren't 14LOCAL_BUILD_DIR = "build" 15 16IS_GH_ACTIONS = len(argv) > 1 and argv[1] == "gh-pages-deploy" 17BUILD_DIRECTORY = GITHUB_BUILD_DIR if IS_GH_ACTIONS else LOCAL_BUILD_DIR 18 19 20PAGES = { 21"index.html": "index.html", 22"blog-list.html": "blog/index.html", 23"blog-feed.rss": "blog/feed.rss", 24"blog-feed.atom": "blog/feed.atom", 25"404.html": "404.html", 26} 27 28DISALLOWED_SITEMAP = { 29"404.html", 30"blog-feed.rss", 31"blog-feed.atom", 32"blog-list.html", 33"index.html", 34} 35 36REDIRECTS = { 37"link-tree.html": "list/link-tree.html", # Old location -> new location 38} 39 40SITEMAP_HREF = "https://steve0greatness.github.io/" 41sitemap = [ 42SITEMAP_HREF + "blog/", 43SITEMAP_HREF, 44] 45 46def EscapeHTMLForRSS(HTML: str) -> str: 47values = { 48"&": "&", # this is here for a reason, do not change order. 49"<": "<", 50">": ">", 51"§": "§", 52} 53RssHtml = HTML 54for old, new in values.items(): 55RssHtml = RssHtml.replace(old, new) 56return RssHtml 57 58def WipeFinalDir(): 59if not PathExists(BUILD_DIRECTORY): 60print("Directory didn't existing, creating it...") 61CreateDirectory(BUILD_DIRECTORY) 62return 63print("Directory exists, wiping it...") 64for item in ListDirectory(BUILD_DIRECTORY): 65path = BUILD_DIRECTORY + "/" + item 66if IsFile(path): 67DeleteFile(path) 68continue 69DeleteDirectory(path) 70 71def PostDateToDateObj(Date): 72return datetime.strptime(Date, "%Y %b %d") 73 74def PostSortHelper(Post): 75return PostDateToDateObj(Post["date"]) 76 77def GetBlogList(): 78print("Grabbing post list") 79PostSlugs: tuple[tuple[Literal["blog-posts", "drafts"], str], ...] = tuple( ("blog-posts", file) for file in ListDirectory("blog-posts") ) 80if not IS_GH_ACTIONS and PathExists("drafts"): 81PostSlugs = PostSlugs + tuple( ("drafts", file) for file in ListDirectory("drafts") ) 82Posts = [] 83for dir, slug in PostSlugs: 84if not slug.endswith(".md"): 85continue 86print("Grabbing post list blog-posts/%s" % (slug)) 87with open(dir + "/" + slug, encoding="utf-8") as MDFile: 88RawMD = MDFile.read() 89PostHTML = RenderMarkdown(RawMD) 90Item = PostHTML.metadata 91Item["content"] = PostHTML 92Item["raw-content"] = RawMD 93Item["rss-content"] = EscapeHTMLForRSS(PostHTML) 94Item["rss-post-time"] = PostDateToDateObj(Item["date"]).strftime("%a, %d %b %Y") + " 00:00:00 GMT" 95Item["atom-post-time"] = PostDateToDateObj(Item["date"]).strftime("%Y-%m-%d") + "T00:00:00Z" 96Item["opengraph-date"] = PostDateToDateObj(Item["date"]).strftime("%Y-%m-%d") 97Item["opengraph-update"] = Item["opengraph-date"] 98Item["atom-update-time"] = Item["atom-post-time"] 99if "updated" in Item: 100Item["atom-update-time"] = PostDateToDateObj(Item["updated"]).strftime("%Y-%m-%d") + "T00:00:00Z" 101Item["opengraph-update"] = PostDateToDateObj(Item["updated"]).strftime("%Y-%m-%d") 102Item["pathname"] = slug.replace(".md", ".html") 103Item["plaintext"] = slug.replace(".md", ".txt") 104Item["origin"] = slug 105Item["is-draft"] = dir == "drafts" 106Posts.append(Item) 107PostsByDate = sorted(Posts, key=PostSortHelper, reverse=True) 108return PostsByDate 109 110PostList = [] 111 112 113def ListParseCategory(Obj, depth): 114html = "<h%d id=\"%s\">%s</h%d>" % (2+depth, Obj["id"], Obj["title"], 2+depth) 115if "paragraph" in Obj: 116html += "<p>%s</p>" % Obj["paragraph"] 117listType = "ul" 118if "list-type" in Obj and Obj["list-type"] == "ordered": 119listType = "ol" 120html += "<%s>" % listType 121for item in Obj["list"]: 122html += "<li>" + LIST_PARSER_DICT[item["type"]](item, depth + 1) + "</li>" 123html += "</%s>" % listType 124return html 125 126def ListParseLink(Obj, depth): 127html = "<a href=\"%s\">" % Obj["href"] 128text = Obj["text"] 129if "text-type" in Obj and Obj["text-type"] == "text/markdown": 130text = RenderMarkdown(text).replace("<p>", "").replace("</p>", "") 131html += text + "</a>" 132if "comment" in Obj: 133html += "(%s)" % Obj["comment"] 134return html 135 136def ListParseText(Obj, depth): 137text = Obj["text"] 138# if "text-type" in Obj and Obj["text-type"] == "text/markdown": 139# print(RenderMarkdown(text)) 140# text = RenderMarkdown(text) # this doesn't work??? 141if "comment" in Obj: 142text += "(%s)" % Obj["comment"] 143return text 144 145LIST_PARSER_DICT = { 146"category": ListParseCategory, 147"link": ListParseLink, 148"text": ListParseText, 149} 150 151def GetLists(): 152ListSlugs = ListDirectory("lists") 153Lists = [] 154for slug in ListSlugs: 155List = { 156"title": "", 157"content": "", 158"filename": slug 159} 160with open("lists/" + slug) as ListYML: 161ListDict = LoadYML(ListYML.read()) 162List["title"] = ListDict["title"] 163if "paragraph" in ListDict: 164List["content"] += "<p>%s</p>" % ListDict["paragraph"] 165List["content"] += "<ul>" 166for item in ListDict["list"]: 167List["content"] += "<li>" + LIST_PARSER_DICT[item["type"]](item, 0) + "</li>" 168List["content"] += "</ul>" 169Lists.append(List) 170sitemap.append(SITEMAP_HREF + "list/" + slug) 171sitemap.append(SITEMAP_HREF + "list/") 172return Lists 173 174def RenderPosts(): 175global PostList 176for post in PostList: 177Revised = post["updated"] if "updated" in post else False 178RenderedHTML = RenderTemplate( 179"blog-post.html", 180Revised=Revised, 181Title=post["title"], 182PostDate=post["date"], 183Content=post["content"], 184PostPath=post["pathname"], 185PlaintextPath=post["plaintext"], 186IsDraft=post["is-draft"], 187OpenGraphDate=post["opengraph-date"], 188post=post 189) 190print("Building blog-posts/%s to %s/blog/%s" % (post["origin"], BUILD_DIRECTORY, post["pathname"])) 191with open(BUILD_DIRECTORY + "/blog/" + post["pathname"], "w", encoding="utf-8") as PostHTMLFile: 192PostHTMLFile.write(RenderedHTML) 193print("Copying blog-posts/%s to %s/blog/%s" % (post["origin"], BUILD_DIRECTORY, post["plaintext"])) 194with open(BUILD_DIRECTORY + "/blog/" + post["plaintext"], "w", encoding="utf-8") as PostHTMLFile: 195PostHTMLFile.write(post["raw-content"]) 196sitemap.append(SITEMAP_HREF + "blog/" + post["pathname"]) 197 198def RenderPage(PageInput: str, ContentDest: str, AllowSitemap: bool = True, **kwargs): 199print("Building views/%s to %s/%s" % (PageInput, BUILD_DIRECTORY, ContentDest)) 200if AllowSitemap: 201sitemap.append(SITEMAP_HREF + ContentDest) 202with open(BUILD_DIRECTORY + "/" + ContentDest, "w", encoding="utf-8") as DestLocation: 203DestLocation.write(RenderTemplate(PageInput, **kwargs)) 204 205def CreateJSONFeed(): 206global PostList 207CreatedJSON = { 208"version": "https://jsonfeed.org/version/1", 209"title": "Steve0Greatness' Blog", 210"home_page_url": "https://steve0greatness.github.io", 211"feed_url": "https://steve0greatness.github.io/blog/feed.rss", 212"language": "en-US", 213"favicon": "https://steve0greatness.github.io/favicon.ico", 214"description": "A blog by a human being.", 215"authors": [ 216{ 217"name": "Steve0Greatness", 218"url": "https://steve0greatness.github.io" 219} 220], 221"items": [] 222} 223for post in PostList: 224CreatedJSON["items"].append({ 225"id": "https://steve0greatness.github.io/blog/" + post["pathname"], 226"title": "JSON Feed version 1.1", 227"icon": "https://steve0greatness.github.io/favicon.ico", 228"content_html": post["content"], 229"date_published": post["atom-post-time"], 230"date_modified": post["atom-update-time"], 231"url": "https://steve0greatness.github.io/blog/" + post["pathname"] 232}) 233with open(BUILD_DIRECTORY + "/blog/feed.json", "w") as JSONFeedFile: 234DumpJSON(CreatedJSON, JSONFeedFile) 235 236def RenderLists(): 237Lists = GetLists() 238CreateDirectory(BUILD_DIRECTORY + "/list/") 239ListIndex = "<ul>" 240for List in Lists: 241FileLocation = "/list/" + List["filename"].replace(".yml", ".html") 242Title = List["title"] 243print("%s -> %s" % ("lists/" + List["filename"], BUILD_DIRECTORY + FileLocation)) 244with open(BUILD_DIRECTORY + FileLocation, "w") as file: 245file.write(RenderTemplate("list.html", Content=List["content"], Title=Title, Location=FileLocation)) 246ListIndex += "<li><a href=\"%s\">%s</a></li>" % (FileLocation, Title) 247ListIndex += "</ul>" 248print("Building list index") 249with open(BUILD_DIRECTORY + "/list/index.html", "w") as file: 250file.write(RenderTemplate("list-index.html", Content=ListIndex)) 251 252def main(): 253global PostList 254PostList = GetBlogList() 255print("Wiping directory") 256WipeFinalDir() 257print("Creating blog holder") 258CreateDirectory(BUILD_DIRECTORY + "/blog") 259print("Rendering posts") 260RenderPosts() 261CreateJSONFeed() 262print("Copying static directory") 263CopyDirectory("static", BUILD_DIRECTORY) 264print("Creating lists") 265RenderLists() 266 267print("Building pages") 268for file, path in PAGES.items(): 269AllowSitemap = file not in DISALLOWED_SITEMAP 270RenderPage(file, path, AllowSitemap, PostList=PostList) 271 272print("Building redirects") 273for OldLocation, NewLocation in REDIRECTS.items(): 274RenderPage("redirect.html", OldLocation, False, redirect=NewLocation, old=OldLocation) 275 276with open(BUILD_DIRECTORY + "/sitemap.txt", "w") as SitemapFile: 277SitemapFile.write("\n".join(sitemap)) 278 279if __name__ == "__main__": 280main() 281