Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/box.py: 44%
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
1import sys
2from typing import TYPE_CHECKING, Iterable, List
4if sys.version_info >= (3, 8):
5 from typing import Literal
6else:
7 from typing_extensions import Literal # pragma: no cover
10from ._loop import loop_last
12if TYPE_CHECKING:
13 from rich.console import ConsoleOptions
16class Box:
17 """Defines characters to render boxes.
19 ┌─┬┐ top
20 │ ││ head
21 ├─┼┤ head_row
22 │ ││ mid
23 ├─┼┤ row
24 ├─┼┤ foot_row
25 │ ││ foot
26 └─┴┘ bottom
28 Args:
29 box (str): Characters making up box.
30 ascii (bool, optional): True if this box uses ascii characters only. Default is False.
31 """
33 def __init__(self, box: str, *, ascii: bool = False) -> None:
34 self._box = box
35 self.ascii = ascii
36 line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()
37 # top
38 self.top_left, self.top, self.top_divider, self.top_right = iter(line1)
39 # head
40 self.head_left, _, self.head_vertical, self.head_right = iter(line2)
41 # head_row
42 (
43 self.head_row_left,
44 self.head_row_horizontal,
45 self.head_row_cross,
46 self.head_row_right,
47 ) = iter(line3)
49 # mid
50 self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)
51 # row
52 self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5)
53 # foot_row
54 (
55 self.foot_row_left,
56 self.foot_row_horizontal,
57 self.foot_row_cross,
58 self.foot_row_right,
59 ) = iter(line6)
60 # foot
61 self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7)
62 # bottom
63 self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter(
64 line8
65 )
67 def __repr__(self) -> str:
68 return "Box(...)"
70 def __str__(self) -> str:
71 return self._box
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.
76 Args:
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.
81 Returns:
82 Box: A different Box or the same Box.
83 """
84 box = self
85 if options.legacy_windows and safe:
86 box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box)
87 if options.ascii_only and not box.ascii:
88 box = ASCII
89 return box
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.
95 Returns:
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.
98 """
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.
104 Args:
105 widths (List[int]): Widths of columns.
107 Returns:
108 str: A string of box characters.
109 """
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)
116 if not last:
117 append(self.top_divider)
118 append(self.top_right)
119 return "".join(parts)
121 def get_row(
122 self,
123 widths: Iterable[int],
124 level: Literal["head", "row", "foot", "mid"] = "row",
125 edge: bool = True,
126 ) -> str:
127 """Get the top of a simple box.
129 Args:
130 width (List[int]): Widths of columns.
132 Returns:
133 str: A string of box characters.
134 """
135 if level == "head":
136 left = self.head_row_left
137 horizontal = self.head_row_horizontal
138 cross = self.head_row_cross
139 right = self.head_row_right
140 elif level == "row":
141 left = self.row_left
142 horizontal = self.row_horizontal
143 cross = self.row_cross
144 right = self.row_right
145 elif level == "mid":
146 left = self.mid_left
147 horizontal = " "
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
155 else:
156 raise ValueError("level must be 'head', 'row' or 'foot'")
158 parts: List[str] = []
159 append = parts.append
160 if edge:
161 append(left)
162 for last, width in loop_last(widths):
163 append(horizontal * width)
164 if not last:
165 append(cross)
166 if edge:
167 append(right)
168 return "".join(parts)
170 def get_bottom(self, widths: Iterable[int]) -> str:
171 """Get the bottom of a simple box.
173 Args:
174 widths (List[int]): Widths of columns.
176 Returns:
177 str: A string of box characters.
178 """
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)
185 if not last:
186 append(self.bottom_divider)
187 append(self.bottom_right)
188 return "".join(parts)
191# fmt: off
192ASCII: Box = Box(
193 "+--+\n"
194 "| ||\n"
195 "|-+|\n"
196 "| ||\n"
197 "|-+|\n"
198 "|-+|\n"
199 "| ||\n"
200 "+--+\n",
201 ascii=True,
202)
204ASCII2: Box = Box(
205 "+-++\n"
206 "| ||\n"
207 "+-++\n"
208 "| ||\n"
209 "+-++\n"
210 "+-++\n"
211 "| ||\n"
212 "+-++\n",
213 ascii=True,
214)
216ASCII_DOUBLE_HEAD: Box = Box(
217 "+-++\n"
218 "| ||\n"
219 "+=++\n"
220 "| ||\n"
221 "+-++\n"
222 "+-++\n"
223 "| ||\n"
224 "+-++\n",
225 ascii=True,
226)
228SQUARE: Box = Box(
229 "┌─┬┐\n"
230 "│ ││\n"
231 "├─┼┤\n"
232 "│ ││\n"
233 "├─┼┤\n"
234 "├─┼┤\n"
235 "│ ││\n"
236 "└─┴┘\n"
237)
239SQUARE_DOUBLE_HEAD: Box = Box(
240 "┌─┬┐\n"
241 "│ ││\n"
242 "╞═╪╡\n"
243 "│ ││\n"
244 "├─┼┤\n"
245 "├─┼┤\n"
246 "│ ││\n"
247 "└─┴┘\n"
248)
250MINIMAL: Box = Box(
251 " ╷ \n"
252 " │ \n"
253 "╶─┼╴\n"
254 " │ \n"
255 "╶─┼╴\n"
256 "╶─┼╴\n"
257 " │ \n"
258 " ╵ \n"
259)
262MINIMAL_HEAVY_HEAD: Box = Box(
263 " ╷ \n"
264 " │ \n"
265 "╺━┿╸\n"
266 " │ \n"
267 "╶─┼╴\n"
268 "╶─┼╴\n"
269 " │ \n"
270 " ╵ \n"
271)
273MINIMAL_DOUBLE_HEAD: Box = Box(
274 " ╷ \n"
275 " │ \n"
276 " ═╪ \n"
277 " │ \n"
278 " ─┼ \n"
279 " ─┼ \n"
280 " │ \n"
281 " ╵ \n"
282)
285SIMPLE: Box = Box(
286 " \n"
287 " \n"
288 " ── \n"
289 " \n"
290 " \n"
291 " ── \n"
292 " \n"
293 " \n"
294)
296SIMPLE_HEAD: Box = Box(
297 " \n"
298 " \n"
299 " ── \n"
300 " \n"
301 " \n"
302 " \n"
303 " \n"
304 " \n"
305)
308SIMPLE_HEAVY: Box = Box(
309 " \n"
310 " \n"
311 " ━━ \n"
312 " \n"
313 " \n"
314 " ━━ \n"
315 " \n"
316 " \n"
317)
320HORIZONTALS: Box = Box(
321 " ── \n"
322 " \n"
323 " ── \n"
324 " \n"
325 " ── \n"
326 " ── \n"
327 " \n"
328 " ── \n"
329)
331ROUNDED: Box = Box(
332 "╭─┬╮\n"
333 "│ ││\n"
334 "├─┼┤\n"
335 "│ ││\n"
336 "├─┼┤\n"
337 "├─┼┤\n"
338 "│ ││\n"
339 "╰─┴╯\n"
340)
342HEAVY: Box = Box(
343 "┏━┳┓\n"
344 "┃ ┃┃\n"
345 "┣━╋┫\n"
346 "┃ ┃┃\n"
347 "┣━╋┫\n"
348 "┣━╋┫\n"
349 "┃ ┃┃\n"
350 "┗━┻┛\n"
351)
353HEAVY_EDGE: Box = Box(
354 "┏━┯┓\n"
355 "┃ │┃\n"
356 "┠─┼┨\n"
357 "┃ │┃\n"
358 "┠─┼┨\n"
359 "┠─┼┨\n"
360 "┃ │┃\n"
361 "┗━┷┛\n"
362)
364HEAVY_HEAD: Box = Box(
365 "┏━┳┓\n"
366 "┃ ┃┃\n"
367 "┡━╇┩\n"
368 "│ ││\n"
369 "├─┼┤\n"
370 "├─┼┤\n"
371 "│ ││\n"
372 "└─┴┘\n"
373)
375DOUBLE: Box = Box(
376 "╔═╦╗\n"
377 "║ ║║\n"
378 "╠═╬╣\n"
379 "║ ║║\n"
380 "╠═╬╣\n"
381 "╠═╬╣\n"
382 "║ ║║\n"
383 "╚═╩╝\n"
384)
386DOUBLE_EDGE: Box = Box(
387 "╔═╤╗\n"
388 "║ │║\n"
389 "╟─┼╢\n"
390 "║ │║\n"
391 "╟─┼╢\n"
392 "╟─┼╢\n"
393 "║ │║\n"
394 "╚═╧╝\n"
395)
397MARKDOWN: Box = Box(
398 " \n"
399 "| ||\n"
400 "|-||\n"
401 "| ||\n"
402 "|-||\n"
403 "|-||\n"
404 "| ||\n"
405 " \n",
406 ascii=True,
407)
408# fmt: on
410# Map Boxes that don't render with raster fonts on to equivalent that do
411LEGACY_WINDOWS_SUBSTITUTIONS = {
412 ROUNDED: SQUARE,
413 MINIMAL_HEAVY_HEAD: MINIMAL,
414 SIMPLE_HEAVY: SIMPLE,
415 HEAVY: SQUARE,
416 HEAVY_EDGE: SQUARE,
417 HEAVY_HEAD: SQUARE,
418}
420# Map headed boxes to their headerless equivalents
421PLAIN_HEADED_SUBSTITUTIONS = {
422 HEAVY_HEAD: SQUARE,
423 SQUARE_DOUBLE_HEAD: SQUARE,
424 MINIMAL_DOUBLE_HEAD: MINIMAL,
425 MINIMAL_HEAVY_HEAD: MINIMAL,
426 ASCII_DOUBLE_HEAD: ASCII2,
427}
430if __name__ == "__main__": # pragma: no cover
431 from rich.columns import Columns
432 from rich.panel import Panel
434 from . import box as box
435 from .console import Console
436 from .table import Table
437 from .text import Text
439 console = Console(record=True)
441 BOXES = [
442 "ASCII",
443 "ASCII2",
444 "ASCII_DOUBLE_HEAD",
445 "SQUARE",
446 "SQUARE_DOUBLE_HEAD",
447 "MINIMAL",
448 "MINIMAL_HEAVY_HEAD",
449 "MINIMAL_DOUBLE_HEAD",
450 "SIMPLE",
451 "SIMPLE_HEAD",
452 "SIMPLE_HEAVY",
453 "HORIZONTALS",
454 "ROUNDED",
455 "HEAVY",
456 "HEAVY_EDGE",
457 "HEAVY_HEAD",
458 "DOUBLE",
459 "DOUBLE_EDGE",
460 "MARKDOWN",
461 ]
463 console.print(Panel("[bold green]Box Constants", style="green"), justify="center")
464 console.print()
466 columns = Columns(expand=True, padding=2)
467 for box_name in sorted(BOXES):
468 table = Table(
469 show_footer=True, style="dim", border_style="not dim", expand=True
470 )
471 table.add_column("Header 1", "Footer 1")
472 table.add_column("Header 2", "Footer 2")
473 table.add_row("Cell", "Cell")
474 table.add_row("Cell", "Cell")
475 table.box = getattr(box, box_name)
476 table.title = Text(f"box.{box_name}", style="magenta")
477 columns.add_renderable(table)
478 console.print(columns)
480 # console.save_svg("box.svg")