chgksuite 0.25.1__py3-none-any.whl → 0.26.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chgksuite/cli.py +292 -31
- chgksuite/composer/composer_common.py +2 -0
- chgksuite/composer/docx.py +520 -292
- chgksuite/composer/pptx.py +16 -4
- chgksuite/composer/telegram.py +68 -46
- chgksuite/handouter/__init__.py +0 -0
- chgksuite/handouter/gen.py +143 -0
- chgksuite/handouter/installer.py +245 -0
- chgksuite/handouter/pack.py +79 -0
- chgksuite/handouter/runner.py +237 -0
- chgksuite/handouter/tex_internals.py +47 -0
- chgksuite/handouter/utils.py +88 -0
- chgksuite/parser.py +210 -17
- chgksuite/resources/regexes_by.json +1 -1
- chgksuite/resources/regexes_en.json +1 -1
- chgksuite/resources/regexes_kz_cyr.json +1 -1
- chgksuite/resources/regexes_ru.json +2 -2
- chgksuite/resources/regexes_sr.json +1 -1
- chgksuite/resources/regexes_ua.json +1 -1
- chgksuite/resources/regexes_uz_cyr.json +1 -1
- chgksuite/version.py +1 -1
- {chgksuite-0.25.1.dist-info → chgksuite-0.26.0.dist-info}/METADATA +4 -2
- {chgksuite-0.25.1.dist-info → chgksuite-0.26.0.dist-info}/RECORD +27 -21
- {chgksuite-0.25.1.dist-info → chgksuite-0.26.0.dist-info}/WHEEL +1 -1
- chgksuite/resources/template_shorin.pptx +0 -0
- {chgksuite-0.25.1.dist-info → chgksuite-0.26.0.dist-info}/entry_points.txt +0 -0
- {chgksuite-0.25.1.dist-info → chgksuite-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {chgksuite-0.25.1.dist-info → chgksuite-0.26.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from pypdf import PdfWriter
|
|
8
|
+
|
|
9
|
+
from chgksuite.handouter.utils import parse_handouts
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_hndt(fullpath, args):
|
|
13
|
+
spargs = ["hndt"]
|
|
14
|
+
if args.font:
|
|
15
|
+
spargs.extend(["--font", args.font])
|
|
16
|
+
spargs.append(fullpath)
|
|
17
|
+
proc = subprocess.run(spargs, cwd=args.folder, check=True, capture_output=True)
|
|
18
|
+
ns = globals()
|
|
19
|
+
ns.update(locals())
|
|
20
|
+
lines = [line for line in proc.stdout.decode("utf8").split("\n") if line]
|
|
21
|
+
return lines[-1].split("Output file:")[1].strip()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def pdf_output(pages, filename):
|
|
25
|
+
print(f"merging to {filename}, total pages {len(pages)}...")
|
|
26
|
+
merger = PdfWriter()
|
|
27
|
+
|
|
28
|
+
for pdf in pages:
|
|
29
|
+
merger.append(pdf)
|
|
30
|
+
|
|
31
|
+
merger.write(filename)
|
|
32
|
+
merger.close()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def pack_handouts(args):
|
|
36
|
+
if not args.folder:
|
|
37
|
+
args.folder = os.getcwd()
|
|
38
|
+
args.folder = os.path.abspath(args.folder)
|
|
39
|
+
|
|
40
|
+
color_pages = []
|
|
41
|
+
bw_pages = []
|
|
42
|
+
|
|
43
|
+
for fn in sorted(os.listdir(args.folder)):
|
|
44
|
+
if not fn.endswith(".txt"):
|
|
45
|
+
continue
|
|
46
|
+
fullpath = os.path.join(args.folder, fn)
|
|
47
|
+
with open(fullpath, encoding="utf8") as f:
|
|
48
|
+
contents = f.read()
|
|
49
|
+
parsed = parse_handouts(contents)
|
|
50
|
+
if len(parsed) > 1:
|
|
51
|
+
print(f"skipping {fn}: more than one handout per txt is not supported")
|
|
52
|
+
continue
|
|
53
|
+
color = parsed[0].get("color") or 0
|
|
54
|
+
handouts_per_team = parsed[0].get("handouts_per_team") or 3
|
|
55
|
+
total_handouts_per_page = parsed[0]["columns"] * parsed[0]["rows"]
|
|
56
|
+
teams_per_page = total_handouts_per_page / handouts_per_team
|
|
57
|
+
pages = math.ceil((args.n_teams + 1) / teams_per_page)
|
|
58
|
+
print(f"processing {fn}")
|
|
59
|
+
print(f"color = {color}")
|
|
60
|
+
print(f"handouts_per_team = {handouts_per_team}")
|
|
61
|
+
print(f"total_handouts_per_page = {total_handouts_per_page}")
|
|
62
|
+
print(f"teams_per_page = {round(teams_per_page, 1)}")
|
|
63
|
+
print(f"pages = {pages}")
|
|
64
|
+
print("running hndt...")
|
|
65
|
+
output_file = run_hndt(fullpath, args)
|
|
66
|
+
if color:
|
|
67
|
+
color_pages += [output_file] * pages
|
|
68
|
+
else:
|
|
69
|
+
bw_pages += [output_file] * pages
|
|
70
|
+
if color_pages:
|
|
71
|
+
pdf_output(
|
|
72
|
+
color_pages,
|
|
73
|
+
os.path.join(args.folder, args.output_filename_prefix + "_color.pdf"),
|
|
74
|
+
)
|
|
75
|
+
if bw_pages:
|
|
76
|
+
pdf_output(
|
|
77
|
+
bw_pages,
|
|
78
|
+
os.path.join(args.folder, args.output_filename_prefix + "_bw.pdf"),
|
|
79
|
+
)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from watchdog.events import FileSystemEventHandler
|
|
9
|
+
from watchdog.observers import Observer
|
|
10
|
+
|
|
11
|
+
from chgksuite.handouter.gen import generate_handouts
|
|
12
|
+
from chgksuite.handouter.pack import pack_handouts
|
|
13
|
+
from chgksuite.handouter.installer import get_tectonic_path, install_tectonic
|
|
14
|
+
from chgksuite.handouter.tex_internals import (
|
|
15
|
+
GREYTEXT,
|
|
16
|
+
GREYTEXT_LANGS,
|
|
17
|
+
HEADER,
|
|
18
|
+
IMG,
|
|
19
|
+
IMGWIDTH,
|
|
20
|
+
TIKZBOX_END,
|
|
21
|
+
TIKZBOX_INNER,
|
|
22
|
+
TIKZBOX_START,
|
|
23
|
+
)
|
|
24
|
+
from chgksuite.handouter.utils import parse_handouts, read_file, replace_ext, write_file
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HandoutGenerator:
|
|
28
|
+
SPACE = 1.5 # mm
|
|
29
|
+
|
|
30
|
+
def __init__(self, args):
|
|
31
|
+
self.args = args
|
|
32
|
+
self.blocks = [self.get_header()]
|
|
33
|
+
|
|
34
|
+
def get_header(self):
|
|
35
|
+
header = HEADER
|
|
36
|
+
header = (
|
|
37
|
+
header.replace("<PAPERWIDTH>", str(self.args.paperwidth))
|
|
38
|
+
.replace("<PAPERHEIGHT>", str(self.args.paperheight))
|
|
39
|
+
.replace("<MARGIN_LEFT>", str(self.args.margin_left))
|
|
40
|
+
.replace("<MARGIN_RIGHT>", str(self.args.margin_right))
|
|
41
|
+
.replace("<MARGIN_TOP>", str(self.args.margin_top))
|
|
42
|
+
.replace("<MARGIN_BOTTOM>", str(self.args.margin_bottom))
|
|
43
|
+
.replace("<TIKZ_MM>", str(self.args.tikz_mm))
|
|
44
|
+
)
|
|
45
|
+
if self.args.font:
|
|
46
|
+
header = header.replace("Arial", self.args.font)
|
|
47
|
+
return header
|
|
48
|
+
|
|
49
|
+
def parse_input(self, filepath):
|
|
50
|
+
contents = read_file(filepath)
|
|
51
|
+
return parse_handouts(contents)
|
|
52
|
+
|
|
53
|
+
def generate_for_question(self, question_num):
|
|
54
|
+
return GREYTEXT.replace(
|
|
55
|
+
"<GREYTEXT>", GREYTEXT_LANGS[self.args.lang].format(question_num)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def make_tikzbox(self, block):
|
|
59
|
+
if block.get("no_center"):
|
|
60
|
+
align = ""
|
|
61
|
+
else:
|
|
62
|
+
align = ", align=center"
|
|
63
|
+
textwidth = ", text width=\\boxwidthinner"
|
|
64
|
+
fs = block.get("font_size") or self.args.font_size
|
|
65
|
+
fontsize = "\\fontsize{FSpt}{LHpt}\\selectfont ".replace("FS", str(fs)).replace(
|
|
66
|
+
"LH", str(round(fs * 1.2, 1))
|
|
67
|
+
)
|
|
68
|
+
contents = block["contents"]
|
|
69
|
+
if block.get("font_family"):
|
|
70
|
+
contents = "\\fontspec{" + block["font_family"] + "}" + contents
|
|
71
|
+
return (
|
|
72
|
+
TIKZBOX_INNER.replace("<CONTENTS>", contents)
|
|
73
|
+
.replace("<ALIGN>", align)
|
|
74
|
+
.replace("<TEXTWIDTH>", textwidth)
|
|
75
|
+
.replace("<FONTSIZE>", fontsize)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def get_page_width(self):
|
|
79
|
+
return self.args.paperwidth - self.args.margin_left - self.args.margin_right - 2
|
|
80
|
+
|
|
81
|
+
def generate_regular_block(self, block_):
|
|
82
|
+
block = block_.copy()
|
|
83
|
+
if not (block.get("image") or block.get("text")):
|
|
84
|
+
return
|
|
85
|
+
columns = block["columns"]
|
|
86
|
+
spaces = block["columns"] - 1
|
|
87
|
+
boxwidth = self.args.boxwidth or round(
|
|
88
|
+
(self.get_page_width() - spaces * self.SPACE) / block["columns"],
|
|
89
|
+
3,
|
|
90
|
+
)
|
|
91
|
+
total_width = boxwidth * columns + spaces * self.SPACE
|
|
92
|
+
if self.args.debug:
|
|
93
|
+
print(
|
|
94
|
+
f"columns: {columns}, boxwidth: {boxwidth}, total width: {total_width}"
|
|
95
|
+
)
|
|
96
|
+
boxwidthinner = self.args.boxwidthinner or (boxwidth - 2 * self.args.tikz_mm)
|
|
97
|
+
header = [
|
|
98
|
+
r"\setlength{\boxwidth}{<Q>mm}%".replace("<Q>", str(boxwidth)),
|
|
99
|
+
r"\setlength{\boxwidthinner}{<Q>mm}%".replace("<Q>", str(boxwidthinner)),
|
|
100
|
+
]
|
|
101
|
+
rows = []
|
|
102
|
+
contents = []
|
|
103
|
+
if block.get("image"):
|
|
104
|
+
img_qwidth = block.get("resize_image") or 1.0
|
|
105
|
+
imgwidth = IMGWIDTH.replace("<QWIDTH>", str(img_qwidth))
|
|
106
|
+
contents.append(
|
|
107
|
+
IMG.replace("<IMGPATH>", block["image"]).replace("<IMGWIDTH>", imgwidth)
|
|
108
|
+
)
|
|
109
|
+
if block.get("text"):
|
|
110
|
+
contents.append(block["text"])
|
|
111
|
+
block["contents"] = "\\linebreak\n".join(contents)
|
|
112
|
+
if block.get("no_center"):
|
|
113
|
+
block["centering"] = ""
|
|
114
|
+
else:
|
|
115
|
+
block["centering"] = "\\centering"
|
|
116
|
+
for _ in range(block.get("rows") or 1):
|
|
117
|
+
row = (
|
|
118
|
+
TIKZBOX_START.replace("<CENTERING>", block["centering"])
|
|
119
|
+
+ "\n".join([self.make_tikzbox(block)] * block["columns"])
|
|
120
|
+
+ TIKZBOX_END
|
|
121
|
+
)
|
|
122
|
+
rows.append(row)
|
|
123
|
+
return "\n".join(header) + "\n" + "\n\n\\vspace{1mm}\n\n".join(rows)
|
|
124
|
+
|
|
125
|
+
def generate(self):
|
|
126
|
+
for block in self.parse_input(self.args.filename):
|
|
127
|
+
if not block:
|
|
128
|
+
self.blocks.append("\n\\clearpage\n")
|
|
129
|
+
continue
|
|
130
|
+
if self.args.debug:
|
|
131
|
+
print(block)
|
|
132
|
+
if block.get("for_question"):
|
|
133
|
+
self.blocks.append(self.generate_for_question(block["for_question"]))
|
|
134
|
+
if block.get("columns"):
|
|
135
|
+
block = self.generate_regular_block(block)
|
|
136
|
+
if block:
|
|
137
|
+
self.blocks.append(block)
|
|
138
|
+
self.blocks.append("\\end{document}")
|
|
139
|
+
return "\n\n".join(self.blocks)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def process_file(args, file_dir, bn):
|
|
143
|
+
tex_contents = HandoutGenerator(args).generate()
|
|
144
|
+
tex_path = os.path.join(file_dir, f"{bn}_{args.lang}.tex")
|
|
145
|
+
write_file(tex_path, tex_contents)
|
|
146
|
+
|
|
147
|
+
tectonic_path = get_tectonic_path()
|
|
148
|
+
if not tectonic_path:
|
|
149
|
+
print("tectonic is not present, installing it...")
|
|
150
|
+
install_tectonic(args)
|
|
151
|
+
tectonic_path = get_tectonic_path()
|
|
152
|
+
if not tectonic_path:
|
|
153
|
+
raise Exception("tectonic couldn't be installed successfully :(")
|
|
154
|
+
if args.debug:
|
|
155
|
+
print(f"tectonic found at `{tectonic_path}`")
|
|
156
|
+
|
|
157
|
+
subprocess.run(
|
|
158
|
+
[tectonic_path, os.path.basename(tex_path)], check=True, cwd=file_dir
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
output_file = replace_ext(tex_path, "pdf")
|
|
162
|
+
|
|
163
|
+
if args.compress:
|
|
164
|
+
print(f"compressing {output_file}")
|
|
165
|
+
size_before = round(os.stat(output_file).st_size / 1024)
|
|
166
|
+
output_file_compressed = output_file[:-4] + ".compressed.pdf"
|
|
167
|
+
subprocess.run(
|
|
168
|
+
[
|
|
169
|
+
"gs",
|
|
170
|
+
"-sDEVICE=pdfwrite",
|
|
171
|
+
"-dCompatibilityLevel=1.5",
|
|
172
|
+
f"-dPDFSETTINGS=/{args.pdfsettings}",
|
|
173
|
+
"-dNOPAUSE",
|
|
174
|
+
"-dQUIET",
|
|
175
|
+
"-dBATCH",
|
|
176
|
+
f"-sOutputFile={output_file_compressed}",
|
|
177
|
+
output_file,
|
|
178
|
+
],
|
|
179
|
+
check=True,
|
|
180
|
+
)
|
|
181
|
+
shutil.move(output_file_compressed, output_file)
|
|
182
|
+
size_after = round(os.stat(output_file).st_size / 1024)
|
|
183
|
+
q = round(size_after / size_before, 1)
|
|
184
|
+
print(f"before: {size_before}kb, after: {size_after}kb, compression: {q}")
|
|
185
|
+
|
|
186
|
+
print(f"Output file: {output_file}")
|
|
187
|
+
|
|
188
|
+
if not args.debug:
|
|
189
|
+
os.remove(tex_path)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class FileChangeHandler(FileSystemEventHandler):
|
|
193
|
+
def __init__(self, args, file_dir, bn):
|
|
194
|
+
self.args = args
|
|
195
|
+
self.file_dir = file_dir
|
|
196
|
+
self.bn = bn
|
|
197
|
+
self.last_processed = 0
|
|
198
|
+
|
|
199
|
+
def on_modified(self, event):
|
|
200
|
+
if event.src_path == os.path.abspath(self.args.filename):
|
|
201
|
+
# Debounce to avoid processing the same change multiple times
|
|
202
|
+
current_time = time.time()
|
|
203
|
+
if current_time - self.last_processed > 1:
|
|
204
|
+
print(f"File {self.args.filename} changed, regenerating PDF...")
|
|
205
|
+
process_file(self.args, self.file_dir, self.bn)
|
|
206
|
+
self.last_processed = current_time
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def run_handouter(args):
|
|
210
|
+
file_dir = os.path.dirname(os.path.abspath(args.filename))
|
|
211
|
+
bn, _ = os.path.splitext(os.path.basename(args.filename))
|
|
212
|
+
|
|
213
|
+
process_file(args, file_dir, bn)
|
|
214
|
+
|
|
215
|
+
if args.watch:
|
|
216
|
+
print(f"Watching {args.filename} for changes. Press Ctrl+C to stop.")
|
|
217
|
+
event_handler = FileChangeHandler(args, file_dir, bn)
|
|
218
|
+
observer = Observer()
|
|
219
|
+
observer.schedule(event_handler, path=file_dir, recursive=False)
|
|
220
|
+
observer.start()
|
|
221
|
+
try:
|
|
222
|
+
while True:
|
|
223
|
+
time.sleep(1)
|
|
224
|
+
except KeyboardInterrupt:
|
|
225
|
+
observer.stop()
|
|
226
|
+
observer.join()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def gui_handouter(args):
|
|
230
|
+
if args.handoutssubcommand == "run":
|
|
231
|
+
run_handouter(args)
|
|
232
|
+
elif args.handoutssubcommand == "generate":
|
|
233
|
+
generate_handouts(args)
|
|
234
|
+
elif args.handoutssubcommand == "pack":
|
|
235
|
+
pack_handouts(args)
|
|
236
|
+
elif args.handoutssubcommand == "install":
|
|
237
|
+
install_tectonic(args)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
HEADER = r"""
|
|
2
|
+
\documentclass{minimal}
|
|
3
|
+
\usepackage[paperwidth=<PAPERWIDTH>mm,paperheight=<PAPERHEIGHT>mm,top=<MARGIN_TOP>mm,bottom=<MARGIN_BOTTOM>mm,left=<MARGIN_LEFT>mm,right=<MARGIN_RIGHT>mm]{geometry}
|
|
4
|
+
\frenchspacing
|
|
5
|
+
\usepackage{fontspec}
|
|
6
|
+
\usepackage{xcolor}
|
|
7
|
+
\usepackage{tikz}
|
|
8
|
+
\usepackage{calc}
|
|
9
|
+
\usepackage[document]{ragged2e}
|
|
10
|
+
\setmainfont{Arial}
|
|
11
|
+
\newlength{\boxwidth}
|
|
12
|
+
\newlength{\boxwidthinner}
|
|
13
|
+
\begin{document}
|
|
14
|
+
\fontsize{14pt}{16pt}\selectfont
|
|
15
|
+
\setlength\parindent{0pt}
|
|
16
|
+
\tikzstyle{box}=[draw, dashed, rectangle, inner sep=<TIKZ_MM>mm]
|
|
17
|
+
\raggedright
|
|
18
|
+
\raggedbottom
|
|
19
|
+
""".strip()
|
|
20
|
+
|
|
21
|
+
GREYTEXT = r"""{\fontsize{9pt}{11pt}\selectfont \textcolor{gray}{<GREYTEXT>}}"""
|
|
22
|
+
|
|
23
|
+
GREYTEXT_LANGS = {
|
|
24
|
+
"by": "Да пытаньня {}",
|
|
25
|
+
"en": "Handout for question {}",
|
|
26
|
+
"kz": "{}-сұрақтың үлестіру материалы",
|
|
27
|
+
"ro": "Material care urmează a fi distribuit pentru întrebarea {}",
|
|
28
|
+
"ru": "К вопросу {}",
|
|
29
|
+
"sr": "Materijal za deljenje uz pitanje {}",
|
|
30
|
+
"ua": "До запитання {}",
|
|
31
|
+
"uz": "{} саволга тарқатма материал",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
TIKZBOX_START = r"""{<CENTERING>
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
TIKZBOX_INNER = r"""
|
|
38
|
+
\begin{tikzpicture}
|
|
39
|
+
\node[box, minimum width=\boxwidth<TEXTWIDTH><ALIGN>] {<FONTSIZE><CONTENTS>\par};
|
|
40
|
+
\end{tikzpicture}
|
|
41
|
+
""".strip()
|
|
42
|
+
|
|
43
|
+
TIKZBOX_END = "\n}"
|
|
44
|
+
|
|
45
|
+
IMG = r"""\includegraphics<IMGWIDTH>{<IMGPATH>}"""
|
|
46
|
+
|
|
47
|
+
IMGWIDTH = r"[width=<QWIDTH>\textwidth]"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from chgksuite.handouter.installer import escape_latex
|
|
4
|
+
|
|
5
|
+
RESERVED_WORDS = [
|
|
6
|
+
"image",
|
|
7
|
+
"for_question",
|
|
8
|
+
"columns",
|
|
9
|
+
"rows",
|
|
10
|
+
"resize_image",
|
|
11
|
+
"font_size",
|
|
12
|
+
"font_family",
|
|
13
|
+
"no_center",
|
|
14
|
+
"raw_tex",
|
|
15
|
+
"color",
|
|
16
|
+
"handouts_per_team",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def read_file(filepath):
|
|
21
|
+
with open(filepath, "r", encoding="utf8") as f:
|
|
22
|
+
contents = f.read()
|
|
23
|
+
return contents
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def write_file(filepath, contents):
|
|
27
|
+
with open(filepath, "w", encoding="utf8") as f:
|
|
28
|
+
f.write(contents)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def replace_ext(filepath, new_ext):
|
|
32
|
+
if not new_ext.startswith("."):
|
|
33
|
+
new_ext = "." + new_ext
|
|
34
|
+
dirname = os.path.dirname(filepath)
|
|
35
|
+
basename = os.path.basename(filepath)
|
|
36
|
+
base, _ = os.path.splitext(basename)
|
|
37
|
+
return os.path.join(dirname, base + new_ext)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def wrap_val(key, val):
|
|
41
|
+
if key in ("columns", "rows", "no_center", "color", "handouts_per_team"):
|
|
42
|
+
return int(val.strip())
|
|
43
|
+
if key in ("resize_image", "font_size"):
|
|
44
|
+
return float(val.strip())
|
|
45
|
+
return val.strip()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def split_array_by_value(arr, delimiter):
|
|
49
|
+
result = []
|
|
50
|
+
current_subarray = []
|
|
51
|
+
for item in arr:
|
|
52
|
+
if item == delimiter:
|
|
53
|
+
result.append(current_subarray)
|
|
54
|
+
current_subarray = []
|
|
55
|
+
else:
|
|
56
|
+
current_subarray.append(item)
|
|
57
|
+
result.append(current_subarray)
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def split_blocks(contents):
|
|
62
|
+
lines = contents.split("\n")
|
|
63
|
+
sp = ["\n".join(x) for x in split_array_by_value(lines, "---")]
|
|
64
|
+
if not sp[0].strip():
|
|
65
|
+
sp = sp[1:]
|
|
66
|
+
return sp
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_handouts(contents):
|
|
70
|
+
blocks = split_blocks(contents)
|
|
71
|
+
result = []
|
|
72
|
+
for block_ in blocks:
|
|
73
|
+
block = block_.strip()
|
|
74
|
+
block_dict = {}
|
|
75
|
+
text = []
|
|
76
|
+
lines = block.split("\n")
|
|
77
|
+
for line in lines:
|
|
78
|
+
sp = line.split(":", 1)
|
|
79
|
+
if sp[0] in RESERVED_WORDS:
|
|
80
|
+
block_dict[sp[0]] = wrap_val(sp[0], sp[1])
|
|
81
|
+
elif line.strip():
|
|
82
|
+
text.append(line.strip())
|
|
83
|
+
if text:
|
|
84
|
+
block_dict["text"] = "\n".join(text).strip()
|
|
85
|
+
if not block_dict.get("raw_tex"):
|
|
86
|
+
block_dict["text"] = escape_latex(block_dict["text"])
|
|
87
|
+
result.append(block_dict)
|
|
88
|
+
return result
|