123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- import sys
- from typing import TYPE_CHECKING, Iterable, List
- if sys.version_info >= (3, 8):
- from typing import Literal
- else:
- from pip._vendor.typing_extensions import Literal # pragma: no cover
- from ._loop import loop_last
- if TYPE_CHECKING:
- from pip._vendor.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)
- ASCII: Box = Box(
- """\
- +--+
- | ||
- |-+|
- | ||
- |-+|
- |-+|
- | ||
- +--+
- """,
- ascii=True,
- )
- ASCII2: Box = Box(
- """\
- +-++
- | ||
- +-++
- | ||
- +-++
- +-++
- | ||
- +-++
- """,
- ascii=True,
- )
- ASCII_DOUBLE_HEAD: Box = Box(
- """\
- +-++
- | ||
- +=++
- | ||
- +-++
- +-++
- | ||
- +-++
- """,
- ascii=True,
- )
- SQUARE: Box = Box(
- """\
- ┌─┬┐
- │ ││
- ├─┼┤
- │ ││
- ├─┼┤
- ├─┼┤
- │ ││
- └─┴┘
- """
- )
- SQUARE_DOUBLE_HEAD: Box = Box(
- """\
- ┌─┬┐
- │ ││
- ╞═╪╡
- │ ││
- ├─┼┤
- ├─┼┤
- │ ││
- └─┴┘
- """
- )
- MINIMAL: Box = Box(
- """\
- ╷
- │
- ╶─┼╴
- │
- ╶─┼╴
- ╶─┼╴
- │
- ╵
- """
- )
- MINIMAL_HEAVY_HEAD: Box = Box(
- """\
- ╷
- │
- ╺━┿╸
- │
- ╶─┼╴
- ╶─┼╴
- │
- ╵
- """
- )
- MINIMAL_DOUBLE_HEAD: Box = Box(
- """\
- ╷
- │
- ═╪
- │
- ─┼
- ─┼
- │
- ╵
- """
- )
- SIMPLE: Box = Box(
- """\
-
-
- ──
-
-
- ──
-
-
- """
- )
- SIMPLE_HEAD: Box = Box(
- """\
-
-
- ──
-
-
-
-
-
- """
- )
- SIMPLE_HEAVY: Box = Box(
- """\
-
-
- ━━
-
-
- ━━
-
-
- """
- )
- HORIZONTALS: Box = Box(
- """\
- ──
-
- ──
-
- ──
- ──
-
- ──
- """
- )
- ROUNDED: Box = Box(
- """\
- ╭─┬╮
- │ ││
- ├─┼┤
- │ ││
- ├─┼┤
- ├─┼┤
- │ ││
- ╰─┴╯
- """
- )
- HEAVY: Box = Box(
- """\
- ┏━┳┓
- ┃ ┃┃
- ┣━╋┫
- ┃ ┃┃
- ┣━╋┫
- ┣━╋┫
- ┃ ┃┃
- ┗━┻┛
- """
- )
- HEAVY_EDGE: Box = Box(
- """\
- ┏━┯┓
- ┃ │┃
- ┠─┼┨
- ┃ │┃
- ┠─┼┨
- ┠─┼┨
- ┃ │┃
- ┗━┷┛
- """
- )
- HEAVY_HEAD: Box = Box(
- """\
- ┏━┳┓
- ┃ ┃┃
- ┡━╇┩
- │ ││
- ├─┼┤
- ├─┼┤
- │ ││
- └─┴┘
- """
- )
- DOUBLE: Box = Box(
- """\
- ╔═╦╗
- ║ ║║
- ╠═╬╣
- ║ ║║
- ╠═╬╣
- ╠═╬╣
- ║ ║║
- ╚═╩╝
- """
- )
- DOUBLE_EDGE: Box = Box(
- """\
- ╔═╤╗
- ║ │║
- ╟─┼╢
- ║ │║
- ╟─┼╢
- ╟─┼╢
- ║ │║
- ╚═╧╝
- """
- )
- MARKDOWN: Box = Box(
- """\
-
- | ||
- |-||
- | ||
- |-||
- |-||
- | ||
-
- """,
- ascii=True,
- )
- # 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 pip._vendor.rich.columns import Columns
- from pip._vendor.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")
|