Coverage for liitos/tables.py: 78.05%
453 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-11-25 15:36:16 +00:00
« prev ^ index » next coverage.py v7.6.8, created at 2024-11-25 15:36:16 +00:00
1"""Apply all pairs in patch module on document."""
3import re
4from collections.abc import Iterable, Iterator
5from typing import Union, no_type_check
7from liitos import log
9# The "target pattern for a line base minimal regex parser"
10_ = r"""
11\columns=
13\begin{longtable}[]{
14... \real{fwidth_1}}
15...
16... \real{fwidth_n}}@{}}
17\toprule\noalign{}
18\begin{minipage}[
19text_1
20\end{minipage} & \begin{minipage}[
21text_2
22...
23\end{minipage} & \begin{minipage}[
24text_n
25\end{minipage} \\
26\midrule\noalign{}
27\endfirsthead
28\toprule\noalign{}
29\begin{minipage}[
30text_1
31\end{minipage} & \begin{minipage}[
32text_2
33...
34\end{minipage} & \begin{minipage}[
35text_n
36\end{minipage} \\
37\midrule\noalign{}
38\endhead
39\bottomrule\noalign{}
40\endlastfoot
41cell_1_1 & cell_1_2 & ... & cell_1_n \\
42cell_1_2 & cell_2_2 & ... & cell_2_n \\
43...
44row_1_m & row_2_m & ... & cell_n_m \\
45\rowcolor{white}
46\caption{cap_text_x
47cap_text_y\tabularnewline
48\end{longtable}
49"""
51TAB_START_TOK = r'\begin{longtable}[]{' # '@{}'
52TOP_RULE = r'\toprule()'
53MID_RULE = r'\midrule()'
54END_HEAD = r'\endhead'
55END_DATA_ROW = r'\\'
56BOT_RULE = r'\bottomrule()'
57TAB_END_TOK = r'\end{longtable}'
59TAB_NEW_START = r"""\begin{small}
60\begin{longtable}[]{|
61>{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.1500}}|
62>{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.5500}}|
63>{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.1500}}|
64>{\raggedright\arraybackslash}p{(\columnwidth - 12\tabcolsep) * \real{0.2000}}|}
65\hline"""
67TAB_HACKED_HEAD = r"""\begin{minipage}[b]{\linewidth}\raggedright
68\ \mbox{\textbf{Key}}
69\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
70\mbox{\textbf{Summary}}
71\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
72\mbox{\textbf{Parent}} \mbox{\textbf{Requirement}}
73\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
74\mbox{\textbf{Means of}} \mbox{\textbf{Compliance (MOC)}}
75\end{minipage} \\
76\hline
77\endfirsthead
78\multicolumn{4}{@{}l}{\small \ldots continued}\\\hline
79\hline
80\begin{minipage}[b]{\linewidth}\raggedright
81\ \mbox{\textbf{Key}}
82\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
83\mbox{\textbf{Summary}}
84\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
85\mbox{\textbf{Parent}} \mbox{\textbf{Requirement}}
86\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
87\mbox{\textbf{Means of}} \mbox{\textbf{Compliance (MOC)}}
88\end{minipage} \\
89\endhead
90\hline"""
92NEW_RULE = r'\hline'
94TAB_NEW_END = r"""\end{longtable}
95\end{small}
96\vspace*{-2em}
97\begin{footnotesize}
98ANNOTATION
99\end{footnotesize}"""
101COMMA = ','
104class Table:
105 """Some adhoc structure to encapsulate the source and target table."""
107 SourceMapType = list[tuple[int, str]]
108 ColumnsType = dict[str, dict[str, Union[float, int, str]]]
110 # ---- begin of LBP skeleton / shape ---
111 LBP_STARTSWITH_TAB_ENV_BEGIN = r'\begin{longtable}[]{'
112 LBP_REAL_INNER_COLW_PAT = re.compile(r'^(?P<clspec>.+)\\real{(?P<cwval>[0-9.]+)}}\s*$')
113 LBP_REAL_OUTER_COLW_PAT = re.compile(r'^(?P<clspec>.+)\\real{(?P<cwval>[0-9.]+)}}@{}}\s*$')
114 # Width lines for header:
115 FUT_LSPLIT_ONCE_FOR_PREFIX_VAL_COMMA_RIGHT = '}}'
116 FUT_LSPLIT_ONCE_FOR_PREFIX_COMMA_VAL = r'\real{'
117 # then concat PREFIX + r'\real{' + str(column_width_new) + '}}' + RIGHT
119 LBP_TOP_RULE_CONTEXT_STARTSWITH = r'\toprule\noalign{}'
120 LPB_START_COLUMN_LABEL_STARTSWITH = r'\begin{minipage}['
121 LBP_SEP_COLUMN_LABEL_STARTSWITH = r'\end{minipage} & \begin{minipage}['
122 LBP_STOP_COLUMN_LABEL_STARTSWITH = r'\end{minipage} \\'
124 LBP_MID_RULE_CONTEXT_STARTSWITH = r'\midrule\noalign{}'
126 LBP_END_FIRST_HEAD_STARTSWITH = r'\endfirsthead'
128 # LBP_TOP_RULE_CONTEXT_STARTSWITH = r'\toprule\noalign{}'
129 # LPB_START_COLUMN_LABEL_STARTSWITH = r'\begin{minipage}['
130 # LBP_SEP_COLUMN_LABEL_STARTSWITH = r'\end{minipage} & \begin{minipage}['
131 # LBP_STOP_COLUMN_LABEL_STARTSWITH = r'\end{minipage} \\'
133 # LBP_MID_RULE_CONTEXT_STARTSWITH = r'\midrule\noalign{}'
135 LBP_END_ALL_HEAD_STARTSWITH = r'\endhead'
137 LBP_BOTTOM_RULE_CONTEXT_STARTSWITH = r'\bottomrule\noalign{}'
139 LBP_END_LAST_FOOT_STARTSWITH = r'\endlastfoot'
141 # ... data lines - we want inject of r'\hline' following every data line (not text line)
142 # -> that is, inject after lines ending with r'\\'
144 LBP_END_OF_DATA_STARTSWITH = r'\rowcolor{white}'
145 LBP_START_CAP_STARTSWITH = r'\caption{'
146 LBP_STOP_CAP_ENDSWITH = r'\tabularnewline'
147 LBP_STARTSWITH_TAB_ENV_END = r'\end{longtable}'
149 # ---- end of LBP skeleton / shape ---
150 @no_type_check
151 def __init__(
152 self,
153 anchor: int,
154 start_line: str,
155 text_lines: Iterator[str],
156 widths: list[float],
157 font_sz: str = '',
158 style: str = 'readable',
159 ):
160 """Initialize the table from source text lines anchored at anchor.
161 The implementation allows reuse of the iterator on caller site for extracting subsequent tables in one go.
162 """
163 self.src_map: Table.SourceMapType = [(anchor, start_line.rstrip())]
164 self.data_row_ends: Table.SourceMapType = []
165 self.columns: Table.ColumnsType = {}
166 self.target_widths: list[float] = widths
167 self.source_widths: list[float] = []
168 self.font_size = font_sz
169 self.style: str = style if style in ('readable', 'ugly') else 'readable'
170 log.info(f'Received {anchor=}, {start_line=}, target {widths=}, {font_sz=}, and {style=}')
171 local_number = 0
172 consumed = False
173 while not consumed:
174 local_number += 1
175 pos = local_number + anchor
176 line = next(text_lines).rstrip()
177 self.src_map.append((pos, line))
178 if line.startswith(Table.LBP_STARTSWITH_TAB_ENV_END):
179 consumed = True
181 self.parse_columns()
182 self.parse_data_rows()
183 self.data_row_count = len(self.data_row_ends)
184 self.cw_patches: dict[str, str] = {}
185 self.create_width_patches()
186 log.info(f'Parsed {len(self.target_widths)} x {self.data_row_count} table starting at anchor {anchor}')
188 @no_type_check
189 def create_width_patches(self):
190 """If widths are meaningful and consistent create the patches with the zero-based line-numbers as keys."""
191 if not self.source_widths:
192 log.warning('Found no useful width information')
193 return {}
194 wrapper = r'\real{'
195 postfix = '}}'
196 finalize = '@{}}'
197 if self.style == 'ugly':
198 finalize = '|@{}}'
199 ranks = list(self.columns)
200 for rank in ranks:
201 anchor_str = str(self.columns[rank]['col_spec_line'])
202 prefix = self.columns[rank]['colspec_prefix']
203 value = self.columns[rank]['width']
204 if self.style == 'ugly' and prefix.lstrip().startswith('>{'):
205 prefix = prefix.replace('>{', '|>{', 1)
206 # concat PREFIX + r'\real{' + str(column_width_new) + '}}'
207 self.cw_patches[anchor_str] = prefix + wrapper + str(value) + postfix
208 if rank == ranks[-1]:
209 self.cw_patches[anchor_str] += finalize
211 def width_patches(self) -> dict[str, str]:
212 """Return the map of width patches with the zero-based line-numbers as keys."""
213 return self.cw_patches
215 def source_map(self) -> SourceMapType:
216 """Return the source map data (a random accessible sequence of pairs) mapping abs line number to text line."""
217 return self.src_map
219 def column_data(self) -> ColumnsType:
220 """Return the column data (an ordered dict of first labels, other labels, and widths) with abs line map."""
221 return self.columns
223 def column_source_widths(self) -> list[float]:
224 """Return the incoming column widths."""
225 return self.source_widths
227 def column_target_widths(self) -> list[float]:
228 """Return the outgoing column widths."""
229 return self.target_widths
231 @no_type_check
232 def table_width(self) -> float:
233 """Return the sum of all column widths."""
234 return sum(self.columns[r].get('width', 0) for r in self.columns)
236 def data_row_seps(self) -> SourceMapType:
237 """Return the map to the data row ends for injecting separators."""
238 return self.data_row_ends
240 @no_type_check
241 def transform_widths(self) -> None:
242 """Apply the target transform to column widths."""
243 self.source_widths = [self.columns[rank]['width'] for rank in self.columns]
244 if not self.target_widths:
245 log.info('No target widths given - maintaining source column widths')
246 self.target_widths = self.source_widths
247 return
248 if len(self.target_widths) != len(self.source_widths): 248 ↛ 249line 248 didn't jump to line 249 because the condition on line 248 was never true
249 if len(self.source_widths):
250 log.warning(
251 f'Mismatching {len(self.target_widths)} target widths given - maintaining'
252 f' the {len(self.source_widths)} source column widths'
253 )
254 self.target_widths = self.source_widths
255 return
256 else:
257 log.warning(
258 f'Lacking implementation for {len(self.target_widths)} target widths given - maintaining'
259 f' the {len(self.source_widths)} source column widths'
260 )
261 self.target_widths = self.source_widths
262 return
264 log.info('Applying target widths given - adapting source column widths')
265 for rank, target_width in zip(self.columns, self.target_widths):
266 self.columns[rank]['width'] = target_width
268 @no_type_check
269 def parse_columns(self) -> None:
270 """Parse the head to extract the columns."""
271 self.parse_column_widths()
272 self.parse_column_first_head()
273 self.parse_column_other_head()
274 self.transform_widths()
276 def parse_column_widths(self) -> None:
277 r"""Parse the column width declarations to initialize the columns data.
279 \begin{longtable}[]{@{}%wun-based-line-9
280 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.1118}}
281 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.5776}}
282 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.1739}}
283 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.1366}}@{}}
284 \toprule\noalign{}
285 """
286 rank = 0
287 for anchor, text in self.src_map:
288 if text.startswith(Table.LBP_STARTSWITH_TAB_ENV_BEGIN):
289 continue
290 if text.startswith(Table.LBP_TOP_RULE_CONTEXT_STARTSWITH):
291 break
292 m = Table.LBP_REAL_INNER_COLW_PAT.match(text)
293 if m:
294 self.columns[str(rank)] = {
295 'first_label': '',
296 'first_label_line': -1,
297 'continued_label': '',
298 'continued_label_line': -1,
299 'col_spec_line': anchor,
300 'colspec_prefix': m.groupdict()['clspec'],
301 'width': float(m.groupdict()['cwval']),
302 }
303 rank += 1
304 continue
305 m = Table.LBP_REAL_OUTER_COLW_PAT.match(text)
306 if m:
307 self.columns[str(rank)] = {
308 'first_label': '',
309 'first_label_line': -1,
310 'continued_label': '',
311 'continued_label_line': -1,
312 'col_spec_line': anchor,
313 'colspec_prefix': m.groupdict()['clspec'],
314 'width': float(m.groupdict()['cwval']),
315 }
316 rank += 1
317 continue
319 def parse_column_first_head(self) -> None:
320 r"""Parse the head to extract the columns.
322 \begin{minipage}[b]{\linewidth}\raggedright
323 Parameter
324 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
325 Description
326 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
327 Name
328 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
329 Example
330 \end{minipage} \\
331 \midrule\noalign{}
332 \endfirsthead
333 """
334 rank = 0
335 first_head = False
336 label_next = False
337 for anchor, text in self.src_map:
338 if text.startswith(Table.LBP_TOP_RULE_CONTEXT_STARTSWITH):
339 first_head = True
340 continue
341 if not first_head:
342 continue
343 if text.startswith(Table.LBP_END_FIRST_HEAD_STARTSWITH): 343 ↛ 344line 343 didn't jump to line 344 because the condition on line 343 was never true
344 break
345 if text.startswith(Table.LPB_START_COLUMN_LABEL_STARTSWITH):
346 label_next = True
347 continue
348 if text.startswith(Table.LBP_SEP_COLUMN_LABEL_STARTSWITH):
349 label_next = True
350 continue
351 if text.startswith(Table.LBP_STOP_COLUMN_LABEL_STARTSWITH): 351 ↛ 352line 351 didn't jump to line 352 because the condition on line 351 was never true
352 label_next = True
353 continue
354 if label_next: 354 ↛ 337line 354 didn't jump to line 337 because the condition on line 354 was always true
355 self.columns[str(rank)]['first_label'] = text.strip()
356 self.columns[str(rank)]['first_label_line'] = anchor
357 rank += 1
358 if str(rank) in self.columns:
359 continue
360 break
362 def parse_column_other_head(self) -> None:
363 r"""Parse the other heads to extract the column labelss.
365 \endfirsthead
366 \toprule\noalign{}
367 \begin{minipage}[b]{\linewidth}\raggedright
368 Parameter
369 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
370 Description
371 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
372 Name
373 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
374 Example
375 \end{minipage} \\
376 \midrule\noalign{}
377 \endhead
378 """
379 rank = 0
380 continued_head = False
381 label_next = False
382 for anchor, text in self.src_map:
383 if text.startswith(Table.LBP_END_FIRST_HEAD_STARTSWITH):
384 continued_head = True
385 continue
386 if not continued_head:
387 continue
388 if text.startswith(Table.LBP_END_ALL_HEAD_STARTSWITH):
389 break
390 if text.startswith(Table.LPB_START_COLUMN_LABEL_STARTSWITH):
391 label_next = True
392 continue
393 if text.startswith(Table.LBP_SEP_COLUMN_LABEL_STARTSWITH):
394 label_next = True
395 continue
396 if text.startswith(Table.LBP_STOP_COLUMN_LABEL_STARTSWITH): 396 ↛ 397line 396 didn't jump to line 397 because the condition on line 396 was never true
397 label_next = True
398 continue
399 if label_next:
400 self.columns[str(rank)]['continued_label'] = text.strip()
401 self.columns[str(rank)]['continued_label_line'] = anchor
402 rank += 1
403 if str(rank) in self.columns:
404 continue
405 break
407 def parse_data_rows(self) -> None:
408 r"""Parse the data rows.
410 \endlastfoot
411 A2 & B2 & C2 & D2 \\
412 \end{longtable}
413 """
414 data_section = False
415 for anchor, text in self.src_map: 415 ↛ exitline 415 didn't return from function 'parse_data_rows' because the loop on line 415 didn't complete
416 if text.startswith(Table.LBP_END_LAST_FOOT_STARTSWITH):
417 data_section = True
418 continue
419 if text.startswith(Table.LBP_STARTSWITH_TAB_ENV_END):
420 break
421 if data_section and r'\\' in text:
422 if self.style == 'ugly':
423 text += r' \hline'
424 self.data_row_ends.append((anchor, text))
425 continue
428def parse_table_font_size_command(slot: int, text_line: str) -> tuple[bool, str, str]:
429 """Parse the \\tablefontsize=footnotesize command."""
430 backslash = '\\'
431 known_sizes = (
432 'tiny',
433 'scriptsize',
434 'footnotesize',
435 'small',
436 'normalsize',
437 'large',
438 'Large',
439 'LARGE',
440 'huge',
441 'Huge',
442 )
443 if text_line.startswith(r'\tablefontsize='):
444 log.info(f'trigger a fontsize mod for the next table environment at line #{slot + 1}|{text_line}')
445 try:
446 font_size = text_line.split('=', 1)[1].strip() # r'\tablefontsize=Huge' --> 'Huge'
447 if font_size.startswith(backslash):
448 font_size = font_size.lstrip(backslash)
449 if font_size not in known_sizes:
450 log.error(f'failed to map given fontsize ({font_size}) into known sizes ({",".join(known_sizes)})')
451 return False, text_line, ''
452 log.info(f' -> parsed table fontsize mod as ({font_size})')
453 return True, '', font_size
454 except Exception as err:
455 log.error(f'failed to parse table fontsize value from {text_line.strip()} with err: {err}')
456 return False, text_line, ''
457 else:
458 return False, text_line, ''
461def parse_columns_command(slot: int, text_line: str) -> tuple[bool, str, list[float]]:
462 """Parse the \\columns=,0.2,0.7 command."""
463 if text_line.startswith(r'\columns='): 463 ↛ 477line 463 didn't jump to line 477 because the condition on line 463 was always true
464 log.info(f'trigger a columns mod for the next table environment at line #{slot + 1}|{text_line}')
465 try:
466 cols_csv = text_line.split('=', 1)[1].strip() # r'\columns = , 20\%,70\%' --> r', 20\%,70\%'
467 cols = [v.strip() for v in cols_csv.split(COMMA)]
468 widths = [float(v.replace(r'\%', '')) / 100 if r'\%' in v else (float(v) if v else 0) for v in cols]
469 rest = round(1 - sum(round(w, 5) for w in widths), 5)
470 widths = [v if v else rest for v in widths]
471 log.info(f' -> parsed columns mod as | {" | ".join(str(round(v, 2)) for v in widths)} |')
472 return True, '', widths
473 except Exception as err:
474 log.error(f'failed to parse columns values from {text_line.strip()} with err: {err}')
475 return False, text_line, []
476 else:
477 return False, text_line, []
480@no_type_check
481def patch(incoming: Iterable[str], lookup: Union[dict[str, str], None] = None) -> list[str]:
482 """Later alligator. \\columns=,0.2,0.7 as mandatory trigger"""
483 table_style = 'readable' if lookup is None else lookup.get('table_style', 'readable')
484 log.info(f'requested table style is ({table_style})')
485 table_section, head, annotation = False, False, False
486 table_ranges = []
487 guess_slot = 0
488 table_range = {}
489 has_column = False
490 has_font_size = False
491 widths: list[float] = []
492 font_size = ''
493 comment_outs = []
494 for n, text in enumerate(incoming):
495 if not table_section: 495 ↛ 518line 495 didn't jump to line 518 because the condition on line 495 was always true
496 if not has_font_size: 496 ↛ 500line 496 didn't jump to line 500 because the condition on line 496 was always true
497 has_font_size, text_line, font_size = parse_table_font_size_command(n, text)
498 if has_font_size: 498 ↛ 499line 498 didn't jump to line 499 because the condition on line 498 was never true
499 comment_outs.append(n)
500 if not has_font_size: 500 ↛ 503line 500 didn't jump to line 503 because the condition on line 500 was always true
501 continue
503 if not has_column:
504 has_column, text_line, widths = parse_columns_command(n, text)
505 if has_column:
506 comment_outs.append(n)
507 if not has_column:
508 continue
510 if not text.startswith(TAB_START_TOK):
511 continue
512 table_range['start'] = n
513 table_section = True
514 head = True
515 table_range['end_data_row'] = []
516 continue
518 if text.startswith(TOP_RULE):
519 table_range['top_rule'] = n
520 continue
522 if text.startswith(MID_RULE):
523 table_range['mid_rule'] = n
524 continue
526 if text.startswith(END_HEAD):
527 table_range['end_head'] = n
528 head = False
529 continue
531 if not head and text.strip().endswith(END_DATA_ROW):
532 table_range['end_data_row'].append(n)
533 continue
535 if text.startswith(BOT_RULE):
536 table_range['bottom_rule'] = n
537 continue
539 if text.startswith(TAB_END_TOK):
540 table_range['end'] = n
541 annotation = True
542 guess_slot = n + 2
543 continue
545 if annotation and n == guess_slot:
546 table_range['amend'] = n
547 table_ranges.append(table_range)
548 table_range = {}
549 annotation, table_section = False, False
551 log.info(f'Detected {len(table_ranges)} tables (method from before version 2023.2.12):')
552 for thing in table_ranges: 552 ↛ 553line 552 didn't jump to line 553 because the loop on line 552 never started
553 log.info(f'- {thing}')
555 tables_in, on_off_slots = [], []
556 for table in table_ranges: 556 ↛ 557line 556 didn't jump to line 557 because the loop on line 556 never started
557 from_here = table['start']
558 thru_there = table['amend']
559 log.info('Table:')
560 log.info(f'-from {incoming[from_here]}')
561 log.info(f'-thru {incoming[thru_there]}')
562 on_off = (from_here, thru_there + 1)
563 on_off_slots.append(on_off)
564 tables_in.append((on_off, [line for line in incoming[on_off[0] : on_off[1]]]))
566 log.debug('# - - - 8< - - -')
567 if tables_in: 567 ↛ 568line 567 didn't jump to line 568 because the condition on line 567 was never true
568 log.debug(str('\n'.join(tables_in[0][1])))
569 log.debug('# - - - 8< - - -')
571 reader = iter(incoming)
572 tables = []
573 comment_outs = []
574 n = 0
575 widths = []
576 font_size = ''
577 for line in reader:
578 log.debug(f'zero-based-line-no={n}, text=({line}) table-count={len(tables)}')
579 if not line.startswith(Table.LBP_STARTSWITH_TAB_ENV_BEGIN):
580 if line.startswith(r'\tablefontsize='): 580 ↛ 581line 580 didn't jump to line 581 because the condition on line 580 was never true
581 has_font_size, text_line, font_size = parse_table_font_size_command(n, line)
582 log.info(f' + {has_font_size=}, {text_line=}, {font_size=}')
583 if has_font_size:
584 comment_outs.append(n)
585 log.info(f'FONT-SIZE at <<{n}>>')
586 if line.startswith(r'\columns='):
587 has_column, text_line, widths = parse_columns_command(n, line)
588 log.info(f' + {has_column=}, {text_line=}, {widths=}')
589 if has_column: 589 ↛ 592line 589 didn't jump to line 592 because the condition on line 589 was always true
590 comment_outs.append(n)
591 log.info(f'COLUMNS-WIDTH at <<{n}>>')
592 n += 1
593 else:
594 table = Table(n, line, reader, widths, font_size, style=table_style)
595 tables.append(table)
596 n += len(tables[-1].source_map())
597 log.debug(f'- incremented n to {n}')
598 log.debug(f'! next n (zero offset) is {n}')
599 widths = []
600 font_size = ''
602 log.info('---')
603 for n, table in enumerate(tables, start=1):
604 log.info(f'Table #{n} (total width = {table.table_width()}):')
605 for rank, column in table.column_data().items():
606 log.info(f'{rank} -> {column}')
607 log.info(f'- source widths = {table.column_source_widths()}):')
608 log.info(f'- target widths = {table.column_target_widths()}):')
609 for numba, replacement in table.width_patches().items():
610 log.info(f'{numba} -> {replacement}')
611 for anchor, text in table.data_row_seps():
612 log.info(f'[data-row-seps]:{anchor} -> {text}')
613 log.info(f'= (fontsize command = "{table.font_size}"):')
614 log.info('---')
615 log.info(f'Comment out the following {len(comment_outs)} lines (zero based numbers) - punch:')
616 for number in comment_outs:
617 log.info(f'- {number}')
619 wideners = {}
620 for table in tables:
621 for numba, replacement in table.width_patches().items():
622 wideners[numba] = replacement
623 widen_me = set(wideners)
624 log.debug('widen me has:')
625 log.debug(list(widen_me))
626 log.debug('--- global replacement width lines: ---')
627 for numba, replacement in wideners.items():
628 log.debug(f'{numba} => {replacement}')
629 log.debug('---')
631 sizers = {}
632 for table in tables:
633 if table.font_size: 633 ↛ 634line 633 didn't jump to line 634 because the condition on line 633 was never true
634 sizers[str(table.src_map[0][0])] = '\\begin{' + table.font_size + '}\n' + table.src_map[0][1]
635 sizers[str(table.src_map[-1][0])] = table.src_map[-1][1] + '\n' + '\\end{' + table.font_size + '}'
636 size_me = set(sizers)
637 log.debug('size me has:')
638 log.debug(list(size_me))
639 log.debug('--- global replacement sizer lines: ---')
640 for numba, replacement in sizers.items(): 640 ↛ 641line 640 didn't jump to line 641 because the loop on line 640 never started
641 log.debug(f'{numba} => {replacement}')
642 log.debug('---')
644 out = []
645 # next_slot = 0
646 punch_me = set(comment_outs)
647 for n, line in enumerate(incoming):
648 if n in punch_me:
649 corrected = f'%CONSIDERED_{line}'
650 out.append(corrected)
651 log.info(f' (x) Punched out line {n} -> ({corrected})')
652 continue
653 if str(n) in widen_me:
654 out.append(wideners[str(n)])
655 log.info(f' (<) Incoming: ({line})')
656 log.info(f' (>) Outgoing: ({wideners[str(n)]})')
657 continue
658 if str(n) in size_me: 658 ↛ 659line 658 didn't jump to line 659 because the condition on line 658 was never true
659 out.append(sizers[str(n)])
660 log.info(f' (<) Incoming: ({line})')
661 log.info(f' (>) Outgoing: ({sizers[str(n)]})')
662 continue
663 out.append(line)
665 # if next_slot < len(on_off_slots):
666 # trigger_on, trigger_off = on_off_slots[next_slot]
667 # tb = table_ranges[next_slot]
668 # else:
669 # trigger_on = None
670 # if trigger_on is None:
671 # out.append(line)
672 # continue
673 #
674 # if n < trigger_on:
675 # out.append(line)
676 # continue
677 # if n == trigger_on:
678 # out.append(TAB_NEW_START)
679 # out.append(TAB_HACKED_HEAD)
680 # continue
681 # if n <= tb['end_head']:
682 # continue
683 # if n < tb.get('bottom_rule', 0):
684 # out.append(line)
685 # if n in tb['end_data_row']: # type: ignore
686 # out.append(NEW_RULE)
687 # continue
688 # if tb.get('bottom_rule', 0) <= n < tb['amend']:
689 # continue
690 # if n == tb['amend']:
691 # out.append(TAB_NEW_END.replace('ANNOTATION', line))
692 # next_slot += 1
694 log.warning('Disabled naive table patching from before version 2023.2.12 for now')
696 if table_style == 'ugly':
697 tds_plain = r'\begin{longtable}[]{@{}'
698 tde_plain = r'@{}}'
699 col_placeholders = ('l', 'c', 'r')
700 ut_count = 0
701 udr_count = 0
702 in_table_data_rows = False
703 _out = []
704 for n, line in enumerate(out):
705 if line.startswith(tds_plain) and line.endswith(tde_plain): 705 ↛ 706line 705 didn't jump to line 706 because the condition on line 705 was never true
706 probe = line.strip().replace(tds_plain, '').replace(tde_plain, '')
707 robe = probe
708 for col_ph in col_placeholders:
709 robe = robe.replace(col_ph, '')
710 if not robe:
711 for col_ph in col_placeholders:
712 probe = probe.replace(col_ph, f'|{col_ph}')
713 line = tds_plain + probe + '|' + tde_plain
714 else:
715 log.warning(f'Encountered pseudo-plain table declaration ({line})')
716 if in_table_data_rows and line.endswith(r'\\'):
717 if not out[n + 1].startswith(r'\end{longtable}'):
718 line += r' \hline'
719 udr_count += 1
720 better_rules = (r'\toprule', r'\midrule', r'\bottomrule')
721 for rule in better_rules:
722 if rule in line:
723 if rule == r'\bottomrule':
724 ut_count += 1
725 line = line.replace(rule, r'\hline')
726 if rule == r'\toprule':
727 line += r'\rowcolor{light-gray}'
728 if line.startswith(r'\endlastfoot'):
729 in_table_data_rows = True
730 if line.startswith(r'\end{longtable}'):
731 in_table_data_rows = False
732 _out.append(line)
733 out = [line for line in _out]
734 del _out
735 sp_suffix = '' if ut_count == 1 else 's'
736 log.warning(f'Uglified horizontal rules in {ut_count} table{sp_suffix}')
737 sp_suffix = '' if udr_count == 1 else 's'
738 log.warning(f'Uglified a total of {udr_count} data row{sp_suffix}')
740 log.debug(' -----> ')
741 log.debug('# - - - 8< - - -')
742 log.debug(str('\n'.join(out)))
743 log.debug('# - - - 8< - - -')
745 return out