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