-
Notifications
You must be signed in to change notification settings - Fork 1
/
build.py
188 lines (136 loc) · 4.92 KB
/
build.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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This script builds the presentation by filling the boilerplate with content
"""
import codecs
import configparser
import glob
import re
from itertools import zip_longest
from math import ceil
IGNORED_DOCS = [
"CHANGELOG.md",
"README.md",
"README-remarkjs.md",
"handout.md",
"labs.md",
]
"""
IGNORED_DOCS: documents that will never be imported
"""
SLIDE_DIVIDER = "\n---\n"
POINTS_PER_AGENDA_SLIDE = 9
AGENDA_TEMPLATE = """
# Agenda {counter}
{points}
"""
def main():
"""
Main function, starts the logic based on parameters.
"""
config = read_config("settings.ini")
slides = render_slides(read_slides(), config)
template = read_template("template.html")
rendered_template = render_metadata(template, config)
rendered_template = rendered_template.replace("{{ content }}", slides)
write_file("presentation.html", rendered_template)
def read_config(filename: str):
"Read the config from `filename` and return it"
config = configparser.ConfigParser()
config.read(filename, encoding="utf-8")
return config
def read_template(filename: str):
"Read the content of the template at `filename` and return it"
with open(filename) as file_:
return file_.read()
def read_slides():
"Read all slides from the given files"
def create_stripped_slides(content):
"Return a list of slides with no leading or trailing whitespace"
return [slide.strip() for slide in content.split(SLIDE_DIVIDER)]
slides = []
for file in sorted(glob.iglob("*.md")):
if file not in IGNORED_DOCS:
with open(file, "r", encoding="utf-8") as slide_file:
content = slide_file.read()
slides.extend(create_stripped_slides(content))
if not slides:
raise RuntimeError(
"No slides loaded. Please add some slides or adjust IGNORED_DOCS."
)
return slides
def render_slides(slides, config: dict) -> list:
"Render the given files by filling the placeholders"
agenda = create_agenda(slides)
print("On our agenda: {}".format(", ".join(agenda)))
rendered_agenda = render_agenda(agenda)
combined_slides = SLIDE_DIVIDER.join(slides)
rendered_slides = render_metadata(combined_slides, config)
rendered_slides = rendered_slides.replace("{{ agenda }}", rendered_agenda)
return rendered_slides
def create_agenda(slides):
"Collect agenda points from all slides"
agenda = []
for slide in slides[1:]: # ignore title slide
title = get_title(slide)
if not title:
continue
if title not in agenda:
agenda.append(title)
return agenda
def get_title(slide):
"Get the title of a slide"
match = re.match(
r"^(class: .*\n+){0,1}#\s+(?P<title>.*)$", slide, flags=re.MULTILINE
)
if match:
title = match.group("title").strip()
return title
return None
def render_agenda(agenda: list) -> str:
"Render slides meant for an agenda"
if not agenda:
# Avoid having an empty slide.
return (
"Unable to detect agenda. "
"Please add at least one first-level heading (`# Title`) "
"or remove the `{{ agenda }}` tag from your slides."
)
slide_count = ceil(len(agenda) / POINTS_PER_AGENDA_SLIDE)
agenda_points_per_slide = chunks(agenda, POINTS_PER_AGENDA_SLIDE)
counter_template = "{index}/{count}"
filled_agenda = []
for index, agenda_points in enumerate(agenda_points_per_slide):
if slide_count < 2:
count = ""
else:
count = counter_template.format(index=index + 1, count=slide_count)
topics = ["- %s" % t for t in agenda_points if t is not None]
points = "\n".join(topics)
formatted_slide = AGENDA_TEMPLATE.format(counter=count, points=points)
filled_agenda.append(formatted_slide)
return SLIDE_DIVIDER.join(filled_agenda)
def chunks(iterable, count):
"Collect data into fixed-length chunks or blocks"
# chunks('ABCDEFG', 3) --> ABC DEF Gxx"
args = [iter(iterable)] * count
return zip_longest(*args)
def render_metadata(slides: str, metadata: dict) -> str:
"Fill the placeholders in a slide with real values"
rendered = slides.replace("{{ title }}", metadata["meta"]["title"])
customer = metadata["meta"].get("customer", "")
rendered = rendered.replace("{{ customer }}", customer)
rendered = rendered.replace("{{ ratio }}", metadata["layout"]["ratio"])
if customer:
slide_format = f"%current% | %total% - KOPIE: {customer}"
else:
slide_format = "%current% | %total%"
rendered = rendered.replace("{{ slideNumberFormat }}", slide_format)
return rendered
def write_file(filename, content):
"Write the `content` to `filename`"
with codecs.open(filename, "w", "utf-8") as file_:
file_.write(content)
if __name__ == "__main__":
main()