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.
32 Example:
33 .. code-block:: python
35 from rich.console import Console
36 from rich.align import Align
37 from rich.panel import Panel
39 console = Console()
40 # Create a panel 20 characters wide
41 p = Panel("Hello, [b]World[/b]!", style="on green", width=20)
43 # Renders the panel centered in the terminal
44 console.print(Align(p, align="center"))
45 """
47 def __init__(
48 self,
49 renderable: "RenderableType",
50 align: AlignMethod = "left",
51 style: Optional[StyleType] = None,
52 *,
53 vertical: Optional[VerticalAlignMethod] = None,
54 pad: bool = True,
55 width: Optional[int] = None,
56 height: Optional[int] = None,
57 ) -> None:
58 if align not in ("left", "center", "right"):
59 raise ValueError(
60 f'invalid value for align, expected "left", "center", or "right" (not {align!r})'
61 )
62 if vertical is not None and vertical not in ("top", "middle", "bottom"):
63 raise ValueError(
64 f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})'
65 )
66 self.renderable = renderable
67 self.align = align
68 self.style = style
69 self.vertical = vertical
70 self.pad = pad
71 self.width = width
72 self.height = height
74 def __repr__(self) -> str:
75 return f"Align({self.renderable!r}, {self.align!r})"
77 @classmethod
78 def left(
79 cls,
80 renderable: "RenderableType",
81 style: Optional[StyleType] = None,
82 *,
83 vertical: Optional[VerticalAlignMethod] = None,
84 pad: bool = True,
85 width: Optional[int] = None,
86 height: Optional[int] = None,
87 ) -> "Align":
88 """Align a renderable to the left."""
89 return cls(
90 renderable,
91 "left",
92 style=style,
93 vertical=vertical,
94 pad=pad,
95 width=width,
96 height=height,
97 )
99 @classmethod
100 def center(
101 cls,
102 renderable: "RenderableType",
103 style: Optional[StyleType] = None,
104 *,
105 vertical: Optional[VerticalAlignMethod] = None,
106 pad: bool = True,
107 width: Optional[int] = None,
108 height: Optional[int] = None,
109 ) -> "Align":
110 """Align a renderable to the center."""
111 return cls(
112 renderable,
113 "center",
114 style=style,
115 vertical=vertical,
116 pad=pad,
117 width=width,
118 height=height,
119 )
121 @classmethod
122 def right(
123 cls,
124 renderable: "RenderableType",
125 style: Optional[StyleType] = None,
126 *,
127 vertical: Optional[VerticalAlignMethod] = None,
128 pad: bool = True,
129 width: Optional[int] = None,
130 height: Optional[int] = None,
131 ) -> "Align":
132 """Align a renderable to the right."""
133 return cls(
134 renderable,
135 "right",
136 style=style,
137 vertical=vertical,
138 pad=pad,
139 width=width,
140 height=height,
141 )
143 def __rich_console__(
144 self, console: "Console", options: "ConsoleOptions"
145 ) -> "RenderResult":
146 align = self.align
147 width = console.measure(self.renderable, options=options).maximum
148 rendered = console.render(
149 Constrain(
150 self.renderable, width if self.width is None else min(width, self.width)
151 ),
152 options.update(height=None),
153 )
154 lines = list(Segment.split_lines(rendered))
155 width, height = Segment.get_shape(lines)
156 lines = Segment.set_shape(lines, width, height)
157 new_line = Segment.line()
158 excess_space = options.max_width - width
159 style = console.get_style(self.style) if self.style is not None else None
161 def generate_segments() -> Iterable[Segment]:
162 if excess_space <= 0:
163 # Exact fit
164 for line in lines:
165 yield from line
166 yield new_line
168 elif align == "left":
169 # Pad on the right
170 pad = Segment(" " * excess_space, style) if self.pad else None
171 for line in lines:
172 yield from line
173 if pad:
174 yield pad
175 yield new_line
177 elif align == "center":
178 # Pad left and right
179 left = excess_space // 2
180 pad = Segment(" " * left, style)
181 pad_right = (
182 Segment(" " * (excess_space - left), style) if self.pad else None
183 )
184 for line in lines:
185 if left:
186 yield pad
187 yield from line
188 if pad_right:
189 yield pad_right
190 yield new_line
192 elif align == "right":
193 # Padding on left
194 pad = Segment(" " * excess_space, style)
195 for line in lines:
196 yield pad
197 yield from line
198 yield new_line
200 blank_line = (
201 Segment(f"{' ' * (self.width or options.max_width)}\n", style)
202 if self.pad
203 else Segment("\n")
204 )
206 def blank_lines(count: int) -> Iterable[Segment]:
207 if count > 0:
208 for _ in range(count):
209 yield blank_line
211 vertical_height = self.height or options.height
212 iter_segments: Iterable[Segment]
213 if self.vertical and vertical_height is not None:
214 if self.vertical == "top":
215 bottom_space = vertical_height - height
216 iter_segments = chain(generate_segments(), blank_lines(bottom_space))
217 elif self.vertical == "middle":
218 top_space = (vertical_height - height) // 2
219 bottom_space = vertical_height - top_space - height
220 iter_segments = chain(
221 blank_lines(top_space),
222 generate_segments(),
223 blank_lines(bottom_space),
224 )
225 else: # self.vertical == "bottom":
226 top_space = vertical_height - height
227 iter_segments = chain(blank_lines(top_space), generate_segments())
228 else:
229 iter_segments = generate_segments()
230 if self.style:
231 style = console.get_style(self.style)
232 iter_segments = Segment.apply_style(iter_segments, style)
233 yield from iter_segments
235 def __rich_measure__(
236 self, console: "Console", options: "ConsoleOptions"
237 ) -> Measurement:
238 measurement = Measurement.get(console, options, self.renderable)
239 return measurement
242class VerticalCenter(JupyterMixin):
243 """Vertically aligns a renderable.
245 Warn:
246 This class is deprecated and may be removed in a future version. Use Align class with
247 `vertical="middle"`.
249 Args:
250 renderable (RenderableType): A renderable object.
251 style (StyleType, optional): An optional style to apply to the background. Defaults to None.
252 """
254 def __init__(
255 self,
256 renderable: "RenderableType",
257 style: Optional[StyleType] = None,
258 ) -> None:
259 self.renderable = renderable
260 self.style = style
262 def __repr__(self) -> str:
263 return f"VerticalCenter({self.renderable!r})"
265 def __rich_console__(
266 self, console: "Console", options: "ConsoleOptions"
267 ) -> "RenderResult":
268 style = console.get_style(self.style) if self.style is not None else None
269 lines = console.render_lines(
270 self.renderable, options.update(height=None), pad=False
271 )
272 width, _height = Segment.get_shape(lines)
273 new_line = Segment.line()
274 height = options.height or options.size.height
275 top_space = (height - len(lines)) // 2
276 bottom_space = height - top_space - len(lines)
277 blank_line = Segment(f"{' ' * width}", style)
279 def blank_lines(count: int) -> Iterable[Segment]:
280 for _ in range(count):
281 yield blank_line
282 yield new_line
284 if top_space > 0:
285 yield from blank_lines(top_space)
286 for line in lines:
287 yield from line
288 yield new_line
289 if bottom_space > 0:
290 yield from blank_lines(bottom_space)
292 def __rich_measure__(
293 self, console: "Console", options: "ConsoleOptions"
294 ) -> Measurement:
295 measurement = Measurement.get(console, options, self.renderable)
296 return measurement
299if __name__ == "__main__": # pragma: no cover
300 from rich.console import Console, Group
301 from rich.highlighter import ReprHighlighter
302 from rich.panel import Panel
304 highlighter = ReprHighlighter()
305 console = Console()
307 panel = Panel(
308 Group(
309 Align.left(highlighter("align='left'")),
310 Align.center(highlighter("align='center'")),
311 Align.right(highlighter("align='right'")),
312 ),
313 width=60,
314 style="on dark_blue",
315 title="Align",
316 )
318 console.print(
319 Align.center(panel, vertical="middle", style="on red", height=console.height)
320 )