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