-
Notifications
You must be signed in to change notification settings - Fork 35
/
build.py
294 lines (262 loc) · 9.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
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
"""
This file is used to build the `README.md` file from all markdown files in the current directory.
It also converts the markdown files to a format that can be used by the `mdbook` tool, which we
use to generate the website.
"""
import os
import re
from pathlib import Path
def _create_pretty_warning_box(msg):
return f"""
<table style="width: 100%; border: 1px solid black;">
<tr>
<td>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 10%;">
<img src="https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/images/warning_platypus.webp" alt="Description of image" style="width: 100%;">
</div>
<div style="width: 90%;">
{msg}
</div>
</div>
</td>
</tr>
</table>
"""
def _create_tip_box(msg):
return f"""
<table style="width: 100%; border: 1px solid black;">
<tr>
<td>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 10%;">
<img src="https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/images/idea_platypus.webp" alt="Description of image" style="width: 100%;">
</div>
<div style="width: 90%;">
{msg}
</div>
</div>
</td>
</tr>
</table>
"""
def _create_info_box(msg):
return f"""
<table style="width: 100%; border: 1px solid black;">
<tr>
<td>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 10%;">
<img src="https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/images/info_platypus.webp" alt="Description of image" style="width: 100%;">
</div>
<div style="width: 90%;">
{msg}
</div>
</div>
</td>
</tr>
</table>
"""
def replace_warning_boxes(content):
"""
A warning box starts with `> :warning:` and ends with a line that does not start with `>`.
"""
lines = content.split("\n")
new_content = ""
collect_warning = False
warning_msg = ""
for line in lines:
if line.startswith("> :warning:"):
collect_warning = True
warning_msg += line[len("> :warning:") :] + "\n"
elif line.startswith("> [!WARNING]"):
collect_warning = True
warning_msg += line[len("> [!WARNING]") :] + "\n"
elif collect_warning:
if line == ">":
continue
if line.startswith("> "):
warning_msg += line[len("> ") :] + "\n"
else:
new_content += _create_pretty_warning_box(warning_msg)
new_content += "\n"
collect_warning = False
warning_msg = ""
new_content += line + "\n"
else:
new_content += line + "\n"
return new_content
def replace_tip_boxes(content):
"""
A tip box starts with `> [!TIP]` and ends with a line that does not start with `>`.
"""
lines = content.split("\n")
new_content = ""
collect_tip = False
tip_msg = ""
for line in lines:
if line.startswith("> [!TIP]"):
collect_tip = True
tip_msg += line[len("> [!TIP]") :] + "\n"
elif collect_tip:
if line == ">":
continue
if line.startswith("> "):
tip_msg += line[len("> ") :] + "\n"
else:
new_content += _create_tip_box(tip_msg)
new_content += "\n"
collect_tip = False
tip_msg = ""
new_content += line + "\n"
else:
new_content += line + "\n"
return new_content
def replace_info_boxes(content):
"""
An info box starts with `> [!NOTE]` and ends with a line that does not start with `>`.
"""
lines = content.split("\n")
new_content = ""
collect_info = False
info_msg = ""
for line in lines:
if line.startswith("> [!NOTE]"):
collect_info = True
info_msg += line[len("> [!NOTE]") :] + "\n"
elif collect_info:
if line == ">":
continue
if line.startswith("> "):
info_msg += line[len("> ") :] + "\n"
else:
new_content += _create_info_box(info_msg)
new_content += "\n"
collect_info = False
info_msg = ""
new_content += line + "\n"
else:
new_content += line + "\n"
return new_content
def convert_for_mdbook(content):
footer = """
---
*The CP-SAT Primer is authored by [Dominik Krupke](https://github.com/d-krupke) at [TU Braunschweig, Algorithms Division](https://www.ibr.cs.tu-bs.de/alg/index.html). It is licensed under the [CC-BY-4.0 license](https://creativecommons.org/licenses/by/4.0/).*
*The primer is written for educational purposes and may be incomplete or incorrect in places. If you find this primer helpful, please star the [GitHub repository](https://github.com/d-krupke/cpsat-primer/), to let me know with a single click that it has made an impact. As an academic, I also enjoy hearing about how you use CP-SAT to solve real-world problems.*
"""
content = (
"<!-- This file was generated by the `build.py` script. Do not edit it manually. -->\n"
+ content
)
# replace all inline math `$...$` with `\\( ... \\)` using regex.
# always use the smallest possible match for the `...` part.
content = re.sub(r"\$(.*?)\$", r"\\\\( \1 \\\\)", content)
# replace all math modes "```math ... ```" with `\\[ ... \\]` using regex.
# always use the smallest possible match for the `...` part.
content = re.sub(r"```math(.*?)```", r"\\\\[ \1 \\\\]", content, flags=re.DOTALL)
content = replace_warning_boxes(content)
content = replace_tip_boxes(content)
content = replace_info_boxes(content)
# replace all `:warning:` with the unicode character for a warning sign.
content = content.replace(":warning:", "⚠️")
# replace all anchor links `(#01-installation)` by `(./01_installation.md)`.
# you have to replace the `#` with `./` and `-` with `_`, and attach `.md` at the end.
def replace_relative(match):
md_path = match.group(1).replace("-", "_") + ".md"
all_md_files = [f for f in os.listdir() if f.endswith(".md")]
for file in all_md_files:
if file.endswith(md_path):
md_path = file
break
return f"(./{md_path})" if Path(md_path).exists() else f"(#{match.group(1)})"
content = re.sub(
r"\(#(.*?)\)",
replace_relative,
content,
)
# replace in all links that lead to a .png file the `github.com` with `raw.githubusercontent.com`.
content = re.sub(
r"\((.*?\.png)\)",
lambda match: match.group(0).replace(
"https://github.com/d-krupke/cpsat-primer/blob/main/",
"https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/",
),
content,
)
content = re.sub(
r"\((.*?\.gif)\)",
lambda match: match.group(0).replace(
"https://github.com/d-krupke/cpsat-primer/blob/main/",
"https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/",
),
content,
)
content = re.sub(
r"\((.*?\.jpg)\)",
lambda match: match.group(0).replace(
"https://github.com/d-krupke/cpsat-primer/blob/main/",
"https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/",
),
content,
)
content = re.sub(
r"\((.*?\.webp)\)",
lambda match: match.group(0).replace(
"https://github.com/d-krupke/cpsat-primer/blob/main/",
"https://raw.githubusercontent.com/d-krupke/cpsat-primer/main/",
),
content,
)
content += footer
return content
def convert_for_readme(content: str) -> str:
# If we have a `<!-- START_SKIP_FOR_README -->` and `<!-- STOP_SKIP_FOR_README -->` in the content, we skip this part.
# These will be single lines, so we can use `splitlines()` to get the lines and then check if we should skip after having
# removed all whitespaces which can be created by the formatting.
lines = content.splitlines()
skip = False
new_lines = []
for line in lines:
if "<!-- START_SKIP_FOR_README -->" in line:
skip = True
if not skip:
new_lines.append(line)
if "<!-- STOP_SKIP_FOR_README -->" in line:
skip = False
content = "\n".join(new_lines)
return content
if __name__ == "__main__":
# get all markdown files that start with a number
# [f for f in os.listdir() if f.endswith(".md") and f[0].isdigit()]
markdown_files = [
"00_intro.md",
"01_installation.md",
"02_example.md",
"04_modelling.md",
"04B_advanced_modelling.md",
"05_parameters.md",
"understanding_the_log.md",
"07_under_the_hood.md",
"03_big_picture.md",
"06_coding_patterns.md",
"building_an_optimization_api.md",
"08_benchmarking.md",
"09_lns.md",
]
# concat them and write them to `README.md`
with open("README.md", "w") as f:
f.write(
"*A book-style version of this primer is available at [https://d-krupke.github.io/cpsat-primer/](https://d-krupke.github.io/cpsat-primer/).*\n\n"
)
disclaimer = "<!-- This file was generated by the `build.py` script. Do not edit it manually. -->\n"
for file in markdown_files:
print(f"Adding {file} to README.md")
with open(file, "r") as current_file:
content = current_file.read()
f.write(disclaimer)
f.write(f"<!-- {file} -->\n")
f.write(convert_for_readme(content))
f.write("\n\n")
Path("./.mdbook/").mkdir(parents=True, exist_ok=True)
with open(Path("./.mdbook/") / file, "w") as book_file:
book_file.write(convert_for_mdbook(content))