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