Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/panel.py: 18%
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 typing import TYPE_CHECKING, Optional
3from .align import AlignMethod
4from .box import ROUNDED, Box
5from .cells import cell_len
6from .jupyter import JupyterMixin
7from .measure import Measurement, measure_renderables
8from .padding import Padding, PaddingDimensions
9from .segment import Segment
10from .style import Style, StyleType
11from .text import Text, TextType
13if TYPE_CHECKING:
14 from .console import Console, ConsoleOptions, RenderableType, RenderResult
17class Panel(JupyterMixin):
18 """A console renderable that draws a border around its contents.
20 Example:
21 >>> console.print(Panel("Hello, World!"))
23 Args:
24 renderable (RenderableType): A console renderable object.
25 box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`.
26 Defaults to box.ROUNDED.
27 safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
28 expand (bool, optional): If True the panel will stretch to fill the console
29 width, otherwise it will be sized to fit the contents. Defaults to True.
30 style (str, optional): The style of the panel (border and contents). Defaults to "none".
31 border_style (str, optional): The style of the border. Defaults to "none".
32 width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
33 height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.
34 padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.
35 highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.
36 """
38 def __init__(
39 self,
40 renderable: "RenderableType",
41 box: Box = ROUNDED,
42 *,
43 title: Optional[TextType] = None,
44 title_align: AlignMethod = "center",
45 subtitle: Optional[TextType] = None,
46 subtitle_align: AlignMethod = "center",
47 safe_box: Optional[bool] = None,
48 expand: bool = True,
49 style: StyleType = "none",
50 border_style: StyleType = "none",
51 width: Optional[int] = None,
52 height: Optional[int] = None,
53 padding: PaddingDimensions = (0, 1),
54 highlight: bool = False,
55 ) -> None:
56 self.renderable = renderable
57 self.box = box
58 self.title = title
59 self.title_align: AlignMethod = title_align
60 self.subtitle = subtitle
61 self.subtitle_align = subtitle_align
62 self.safe_box = safe_box
63 self.expand = expand
64 self.style = style
65 self.border_style = border_style
66 self.width = width
67 self.height = height
68 self.padding = padding
69 self.highlight = highlight
71 @classmethod
72 def fit(
73 cls,
74 renderable: "RenderableType",
75 box: Box = ROUNDED,
76 *,
77 title: Optional[TextType] = None,
78 title_align: AlignMethod = "center",
79 subtitle: Optional[TextType] = None,
80 subtitle_align: AlignMethod = "center",
81 safe_box: Optional[bool] = None,
82 style: StyleType = "none",
83 border_style: StyleType = "none",
84 width: Optional[int] = None,
85 height: Optional[int] = None,
86 padding: PaddingDimensions = (0, 1),
87 highlight: bool = False,
88 ) -> "Panel":
89 """An alternative constructor that sets expand=False."""
90 return cls(
91 renderable,
92 box,
93 title=title,
94 title_align=title_align,
95 subtitle=subtitle,
96 subtitle_align=subtitle_align,
97 safe_box=safe_box,
98 style=style,
99 border_style=border_style,
100 width=width,
101 height=height,
102 padding=padding,
103 highlight=highlight,
104 expand=False,
105 )
107 @property
108 def _title(self) -> Optional[Text]:
109 if self.title:
110 title_text = (
111 Text.from_markup(self.title)
112 if isinstance(self.title, str)
113 else self.title.copy()
114 )
115 title_text.end = ""
116 title_text.plain = title_text.plain.replace("\n", " ")
117 title_text.no_wrap = True
118 title_text.expand_tabs()
119 title_text.pad(1)
120 return title_text
121 return None
123 @property
124 def _subtitle(self) -> Optional[Text]:
125 if self.subtitle:
126 subtitle_text = (
127 Text.from_markup(self.subtitle)
128 if isinstance(self.subtitle, str)
129 else self.subtitle.copy()
130 )
131 subtitle_text.end = ""
132 subtitle_text.plain = subtitle_text.plain.replace("\n", " ")
133 subtitle_text.no_wrap = True
134 subtitle_text.expand_tabs()
135 subtitle_text.pad(1)
136 return subtitle_text
137 return None
139 def __rich_console__(
140 self, console: "Console", options: "ConsoleOptions"
141 ) -> "RenderResult":
142 _padding = Padding.unpack(self.padding)
143 renderable = (
144 Padding(self.renderable, _padding) if any(_padding) else self.renderable
145 )
146 style = console.get_style(self.style)
147 border_style = style + console.get_style(self.border_style)
148 width = (
149 options.max_width
150 if self.width is None
151 else min(options.max_width, self.width)
152 )
154 safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
155 box = self.box.substitute(options, safe=safe_box)
157 def align_text(
158 text: Text, width: int, align: str, character: str, style: Style
159 ) -> Text:
160 """Gets new aligned text.
162 Args:
163 text (Text): Title or subtitle text.
164 width (int): Desired width.
165 align (str): Alignment.
166 character (str): Character for alignment.
167 style (Style): Border style
169 Returns:
170 Text: New text instance
171 """
172 text = text.copy()
173 text.truncate(width)
174 excess_space = width - cell_len(text.plain)
175 if excess_space:
176 if align == "left":
177 return Text.assemble(
178 text,
179 (character * excess_space, style),
180 no_wrap=True,
181 end="",
182 )
183 elif align == "center":
184 left = excess_space // 2
185 return Text.assemble(
186 (character * left, style),
187 text,
188 (character * (excess_space - left), style),
189 no_wrap=True,
190 end="",
191 )
192 else:
193 return Text.assemble(
194 (character * excess_space, style),
195 text,
196 no_wrap=True,
197 end="",
198 )
199 return text
201 title_text = self._title
202 if title_text is not None:
203 title_text.stylize_before(border_style)
205 child_width = (
206 width - 2
207 if self.expand
208 else console.measure(
209 renderable, options=options.update_width(width - 2)
210 ).maximum
211 )
212 child_height = self.height or options.height or None
213 if child_height:
214 child_height -= 2
215 if title_text is not None:
216 child_width = min(
217 options.max_width - 2, max(child_width, title_text.cell_len + 2)
218 )
220 width = child_width + 2
221 child_options = options.update(
222 width=child_width, height=child_height, highlight=self.highlight
223 )
224 lines = console.render_lines(renderable, child_options, style=style)
226 line_start = Segment(box.mid_left, border_style)
227 line_end = Segment(f"{box.mid_right}", border_style)
228 new_line = Segment.line()
229 if title_text is None or width <= 4:
230 yield Segment(box.get_top([width - 2]), border_style)
231 else:
232 title_text = align_text(
233 title_text,
234 width - 4,
235 self.title_align,
236 box.top,
237 border_style,
238 )
239 yield Segment(box.top_left + box.top, border_style)
240 yield from console.render(title_text, child_options.update_width(width - 4))
241 yield Segment(box.top + box.top_right, border_style)
243 yield new_line
244 for line in lines:
245 yield line_start
246 yield from line
247 yield line_end
248 yield new_line
250 subtitle_text = self._subtitle
251 if subtitle_text is not None:
252 subtitle_text.stylize_before(border_style)
254 if subtitle_text is None or width <= 4:
255 yield Segment(box.get_bottom([width - 2]), border_style)
256 else:
257 subtitle_text = align_text(
258 subtitle_text,
259 width - 4,
260 self.subtitle_align,
261 box.bottom,
262 border_style,
263 )
264 yield Segment(box.bottom_left + box.bottom, border_style)
265 yield from console.render(
266 subtitle_text, child_options.update_width(width - 4)
267 )
268 yield Segment(box.bottom + box.bottom_right, border_style)
270 yield new_line
272 def __rich_measure__(
273 self, console: "Console", options: "ConsoleOptions"
274 ) -> "Measurement":
275 _title = self._title
276 _, right, _, left = Padding.unpack(self.padding)
277 padding = left + right
278 renderables = [self.renderable, _title] if _title else [self.renderable]
280 if self.width is None:
281 width = (
282 measure_renderables(
283 console,
284 options.update_width(options.max_width - padding - 2),
285 renderables,
286 ).maximum
287 + padding
288 + 2
289 )
290 else:
291 width = self.width
292 return Measurement(width, width)
295if __name__ == "__main__": # pragma: no cover
296 from .console import Console
298 c = Console()
300 from .box import DOUBLE, ROUNDED
301 from .padding import Padding
303 p = Panel(
304 "Hello, World!",
305 title="rich.Panel",
306 style="white on blue",
307 box=DOUBLE,
308 padding=1,
309 )
311 c.print()
312 c.print(p)