Coverage for liitos/captions.py: 100.00%
59 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-05 17:22:35 +00:00
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-05 17:22:35 +00:00
1"""Move a caption below a table.
3Implementer note: We use a three state machine with transitions COPY [-> TABLE [-> CAPTION -> TABLE] -> COPY].
4"""
6from collections.abc import Iterable
7from enum import Enum
8from typing import Union
10from liitos import log
12Caption = list[str]
13Table = list[str]
15Modus = Enum('Modus', 'COPY TABLE CAPTION')
17TABLE_START_TRIGGER_STARTSWITH = r'\begin{longtable}'
18CAPTION_START_TRIGGER_STARTSWITH = r'\caption{'
19CAPTION_END_TRIGGER_ENDSWITH = r'}\tabularnewline'
20TABLE_END_TRIGGER_STARTSWITH = r'\end{longtable}'
23def filter_seek_table(line: str, slot: int, modus: Modus, outgoing: list[str], table: Table, caption: Caption) -> Modus:
24 r"""Filter line, seek for a table, if found init table and caption, and return updated mnodus.
26 Examples:
28 >>> o = []
29 >>> line = TABLE_START_TRIGGER_STARTSWITH # r'\begin{longtable}'
30 >>> t, c = [], []
31 >>> m = filter_seek_table(line, 0, Modus.COPY, o, t, c)
32 >>> assert not o
33 >>> m.name
34 'TABLE'
35 >>> t
36 ['\\begin{longtable}']
37 >>> assert c == []
38 """
39 if line.startswith(TABLE_START_TRIGGER_STARTSWITH):
40 log.info(f'start of a table environment at line #{slot + 1}')
41 modus = Modus.TABLE
42 table.append(line)
43 caption.clear()
44 else:
45 outgoing.append(line)
47 return modus
50def filter_seek_caption(
51 line: str, slot: int, modus: Modus, outgoing: list[str], table: Table, caption: Caption
52) -> Modus:
53 r"""Filter line in table, seek for a caption, and return updated mnodus.
55 Examples:
57 >>> o = []
58 >>> line = CAPTION_START_TRIGGER_STARTSWITH # r'\caption{'
59 >>> t, c = [], []
60 >>> m = filter_seek_caption(line, 0, Modus.TABLE, o, t, c)
61 >>> assert not o
62 >>> m.name
63 'CAPTION'
64 >>> t
65 []
66 >>> c
67 ['\\caption{']
69 >>> o = []
70 >>> line = r'\caption{something maybe}\tabularnewline'
71 >>> t, c = ['foo'], ['bar']
72 >>> m = filter_seek_caption(line, 0, Modus.TABLE, o, t, c)
73 >>> o
74 []
75 >>> m.name
76 'TABLE'
77 >>> t
78 ['foo']
79 >>> c
80 ['bar', '\\caption{something maybe}\\tabularnewline']
82 >>> o = []
83 >>> line = r'\end{longtable}'
84 >>> t, c = ['foo', r'\endlastfoot'], ['bar']
85 >>> m = filter_seek_caption(line, 0, Modus.TABLE, o, t, c)
86 >>> o
87 ['foo', 'bar', '\\endlastfoot', '\\end{longtable}']
88 >>> m.name
89 'COPY'
90 >>> assert t == []
91 >>> assert c == []
92 """
93 if line.startswith(CAPTION_START_TRIGGER_STARTSWITH):
94 log.info(f'- found the caption start at line #{slot + 1}')
95 caption.append(line)
96 if not line.strip().endswith(CAPTION_END_TRIGGER_ENDSWITH):
97 log.info(f'- multi line caption at line #{slot + 1}')
98 modus = Modus.CAPTION
99 elif line.startswith(TABLE_END_TRIGGER_STARTSWITH):
100 log.info(f'end of table env detected at line #{slot + 1}')
101 while table:
102 stmt = table.pop(0)
103 if not stmt.startswith(r'\endlastfoot'):
104 outgoing.append(stmt)
105 continue
106 else:
107 while caption:
108 outgoing.append(caption.pop(0))
109 outgoing.append(stmt)
110 outgoing.append(line)
111 modus = Modus.COPY
112 else:
113 log.debug('- table continues')
114 table.append(line)
116 return modus
119def filter_collect_caption(
120 line: str, slot: int, modus: Modus, outgoing: list[str], table: Table, caption: Caption
121) -> Modus:
122 r"""Filter line in caption until end marker, and return updated mnodus.
124 Examples:
126 >>> o = []
127 >>> line = 'some caption text'
128 >>> t, c = ['foo'], ['bar']
129 >>> m = filter_collect_caption(line, 0, Modus.CAPTION, o, t, c)
130 >>> assert not o
131 >>> m.name
132 'CAPTION'
133 >>> assert t == ['foo']
134 >>> assert c == ['bar', 'some caption text']
136 >>> o = []
137 >>> line = r'}\tabularnewline'
138 >>> t, c = ['foo'], ['bar']
139 >>> m = filter_collect_caption(line, 0, Modus.CAPTION, o, t, c)
140 >>> assert not o
141 >>> m.name
142 'TABLE'
143 >>> assert t == ['foo']
144 >>> assert c == ['bar', r'}\tabularnewline']
145 """
146 caption.append(line)
147 if line.strip().endswith(CAPTION_END_TRIGGER_ENDSWITH):
148 log.info(f'- caption read at line #{slot + 1}')
149 modus = Modus.TABLE
151 return modus
154def weave(incoming: Iterable[str], lookup: Union[dict[str, str], None] = None) -> list[str]:
155 r"""Weave the table caption inside foot from (default) head of table.
157 Examples:
159 >>> incoming = ['']
160 >>> o = weave(incoming)
161 >>> o
162 ['']
164 >>> i = [
165 ... r'\begin{longtable}[]{@{}|l|c|r|@{}}',
166 ... r'\caption{The old tune\label{tab:tuna}}\tabularnewline',
167 ... r'\hline\noalign{}\rowcolor{light-gray}',
168 ... r'Foo & Bar & Baz \\',
169 ... r'\hline\noalign{}',
170 ... r'\endfirsthead',
171 ... r'\hline\noalign{}\rowcolor{light-gray}',
172 ... r'Foo & Bar & Baz \\',
173 ... r'\hline\noalign{}',
174 ... r'\endhead',
175 ... r'\hline\noalign{}',
176 ... r'\endlastfoot',
177 ... r'Quux & x & 42 \\ \hline',
178 ... r'xQuu & y & -1 \\ \hline',
179 ... r'uxQu & z & true \\',
180 ... r'\end{longtable}',
181 ... ]
182 >>> o = weave(i)
183 >>> o
184 ['\\begin{longtable}[]{@{}|l|c|r|@{}}', ... '\\caption{The old tune..., '\\endlastfoot', ...]
185 """
186 outgoing: list[str] = []
187 modus = Modus.COPY
188 table: Table = []
189 caption: Caption = []
190 for slot, line in enumerate(incoming):
191 if modus == Modus.COPY:
192 modus = filter_seek_table(line, slot, modus, outgoing, table, caption)
193 elif modus == Modus.TABLE:
194 modus = filter_seek_caption(line, slot, modus, outgoing, table, caption)
195 else: # modus == Modus.CAPTION:
196 modus = filter_collect_caption(line, slot, modus, outgoing, table, caption)
198 return outgoing