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