2 from typing
import TYPE_CHECKING
, Iterable
, List
4 if sys
.version_info
>= (3, 8):
5 from typing
import Literal
7 from pip
._vendor
.typing_extensions
import Literal
# pragma: no cover
10 from ._loop
import loop_last
13 from pip
._vendor
.rich
.console
import ConsoleOptions
17 """Defines characters to render boxes.
29 box (str): Characters making up box.
30 ascii (bool, optional): True if this box uses ascii characters only. Default is False.
33 def __init__(self
, box
: str, *, ascii
: bool = False) -> None:
36 line1
, line2
, line3
, line4
, line5
, line6
, line7
, line8
= box
.splitlines()
38 self
.top_left
, self
.top
, self
.top_divider
, self
.top_right
= iter(line1
)
40 self
.head_left
, _
, self
.head_vertical
, self
.head_right
= iter(line2
)
44 self
.head_row_horizontal
,
50 self
.mid_left
, _
, self
.mid_vertical
, self
.mid_right
= iter(line4
)
52 self
.row_left
, self
.row_horizontal
, self
.row_cross
, self
.row_right
= iter(line5
)
56 self
.foot_row_horizontal
,
61 self
.foot_left
, _
, self
.foot_vertical
, self
.foot_right
= iter(line7
)
63 self
.bottom_left
, self
.bottom
, self
.bottom_divider
, self
.bottom_right
= iter(
67 def __repr__(self
) -> str:
70 def __str__(self
) -> str:
73 def substitute(self
, options
: "ConsoleOptions", safe
: bool = True) -> "Box":
74 """Substitute this box for another if it won't render due to platform issues.
77 options (ConsoleOptions): Console options used in rendering.
78 safe (bool, optional): Substitute this for another Box if there are known problems
79 displaying on the platform (currently only relevant on Windows). Default is True.
82 Box: A different Box or the same Box.
85 if options
.legacy_windows
and safe
:
86 box
= LEGACY_WINDOWS_SUBSTITUTIONS
.get(box
, box
)
87 if options
.ascii_only
and not box
.ascii
:
91 def get_plain_headed_box(self
) -> "Box":
92 """If this box uses special characters for the borders of the header, then
93 return the equivalent box that does not.
96 Box: The most similar Box that doesn't use header-specific box characters.
97 If the current Box already satisfies this criterion, then it's returned.
99 return PLAIN_HEADED_SUBSTITUTIONS
.get(self
, self
)
101 def get_top(self
, widths
: Iterable
[int]) -> str:
102 """Get the top of a simple box.
105 widths (List[int]): Widths of columns.
108 str: A string of box characters.
111 parts
: List
[str] = []
112 append
= parts
.append
113 append(self
.top_left
)
114 for last
, width
in loop_last(widths
):
115 append(self
.top
* width
)
117 append(self
.top_divider
)
118 append(self
.top_right
)
119 return "".join(parts
)
123 widths
: Iterable
[int],
124 level
: Literal
["head", "row", "foot", "mid"] = "row",
127 """Get the top of a simple box.
130 width (List[int]): Widths of columns.
133 str: A string of box characters.
136 left
= self
.head_row_left
137 horizontal
= self
.head_row_horizontal
138 cross
= self
.head_row_cross
139 right
= self
.head_row_right
142 horizontal
= self
.row_horizontal
143 cross
= self
.row_cross
144 right
= self
.row_right
148 cross
= self
.mid_vertical
149 right
= self
.mid_right
150 elif level
== "foot":
151 left
= self
.foot_row_left
152 horizontal
= self
.foot_row_horizontal
153 cross
= self
.foot_row_cross
154 right
= self
.foot_row_right
156 raise ValueError("level must be 'head', 'row' or 'foot'")
158 parts
: List
[str] = []
159 append
= parts
.append
162 for last
, width
in loop_last(widths
):
163 append(horizontal
* width
)
168 return "".join(parts
)
170 def get_bottom(self
, widths
: Iterable
[int]) -> str:
171 """Get the bottom of a simple box.
174 widths (List[int]): Widths of columns.
177 str: A string of box characters.
180 parts
: List
[str] = []
181 append
= parts
.append
182 append(self
.bottom_left
)
183 for last
, width
in loop_last(widths
):
184 append(self
.bottom
* width
)
186 append(self
.bottom_divider
)
187 append(self
.bottom_right
)
188 return "".join(parts
)
219 ASCII_DOUBLE_HEAD
: Box
= Box(
246 SQUARE_DOUBLE_HEAD
: Box
= Box(
273 MINIMAL_HEAVY_HEAD
: Box
= Box(
286 MINIMAL_DOUBLE_HEAD
: Box
= Box(
313 SIMPLE_HEAD
: Box
= Box(
327 SIMPLE_HEAVY
: Box
= Box(
341 HORIZONTALS
: Box
= Box(
380 HEAVY_EDGE
: Box
= Box(
393 HEAVY_HEAD
: Box
= Box(
419 DOUBLE_EDGE
: Box
= Box(
446 # Map Boxes that don't render with raster fonts on to equivalent that do
447 LEGACY_WINDOWS_SUBSTITUTIONS
= {
449 MINIMAL_HEAVY_HEAD
: MINIMAL
,
450 SIMPLE_HEAVY
: SIMPLE
,
456 # Map headed boxes to their headerless equivalents
457 PLAIN_HEADED_SUBSTITUTIONS
= {
459 SQUARE_DOUBLE_HEAD
: SQUARE
,
460 MINIMAL_DOUBLE_HEAD
: MINIMAL
,
461 MINIMAL_HEAVY_HEAD
: MINIMAL
,
462 ASCII_DOUBLE_HEAD
: ASCII2
,
466 if __name__
== "__main__": # pragma: no cover
468 from pip
._vendor
.rich
.columns
import Columns
469 from pip
._vendor
.rich
.panel
import Panel
471 from . import box
as box
472 from .console
import Console
473 from .table
import Table
474 from .text
import Text
476 console
= Console(record
=True)
483 "SQUARE_DOUBLE_HEAD",
485 "MINIMAL_HEAVY_HEAD",
486 "MINIMAL_DOUBLE_HEAD",
500 console
.print(Panel("[bold green]Box Constants", style
="green"), justify
="center")
503 columns
= Columns(expand
=True, padding
=2)
504 for box_name
in sorted(BOXES
):
506 show_footer
=True, style
="dim", border_style
="not dim", expand
=True
508 table
.add_column("Header 1", "Footer 1")
509 table
.add_column("Header 2", "Footer 2")
510 table
.add_row("Cell", "Cell")
511 table
.add_row("Cell", "Cell")
512 table
.box
= getattr(box
, box_name
)
513 table
.title
= Text(f
"box.{box_name}", style
="magenta")
514 columns
.add_renderable(table
)
515 console
.print(columns
)
517 # console.save_svg("box.svg")