-
Notifications
You must be signed in to change notification settings - Fork 0
/
generate.py
210 lines (179 loc) · 6.6 KB
/
generate.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
"""
Usage:
generate.py <indir> <outdir>
Options:
-h --help Show this screen.
"""
import os
import os.path
import shutil
import sys
import re
import errno
from docopt import docopt
import frontmatter
import yaml
from jinja2 import Template, FileSystemLoader, Environment, Undefined
from bs4 import BeautifulSoup
TEMPLATE_DIR = "templates"
DEFAULT_TEMPLATE = "page.html"
TEMPLATE_PATTERN = r"^.*\.html$"
PAGES_PATTERN = r"^.*/index.html?$"
IGNORE_PATTERNS = [r"^.*\.swp$"]
ASSET_DIR = "public"
INDEX_PATTERNS = [r"^(.*/)index.html?$"]
class SilentUndefined(Undefined):
def _fail_with_undefined_error(self, *args, **kwargs):
return ''
def paths(directory, ignore_patterns):
for root, subdirs, files in os.walk(directory, followlinks=True):
for file in files:
path_with_base = os.path.join(root, file)
path = os.path.relpath(path_with_base, directory)
for pattern in ignore_patterns:
if re.match(pattern, path):
break
else:
yield path, {"path": path, "path_with_base": path_with_base}
def urls(files, index_patterns):
for path, file in files:
full_url = url = "/" + path
for pattern in index_patterns:
match = re.match(pattern, full_url)
if match:
url = match.group(1)
break
file["url"] = url
file["full_url"] = full_url
yield path, file
def read(files, metadata):
for path, file in files:
try:
with open(file["path_with_base"]) as f:
file["contents"] = f.read()
file["is_binary"] = False
yield path, file
except UnicodeDecodeError:
file["is_binary"] = True
yield path, file
def parse_frontmatter(files, metadata):
for path, file in files:
if file["is_binary"]:
yield path, file
continue
file_metadata, contents = frontmatter.parse(file["contents"])
file.update(file_metadata)
file["contents"] = contents.strip()
yield path, file
def yaml_frontmatter(files, metadata):
files = dict(files)
for path, file in files.items():
_, extension = os.path.splitext(path)
if extension == ".yml" or extension == ".yaml":
data = yaml.load(file["contents"])
if "for" in data:
target = data["for"]
del data["for"]
target = os.path.join(os.path.split(path)[0], target)
files[target].update(data)
yield path, file
def collection(files, metadata, name, pattern):
if not "collections" in metadata:
collections = metadata["collections"] = {}
collection = collections[name] = []
for path, file in files:
if file.get("no-collections", False):
yield path, file
continue
should_collect = not file.get("no-collections", False)
if should_collect and re.match(pattern, path):
collection.append(file)
file["collection"] = name
yield path, file
def sort_collections(files, metadata):
files = dict(files)
if not "collections" in metadata:
return files
collections = metadata["collections"]
for name, collection in collections.items():
collection.sort(key=lambda file: file["title"].lower())
for index, file in enumerate(collection):
file[name + "_index"] = index
return files
def collection_links(files, metadata):
files = dict(files)
for path, file in files.items():
if "collection" in file:
name = file["collection"]
collection = metadata["collections"][name]
index = file[name + "_index"]
if index > 0:
file["previous"] = collection[index - 1]
if index < len(collection) - 1:
file["next"] = collection[index + 1]
yield path, file
def template(files, metadata, pattern, default, directory):
jinja = Environment(loader=FileSystemLoader(directory),
undefined=SilentUndefined, trim_blocks=True,
lstrip_blocks=True)
for path, file in files:
if re.match(pattern, path):
if "template" not in file:
file["template"] = default
template = jinja.get_template(file["template"])
file["contents"] = template.render(dict(file, **metadata))
yield path, file
def prettify(files, metadata):
for path, file in files:
_, extension = os.path.splitext(path)
if extension == ".html":
soup = BeautifulSoup(file["contents"], "html5lib")
contents = soup.prettify()
contents = re.sub(r"^( +)", r"\1\1\1\1", contents,
flags=re.MULTILINE)
file["contents"] = contents
yield path, file
def write(files, metadata, directory):
if os.path.exists(directory):
shutil.rmtree(directory)
for path, file in files:
output_path = os.path.join(directory, path)
output_directory = os.path.dirname(output_path)
if not os.path.exists(output_directory):
os.makedirs(output_directory)
if file["is_binary"]:
shutil.copy(file["path_with_base"], output_path)
else:
with open(output_path, "w") as f:
f.write(file["contents"])
def assets(source, destination):
for path in os.listdir(source):
source_path = os.path.join(source, path)
destination_path = os.path.join(destination, path)
if os.path.isdir(source_path):
shutil.copytree(source_path, destination_path)
elif os.path.isfile(source_path):
shutil.copy(source_path, destination_path)
if __name__ == "__main__":
arguments = docopt(__doc__)
indir = arguments["<indir>"]
outdir = arguments["<outdir>"]
jinja = Environment(loader=FileSystemLoader(TEMPLATE_DIR))
metadata = {
"site": {
"url": "https://www.jhtrnr.com",
"title": "Joe Turner"
}
}
files = paths(indir, IGNORE_PATTERNS)
files = urls(files, INDEX_PATTERNS)
files = read(files, metadata)
files = parse_frontmatter(files, metadata)
files = yaml_frontmatter(files, metadata)
files = collection(files, metadata, "pages", PAGES_PATTERN)
files = sort_collections(files, metadata)
files = collection_links(files, metadata)
files = template(files, metadata, TEMPLATE_PATTERN, DEFAULT_TEMPLATE, TEMPLATE_DIR)
files = prettify(files, metadata)
write(files, metadata, outdir)
assets(ASSET_DIR, outdir)