A mirror of my website's source code.

Important information: Google announced that, from September 2026, Android devices will require ALL apps to be signed by Google, effectively leading to an iOS situation. Value your right to a computer that does what you want; do not tolerate this monopolistic practice! Contact me if you don't understand why it is bad. Click to learn more.

 build.py

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