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