Varia's website https://varia.zone
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

480 lines
10 KiB

import sys
from typing import TYPE_CHECKING, Iterable, List
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal # pragma: no cover
from ._loop import loop_last
if TYPE_CHECKING:
from rich.console import ConsoleOptions
class Box:
"""Defines characters to render boxes.
┌─┬┐ top
│ ││ head
├─┼┤ head_row
│ ││ mid
├─┼┤ row
├─┼┤ foot_row
│ ││ foot
└─┴┘ bottom
Args:
box (str): Characters making up box.
ascii (bool, optional): True if this box uses ascii characters only. Default is False.
"""
def __init__(self, box: str, *, ascii: bool = False) -> None:
self._box = box
self.ascii = ascii
line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()
# top
self.top_left, self.top, self.top_divider, self.top_right = iter(line1)
# head
self.head_left, _, self.head_vertical, self.head_right = iter(line2)
# head_row
(
self.head_row_left,
self.head_row_horizontal,
self.head_row_cross,
self.head_row_right,
) = iter(line3)
# mid
self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)
# row
self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5)
# foot_row
(
self.foot_row_left,
self.foot_row_horizontal,
self.foot_row_cross,
self.foot_row_right,
) = iter(line6)
# foot
self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7)
# bottom
self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter(
line8
)
def __repr__(self) -> str:
return "Box(...)"
def __str__(self) -> str:
return self._box
def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box":
"""Substitute this box for another if it won't render due to platform issues.
Args:
options (ConsoleOptions): Console options used in rendering.
safe (bool, optional): Substitute this for another Box if there are known problems
displaying on the platform (currently only relevant on Windows). Default is True.
Returns:
Box: A different Box or the same Box.
"""
box = self
if options.legacy_windows and safe:
box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box)
if options.ascii_only and not box.ascii:
box = ASCII
return box
def get_plain_headed_box(self) -> "Box":
"""If this box uses special characters for the borders of the header, then
return the equivalent box that does not.
Returns:
Box: The most similar Box that doesn't use header-specific box characters.
If the current Box already satisfies this criterion, then it's returned.
"""
return PLAIN_HEADED_SUBSTITUTIONS.get(self, self)
def get_top(self, widths: Iterable[int]) -> str:
"""Get the top of a simple box.
Args:
widths (List[int]): Widths of columns.
Returns:
str: A string of box characters.
"""
parts: List[str] = []
append = parts.append
append(self.top_left)
for last, width in loop_last(widths):
append(self.top * width)
if not last:
append(self.top_divider)
append(self.top_right)
return "".join(parts)
def get_row(
self,
widths: Iterable[int],
level: Literal["head", "row", "foot", "mid"] = "row",
edge: bool = True,
) -> str:
"""Get the top of a simple box.
Args:
width (List[int]): Widths of columns.
Returns:
str: A string of box characters.
"""
if level == "head":
left = self.head_row_left
horizontal = self.head_row_horizontal
cross = self.head_row_cross
right = self.head_row_right
elif level == "row":
left = self.row_left
horizontal = self.row_horizontal
cross = self.row_cross
right = self.row_right
elif level == "mid":
left = self.mid_left
horizontal = " "
cross = self.mid_vertical
right = self.mid_right
elif level == "foot":
left = self.foot_row_left
horizontal = self.foot_row_horizontal
cross = self.foot_row_cross
right = self.foot_row_right
else:
raise ValueError("level must be 'head', 'row' or 'foot'")
parts: List[str] = []
append = parts.append
if edge:
append(left)
for last, width in loop_last(widths):
append(horizontal * width)
if not last:
append(cross)
if edge:
append(right)
return "".join(parts)
def get_bottom(self, widths: Iterable[int]) -> str:
"""Get the bottom of a simple box.
Args:
widths (List[int]): Widths of columns.
Returns:
str: A string of box characters.
"""
parts: List[str] = []
append = parts.append
append(self.bottom_left)
for last, width in loop_last(widths):
append(self.bottom * width)
if not last:
append(self.bottom_divider)
append(self.bottom_right)
return "".join(parts)
# fmt: off
ASCII: Box = Box(
"+--+\n"
"| ||\n"
"|-+|\n"
"| ||\n"
"|-+|\n"
"|-+|\n"
"| ||\n"
"+--+\n",
ascii=True,
)
ASCII2: Box = Box(
"+-++\n"
"| ||\n"
"+-++\n"
"| ||\n"
"+-++\n"
"+-++\n"
"| ||\n"
"+-++\n",
ascii=True,
)
ASCII_DOUBLE_HEAD: Box = Box(
"+-++\n"
"| ||\n"
"+=++\n"
"| ||\n"
"+-++\n"
"+-++\n"
"| ||\n"
"+-++\n",
ascii=True,
)
SQUARE: Box = Box(
"┌─┬┐\n"
"│ ││\n"
"├─┼┤\n"
"│ ││\n"
"├─┼┤\n"
"├─┼┤\n"
"│ ││\n"
"└─┴┘\n"
)
SQUARE_DOUBLE_HEAD: Box = Box(
"┌─┬┐\n"
"│ ││\n"
"╞═╪╡\n"
"│ ││\n"
"├─┼┤\n"
"├─┼┤\n"
"│ ││\n"
"└─┴┘\n"
)
MINIMAL: Box = Box(
"\n"
"\n"
"╶─┼╴\n"
"\n"
"╶─┼╴\n"
"╶─┼╴\n"
"\n"
"\n"
)
MINIMAL_HEAVY_HEAD: Box = Box(
"\n"
"\n"
"╺━┿╸\n"
"\n"
"╶─┼╴\n"
"╶─┼╴\n"
"\n"
"\n"
)
MINIMAL_DOUBLE_HEAD: Box = Box(
"\n"
"\n"
" ═╪ \n"
"\n"
" ─┼ \n"
" ─┼ \n"
"\n"
"\n"
)
SIMPLE: Box = Box(
" \n"
" \n"
" ── \n"
" \n"
" \n"
" ── \n"
" \n"
" \n"
)
SIMPLE_HEAD: Box = Box(
" \n"
" \n"
" ── \n"
" \n"
" \n"
" \n"
" \n"
" \n"
)
SIMPLE_HEAVY: Box = Box(
" \n"
" \n"
" ━━ \n"
" \n"
" \n"
" ━━ \n"
" \n"
" \n"
)
HORIZONTALS: Box = Box(
" ── \n"
" \n"
" ── \n"
" \n"
" ── \n"
" ── \n"
" \n"
" ── \n"
)
ROUNDED: Box = Box(
"╭─┬╮\n"
"│ ││\n"
"├─┼┤\n"
"│ ││\n"
"├─┼┤\n"
"├─┼┤\n"
"│ ││\n"
"╰─┴╯\n"
)
HEAVY: Box = Box(
"┏━┳┓\n"
"┃ ┃┃\n"
"┣━╋┫\n"
"┃ ┃┃\n"
"┣━╋┫\n"
"┣━╋┫\n"
"┃ ┃┃\n"
"┗━┻┛\n"
)
HEAVY_EDGE: Box = Box(
"┏━┯┓\n"
"┃ │┃\n"
"┠─┼┨\n"
"┃ │┃\n"
"┠─┼┨\n"
"┠─┼┨\n"
"┃ │┃\n"
"┗━┷┛\n"
)
HEAVY_HEAD: Box = Box(
"┏━┳┓\n"
"┃ ┃┃\n"
"┡━╇┩\n"
"│ ││\n"
"├─┼┤\n"
"├─┼┤\n"
"│ ││\n"
"└─┴┘\n"
)
DOUBLE: Box = Box(
"╔═╦╗\n"
"║ ║║\n"
"╠═╬╣\n"
"║ ║║\n"
"╠═╬╣\n"
"╠═╬╣\n"
"║ ║║\n"
"╚═╩╝\n"
)
DOUBLE_EDGE: Box = Box(
"╔═╤╗\n"
"║ │║\n"
"╟─┼╢\n"
"║ │║\n"
"╟─┼╢\n"
"╟─┼╢\n"
"║ │║\n"
"╚═╧╝\n"
)
MARKDOWN: Box = Box(
" \n"
"| ||\n"
"|-||\n"
"| ||\n"
"|-||\n"
"|-||\n"
"| ||\n"
" \n",
ascii=True,
)
# fmt: on
# Map Boxes that don't render with raster fonts on to equivalent that do
LEGACY_WINDOWS_SUBSTITUTIONS = {
ROUNDED: SQUARE,
MINIMAL_HEAVY_HEAD: MINIMAL,
SIMPLE_HEAVY: SIMPLE,
HEAVY: SQUARE,
HEAVY_EDGE: SQUARE,
HEAVY_HEAD: SQUARE,
}
# Map headed boxes to their headerless equivalents
PLAIN_HEADED_SUBSTITUTIONS = {
HEAVY_HEAD: SQUARE,
SQUARE_DOUBLE_HEAD: SQUARE,
MINIMAL_DOUBLE_HEAD: MINIMAL,
MINIMAL_HEAVY_HEAD: MINIMAL,
ASCII_DOUBLE_HEAD: ASCII2,
}
if __name__ == "__main__": # pragma: no cover
from rich.columns import Columns
from rich.panel import Panel
from . import box as box
from .console import Console
from .table import Table
from .text import Text
console = Console(record=True)
BOXES = [
"ASCII",
"ASCII2",
"ASCII_DOUBLE_HEAD",
"SQUARE",
"SQUARE_DOUBLE_HEAD",
"MINIMAL",
"MINIMAL_HEAVY_HEAD",
"MINIMAL_DOUBLE_HEAD",
"SIMPLE",
"SIMPLE_HEAD",
"SIMPLE_HEAVY",
"HORIZONTALS",
"ROUNDED",
"HEAVY",
"HEAVY_EDGE",
"HEAVY_HEAD",
"DOUBLE",
"DOUBLE_EDGE",
"MARKDOWN",
]
console.print(Panel("[bold green]Box Constants", style="green"), justify="center")
console.print()
columns = Columns(expand=True, padding=2)
for box_name in sorted(BOXES):
table = Table(
show_footer=True, style="dim", border_style="not dim", expand=True
)
table.add_column("Header 1", "Footer 1")
table.add_column("Header 2", "Footer 2")
table.add_row("Cell", "Cell")
table.add_row("Cell", "Cell")
table.box = getattr(box, box_name)
table.title = Text(f"box.{box_name}", style="magenta")
columns.add_renderable(table)
console.print(columns)
# console.save_svg("box.svg")