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