-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuild.py
More file actions
143 lines (115 loc) · 4.19 KB
/
build.py
File metadata and controls
143 lines (115 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env python3
import datetime
import glob
from pathlib import Path
import re
import shutil
import fontTools.subset as subset
import mistletoe
from mistletoe.block_token import Heading
from mistletoe.html_renderer import HtmlRenderer
from mistletoe.span_token import RawText
from PIL import Image
POST_FILENAME_GLOB = "posts/*/*/*/*.md"
POST_FILENAME_REGEX = r"posts[\\/](\d{4})[\\/](\d\d)[\\/](\d\d)[\\/][^\\/]+\.md"
NEWS_PLACEHOLDER = '<!-- NEWS PLACEHOLDER -->'
FAQ_FILENAME = 'posts/faq.md'
FAQ_PLACEHOLDER = '<!-- FAQ PLACEHOLDER -->'
SRC_DIR = Path('src')
DIST_DIR = Path('dist')
INDEX_HTML = 'index.html'
FONT_GLOB = "fonts/*.ttf"
def generate_posts():
posts = []
renderer = HtmlRenderer()
post_filenames = glob.glob(POST_FILENAME_GLOB)
post_filenames.sort(reverse=True)
for fn in post_filenames:
# parse the post date
match = re.fullmatch(POST_FILENAME_REGEX, fn)
if match is None:
raise RuntimeError(f'post {fn} does not match pattern "posts/YYYY/MM/DD/NAME.md"')
date = datetime.date(int(match.group(1)), int(match.group(2)), int(match.group(3)))
# load the document
with open(fn) as f:
document = mistletoe.Document(f)
# fetch the post title
header_error = RuntimeError(f"post {fn} should start with a `##` header for the post title")
if len(document.children) == 0:
raise header_error
heading = document.children[0]
if not isinstance(heading, Heading) or heading.level != 2 or len(heading.children) < 0:
raise header_error
raw_text = heading.children[0]
if not isinstance(raw_text, RawText):
raise header_error
title = raw_text.content
# remove the title from the markdown
del document.children[0]
# generate the header
header = f'<article><header><h2>{title}</h2><time datetime="{date}">{date.strftime("%d %b %Y")}</time></header>'
footer = f'</article>'
posts.append(header + renderer.render(document))
return posts
def generate_faq():
faq = []
renderer = HtmlRenderer()
post_filenames = glob.glob(POST_FILENAME_GLOB)
post_filenames.sort(reverse=True)
# load the document
with open(FAQ_FILENAME) as f:
document = mistletoe.Document(f)
# generate the header
header = f'<article><header><h2>Frequently Asked Questions</h2></header>'
footer = f'</article>'
faq.append(header + renderer.render(document))
return faq
def insert_posts():
posts = generate_posts()
with open(DIST_DIR / INDEX_HTML) as f:
html = f.read()
if NEWS_PLACEHOLDER not in html:
raise RuntimeError("news placeholder not found")
html = html.replace(NEWS_PLACEHOLDER, ''.join(posts))
with open(DIST_DIR / INDEX_HTML, "w") as f:
f.write(html)
def insert_faq():
post = generate_faq()
with open(DIST_DIR / INDEX_HTML) as f:
html = f.read()
if FAQ_PLACEHOLDER not in html:
raise RuntimeError("faq placeholder not found")
html = html.replace(FAQ_PLACEHOLDER, ''.join(post))
with open(DIST_DIR / INDEX_HTML, "w") as f:
f.write(html)
def size_images():
with open(DIST_DIR / INDEX_HTML) as f:
html = f.read()
for fn in re.findall('<img [^>/]*src="(images/[^.]+.png)"', html):
im = Image.open(DIST_DIR / fn)
w, h = im.size
html = html.replace(f'src="{fn}"', f'width="{w}" height="{h}" src="{fn}"')
with open(DIST_DIR / INDEX_HTML, "w") as f:
f.write(html)
def main():
# copy files
shutil.rmtree(DIST_DIR, ignore_errors=True)
shutil.copytree(SRC_DIR, DIST_DIR)
# insert blogposts into html
insert_posts()
# insert faq
insert_faq()
# insert image sizes
size_images()
# minify font
with open(DIST_DIR / INDEX_HTML) as f:
chars = {ord(c) for c in f.read()}
opts = subset.Options(layout_features='')
subsetter = subset.Subsetter(options=opts)
subsetter.populate(unicodes=chars)
for fn in glob.glob(str(DIST_DIR / FONT_GLOB)):
f = subset.load_font(fn, opts, lazy=False)
subsetter.subset(f)
subset.save_font(f, fn, opts)
if __name__ == "__main__":
main()