Coverage for app/venv/lib/python3.14/site-packages/weblate/utils/rst.py: 0%

75 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 07:15 +0000

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7import re 

8from collections import defaultdict 

9from itertools import zip_longest 

10from typing import TYPE_CHECKING, cast 

11 

12PARAM_RE = re.compile(r"(?:(?<=\s)|^)(--[a-z-]+)(?=\s|$)") 

13PROGRAM_RE = re.compile(r"'([a-z]+ --[a-z-]+)'") 

14 

15if TYPE_CHECKING: 

16 from collections.abc import Generator 

17 

18 from django_stubs_ext import StrOrPromise 

19 

20 

21def format_rst_string(text: StrOrPromise) -> str: 

22 return PROGRAM_RE.sub(r"``\1``", PARAM_RE.sub(r"``\1``", str(text))) 

23 

24 

25def get_cell_length(text: str) -> int: 

26 """Get cell length handling newlines.""" 

27 return max(len(part) for part in text.split("\n")) 

28 

29 

30def get_row_lines(row: list[str | None]) -> Generator[list[str]]: 

31 """Generate row lines for row data expanding newlines.""" 

32 data = [[] if item is None else item.split("\n") for item in row] 

33 yield from zip_longest(*data, fillvalue="") 

34 

35 

36CellType = str | list[list["CellType"]] 

37 

38 

39def get_cell_widths( 

40 table: list[list[CellType]] | list[list[str]], *, existing: list[int] | None = None 

41) -> list[int]: 

42 """Find maximal table cell widths.""" 

43 widths: dict[int, int] = defaultdict(int) 

44 if existing: 

45 widths.update(enumerate(existing)) 

46 for row in table: 

47 for column, item in enumerate(row): 

48 if isinstance(item, str): 

49 # Direct value 

50 widths[column] = max(widths[column], get_cell_length(item)) 

51 else: 

52 # Row span 

53 for span_column, span_width in enumerate(get_cell_widths(item)): 

54 widths[column + span_column] = max( 

55 widths[column + span_column], span_width 

56 ) 

57 # This relies on dict keeping order 

58 return list(widths.values()) 

59 

60 

61def generate_table_cells( 

62 table: list[list[CellType]], widths: list[int] 

63) -> Generator[list[str | None]]: 

64 """ 

65 Generate table cell data. 

66 

67 Span cells are generated as None. 

68 """ 

69 for row in table: 

70 row_output: list[str | None] = [] 

71 for column, value in enumerate(row): 

72 value = row[column] 

73 if isinstance(value, str): 

74 row_output.append(value) 

75 else: 

76 row_spaces = [None] * len(row_output) 

77 for span_column, span_value in enumerate( 

78 generate_table_cells(value, widths[column:]) 

79 ): 

80 if span_column == 0: 

81 yield [*row_output, *span_value] 

82 else: 

83 yield [*row_spaces, *span_value] 

84 row_output = [] 

85 if row_output: 

86 yield row_output 

87 

88 

89def render_line( 

90 widths: list[int], next_row: list[str | None] | None, *, border: str = "-" 

91) -> str: 

92 """Render table line with a given border.""" 

93 parts: list[str] = [] 

94 for column, width in enumerate(widths): 

95 if next_row is None or isinstance(next_row[column], str): 

96 parts.extend(("+", border * (width + 2))) 

97 else: 

98 parts.extend(("|", " " * (width + 2))) 

99 parts.append("+\n") 

100 return "".join(parts) 

101 

102 

103def render_table( 

104 cells: list[list[str | None]], widths: list[int], *, border: str = "-" 

105) -> Generator[str]: 

106 """Render table content.""" 

107 parts: list[str] = [] 

108 for row_number, row_data in enumerate(cells): 

109 for line in get_row_lines(row_data): 

110 for column, width in enumerate(widths): 

111 value = line[column] 

112 if value is None: 

113 value = "" 

114 parts.append(f"| {value:{width}} ") 

115 parts.append("|\n") 

116 yield "".join(parts) 

117 parts = [] 

118 yield render_line( 

119 widths, 

120 None if row_number == len(cells) - 1 else cells[row_number + 1], 

121 border=border, 

122 ) 

123 

124 

125def format_table(table: list[list[CellType]], header: list[str] | None) -> list[str]: 

126 """ 

127 Format reStructuredText table. 

128 

129 * The table is list of lists. 

130 * Row spans can be achieved by nested list: 

131 [["cell", [["span row 1 cell 1", "span row 1 cell 2"], ["span row 2 cell 1", "span row 2 cell 2"]]]] 

132 * The cells can contain newlines. 

133 """ 

134 # Get cells and header widths 

135 widths: list[int] = get_cell_widths(table) 

136 if header: 

137 widths = get_cell_widths([header], existing=widths) 

138 

139 # Generate table cells 

140 table_cells = list(generate_table_cells(table, widths)) 

141 

142 output: list[str] = [ 

143 # Render initial line 

144 render_line(widths, None), 

145 ] 

146 

147 # Header 

148 if header: 

149 output.extend( 

150 render_table([cast("list[str | None]", header)], widths, border="=") 

151 ) 

152 

153 # Table content 

154 output.extend(render_table(table_cells, widths)) 

155 

156 return output