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