11.15%

398 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-10 18:56:07 +00:00

1"""Apply all pairs in patch module on document.""" 

2 

3import re 

4from collections.abc import Iterable, Iterator 

5from typing import Union, no_type_check 

6 

7from liitos import log 

8 

9# The "target pattern for a line base minimal regex parser" 

10_ = r""" 

11\columns= 

12 

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""" 

50 

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}' 

58 

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""" 

66 

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""" 

91 

92NEW_RULE = r'\hline' 

93 

94TAB_NEW_END = r"""\end{longtable} 

95\end{small} 

96\vspace*{-2em} 

97\begin{footnotesize} 

98ANNOTATION 

99\end{footnotesize}""" 

100 

101COMMA = ',' 

102 

103 

104class Table: 

105 """Some adhoc structure to encapsulate the source and target table.""" 

106 

107 SourceMapType = list[tuple[int, str]] 

108 ColumnsType = dict[str, dict[str, Union[float, int, str]]] 

109 

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 

118 

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} \\' 

123 

124 LBP_MID_RULE_CONTEXT_STARTSWITH = r'\midrule\noalign{}' 

125 

126 LBP_END_FIRST_HEAD_STARTSWITH = r'\endfirsthead' 

127 

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} \\' 

132 

133 # LBP_MID_RULE_CONTEXT_STARTSWITH = r'\midrule\noalign{}' 

134 

135 LBP_END_ALL_HEAD_STARTSWITH = r'\endhead' 

136 

137 LBP_BOTTOM_RULE_CONTEXT_STARTSWITH = r'\bottomrule\noalign{}' 

138 

139 LBP_END_LAST_FOOT_STARTSWITH = r'\endlastfoot' 

140 

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'\\' 

143 

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}' 

148 

149 # ---- end of LBP skeleton / shape --- 

150 @no_type_check 

151 def __init__(self, anchor: int, start_line: str, text_lines: Iterator[str], widths: list[float], font_sz: str = ''): 

152 """Initialize the table from source text lines anchored at anchor. 

153 The implementation allows reuse of the iterator on caller site for extracting subsequent tables in one go. 

154 """ 

155 self.src_map: Table.SourceMapType = [(anchor, start_line.rstrip())] 

156 self.data_row_ends: Table.SourceMapType = [] 

157 self.columns: Table.ColumnsType = {} 

158 self.target_widths: list[float] = widths 

159 self.source_widths: list[float] = [] 

160 self.font_size = font_sz 

161 log.info(f'Received {anchor=}, {start_line=}, target {widths=}, and {font_sz=}') 

162 local_number = 0 

163 consumed = False 

164 while not consumed: 

165 local_number += 1 

166 pos = local_number + anchor 

167 line = next(text_lines).rstrip() 

168 self.src_map.append((pos, line)) 

169 if line.startswith(Table.LBP_STARTSWITH_TAB_ENV_END): 

170 consumed = True 

171 

172 self.parse_columns() 

173 self.parse_data_rows() 

174 self.data_row_count = len(self.data_row_ends) 

175 self.cw_patches: dict[str, str] = {} 

176 self.create_width_patches() 

177 log.info(f'Parsed {len(self.target_widths)} x {self.data_row_count} table starting at anchor {anchor}') 

178 

179 @no_type_check 

180 def create_width_patches(self): 

181 """If widths are meaningful and consistent create the patches with the zero-based line-numbers as keys.""" 

182 if not self.source_widths: 

183 log.warning('Found no useful width information') 

184 return {} 

185 wrapper = r'\real{' 

186 postfix = '}}' 

187 finalize = '@{}}' 

188 ranks = list(self.columns) 

189 for rank in ranks: 

190 anchor_str = str(self.columns[rank]['col_spec_line']) 

191 prefix = self.columns[rank]['colspec_prefix'] 

192 value = self.columns[rank]['width'] 

193 # concat PREFIX + r'\real{' + str(column_width_new) + '}}' 

194 self.cw_patches[anchor_str] = prefix + wrapper + str(value) + postfix 

195 if rank == ranks[-1]: 

196 self.cw_patches[anchor_str] += finalize 

197 

198 def width_patches(self) -> dict[str, str]: 

199 """Return the map of width patches with the zero-based line-numbers as keys.""" 

200 return self.cw_patches 

201 

202 def source_map(self) -> SourceMapType: 

203 """Return the source map data (a random accessible sequence of pairs) mapping abs line number to text line.""" 

204 return self.src_map 

205 

206 def column_data(self) -> ColumnsType: 

207 """Return the column data (an ordered dict of first labels, other labels, and widths) with abs line map.""" 

208 return self.columns 

209 

210 def column_source_widths(self) -> list[float]: 

211 """Return the incoming column widths.""" 

212 return self.source_widths 

213 

214 def column_target_widths(self) -> list[float]: 

215 """Return the outgoing column widths.""" 

216 return self.target_widths 

217 

218 @no_type_check 

219 def table_width(self) -> float: 

220 """Return the sum of all column widths.""" 

221 return sum(self.columns[r].get('width', 0) for r in self.columns) 

222 

223 def data_row_seps(self) -> SourceMapType: 

224 """Return the map to the data row ends for injecting separators.""" 

225 return self.data_row_ends 

226 

227 @no_type_check 

228 def transform_widths(self) -> None: 

229 """Apply the target transform to column widths.""" 

230 self.source_widths = [self.columns[rank]['width'] for rank in self.columns] 

231 if not self.target_widths: 

232 log.info('No target widths given - maintaining source column widths') 

233 self.target_widths = self.source_widths 

234 return 

235 if len(self.target_widths) != len(self.source_widths): 

236 log.warning( 

237 f'Mismatching {len(self.target_widths)} target widths given - maintaining' 

238 f'the {len(self.source_widths)} source column widths' 

239 ) 

240 self.target_widths = self.source_widths 

241 return 

242 log.info('Applying target widths given - adapting source column widths') 

243 for rank, target_width in zip(self.columns, self.target_widths): 

244 self.columns[rank]['width'] = target_width 

245 

246 @no_type_check 

247 def parse_columns(self) -> None: 

248 """Parse the head to extract the columns.""" 

249 self.parse_column_widths() 

250 self.parse_column_first_head() 

251 self.parse_column_other_head() 

252 self.transform_widths() 

253 

254 def parse_column_widths(self) -> None: 

255 r"""Parse the column width declarations to initialize the columns data. 

256 

257 \begin{longtable}[]{@{}%wun-based-line-9 

258 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.1118}} 

259 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.5776}} 

260 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.1739}} 

261 >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.1366}}@{}} 

262 \toprule\noalign{} 

263 """ 

264 rank = 0 

265 for anchor, text in self.src_map: 

266 if text.startswith(Table.LBP_STARTSWITH_TAB_ENV_BEGIN): 

267 continue 

268 if text.startswith(Table.LBP_TOP_RULE_CONTEXT_STARTSWITH): 

269 break 

270 m = Table.LBP_REAL_INNER_COLW_PAT.match(text) 

271 if m: 

272 self.columns[str(rank)] = { 

273 'first_label': '', 

274 'first_label_line': -1, 

275 'continued_label': '', 

276 'continued_label_line': -1, 

277 'col_spec_line': anchor, 

278 'colspec_prefix': m.groupdict()['clspec'], 

279 'width': float(m.groupdict()['cwval']), 

280 } 

281 rank += 1 

282 continue 

283 m = Table.LBP_REAL_OUTER_COLW_PAT.match(text) 

284 if m: 

285 self.columns[str(rank)] = { 

286 'first_label': '', 

287 'first_label_line': -1, 

288 'continued_label': '', 

289 'continued_label_line': -1, 

290 'col_spec_line': anchor, 

291 'colspec_prefix': m.groupdict()['clspec'], 

292 'width': float(m.groupdict()['cwval']), 

293 } 

294 rank += 1 

295 continue 

296 

297 def parse_column_first_head(self) -> None: 

298 r"""Parse the head to extract the columns. 

299 

300 \begin{minipage}[b]{\linewidth}\raggedright 

301 Parameter 

302 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright 

303 Description 

304 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright 

305 Name 

306 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright 

307 Example 

308 \end{minipage} \\ 

309 \midrule\noalign{} 

310 \endfirsthead 

311 """ 

312 rank = 0 

313 first_head = False 

314 label_next = False 

315 for anchor, text in self.src_map: 

316 if text.startswith(Table.LBP_TOP_RULE_CONTEXT_STARTSWITH): 

317 first_head = True 

318 continue 

319 if not first_head: 

320 continue 

321 if text.startswith(Table.LBP_END_FIRST_HEAD_STARTSWITH): 

322 break 

323 if text.startswith(Table.LPB_START_COLUMN_LABEL_STARTSWITH): 

324 label_next = True 

325 continue 

326 if text.startswith(Table.LBP_SEP_COLUMN_LABEL_STARTSWITH): 

327 label_next = True 

328 continue 

329 if text.startswith(Table.LBP_STOP_COLUMN_LABEL_STARTSWITH): 

330 label_next = True 

331 continue 

332 if label_next: 

333 self.columns[str(rank)]['first_label'] = text.strip() 

334 self.columns[str(rank)]['first_label_line'] = anchor 

335 rank += 1 

336 if str(rank) in self.columns: 

337 continue 

338 break 

339 

340 def parse_column_other_head(self) -> None: 

341 r"""Parse the other heads to extract the column labelss. 

342 

343 \endfirsthead 

344 \toprule\noalign{} 

345 \begin{minipage}[b]{\linewidth}\raggedright 

346 Parameter 

347 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright 

348 Description 

349 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright 

350 Name 

351 \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright 

352 Example 

353 \end{minipage} \\ 

354 \midrule\noalign{} 

355 \endhead 

356 """ 

357 rank = 0 

358 continued_head = False 

359 label_next = False 

360 for anchor, text in self.src_map: 

361 if text.startswith(Table.LBP_END_FIRST_HEAD_STARTSWITH): 

362 continued_head = True 

363 continue 

364 if not continued_head: 

365 continue 

366 if text.startswith(Table.LBP_END_ALL_HEAD_STARTSWITH): 

367 break 

368 if text.startswith(Table.LPB_START_COLUMN_LABEL_STARTSWITH): 

369 label_next = True 

370 continue 

371 if text.startswith(Table.LBP_SEP_COLUMN_LABEL_STARTSWITH): 

372 label_next = True 

373 continue 

374 if text.startswith(Table.LBP_STOP_COLUMN_LABEL_STARTSWITH): 

375 label_next = True 

376 continue 

377 if label_next: 

378 self.columns[str(rank)]['continued_label'] = text.strip() 

379 self.columns[str(rank)]['continued_label_line'] = anchor 

380 rank += 1 

381 if str(rank) in self.columns: 

382 continue 

383 break 

384 

385 def parse_data_rows(self) -> None: 

386 r"""Parse the data rows. 

387 

388 \endlastfoot 

389 A2 & B2 & C2 & D2 \\ 

390 \end{longtable} 

391 """ 

392 data_section = False 

393 for anchor, text in self.src_map: 

394 if text.startswith(Table.LBP_END_LAST_FOOT_STARTSWITH): 

395 data_section = True 

396 continue 

397 if text.startswith(Table.LBP_STARTSWITH_TAB_ENV_END): 

398 break 

399 if data_section and r'\\' in text: 

400 self.data_row_ends.append((anchor, text)) 

401 continue 

402 

403 

404def parse_table_font_size_command(slot: int, text_line: str) -> tuple[bool, str, str]: 

405 """Parse the \\tablefontsize=footnotesize command.""" 

406 backslash = '\\' 

407 known_sizes = ( 

408 'tiny', 

409 'scriptsize', 

410 'footnotesize', 

411 'small', 

412 'normalsize', 

413 'large', 

414 'Large', 

415 'LARGE', 

416 'huge', 

417 'Huge', 

418 ) 

419 if text_line.startswith(r'\tablefontsize='): 

420 log.info(f'trigger a fontsize mod for the next table environment at line #{slot + 1}|{text_line}') 

421 try: 

422 font_size = text_line.split('=', 1)[1].strip() # r'\tablefontsize=Huge' --> 'Huge' 

423 if font_size.startswith(backslash): 

424 font_size = font_size.lstrip(backslash) 

425 if font_size not in known_sizes: 

426 log.error(f'failed to map given fontsize ({font_size}) into known sizes ({",".join(known_sizes)})') 

427 return False, text_line, '' 

428 log.info(f' -> parsed table fontsize mod as ({font_size})') 

429 return True, '', font_size 

430 except Exception as err: 

431 log.error(f'failed to parse table fontsize value from {text_line.strip()} with err: {err}') 

432 return False, text_line, '' 

433 else: 

434 return False, text_line, '' 

435 

436 

437def parse_columns_command(slot: int, text_line: str) -> tuple[bool, str, list[float]]: 

438 """Parse the \\columns=,0.2,0.7 command.""" 

439 if text_line.startswith(r'\columns='): 

440 log.info(f'trigger a columns mod for the next table environment at line #{slot + 1}|{text_line}') 

441 try: 

442 cols_csv = text_line.split('=', 1)[1].strip() # r'\columns = , 20\%,70\%' --> r', 20\%,70\%' 

443 cols = [v.strip() for v in cols_csv.split(COMMA)] 

444 widths = [float(v.replace(r'\%', '')) / 100 if r'\%' in v else (float(v) if v else 0) for v in cols] 

445 rest = round(1 - sum(round(w, 5) for w in widths), 5) 

446 widths = [v if v else rest for v in widths] 

447 log.info(f' -> parsed columns mod as | {" | ".join(str(round(v, 2)) for v in widths)} |') 

448 return True, '', widths 

449 except Exception as err: 

450 log.error(f'failed to parse columns values from {text_line.strip()} with err: {err}') 

451 return False, text_line, [] 

452 else: 

453 return False, text_line, [] 

454 

455 

456@no_type_check 

457def patch(incoming: Iterable[str], lookup: Union[dict[str, str], None] = None) -> list[str]: 

458 """Later alligator. \\columns=,0.2,0.7 as mandatory trigger""" 

459 table_section, head, annotation = False, False, False 

460 table_ranges = [] 

461 guess_slot = 0 

462 table_range = {} 

463 has_column = False 

464 has_font_size = False 

465 widths: list[float] = [] 

466 font_size = '' 

467 comment_outs = [] 

468 for n, text in enumerate(incoming): 

469 if not table_section: 

470 if not has_font_size: 

471 has_font_size, text_line, font_size = parse_table_font_size_command(n, text) 

472 if has_font_size: 

473 comment_outs.append(n) 

474 if not has_font_size: 

475 continue 

476 

477 if not has_column: 

478 has_column, text_line, widths = parse_columns_command(n, text) 

479 if has_column: 

480 comment_outs.append(n) 

481 if not has_column: 

482 continue 

483 

484 if not text.startswith(TAB_START_TOK): 

485 continue 

486 table_range['start'] = n 

487 table_section = True 

488 head = True 

489 table_range['end_data_row'] = [] 

490 continue 

491 

492 if text.startswith(TOP_RULE): 

493 table_range['top_rule'] = n 

494 continue 

495 

496 if text.startswith(MID_RULE): 

497 table_range['mid_rule'] = n 

498 continue 

499 

500 if text.startswith(END_HEAD): 

501 table_range['end_head'] = n 

502 head = False 

503 continue 

504 

505 if not head and text.strip().endswith(END_DATA_ROW): 

506 table_range['end_data_row'].append(n) 

507 continue 

508 

509 if text.startswith(BOT_RULE): 

510 table_range['bottom_rule'] = n 

511 continue 

512 

513 if text.startswith(TAB_END_TOK): 

514 table_range['end'] = n 

515 annotation = True 

516 guess_slot = n + 2 

517 continue 

518 

519 if annotation and n == guess_slot: 

520 table_range['amend'] = n 

521 table_ranges.append(table_range) 

522 table_range = {} 

523 annotation, table_section = False, False 

524 

525 log.info(f'Detected {len(table_ranges)} tables (method from before version 2023.2.12):') 

526 for thing in table_ranges: 

527 log.info(f'- {thing}') 

528 

529 tables_in, on_off_slots = [], [] 

530 for table in table_ranges: 

531 from_here = table['start'] 

532 thru_there = table['amend'] 

533 log.info('Table:') 

534 log.info(f'-from {incoming[from_here]}') 

535 log.info(f'-thru {incoming[thru_there]}') 

536 on_off = (from_here, thru_there + 1) 

537 on_off_slots.append(on_off) 

538 tables_in.append((on_off, [line for line in incoming[on_off[0] : on_off[1]]])) 

539 

540 log.debug('# - - - 8< - - -') 

541 if tables_in: 

542 log.debug(str('\n'.join(tables_in[0][1]))) 

543 log.debug('# - - - 8< - - -') 

544 

545 reader = iter(incoming) 

546 tables = [] 

547 comment_outs = [] 

548 n = 0 

549 widths = [] 

550 font_size = '' 

551 for line in reader: 

552 log.debug(f'zero-based-line-no={n}, text=({line}) table-count={len(tables)}') 

553 if not line.startswith(Table.LBP_STARTSWITH_TAB_ENV_BEGIN): 

554 if line.startswith(r'\tablefontsize='): 

555 has_font_size, text_line, font_size = parse_table_font_size_command(n, line) 

556 log.info(f' + {has_font_size=}, {text_line=}, {font_size=}') 

557 if has_font_size: 

558 comment_outs.append(n) 

559 log.info(f'FONT-SIZE at <<{n}>>') 

560 if line.startswith(r'\columns='): 

561 has_column, text_line, widths = parse_columns_command(n, line) 

562 log.info(f' + {has_column=}, {text_line=}, {widths=}') 

563 if has_column: 

564 comment_outs.append(n) 

565 log.info(f'COLUMNS-WIDTH at <<{n}>>') 

566 n += 1 

567 else: 

568 table = Table(n, line, reader, widths, font_size) # sharing the meal - instead of iter(lines_buffer[n:])) 

569 tables.append(table) 

570 n += len(tables[-1].source_map()) 

571 log.debug(f'- incremented n to {n}') 

572 log.debug(f'! next n (zero offset) is {n}') 

573 widths = [] 

574 font_size = '' 

575 

576 log.info('---') 

577 for n, table in enumerate(tables, start=1): 

578 log.info(f'Table #{n} (total width = {table.table_width()}):') 

579 for rank, column in table.column_data().items(): 

580 log.info(f'{rank} -> {column}') 

581 log.info(f'- source widths = {table.column_source_widths()}):') 

582 log.info(f'- target widths = {table.column_target_widths()}):') 

583 for numba, replacement in table.width_patches().items(): 

584 log.info(f'{numba} -> {replacement}') 

585 for anchor, text in table.data_row_seps(): 

586 log.info(f'{anchor} -> {text}') 

587 log.info(f'= (fontsize command = "{table.font_size}"):') 

588 log.info('---') 

589 log.info(f'Comment out the following {len(comment_outs)} lines (zero based numbers) - punch:') 

590 for number in comment_outs: 

591 log.info(f'- {number}') 

592 

593 wideners = {} 

594 for table in tables: 

595 for numba, replacement in table.width_patches().items(): 

596 wideners[numba] = replacement 

597 widen_me = set(wideners) 

598 log.debug('widen me has:') 

599 log.debug(list(widen_me)) 

600 log.debug('--- global replacement width lines: ---') 

601 for numba, replacement in wideners.items(): 

602 log.debug(f'{numba} => {replacement}') 

603 log.debug('---') 

604 

605 sizers = {} 

606 for table in tables: 

607 if table.font_size: 

608 sizers[str(table.src_map[0][0])] = '\\begin{' + table.font_size + '}\n' + table.src_map[0][1] 

609 sizers[str(table.src_map[-1][0])] = table.src_map[-1][1] + '\n' + '\\end{' + table.font_size + '}' 

610 size_me = set(sizers) 

611 log.debug('size me has:') 

612 log.debug(list(size_me)) 

613 log.debug('--- global replacement sizer lines: ---') 

614 for numba, replacement in sizers.items(): 

615 log.debug(f'{numba} => {replacement}') 

616 log.debug('---') 

617 

618 out = [] 

619 # next_slot = 0 

620 punch_me = set(comment_outs) 

621 for n, line in enumerate(incoming): 

622 if n in punch_me: 

623 corrected = f'%CONSIDERED_{line}' 

624 out.append(corrected) 

625 log.info(f' (x) Punched out line {n} -> ({corrected})') 

626 continue 

627 if str(n) in widen_me: 

628 out.append(wideners[str(n)]) 

629 log.info(f' (<) Incoming: ({line})') 

630 log.info(f' (>) Outgoing: ({wideners[str(n)]})') 

631 continue 

632 if str(n) in size_me: 

633 out.append(sizers[str(n)]) 

634 log.info(f' (<) Incoming: ({line})') 

635 log.info(f' (>) Outgoing: ({sizers[str(n)]})') 

636 continue 

637 out.append(line) 

638 

639 # if next_slot < len(on_off_slots): 

640 # trigger_on, trigger_off = on_off_slots[next_slot] 

641 # tb = table_ranges[next_slot] 

642 # else: 

643 # trigger_on = None 

644 # if trigger_on is None: 

645 # out.append(line) 

646 # continue 

647 # 

648 # if n < trigger_on: 

649 # out.append(line) 

650 # continue 

651 # if n == trigger_on: 

652 # out.append(TAB_NEW_START) 

653 # out.append(TAB_HACKED_HEAD) 

654 # continue 

655 # if n <= tb['end_head']: 

656 # continue 

657 # if n < tb.get('bottom_rule', 0): 

658 # out.append(line) 

659 # if n in tb['end_data_row']: # type: ignore 

660 # out.append(NEW_RULE) 

661 # continue 

662 # if tb.get('bottom_rule', 0) <= n < tb['amend']: 

663 # continue 

664 # if n == tb['amend']: 

665 # out.append(TAB_NEW_END.replace('ANNOTATION', line)) 

666 # next_slot += 1 

667 

668 log.warning('Disabled naive table patching from before version 2023.2.12 for now') 

669 

670 log.debug(' -----> ') 

671 log.debug('# - - - 8< - - -') 

672 log.debug(str('\n'.join(out))) 

673 log.debug('# - - - 8< - - -') 

674 

675 return out