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

1"""Move a caption below a table. 

2 

3Implementer note: We use a three state machine with transitions COPY [-> TABLE [-> CAPTION -> TABLE] -> COPY]. 

4""" 

5 

6from collections.abc import Iterable 

7from enum import Enum 

8from typing import Union 

9 

10from liitos import log 

11 

12Caption = list[str] 

13Table = list[str] 

14 

15Modus = Enum('Modus', 'COPY TABLE CAPTION') 

16 

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

21 

22 

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. 

25 

26 Examples: 

27 

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) 

46 

47 return modus 

48 

49 

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. 

54 

55 Examples: 

56 

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{'] 

68 

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

81 

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) 

115 

116 return modus 

117 

118 

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. 

123 

124 Examples: 

125 

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

135 

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 

150 

151 return modus 

152 

153 

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. 

156 

157 Examples: 

158 

159 >>> incoming = [''] 

160 >>> o = weave(incoming) 

161 >>> o 

162 [''] 

163 

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) 

197 

198 return outgoing