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
« 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
5from __future__ import annotations
7import re
8from collections import defaultdict
9from itertools import zip_longest
10from typing import TYPE_CHECKING, cast
12PARAM_RE = re.compile(r"(?:(?<=\s)|^)(--[a-z-]+)(?=\s|$)")
13PROGRAM_RE = re.compile(r"'([a-z]+ --[a-z-]+)'")
15if TYPE_CHECKING:
16 from collections.abc import Generator
18 from django_stubs_ext import StrOrPromise
21def format_rst_string(text: StrOrPromise) -> str:
22 return PROGRAM_RE.sub(r"``\1``", PARAM_RE.sub(r"``\1``", str(text)))
25def get_cell_length(text: str) -> int:
26 """Get cell length handling newlines."""
27 return max(len(part) for part in text.split("\n"))
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="")
36CellType = str | list[list["CellType"]]
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())
61def generate_table_cells(
62 table: list[list[CellType]], widths: list[int]
63) -> Generator[list[str | None]]:
64 """
65 Generate table cell data.
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
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)
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 )
125def format_table(table: list[list[CellType]], header: list[str] | None) -> list[str]:
126 """
127 Format reStructuredText table.
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)
139 # Generate table cells
140 table_cells = list(generate_table_cells(table, widths))
142 output: list[str] = [
143 # Render initial line
144 render_line(widths, None),
145 ]
147 # Header
148 if header:
149 output.extend(
150 render_table([cast("list[str | None]", header)], widths, border="=")
151 )
153 # Table content
154 output.extend(render_table(table_cells, widths))
156 return output