chgksuite 0.26.0b11__py3-none-any.whl → 0.27.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/_html2md.py +90 -0
- chgksuite/cli.py +38 -8
- chgksuite/common.py +16 -12
- chgksuite/composer/__init__.py +9 -7
- chgksuite/composer/chgksuite_parser.py +20 -9
- chgksuite/composer/composer_common.py +30 -3
- chgksuite/composer/db.py +1 -2
- chgksuite/composer/docx.py +542 -292
- chgksuite/composer/latex.py +3 -4
- chgksuite/composer/lj.py +1 -2
- chgksuite/composer/{reddit.py → markdown.py} +35 -25
- chgksuite/composer/openquiz.py +2 -3
- chgksuite/composer/pptx.py +18 -6
- chgksuite/composer/telegram.py +22 -10
- chgksuite/handouter/gen.py +11 -7
- chgksuite/handouter/installer.py +0 -0
- chgksuite/handouter/runner.py +237 -10
- chgksuite/handouter/tex_internals.py +12 -13
- chgksuite/handouter/utils.py +22 -1
- chgksuite/lastdir +1 -0
- chgksuite/parser.py +218 -37
- chgksuite/parser_db.py +4 -6
- chgksuite/resources/labels_az.toml +22 -0
- chgksuite/resources/labels_by.toml +1 -2
- chgksuite/resources/labels_by_tar.toml +1 -2
- chgksuite/resources/labels_en.toml +1 -2
- chgksuite/resources/labels_kz_cyr.toml +1 -2
- chgksuite/resources/labels_ru.toml +1 -2
- chgksuite/resources/labels_sr.toml +1 -2
- chgksuite/resources/labels_ua.toml +1 -2
- chgksuite/resources/labels_uz.toml +0 -3
- chgksuite/resources/labels_uz_cyr.toml +1 -2
- chgksuite/resources/regexes_az.json +17 -0
- chgksuite/resources/regexes_by.json +3 -2
- chgksuite/resources/regexes_by_tar.json +17 -0
- chgksuite/resources/regexes_en.json +3 -2
- chgksuite/resources/regexes_kz_cyr.json +3 -2
- chgksuite/resources/regexes_ru.json +3 -2
- chgksuite/resources/regexes_sr.json +3 -2
- chgksuite/resources/regexes_ua.json +3 -2
- chgksuite/resources/regexes_uz.json +16 -0
- chgksuite/resources/regexes_uz_cyr.json +3 -2
- chgksuite/trello.py +8 -9
- chgksuite/typotools.py +9 -8
- chgksuite/version.py +1 -1
- {chgksuite-0.26.0b11.dist-info → chgksuite-0.27.0.dist-info}/METADATA +10 -19
- chgksuite-0.27.0.dist-info/RECORD +63 -0
- {chgksuite-0.26.0b11.dist-info → chgksuite-0.27.0.dist-info}/WHEEL +1 -2
- chgksuite/composer/telegram_parser.py +0 -230
- chgksuite-0.26.0b11.dist-info/RECORD +0 -59
- chgksuite-0.26.0b11.dist-info/top_level.txt +0 -1
- {chgksuite-0.26.0b11.dist-info → chgksuite-0.27.0.dist-info}/entry_points.txt +0 -0
- {chgksuite-0.26.0b11.dist-info → chgksuite-0.27.0.dist-info}/licenses/LICENSE +0 -0
chgksuite/handouter/runner.py
CHANGED
|
@@ -5,15 +5,19 @@ import shutil
|
|
|
5
5
|
import subprocess
|
|
6
6
|
import time
|
|
7
7
|
|
|
8
|
+
import toml
|
|
8
9
|
from watchdog.events import FileSystemEventHandler
|
|
9
10
|
from watchdog.observers import Observer
|
|
10
11
|
|
|
12
|
+
from chgksuite.common import get_source_dirs
|
|
11
13
|
from chgksuite.handouter.gen import generate_handouts
|
|
12
14
|
from chgksuite.handouter.pack import pack_handouts
|
|
13
15
|
from chgksuite.handouter.installer import get_tectonic_path, install_tectonic
|
|
14
16
|
from chgksuite.handouter.tex_internals import (
|
|
17
|
+
EDGE_DASHED,
|
|
18
|
+
EDGE_NONE,
|
|
19
|
+
EDGE_SOLID,
|
|
15
20
|
GREYTEXT,
|
|
16
|
-
GREYTEXT_LANGS,
|
|
17
21
|
HEADER,
|
|
18
22
|
IMG,
|
|
19
23
|
IMGWIDTH,
|
|
@@ -29,6 +33,10 @@ class HandoutGenerator:
|
|
|
29
33
|
|
|
30
34
|
def __init__(self, args):
|
|
31
35
|
self.args = args
|
|
36
|
+
_, resourcedir = get_source_dirs()
|
|
37
|
+
self.labels = toml.loads(
|
|
38
|
+
read_file(os.path.join(resourcedir, f"labels_{args.language}.toml"))
|
|
39
|
+
)
|
|
32
40
|
self.blocks = [self.get_header()]
|
|
33
41
|
|
|
34
42
|
def get_header(self):
|
|
@@ -51,11 +59,33 @@ class HandoutGenerator:
|
|
|
51
59
|
return parse_handouts(contents)
|
|
52
60
|
|
|
53
61
|
def generate_for_question(self, question_num):
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
handout_text = self.labels["general"]["handout_for_question"].format(
|
|
63
|
+
question_num
|
|
56
64
|
)
|
|
65
|
+
return GREYTEXT.replace("<GREYTEXT>", handout_text)
|
|
66
|
+
|
|
67
|
+
def make_tikzbox(self, block, edges=None, ext=None):
|
|
68
|
+
"""
|
|
69
|
+
Create a TikZ box with configurable edge styles and extensions.
|
|
70
|
+
edges is a dict with keys 'top', 'bottom', 'left', 'right'
|
|
71
|
+
values are EDGE_DASHED or EDGE_SOLID
|
|
72
|
+
ext is a dict with edge extensions to close gaps at boundaries
|
|
73
|
+
"""
|
|
74
|
+
if edges is None:
|
|
75
|
+
edges = {
|
|
76
|
+
"top": EDGE_DASHED,
|
|
77
|
+
"bottom": EDGE_DASHED,
|
|
78
|
+
"left": EDGE_DASHED,
|
|
79
|
+
"right": EDGE_DASHED,
|
|
80
|
+
}
|
|
81
|
+
if ext is None:
|
|
82
|
+
ext = {
|
|
83
|
+
"top": ("0pt", "0pt"),
|
|
84
|
+
"bottom": ("0pt", "0pt"),
|
|
85
|
+
"left": ("0pt", "0pt"),
|
|
86
|
+
"right": ("0pt", "0pt"),
|
|
87
|
+
}
|
|
57
88
|
|
|
58
|
-
def make_tikzbox(self, block):
|
|
59
89
|
if block.get("no_center"):
|
|
60
90
|
align = ""
|
|
61
91
|
else:
|
|
@@ -73,19 +103,206 @@ class HandoutGenerator:
|
|
|
73
103
|
.replace("<ALIGN>", align)
|
|
74
104
|
.replace("<TEXTWIDTH>", textwidth)
|
|
75
105
|
.replace("<FONTSIZE>", fontsize)
|
|
106
|
+
.replace("<TOP>", edges["top"])
|
|
107
|
+
.replace("<BOTTOM>", edges["bottom"])
|
|
108
|
+
.replace("<LEFT>", edges["left"])
|
|
109
|
+
.replace("<RIGHT>", edges["right"])
|
|
110
|
+
.replace("<TOP_EXT_L>", ext["top"][0])
|
|
111
|
+
.replace("<TOP_EXT_R>", ext["top"][1])
|
|
112
|
+
.replace("<BOTTOM_EXT_L>", ext["bottom"][0])
|
|
113
|
+
.replace("<BOTTOM_EXT_R>", ext["bottom"][1])
|
|
114
|
+
.replace("<LEFT_EXT_T>", ext["left"][0])
|
|
115
|
+
.replace("<LEFT_EXT_B>", ext["left"][1])
|
|
116
|
+
.replace("<RIGHT_EXT_T>", ext["right"][0])
|
|
117
|
+
.replace("<RIGHT_EXT_B>", ext["right"][1])
|
|
76
118
|
)
|
|
77
119
|
|
|
78
120
|
def get_page_width(self):
|
|
79
121
|
return self.args.paperwidth - self.args.margin_left - self.args.margin_right - 2
|
|
80
122
|
|
|
123
|
+
def get_cut_direction(self, columns, num_rows, handouts_per_team):
|
|
124
|
+
"""
|
|
125
|
+
Determine whether to cut vertically or horizontally.
|
|
126
|
+
Returns (direction, team_size) where:
|
|
127
|
+
- direction is 'vertical', 'horizontal', or None
|
|
128
|
+
- team_size is the number of columns (vertical) or rows (horizontal) per team
|
|
129
|
+
|
|
130
|
+
Falls back to None if handouts can't be evenly divided into teams.
|
|
131
|
+
"""
|
|
132
|
+
total = columns * num_rows
|
|
133
|
+
|
|
134
|
+
# Check if total handouts can be evenly divided
|
|
135
|
+
if total % handouts_per_team != 0:
|
|
136
|
+
return None, None
|
|
137
|
+
|
|
138
|
+
num_teams = total // handouts_per_team
|
|
139
|
+
if num_teams < 2:
|
|
140
|
+
return None, None # Only 1 team, no cuts needed
|
|
141
|
+
|
|
142
|
+
# Try vertical layout (teams as column groups)
|
|
143
|
+
# Each team gets team_cols columns × all rows
|
|
144
|
+
if handouts_per_team % num_rows == 0:
|
|
145
|
+
team_cols = handouts_per_team // num_rows
|
|
146
|
+
if columns % team_cols == 0:
|
|
147
|
+
return "vertical", team_cols
|
|
148
|
+
|
|
149
|
+
# Try horizontal layout (teams as row groups)
|
|
150
|
+
# Each team gets all columns × team_rows rows
|
|
151
|
+
if handouts_per_team % columns == 0:
|
|
152
|
+
team_rows = handouts_per_team // columns
|
|
153
|
+
if num_rows % team_rows == 0:
|
|
154
|
+
return "horizontal", team_rows
|
|
155
|
+
|
|
156
|
+
return None, None
|
|
157
|
+
|
|
158
|
+
def get_edge_styles(
|
|
159
|
+
self, row_idx, col_idx, num_rows, columns, cut_direction, team_size
|
|
160
|
+
):
|
|
161
|
+
"""
|
|
162
|
+
Determine edge styles and extensions for a box at position (row_idx, col_idx).
|
|
163
|
+
Outer edges of team rectangles are solid (thicker), inner edges are dashed.
|
|
164
|
+
Extensions are used to close gaps in ALL solid lines.
|
|
165
|
+
Duplicate dashed edges are skipped to avoid double lines.
|
|
166
|
+
|
|
167
|
+
team_size is the number of columns (vertical) or rows (horizontal) per team.
|
|
168
|
+
"""
|
|
169
|
+
# Default: all dashed, no extension
|
|
170
|
+
edges = {
|
|
171
|
+
"top": EDGE_DASHED,
|
|
172
|
+
"bottom": EDGE_DASHED,
|
|
173
|
+
"left": EDGE_DASHED,
|
|
174
|
+
"right": EDGE_DASHED,
|
|
175
|
+
}
|
|
176
|
+
ext = {
|
|
177
|
+
"top": ("0pt", "0pt"),
|
|
178
|
+
"bottom": ("0pt", "0pt"),
|
|
179
|
+
"left": ("0pt", "0pt"),
|
|
180
|
+
"right": ("0pt", "0pt"),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Gap sizes (half of spacing to extend into)
|
|
184
|
+
h_gap = "0.75mm" # half of SPACE (1.5mm)
|
|
185
|
+
v_gap = "0.5mm" # half of vspace (1mm)
|
|
186
|
+
|
|
187
|
+
# Helper functions to check if position is at a team boundary
|
|
188
|
+
def is_at_right_team_boundary():
|
|
189
|
+
"""Is this box at the right edge of its team (but not at grid edge)?"""
|
|
190
|
+
if cut_direction != "vertical" or not team_size:
|
|
191
|
+
return False
|
|
192
|
+
return (col_idx + 1) % team_size == 0 and col_idx < columns - 1
|
|
193
|
+
|
|
194
|
+
def is_at_left_team_boundary():
|
|
195
|
+
"""Is this box at the left edge of its team (but not at grid edge)?"""
|
|
196
|
+
if cut_direction != "vertical" or not team_size:
|
|
197
|
+
return False
|
|
198
|
+
return col_idx % team_size == 0 and col_idx > 0
|
|
199
|
+
|
|
200
|
+
def is_at_bottom_team_boundary():
|
|
201
|
+
"""Is this box at the bottom edge of its team (but not at grid edge)?"""
|
|
202
|
+
if cut_direction != "horizontal" or not team_size:
|
|
203
|
+
return False
|
|
204
|
+
return (row_idx + 1) % team_size == 0 and row_idx < num_rows - 1
|
|
205
|
+
|
|
206
|
+
def is_at_top_team_boundary():
|
|
207
|
+
"""Is this box at the top edge of its team (but not at grid edge)?"""
|
|
208
|
+
if cut_direction != "horizontal" or not team_size:
|
|
209
|
+
return False
|
|
210
|
+
return row_idx % team_size == 0 and row_idx > 0
|
|
211
|
+
|
|
212
|
+
# Determine which edges are solid
|
|
213
|
+
# Only apply solid edges if we have a valid cut direction
|
|
214
|
+
# Otherwise fall back to all-dashed (default)
|
|
215
|
+
if cut_direction is not None:
|
|
216
|
+
# Outer edges of the entire grid
|
|
217
|
+
if row_idx == 0:
|
|
218
|
+
edges["top"] = EDGE_SOLID
|
|
219
|
+
if row_idx == num_rows - 1:
|
|
220
|
+
edges["bottom"] = EDGE_SOLID
|
|
221
|
+
if col_idx == 0:
|
|
222
|
+
edges["left"] = EDGE_SOLID
|
|
223
|
+
if col_idx == columns - 1:
|
|
224
|
+
edges["right"] = EDGE_SOLID
|
|
225
|
+
|
|
226
|
+
# Team boundary edges
|
|
227
|
+
if is_at_right_team_boundary():
|
|
228
|
+
edges["right"] = EDGE_SOLID
|
|
229
|
+
if is_at_left_team_boundary():
|
|
230
|
+
edges["left"] = EDGE_SOLID
|
|
231
|
+
if is_at_bottom_team_boundary():
|
|
232
|
+
edges["bottom"] = EDGE_SOLID
|
|
233
|
+
if is_at_top_team_boundary():
|
|
234
|
+
edges["top"] = EDGE_SOLID
|
|
235
|
+
|
|
236
|
+
# Skip duplicate dashed edges (to avoid double lines between adjacent boxes)
|
|
237
|
+
if edges["left"] == EDGE_DASHED and col_idx > 0:
|
|
238
|
+
edges["left"] = EDGE_NONE
|
|
239
|
+
|
|
240
|
+
if edges["top"] == EDGE_DASHED and row_idx > 0:
|
|
241
|
+
edges["top"] = EDGE_NONE
|
|
242
|
+
|
|
243
|
+
# Calculate extensions for solid edges to close gaps
|
|
244
|
+
# But don't extend into team boundary gaps!
|
|
245
|
+
|
|
246
|
+
if edges["top"] == EDGE_SOLID:
|
|
247
|
+
at_left_boundary = is_at_left_team_boundary()
|
|
248
|
+
ext_left = "-" + h_gap if col_idx > 0 and not at_left_boundary else "0pt"
|
|
249
|
+
at_right_boundary = is_at_right_team_boundary()
|
|
250
|
+
ext_right = (
|
|
251
|
+
h_gap if col_idx < columns - 1 and not at_right_boundary else "0pt"
|
|
252
|
+
)
|
|
253
|
+
ext["top"] = (ext_left, ext_right)
|
|
254
|
+
|
|
255
|
+
if edges["bottom"] == EDGE_SOLID:
|
|
256
|
+
at_left_boundary = is_at_left_team_boundary()
|
|
257
|
+
ext_left = "-" + h_gap if col_idx > 0 and not at_left_boundary else "0pt"
|
|
258
|
+
at_right_boundary = is_at_right_team_boundary()
|
|
259
|
+
ext_right = (
|
|
260
|
+
h_gap if col_idx < columns - 1 and not at_right_boundary else "0pt"
|
|
261
|
+
)
|
|
262
|
+
ext["bottom"] = (ext_left, ext_right)
|
|
263
|
+
|
|
264
|
+
if edges["left"] == EDGE_SOLID:
|
|
265
|
+
at_top_boundary = is_at_top_team_boundary()
|
|
266
|
+
ext_top = v_gap if row_idx > 0 and not at_top_boundary else "0pt"
|
|
267
|
+
at_bottom_boundary = is_at_bottom_team_boundary()
|
|
268
|
+
ext_bottom = (
|
|
269
|
+
"-" + v_gap
|
|
270
|
+
if row_idx < num_rows - 1 and not at_bottom_boundary
|
|
271
|
+
else "0pt"
|
|
272
|
+
)
|
|
273
|
+
ext["left"] = (ext_top, ext_bottom)
|
|
274
|
+
|
|
275
|
+
if edges["right"] == EDGE_SOLID:
|
|
276
|
+
at_top_boundary = is_at_top_team_boundary()
|
|
277
|
+
ext_top = v_gap if row_idx > 0 and not at_top_boundary else "0pt"
|
|
278
|
+
at_bottom_boundary = is_at_bottom_team_boundary()
|
|
279
|
+
ext_bottom = (
|
|
280
|
+
"-" + v_gap
|
|
281
|
+
if row_idx < num_rows - 1 and not at_bottom_boundary
|
|
282
|
+
else "0pt"
|
|
283
|
+
)
|
|
284
|
+
ext["right"] = (ext_top, ext_bottom)
|
|
285
|
+
|
|
286
|
+
return edges, ext
|
|
287
|
+
|
|
81
288
|
def generate_regular_block(self, block_):
|
|
82
289
|
block = block_.copy()
|
|
83
290
|
if not (block.get("image") or block.get("text")):
|
|
84
291
|
return
|
|
85
292
|
columns = block["columns"]
|
|
86
|
-
|
|
293
|
+
num_rows = block.get("rows") or 1
|
|
294
|
+
handouts_per_team = block.get("handouts_per_team") or 3
|
|
295
|
+
|
|
296
|
+
# Determine cut direction
|
|
297
|
+
cut_direction, cut_after = self.get_cut_direction(
|
|
298
|
+
columns, num_rows, handouts_per_team
|
|
299
|
+
)
|
|
300
|
+
if self.args.debug:
|
|
301
|
+
print(f"cut_direction: {cut_direction}, cut_after: {cut_after}")
|
|
302
|
+
|
|
303
|
+
spaces = columns - 1
|
|
87
304
|
boxwidth = self.args.boxwidth or round(
|
|
88
|
-
(self.get_page_width() - spaces * self.SPACE) /
|
|
305
|
+
(self.get_page_width() - spaces * self.SPACE) / columns,
|
|
89
306
|
3,
|
|
90
307
|
)
|
|
91
308
|
total_width = boxwidth * columns + spaces * self.SPACE
|
|
@@ -98,7 +315,6 @@ class HandoutGenerator:
|
|
|
98
315
|
r"\setlength{\boxwidth}{<Q>mm}%".replace("<Q>", str(boxwidth)),
|
|
99
316
|
r"\setlength{\boxwidthinner}{<Q>mm}%".replace("<Q>", str(boxwidthinner)),
|
|
100
317
|
]
|
|
101
|
-
rows = []
|
|
102
318
|
contents = []
|
|
103
319
|
if block.get("image"):
|
|
104
320
|
img_qwidth = block.get("resize_image") or 1.0
|
|
@@ -113,10 +329,18 @@ class HandoutGenerator:
|
|
|
113
329
|
block["centering"] = ""
|
|
114
330
|
else:
|
|
115
331
|
block["centering"] = "\\centering"
|
|
116
|
-
|
|
332
|
+
|
|
333
|
+
rows = []
|
|
334
|
+
for row_idx in range(num_rows):
|
|
335
|
+
row_boxes = []
|
|
336
|
+
for col_idx in range(columns):
|
|
337
|
+
edges, ext = self.get_edge_styles(
|
|
338
|
+
row_idx, col_idx, num_rows, columns, cut_direction, cut_after
|
|
339
|
+
)
|
|
340
|
+
row_boxes.append(self.make_tikzbox(block, edges, ext))
|
|
117
341
|
row = (
|
|
118
342
|
TIKZBOX_START.replace("<CENTERING>", block["centering"])
|
|
119
|
-
+ "\n".join(
|
|
343
|
+
+ "\n".join(row_boxes)
|
|
120
344
|
+ TIKZBOX_END
|
|
121
345
|
)
|
|
122
346
|
rows.append(row)
|
|
@@ -124,6 +348,9 @@ class HandoutGenerator:
|
|
|
124
348
|
|
|
125
349
|
def generate(self):
|
|
126
350
|
for block in self.parse_input(self.args.filename):
|
|
351
|
+
if not block:
|
|
352
|
+
self.blocks.append("\n\\clearpage\n")
|
|
353
|
+
continue
|
|
127
354
|
if self.args.debug:
|
|
128
355
|
print(block)
|
|
129
356
|
if block.get("for_question"):
|
|
@@ -138,7 +365,7 @@ class HandoutGenerator:
|
|
|
138
365
|
|
|
139
366
|
def process_file(args, file_dir, bn):
|
|
140
367
|
tex_contents = HandoutGenerator(args).generate()
|
|
141
|
-
tex_path = os.path.join(file_dir, f"{bn}_{args.
|
|
368
|
+
tex_path = os.path.join(file_dir, f"{bn}_{args.language}.tex")
|
|
142
369
|
write_file(tex_path, tex_contents)
|
|
143
370
|
|
|
144
371
|
tectonic_path = get_tectonic_path()
|
|
@@ -13,33 +13,32 @@ HEADER = r"""
|
|
|
13
13
|
\begin{document}
|
|
14
14
|
\fontsize{14pt}{16pt}\selectfont
|
|
15
15
|
\setlength\parindent{0pt}
|
|
16
|
-
\tikzstyle{box}=[
|
|
16
|
+
\tikzstyle{box}=[rectangle, inner sep=<TIKZ_MM>mm]
|
|
17
17
|
\raggedright
|
|
18
18
|
\raggedbottom
|
|
19
19
|
""".strip()
|
|
20
20
|
|
|
21
21
|
GREYTEXT = r"""{\fontsize{9pt}{11pt}\selectfont \textcolor{gray}{<GREYTEXT>}}"""
|
|
22
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
23
|
TIKZBOX_START = r"""{<CENTERING>
|
|
35
24
|
"""
|
|
36
25
|
|
|
37
26
|
TIKZBOX_INNER = r"""
|
|
38
27
|
\begin{tikzpicture}
|
|
39
|
-
\node[box, minimum width=\boxwidth<TEXTWIDTH><ALIGN>] {<FONTSIZE><CONTENTS>\par};
|
|
28
|
+
\node[box, minimum width=\boxwidth<TEXTWIDTH><ALIGN>] (b) {<FONTSIZE><CONTENTS>\par};
|
|
29
|
+
\useasboundingbox (b.south west) rectangle (b.north east);
|
|
30
|
+
\draw[<TOP>] ([xshift=<TOP_EXT_L>]b.north west) -- ([xshift=<TOP_EXT_R>]b.north east);
|
|
31
|
+
\draw[<BOTTOM>] ([xshift=<BOTTOM_EXT_L>]b.south west) -- ([xshift=<BOTTOM_EXT_R>]b.south east);
|
|
32
|
+
\draw[<LEFT>] ([yshift=<LEFT_EXT_T>]b.north west) -- ([yshift=<LEFT_EXT_B>]b.south west);
|
|
33
|
+
\draw[<RIGHT>] ([yshift=<RIGHT_EXT_T>]b.north east) -- ([yshift=<RIGHT_EXT_B>]b.south east);
|
|
40
34
|
\end{tikzpicture}
|
|
41
35
|
""".strip()
|
|
42
36
|
|
|
37
|
+
# Line styles for box edges
|
|
38
|
+
EDGE_SOLID = "line width=0.8pt"
|
|
39
|
+
EDGE_DASHED = "dashed"
|
|
40
|
+
EDGE_NONE = "draw=none" # Don't draw this edge (to avoid double dashed lines)
|
|
41
|
+
|
|
43
42
|
TIKZBOX_END = "\n}"
|
|
44
43
|
|
|
45
44
|
IMG = r"""\includegraphics<IMGWIDTH>{<IMGPATH>}"""
|
chgksuite/handouter/utils.py
CHANGED
|
@@ -45,8 +45,29 @@ def wrap_val(key, val):
|
|
|
45
45
|
return val.strip()
|
|
46
46
|
|
|
47
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
|
+
|
|
48
69
|
def parse_handouts(contents):
|
|
49
|
-
blocks = contents
|
|
70
|
+
blocks = split_blocks(contents)
|
|
50
71
|
result = []
|
|
51
72
|
for block_ in blocks:
|
|
52
73
|
block = block_.strip()
|
chgksuite/lastdir
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/Users/pecheny/chgksuite1/tmpz_2mf3o8/tmptke0lqfv
|