forked from vbuterin/blog
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpublish.py
executable file
·294 lines (244 loc) · 8.61 KB
/
publish.py
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#!/usr/bin/python3
import os
import sys
import re
HEADER = """
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="/css/common-vendor.b8ecfc406ac0b5f77a26.css">
<link rel="stylesheet" type="text/css" href="/css/font-vendor.b86e2bf451b246b1a88e.css">
<link rel="stylesheet" type="text/css" href="/css/fretboard.f32f2a8d5293869f0195.css">
<link rel="stylesheet" type="text/css" href="/css/pretty.0ae3265014f89d9850bf.css">
<link rel="stylesheet" type="text/css" href="/css/pretty-vendor.83ac49e057c3eac4fce3.css">
<link rel="stylesheet" type="text/css" href="/css/global.css">
<link rel="stylesheet" type="text/css" href="/css/misc.css">
<script type="text/javascript" id="MathJax-script" async
src="/scripts/mathjax.js">
</script>
<style>
@font-face {
font-family: MJXc-TeX-math-Iw;
src: url("https://assets.hackmd.io/build/MathJax/fonts/HTML-CSS/TeX/woff/MathJax_Main-Regular.woff")
}
@font-face {
font-family: MJXZERO;
src: url("https://assets.hackmd.io/build/MathJax/fonts/HTML-CSS/TeX/woff/MathJax_Main-Regular.woff")
}
@font-face {
font-family: MJXTEX;
src: url("https://assets.hackmd.io/build/MathJax/fonts/HTML-CSS/TeX/woff/MathJax_Main-Regular.woff")
}
.math { font-family: MJXc-TeX-math-Iw }
</style>
<div id="doc" class="container-fluid markdown-body comment-enabled" data-hard-breaks="true">
<script type="text/x-mathjax-config">
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global',
}
};
</script>
<script
type="text/javascript"
id="MathJax-script"
async
src="/scripts/tex-svg.js"
></script>
<div
id="doc"
class="container-fluid markdown-body comment-enabled"
data-hard-breaks="true"
>
<div id="color-mode-switch">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
<input type="checkbox" id="switch" />
<label for="switch">Dark Mode Toggle</label>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
</div>
<script type="text/javascript">
// Update root html class to set CSS colors
const toggleDarkMode = () => {
const root = document.querySelector("html");
root.classList.toggle("dark");
};
// Update local storage value for colorScheme
const toggleColorScheme = () => {
const colorScheme = localStorage.getItem("colorScheme");
if (colorScheme === "light")
localStorage.setItem("colorScheme", "dark");
else localStorage.setItem("colorScheme", "light");
};
// Set toggle input handler
const toggle = document.querySelector(
'#color-mode-switch input[type="checkbox"]'
);
if (toggle)
toggle.onclick = () => {
toggleDarkMode();
toggleColorScheme();
};
// Check for color scheme on init
const checkColorScheme = () => {
const colorScheme = localStorage.getItem("colorScheme");
// Default to light for first view
if (colorScheme === null || colorScheme === undefined)
localStorage.setItem("colorScheme", "light");
// If previously saved to dark, toggle switch and update colors
if (colorScheme === "dark") {
toggle.checked = true;
toggleDarkMode();
}
};
checkColorScheme();
</script>
"""
FOOTER = """
</div>
<div>
<small>noticed a mistake or have a suggestion? submit a pull request <a href="https://github.com/nazariyv/blog" target="_blank">here</a></small>
</div>
"""
TOC_HEADER = """
<br>
<h1>{}</h1>
<br>
<br>
<ul class="post-list">
"""
TOC_FOOTER = """ </ul> """
TOC_ITEM_TEMPLATE = """
<li>
<span class="post-meta">{}</span>
<h3>
<a class="post-link" href="{}">{}</a>
</h3>
</li>
"""
TWITTER_CARD_TEMPLATE = """
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="{}" />
<meta name="twitter:image" content="{}" />
"""
def extract_metadata(fil, filename=None):
metadata = {}
if filename:
assert filename[-3:] == '.md'
metadata["filename"] = filename[:-3]+'.html'
while True:
line = fil.readline()
if line and line[0] == '[' and ']' in line:
key = line[1:line.find(']')]
value_start = line.index('(')+1
value_end = line.index(')')
metadata[key] = line[value_start:value_end]
else:
break
return metadata
def metadata_to_path(metadata):
return os.path.join(metadata['category'].lower(), metadata['date'], metadata['filename'])
def make_twitter_card(metadata, global_config):
return TWITTER_CARD_TEMPLATE.format(metadata['title'], global_config['icon'])
def make_post_header(metadata):
year, month, day = metadata['date'].split('/')
month = 'JanFebMarAprMayJunJulAugSepOctNovDec'[int(month)*3-3:][:3]
return f"""
<br>
<h1 style="margin-bottom:7px"> {metadata['title']} </h1>
<small style="float:left; color: #888"> {year} {month} {day} </small>
<small style="float:right; color: #888"><a href="/">See all posts</a></small>
<br> <br> <br>
<title> {metadata['title']} </title>
"""
def defancify(text):
return text \
.replace("’", "'") \
.replace('“', '"') \
.replace('”', '"') \
.replace('…', '...') \
def make_toc_item(metadata):
year, month, day = metadata['date'].split('/')
month = 'JanFebMarAprMayJunJulAugSepOctNovDec'[int(month)*3-3:][:3]
link = os.path.join('/', metadata_to_path(metadata))
return TOC_ITEM_TEMPLATE.format(year+' '+month+' '+day, link, metadata['title'])
def update_image_srcs(html_content):
# This regex looks for img tags with src attributes starting with "../images/"
pattern = r'<img[^>]*src=["\']\.\.\/images\/([^"\']+)["\']'
# This function will be called for each match
def replace_src(match):
return match.group(0).replace("../images/", "/images/")
# Replace all matching src attributes
return re.sub(pattern, replace_src, html_content)
if __name__ == '__main__':
# Get blog config
global_config = extract_metadata(open('config.md'))
# Normal case: process each provided file
for file_location in sys.argv[1:]:
filename = os.path.split(file_location)[1]
print("Processing file: {}".format(filename))
# Extract path
file_data = open(file_location).read()
metadata = extract_metadata(open(file_location), filename)
path = metadata_to_path(metadata)
print("Path selected: {}".format(path))
# Make sure target directory exists
truncated_path = os.path.split(path)[0]
os.system('mkdir -p {}'.format(os.path.join('site', truncated_path)))
# Generate the html file
out_location = os.path.join('site', path)
options = metadata.get('pandoc', '')
os.system('pandoc -o /tmp/temp_output.html {} {}'.format(file_location, options))
temp_content = open('/tmp/temp_output.html').read()
processed_content = update_image_srcs(temp_content)
total_file_contents = (
HEADER +
make_twitter_card(metadata, global_config) +
make_post_header(metadata) +
defancify(processed_content) +
FOOTER
)
# Put it in the desired location
open(out_location, 'w').write(total_file_contents)
# Reset ToC
metadatas = []
for filename in os.listdir('posts'):
if filename[-4:-1] != '.sw':
metadatas.append(extract_metadata(open(os.path.join('posts', filename)), filename))
sorted_metadatas = sorted(metadatas, key=lambda x: x['date'], reverse=True)
toc_items = [make_toc_item(metadata) for metadata in sorted_metadatas]
toc = (
HEADER +
make_twitter_card(global_config, global_config) +
TOC_HEADER.format(global_config['title']) +
''.join(toc_items) +
TOC_FOOTER
)
open('site/index.html', 'w').write(toc)