Tipragot
628be439b8
Cela permet de ne pas avoir de problèmes de compatibilité car python est dans le git.
309 lines
10 KiB
Python
309 lines
10 KiB
Python
from typing import TYPE_CHECKING, Optional
|
|
|
|
from .align import AlignMethod
|
|
from .box import ROUNDED, Box
|
|
from .cells import cell_len
|
|
from .jupyter import JupyterMixin
|
|
from .measure import Measurement, measure_renderables
|
|
from .padding import Padding, PaddingDimensions
|
|
from .segment import Segment
|
|
from .style import Style, StyleType
|
|
from .text import Text, TextType
|
|
|
|
if TYPE_CHECKING:
|
|
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
|
|
|
|
|
class Panel(JupyterMixin):
|
|
"""A console renderable that draws a border around its contents.
|
|
|
|
Example:
|
|
>>> console.print(Panel("Hello, World!"))
|
|
|
|
Args:
|
|
renderable (RenderableType): A console renderable object.
|
|
box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`.
|
|
Defaults to box.ROUNDED.
|
|
safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
|
|
expand (bool, optional): If True the panel will stretch to fill the console
|
|
width, otherwise it will be sized to fit the contents. Defaults to True.
|
|
style (str, optional): The style of the panel (border and contents). Defaults to "none".
|
|
border_style (str, optional): The style of the border. Defaults to "none".
|
|
width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
|
|
height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.
|
|
padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.
|
|
highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
renderable: "RenderableType",
|
|
box: Box = ROUNDED,
|
|
*,
|
|
title: Optional[TextType] = None,
|
|
title_align: AlignMethod = "center",
|
|
subtitle: Optional[TextType] = None,
|
|
subtitle_align: AlignMethod = "center",
|
|
safe_box: Optional[bool] = None,
|
|
expand: bool = True,
|
|
style: StyleType = "none",
|
|
border_style: StyleType = "none",
|
|
width: Optional[int] = None,
|
|
height: Optional[int] = None,
|
|
padding: PaddingDimensions = (0, 1),
|
|
highlight: bool = False,
|
|
) -> None:
|
|
self.renderable = renderable
|
|
self.box = box
|
|
self.title = title
|
|
self.title_align: AlignMethod = title_align
|
|
self.subtitle = subtitle
|
|
self.subtitle_align = subtitle_align
|
|
self.safe_box = safe_box
|
|
self.expand = expand
|
|
self.style = style
|
|
self.border_style = border_style
|
|
self.width = width
|
|
self.height = height
|
|
self.padding = padding
|
|
self.highlight = highlight
|
|
|
|
@classmethod
|
|
def fit(
|
|
cls,
|
|
renderable: "RenderableType",
|
|
box: Box = ROUNDED,
|
|
*,
|
|
title: Optional[TextType] = None,
|
|
title_align: AlignMethod = "center",
|
|
subtitle: Optional[TextType] = None,
|
|
subtitle_align: AlignMethod = "center",
|
|
safe_box: Optional[bool] = None,
|
|
style: StyleType = "none",
|
|
border_style: StyleType = "none",
|
|
width: Optional[int] = None,
|
|
padding: PaddingDimensions = (0, 1),
|
|
) -> "Panel":
|
|
"""An alternative constructor that sets expand=False."""
|
|
return cls(
|
|
renderable,
|
|
box,
|
|
title=title,
|
|
title_align=title_align,
|
|
subtitle=subtitle,
|
|
subtitle_align=subtitle_align,
|
|
safe_box=safe_box,
|
|
style=style,
|
|
border_style=border_style,
|
|
width=width,
|
|
padding=padding,
|
|
expand=False,
|
|
)
|
|
|
|
@property
|
|
def _title(self) -> Optional[Text]:
|
|
if self.title:
|
|
title_text = (
|
|
Text.from_markup(self.title)
|
|
if isinstance(self.title, str)
|
|
else self.title.copy()
|
|
)
|
|
title_text.end = ""
|
|
title_text.plain = title_text.plain.replace("\n", " ")
|
|
title_text.no_wrap = True
|
|
title_text.expand_tabs()
|
|
title_text.pad(1)
|
|
return title_text
|
|
return None
|
|
|
|
@property
|
|
def _subtitle(self) -> Optional[Text]:
|
|
if self.subtitle:
|
|
subtitle_text = (
|
|
Text.from_markup(self.subtitle)
|
|
if isinstance(self.subtitle, str)
|
|
else self.subtitle.copy()
|
|
)
|
|
subtitle_text.end = ""
|
|
subtitle_text.plain = subtitle_text.plain.replace("\n", " ")
|
|
subtitle_text.no_wrap = True
|
|
subtitle_text.expand_tabs()
|
|
subtitle_text.pad(1)
|
|
return subtitle_text
|
|
return None
|
|
|
|
def __rich_console__(
|
|
self, console: "Console", options: "ConsoleOptions"
|
|
) -> "RenderResult":
|
|
_padding = Padding.unpack(self.padding)
|
|
renderable = (
|
|
Padding(self.renderable, _padding) if any(_padding) else self.renderable
|
|
)
|
|
style = console.get_style(self.style)
|
|
border_style = style + console.get_style(self.border_style)
|
|
width = (
|
|
options.max_width
|
|
if self.width is None
|
|
else min(options.max_width, self.width)
|
|
)
|
|
|
|
safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
|
|
box = self.box.substitute(options, safe=safe_box)
|
|
|
|
def align_text(
|
|
text: Text, width: int, align: str, character: str, style: Style
|
|
) -> Text:
|
|
"""Gets new aligned text.
|
|
|
|
Args:
|
|
text (Text): Title or subtitle text.
|
|
width (int): Desired width.
|
|
align (str): Alignment.
|
|
character (str): Character for alignment.
|
|
style (Style): Border style
|
|
|
|
Returns:
|
|
Text: New text instance
|
|
"""
|
|
text = text.copy()
|
|
text.truncate(width)
|
|
excess_space = width - cell_len(text.plain)
|
|
if excess_space:
|
|
if align == "left":
|
|
return Text.assemble(
|
|
text,
|
|
(character * excess_space, style),
|
|
no_wrap=True,
|
|
end="",
|
|
)
|
|
elif align == "center":
|
|
left = excess_space // 2
|
|
return Text.assemble(
|
|
(character * left, style),
|
|
text,
|
|
(character * (excess_space - left), style),
|
|
no_wrap=True,
|
|
end="",
|
|
)
|
|
else:
|
|
return Text.assemble(
|
|
(character * excess_space, style),
|
|
text,
|
|
no_wrap=True,
|
|
end="",
|
|
)
|
|
return text
|
|
|
|
title_text = self._title
|
|
if title_text is not None:
|
|
title_text.stylize_before(border_style)
|
|
|
|
child_width = (
|
|
width - 2
|
|
if self.expand
|
|
else console.measure(
|
|
renderable, options=options.update_width(width - 2)
|
|
).maximum
|
|
)
|
|
child_height = self.height or options.height or None
|
|
if child_height:
|
|
child_height -= 2
|
|
if title_text is not None:
|
|
child_width = min(
|
|
options.max_width - 2, max(child_width, title_text.cell_len + 2)
|
|
)
|
|
|
|
width = child_width + 2
|
|
child_options = options.update(
|
|
width=child_width, height=child_height, highlight=self.highlight
|
|
)
|
|
lines = console.render_lines(renderable, child_options, style=style)
|
|
|
|
line_start = Segment(box.mid_left, border_style)
|
|
line_end = Segment(f"{box.mid_right}", border_style)
|
|
new_line = Segment.line()
|
|
if title_text is None or width <= 4:
|
|
yield Segment(box.get_top([width - 2]), border_style)
|
|
else:
|
|
title_text = align_text(
|
|
title_text,
|
|
width - 4,
|
|
self.title_align,
|
|
box.top,
|
|
border_style,
|
|
)
|
|
yield Segment(box.top_left + box.top, border_style)
|
|
yield from console.render(title_text, child_options.update_width(width - 4))
|
|
yield Segment(box.top + box.top_right, border_style)
|
|
|
|
yield new_line
|
|
for line in lines:
|
|
yield line_start
|
|
yield from line
|
|
yield line_end
|
|
yield new_line
|
|
|
|
subtitle_text = self._subtitle
|
|
if subtitle_text is not None:
|
|
subtitle_text.stylize_before(border_style)
|
|
|
|
if subtitle_text is None or width <= 4:
|
|
yield Segment(box.get_bottom([width - 2]), border_style)
|
|
else:
|
|
subtitle_text = align_text(
|
|
subtitle_text,
|
|
width - 4,
|
|
self.subtitle_align,
|
|
box.bottom,
|
|
border_style,
|
|
)
|
|
yield Segment(box.bottom_left + box.bottom, border_style)
|
|
yield from console.render(
|
|
subtitle_text, child_options.update_width(width - 4)
|
|
)
|
|
yield Segment(box.bottom + box.bottom_right, border_style)
|
|
|
|
yield new_line
|
|
|
|
def __rich_measure__(
|
|
self, console: "Console", options: "ConsoleOptions"
|
|
) -> "Measurement":
|
|
_title = self._title
|
|
_, right, _, left = Padding.unpack(self.padding)
|
|
padding = left + right
|
|
renderables = [self.renderable, _title] if _title else [self.renderable]
|
|
|
|
if self.width is None:
|
|
width = (
|
|
measure_renderables(
|
|
console,
|
|
options.update_width(options.max_width - padding - 2),
|
|
renderables,
|
|
).maximum
|
|
+ padding
|
|
+ 2
|
|
)
|
|
else:
|
|
width = self.width
|
|
return Measurement(width, width)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
from .console import Console
|
|
|
|
c = Console()
|
|
|
|
from .box import DOUBLE, ROUNDED
|
|
from .padding import Padding
|
|
|
|
p = Panel(
|
|
"Hello, World!",
|
|
title="rich.Panel",
|
|
style="white on blue",
|
|
box=DOUBLE,
|
|
padding=1,
|
|
)
|
|
|
|
c.print()
|
|
c.print(p)
|