python (3.11.7)
       1  import sys
       2  from typing import TYPE_CHECKING, Iterable, List
       3  
       4  if sys.version_info >= (3, 8):
       5      from typing import Literal
       6  else:
       7      from pip._vendor.typing_extensions import Literal  # pragma: no cover
       8  
       9  
      10  from ._loop import loop_last
      11  
      12  if TYPE_CHECKING:
      13      from pip._vendor.rich.console import ConsoleOptions
      14  
      15  
      16  class ESC[4;38;5;81mBox:
      17      """Defines characters to render boxes.
      18  
      19      ┌─┬┐ top
      20      │ ││ head
      21      ├─┼┤ head_row
      22      │ ││ mid
      23      ├─┼┤ row
      24      ├─┼┤ foot_row
      25      │ ││ foot
      26      └─┴┘ bottom
      27  
      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      """
      32  
      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)
      48  
      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          )
      66  
      67      def __repr__(self) -> str:
      68          return "Box(...)"
      69  
      70      def __str__(self) -> str:
      71          return self._box
      72  
      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.
      75  
      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.
      80  
      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
      90  
      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.
      94  
      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)
     100  
     101      def get_top(self, widths: Iterable[int]) -> str:
     102          """Get the top of a simple box.
     103  
     104          Args:
     105              widths (List[int]): Widths of columns.
     106  
     107          Returns:
     108              str: A string of box characters.
     109          """
     110  
     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)
     120  
     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.
     128  
     129          Args:
     130              width (List[int]): Widths of columns.
     131  
     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'")
     157  
     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)
     169  
     170      def get_bottom(self, widths: Iterable[int]) -> str:
     171          """Get the bottom of a simple box.
     172  
     173          Args:
     174              widths (List[int]): Widths of columns.
     175  
     176          Returns:
     177              str: A string of box characters.
     178          """
     179  
     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)
     189  
     190  
     191  ASCII: Box = Box(
     192      """\
     193  +--+
     194  | ||
     195  |-+|
     196  | ||
     197  |-+|
     198  |-+|
     199  | ||
     200  +--+
     201  """,
     202      ascii=True,
     203  )
     204  
     205  ASCII2: Box = Box(
     206      """\
     207  +-++
     208  | ||
     209  +-++
     210  | ||
     211  +-++
     212  +-++
     213  | ||
     214  +-++
     215  """,
     216      ascii=True,
     217  )
     218  
     219  ASCII_DOUBLE_HEAD: Box = Box(
     220      """\
     221  +-++
     222  | ||
     223  +=++
     224  | ||
     225  +-++
     226  +-++
     227  | ||
     228  +-++
     229  """,
     230      ascii=True,
     231  )
     232  
     233  SQUARE: Box = Box(
     234      """\
     235  ┌─┬┐
     236  │ ││
     237  ├─┼┤
     238  │ ││
     239  ├─┼┤
     240  ├─┼┤
     241  │ ││
     242  └─┴┘
     243  """
     244  )
     245  
     246  SQUARE_DOUBLE_HEAD: Box = Box(
     247      """\
     248  ┌─┬┐
     249  │ ││
     250  ╞═╪╡
     251  │ ││
     252  ├─┼┤
     253  ├─┼┤
     254  │ ││
     255  └─┴┘
     256  """
     257  )
     258  
     259  MINIMAL: Box = Box(
     260      """\
     261    ╷ 
     262    │ 
     263  ╶─┼╴
     264    │ 
     265  ╶─┼╴
     266  ╶─┼╴
     267    │ 
     268    ╵ 
     269  """
     270  )
     271  
     272  
     273  MINIMAL_HEAVY_HEAD: Box = Box(
     274      """\
     275    ╷ 
     276    │ 
     277  ╺━┿╸
     278    │ 
     279  ╶─┼╴
     280  ╶─┼╴
     281    │ 
     282    ╵ 
     283  """
     284  )
     285  
     286  MINIMAL_DOUBLE_HEAD: Box = Box(
     287      """\
     288    ╷ 
     289    │ 
     290   ═╪ 
     291    │ 
     292   ─┼ 
     293   ─┼ 
     294    │ 
     295    ╵ 
     296  """
     297  )
     298  
     299  
     300  SIMPLE: Box = Box(
     301      """\
     302      
     303      
     304   ── 
     305      
     306      
     307   ── 
     308      
     309      
     310  """
     311  )
     312  
     313  SIMPLE_HEAD: Box = Box(
     314      """\
     315      
     316      
     317   ── 
     318      
     319      
     320      
     321      
     322      
     323  """
     324  )
     325  
     326  
     327  SIMPLE_HEAVY: Box = Box(
     328      """\
     329      
     330      
     331   ━━ 
     332      
     333      
     334   ━━ 
     335      
     336      
     337  """
     338  )
     339  
     340  
     341  HORIZONTALS: Box = Box(
     342      """\
     343   ── 
     344      
     345   ── 
     346      
     347   ── 
     348   ── 
     349      
     350   ── 
     351  """
     352  )
     353  
     354  ROUNDED: Box = Box(
     355      """\
     356  ╭─┬╮
     357  │ ││
     358  ├─┼┤
     359  │ ││
     360  ├─┼┤
     361  ├─┼┤
     362  │ ││
     363  ╰─┴╯
     364  """
     365  )
     366  
     367  HEAVY: Box = Box(
     368      """\
     369  ┏━┳┓
     370  ┃ ┃┃
     371  ┣━╋┫
     372  ┃ ┃┃
     373  ┣━╋┫
     374  ┣━╋┫
     375  ┃ ┃┃
     376  ┗━┻┛
     377  """
     378  )
     379  
     380  HEAVY_EDGE: Box = Box(
     381      """\
     382  ┏━┯┓
     383  ┃ │┃
     384  ┠─┼┨
     385  ┃ │┃
     386  ┠─┼┨
     387  ┠─┼┨
     388  ┃ │┃
     389  ┗━┷┛
     390  """
     391  )
     392  
     393  HEAVY_HEAD: Box = Box(
     394      """\
     395  ┏━┳┓
     396  ┃ ┃┃
     397  ┡━╇┩
     398  │ ││
     399  ├─┼┤
     400  ├─┼┤
     401  │ ││
     402  └─┴┘
     403  """
     404  )
     405  
     406  DOUBLE: Box = Box(
     407      """\
     408  ╔═╦╗
     409  ║ ║║
     410  ╠═╬╣
     411  ║ ║║
     412  ╠═╬╣
     413  ╠═╬╣
     414  ║ ║║
     415  ╚═╩╝
     416  """
     417  )
     418  
     419  DOUBLE_EDGE: Box = Box(
     420      """\
     421  ╔═╤╗
     422  ║ │║
     423  ╟─┼╢
     424  ║ │║
     425  ╟─┼╢
     426  ╟─┼╢
     427  ║ │║
     428  ╚═╧╝
     429  """
     430  )
     431  
     432  MARKDOWN: Box = Box(
     433      """\
     434      
     435  | ||
     436  |-||
     437  | ||
     438  |-||
     439  |-||
     440  | ||
     441      
     442  """,
     443      ascii=True,
     444  )
     445  
     446  # Map Boxes that don't render with raster fonts on to equivalent that do
     447  LEGACY_WINDOWS_SUBSTITUTIONS = {
     448      ROUNDED: SQUARE,
     449      MINIMAL_HEAVY_HEAD: MINIMAL,
     450      SIMPLE_HEAVY: SIMPLE,
     451      HEAVY: SQUARE,
     452      HEAVY_EDGE: SQUARE,
     453      HEAVY_HEAD: SQUARE,
     454  }
     455  
     456  # Map headed boxes to their headerless equivalents
     457  PLAIN_HEADED_SUBSTITUTIONS = {
     458      HEAVY_HEAD: SQUARE,
     459      SQUARE_DOUBLE_HEAD: SQUARE,
     460      MINIMAL_DOUBLE_HEAD: MINIMAL,
     461      MINIMAL_HEAVY_HEAD: MINIMAL,
     462      ASCII_DOUBLE_HEAD: ASCII2,
     463  }
     464  
     465  
     466  if __name__ == "__main__":  # pragma: no cover
     467  
     468      from pip._vendor.rich.columns import Columns
     469      from pip._vendor.rich.panel import Panel
     470  
     471      from . import box as box
     472      from .console import Console
     473      from .table import Table
     474      from .text import Text
     475  
     476      console = Console(record=True)
     477  
     478      BOXES = [
     479          "ASCII",
     480          "ASCII2",
     481          "ASCII_DOUBLE_HEAD",
     482          "SQUARE",
     483          "SQUARE_DOUBLE_HEAD",
     484          "MINIMAL",
     485          "MINIMAL_HEAVY_HEAD",
     486          "MINIMAL_DOUBLE_HEAD",
     487          "SIMPLE",
     488          "SIMPLE_HEAD",
     489          "SIMPLE_HEAVY",
     490          "HORIZONTALS",
     491          "ROUNDED",
     492          "HEAVY",
     493          "HEAVY_EDGE",
     494          "HEAVY_HEAD",
     495          "DOUBLE",
     496          "DOUBLE_EDGE",
     497          "MARKDOWN",
     498      ]
     499  
     500      console.print(Panel("[bold green]Box Constants", style="green"), justify="center")
     501      console.print()
     502  
     503      columns = Columns(expand=True, padding=2)
     504      for box_name in sorted(BOXES):
     505          table = Table(
     506              show_footer=True, style="dim", border_style="not dim", expand=True
     507          )
     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)
     516  
     517      # console.save_svg("box.svg")