图片解析应用
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.

517 lines
9.6 KiB

  1. import sys
  2. from typing import TYPE_CHECKING, Iterable, List
  3. if sys.version_info >= (3, 8):
  4. from typing import Literal
  5. else:
  6. from pip._vendor.typing_extensions import Literal # pragma: no cover
  7. from ._loop import loop_last
  8. if TYPE_CHECKING:
  9. from pip._vendor.rich.console import ConsoleOptions
  10. class Box:
  11. """Defines characters to render boxes.
  12. top
  13. head
  14. head_row
  15. mid
  16. row
  17. foot_row
  18. foot
  19. bottom
  20. Args:
  21. box (str): Characters making up box.
  22. ascii (bool, optional): True if this box uses ascii characters only. Default is False.
  23. """
  24. def __init__(self, box: str, *, ascii: bool = False) -> None:
  25. self._box = box
  26. self.ascii = ascii
  27. line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()
  28. # top
  29. self.top_left, self.top, self.top_divider, self.top_right = iter(line1)
  30. # head
  31. self.head_left, _, self.head_vertical, self.head_right = iter(line2)
  32. # head_row
  33. (
  34. self.head_row_left,
  35. self.head_row_horizontal,
  36. self.head_row_cross,
  37. self.head_row_right,
  38. ) = iter(line3)
  39. # mid
  40. self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)
  41. # row
  42. self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5)
  43. # foot_row
  44. (
  45. self.foot_row_left,
  46. self.foot_row_horizontal,
  47. self.foot_row_cross,
  48. self.foot_row_right,
  49. ) = iter(line6)
  50. # foot
  51. self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7)
  52. # bottom
  53. self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter(
  54. line8
  55. )
  56. def __repr__(self) -> str:
  57. return "Box(...)"
  58. def __str__(self) -> str:
  59. return self._box
  60. def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box":
  61. """Substitute this box for another if it won't render due to platform issues.
  62. Args:
  63. options (ConsoleOptions): Console options used in rendering.
  64. safe (bool, optional): Substitute this for another Box if there are known problems
  65. displaying on the platform (currently only relevant on Windows). Default is True.
  66. Returns:
  67. Box: A different Box or the same Box.
  68. """
  69. box = self
  70. if options.legacy_windows and safe:
  71. box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box)
  72. if options.ascii_only and not box.ascii:
  73. box = ASCII
  74. return box
  75. def get_plain_headed_box(self) -> "Box":
  76. """If this box uses special characters for the borders of the header, then
  77. return the equivalent box that does not.
  78. Returns:
  79. Box: The most similar Box that doesn't use header-specific box characters.
  80. If the current Box already satisfies this criterion, then it's returned.
  81. """
  82. return PLAIN_HEADED_SUBSTITUTIONS.get(self, self)
  83. def get_top(self, widths: Iterable[int]) -> str:
  84. """Get the top of a simple box.
  85. Args:
  86. widths (List[int]): Widths of columns.
  87. Returns:
  88. str: A string of box characters.
  89. """
  90. parts: List[str] = []
  91. append = parts.append
  92. append(self.top_left)
  93. for last, width in loop_last(widths):
  94. append(self.top * width)
  95. if not last:
  96. append(self.top_divider)
  97. append(self.top_right)
  98. return "".join(parts)
  99. def get_row(
  100. self,
  101. widths: Iterable[int],
  102. level: Literal["head", "row", "foot", "mid"] = "row",
  103. edge: bool = True,
  104. ) -> str:
  105. """Get the top of a simple box.
  106. Args:
  107. width (List[int]): Widths of columns.
  108. Returns:
  109. str: A string of box characters.
  110. """
  111. if level == "head":
  112. left = self.head_row_left
  113. horizontal = self.head_row_horizontal
  114. cross = self.head_row_cross
  115. right = self.head_row_right
  116. elif level == "row":
  117. left = self.row_left
  118. horizontal = self.row_horizontal
  119. cross = self.row_cross
  120. right = self.row_right
  121. elif level == "mid":
  122. left = self.mid_left
  123. horizontal = " "
  124. cross = self.mid_vertical
  125. right = self.mid_right
  126. elif level == "foot":
  127. left = self.foot_row_left
  128. horizontal = self.foot_row_horizontal
  129. cross = self.foot_row_cross
  130. right = self.foot_row_right
  131. else:
  132. raise ValueError("level must be 'head', 'row' or 'foot'")
  133. parts: List[str] = []
  134. append = parts.append
  135. if edge:
  136. append(left)
  137. for last, width in loop_last(widths):
  138. append(horizontal * width)
  139. if not last:
  140. append(cross)
  141. if edge:
  142. append(right)
  143. return "".join(parts)
  144. def get_bottom(self, widths: Iterable[int]) -> str:
  145. """Get the bottom of a simple box.
  146. Args:
  147. widths (List[int]): Widths of columns.
  148. Returns:
  149. str: A string of box characters.
  150. """
  151. parts: List[str] = []
  152. append = parts.append
  153. append(self.bottom_left)
  154. for last, width in loop_last(widths):
  155. append(self.bottom * width)
  156. if not last:
  157. append(self.bottom_divider)
  158. append(self.bottom_right)
  159. return "".join(parts)
  160. ASCII: Box = Box(
  161. """\
  162. +--+
  163. | ||
  164. |-+|
  165. | ||
  166. |-+|
  167. |-+|
  168. | ||
  169. +--+
  170. """,
  171. ascii=True,
  172. )
  173. ASCII2: Box = Box(
  174. """\
  175. +-++
  176. | ||
  177. +-++
  178. | ||
  179. +-++
  180. +-++
  181. | ||
  182. +-++
  183. """,
  184. ascii=True,
  185. )
  186. ASCII_DOUBLE_HEAD: Box = Box(
  187. """\
  188. +-++
  189. | ||
  190. +=++
  191. | ||
  192. +-++
  193. +-++
  194. | ||
  195. +-++
  196. """,
  197. ascii=True,
  198. )
  199. SQUARE: Box = Box(
  200. """\
  201. """
  202. )
  203. SQUARE_DOUBLE_HEAD: Box = Box(
  204. """\
  205. """
  206. )
  207. MINIMAL: Box = Box(
  208. """\
  209. """
  210. )
  211. MINIMAL_HEAVY_HEAD: Box = Box(
  212. """\
  213. """
  214. )
  215. MINIMAL_DOUBLE_HEAD: Box = Box(
  216. """\
  217. """
  218. )
  219. SIMPLE: Box = Box(
  220. """\
  221. """
  222. )
  223. SIMPLE_HEAD: Box = Box(
  224. """\
  225. """
  226. )
  227. SIMPLE_HEAVY: Box = Box(
  228. """\
  229. """
  230. )
  231. HORIZONTALS: Box = Box(
  232. """\
  233. """
  234. )
  235. ROUNDED: Box = Box(
  236. """\
  237. """
  238. )
  239. HEAVY: Box = Box(
  240. """\
  241. """
  242. )
  243. HEAVY_EDGE: Box = Box(
  244. """\
  245. """
  246. )
  247. HEAVY_HEAD: Box = Box(
  248. """\
  249. """
  250. )
  251. DOUBLE: Box = Box(
  252. """\
  253. """
  254. )
  255. DOUBLE_EDGE: Box = Box(
  256. """\
  257. """
  258. )
  259. MARKDOWN: Box = Box(
  260. """\
  261. | ||
  262. |-||
  263. | ||
  264. |-||
  265. |-||
  266. | ||
  267. """,
  268. ascii=True,
  269. )
  270. # Map Boxes that don't render with raster fonts on to equivalent that do
  271. LEGACY_WINDOWS_SUBSTITUTIONS = {
  272. ROUNDED: SQUARE,
  273. MINIMAL_HEAVY_HEAD: MINIMAL,
  274. SIMPLE_HEAVY: SIMPLE,
  275. HEAVY: SQUARE,
  276. HEAVY_EDGE: SQUARE,
  277. HEAVY_HEAD: SQUARE,
  278. }
  279. # Map headed boxes to their headerless equivalents
  280. PLAIN_HEADED_SUBSTITUTIONS = {
  281. HEAVY_HEAD: SQUARE,
  282. SQUARE_DOUBLE_HEAD: SQUARE,
  283. MINIMAL_DOUBLE_HEAD: MINIMAL,
  284. MINIMAL_HEAVY_HEAD: MINIMAL,
  285. ASCII_DOUBLE_HEAD: ASCII2,
  286. }
  287. if __name__ == "__main__": # pragma: no cover
  288. from pip._vendor.rich.columns import Columns
  289. from pip._vendor.rich.panel import Panel
  290. from . import box as box
  291. from .console import Console
  292. from .table import Table
  293. from .text import Text
  294. console = Console(record=True)
  295. BOXES = [
  296. "ASCII",
  297. "ASCII2",
  298. "ASCII_DOUBLE_HEAD",
  299. "SQUARE",
  300. "SQUARE_DOUBLE_HEAD",
  301. "MINIMAL",
  302. "MINIMAL_HEAVY_HEAD",
  303. "MINIMAL_DOUBLE_HEAD",
  304. "SIMPLE",
  305. "SIMPLE_HEAD",
  306. "SIMPLE_HEAVY",
  307. "HORIZONTALS",
  308. "ROUNDED",
  309. "HEAVY",
  310. "HEAVY_EDGE",
  311. "HEAVY_HEAD",
  312. "DOUBLE",
  313. "DOUBLE_EDGE",
  314. "MARKDOWN",
  315. ]
  316. console.print(Panel("[bold green]Box Constants", style="green"), justify="center")
  317. console.print()
  318. columns = Columns(expand=True, padding=2)
  319. for box_name in sorted(BOXES):
  320. table = Table(
  321. show_footer=True, style="dim", border_style="not dim", expand=True
  322. )
  323. table.add_column("Header 1", "Footer 1")
  324. table.add_column("Header 2", "Footer 2")
  325. table.add_row("Cell", "Cell")
  326. table.add_row("Cell", "Cell")
  327. table.box = getattr(box, box_name)
  328. table.title = Text(f"box.{box_name}", style="magenta")
  329. columns.add_renderable(table)
  330. console.print(columns)
  331. # console.save_svg("box.svg")