Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/align.py: 20%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from itertools import chain
2from typing import TYPE_CHECKING, Iterable, Optional, Literal
4from .constrain import Constrain
5from .jupyter import JupyterMixin
6from .measure import Measurement
7from .segment import Segment
8from .style import StyleType
10if TYPE_CHECKING:
11 from .console import Console, ConsoleOptions, RenderableType, RenderResult
13AlignMethod = Literal["left", "center", "right"]
14VerticalAlignMethod = Literal["top", "middle", "bottom"]
17class Align(JupyterMixin):
18 """Align a renderable by adding spaces if necessary.
20 Args:
21 renderable (RenderableType): A console renderable.
22 align (AlignMethod): One of "left", "center", or "right""
23 style (StyleType, optional): An optional style to apply to the background.
24 vertical (Optional[VerticalAlignMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None.
25 pad (bool, optional): Pad the right with spaces. Defaults to True.
26 width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
27 height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None.
29 Raises:
30 ValueError: if ``align`` is not one of the expected values.
31 """
33 def __init__(
34 self,
35 renderable: "RenderableType",
36 align: AlignMethod = "left",
37 style: Optional[StyleType] = None,
38 *,
39 vertical: Optional[VerticalAlignMethod] = None,
40 pad: bool = True,
41 width: Optional[int] = None,
42 height: Optional[int] = None,
43 ) -> None:
44 if align not in ("left", "center", "right"):
45 raise ValueError(
46 f'invalid value for align, expected "left", "center", or "right" (not {align!r})'
47 )
48 if vertical is not None and vertical not in ("top", "middle", "bottom"):
49 raise ValueError(
50 f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})'
51 )
52 self.renderable = renderable
53 self.align = align
54 self.style = style
55 self.vertical = vertical
56 self.pad = pad
57 self.width = width
58 self.height = height
60 def __repr__(self) -> str:
61 return f"Align({self.renderable!r}, {self.align!r})"
63 @classmethod
64 def left(
65 cls,
66 renderable: "RenderableType",
67 style: Optional[StyleType] = None,
68 *,
69 vertical: Optional[VerticalAlignMethod] = None,
70 pad: bool = True,
71 width: Optional[int] = None,
72 height: Optional[int] = None,
73 ) -> "Align":
74 """Align a renderable to the left."""
75 return cls(
76 renderable,
77 "left",
78 style=style,
79 vertical=vertical,
80 pad=pad,
81 width=width,
82 height=height,
83 )
85 @classmethod
86 def center(
87 cls,
88 renderable: "RenderableType",
89 style: Optional[StyleType] = None,
90 *,
91 vertical: Optional[VerticalAlignMethod] = None,
92 pad: bool = True,
93 width: Optional[int] = None,
94 height: Optional[int] = None,
95 ) -> "Align":
96 """Align a renderable to the center."""
97 return cls(
98 renderable,
99 "center",
100 style=style,
101 vertical=vertical,
102 pad=pad,
103 width=width,
104 height=height,
105 )
107 @classmethod
108 def right(
109 cls,
110 renderable: "RenderableType",
111 style: Optional[StyleType] = None,
112 *,
113 vertical: Optional[VerticalAlignMethod] = None,
114 pad: bool = True,
115 width: Optional[int] = None,
116 height: Optional[int] = None,
117 ) -> "Align":
118 """Align a renderable to the right."""
119 return cls(
120 renderable,
121 "right",
122 style=style,
123 vertical=vertical,
124 pad=pad,
125 width=width,
126 height=height,
127 )
129 def __rich_console__(
130 self, console: "Console", options: "ConsoleOptions"
131 ) -> "RenderResult":
132 align = self.align
133 width = console.measure(self.renderable, options=options).maximum
134 rendered = console.render(
135 Constrain(
136 self.renderable, width if self.width is None else min(width, self.width)
137 ),
138 options.update(height=None),
139 )
140 lines = list(Segment.split_lines(rendered))
141 width, height = Segment.get_shape(lines)
142 lines = Segment.set_shape(lines, width, height)
143 new_line = Segment.line()
144 excess_space = options.max_width - width
145 style = console.get_style(self.style) if self.style is not None else None
147 def generate_segments() -> Iterable[Segment]:
148 if excess_space <= 0:
149 # Exact fit
150 for line in lines:
151 yield from line
152 yield new_line
154 elif align == "left":
155 # Pad on the right
156 pad = Segment(" " * excess_space, style) if self.pad else None
157 for line in lines:
158 yield from line
159 if pad:
160 yield pad
161 yield new_line
163 elif align == "center":
164 # Pad left and right
165 left = excess_space // 2
166 pad = Segment(" " * left, style)
167 pad_right = (
168 Segment(" " * (excess_space - left), style) if self.pad else None
169 )
170 for line in lines:
171 if left:
172 yield pad
173 yield from line
174 if pad_right:
175 yield pad_right
176 yield new_line
178 elif align == "right":
179 # Padding on left
180 pad = Segment(" " * excess_space, style)
181 for line in lines:
182 yield pad
183 yield from line
184 yield new_line
186 blank_line = (
187 Segment(f"{' ' * (self.width or options.max_width)}\n", style)
188 if self.pad
189 else Segment("\n")
190 )
192 def blank_lines(count: int) -> Iterable[Segment]:
193 if count > 0:
194 for _ in range(count):
195 yield blank_line
197 vertical_height = self.height or options.height
198 iter_segments: Iterable[Segment]
199 if self.vertical and vertical_height is not None:
200 if self.vertical == "top":
201 bottom_space = vertical_height - height
202 iter_segments = chain(generate_segments(), blank_lines(bottom_space))
203 elif self.vertical == "middle":
204 top_space = (vertical_height - height) // 2
205 bottom_space = vertical_height - top_space - height
206 iter_segments = chain(
207 blank_lines(top_space),
208 generate_segments(),
209 blank_lines(bottom_space),
210 )
211 else: # self.vertical == "bottom":
212 top_space = vertical_height - height
213 iter_segments = chain(blank_lines(top_space), generate_segments())
214 else:
215 iter_segments = generate_segments()
216 if self.style:
217 style = console.get_style(self.style)
218 iter_segments = Segment.apply_style(iter_segments, style)
219 yield from iter_segments
221 def __rich_measure__(
222 self, console: "Console", options: "ConsoleOptions"
223 ) -> Measurement:
224 measurement = Measurement.get(console, options, self.renderable)
225 return measurement
228class VerticalCenter(JupyterMixin):
229 """Vertically aligns a renderable.
231 Warn:
232 This class is deprecated and may be removed in a future version. Use Align class with
233 `vertical="middle"`.
235 Args:
236 renderable (RenderableType): A renderable object.
237 style (StyleType, optional): An optional style to apply to the background. Defaults to None.
238 """
240 def __init__(
241 self,
242 renderable: "RenderableType",
243 style: Optional[StyleType] = None,
244 ) -> None:
245 self.renderable = renderable
246 self.style = style
248 def __repr__(self) -> str:
249 return f"VerticalCenter({self.renderable!r})"
251 def __rich_console__(
252 self, console: "Console", options: "ConsoleOptions"
253 ) -> "RenderResult":
254 style = console.get_style(self.style) if self.style is not None else None
255 lines = console.render_lines(
256 self.renderable, options.update(height=None), pad=False
257 )
258 width, _height = Segment.get_shape(lines)
259 new_line = Segment.line()
260 height = options.height or options.size.height
261 top_space = (height - len(lines)) // 2
262 bottom_space = height - top_space - len(lines)
263 blank_line = Segment(f"{' ' * width}", style)
265 def blank_lines(count: int) -> Iterable[Segment]:
266 for _ in range(count):
267 yield blank_line
268 yield new_line
270 if top_space > 0:
271 yield from blank_lines(top_space)
272 for line in lines:
273 yield from line
274 yield new_line
275 if bottom_space > 0:
276 yield from blank_lines(bottom_space)
278 def __rich_measure__(
279 self, console: "Console", options: "ConsoleOptions"
280 ) -> Measurement:
281 measurement = Measurement.get(console, options, self.renderable)
282 return measurement
285if __name__ == "__main__": # pragma: no cover
286 from rich.console import Console, Group
287 from rich.highlighter import ReprHighlighter
288 from rich.panel import Panel
290 highlighter = ReprHighlighter()
291 console = Console()
293 panel = Panel(
294 Group(
295 Align.left(highlighter("align='left'")),
296 Align.center(highlighter("align='center'")),
297 Align.right(highlighter("align='right'")),
298 ),
299 width=60,
300 style="on dark_blue",
301 title="Align",
302 )
304 console.print(
305 Align.center(panel, vertical="middle", style="on red", height=console.height)
306 )