padding.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union
  2. if TYPE_CHECKING:
  3. from .console import (
  4. Console,
  5. ConsoleOptions,
  6. RenderableType,
  7. RenderResult,
  8. )
  9. from .jupyter import JupyterMixin
  10. from .measure import Measurement
  11. from .style import Style
  12. from .segment import Segment
  13. PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
  14. class Padding(JupyterMixin):
  15. """Draw space around content.
  16. Example:
  17. >>> print(Padding("Hello", (2, 4), style="on blue"))
  18. Args:
  19. renderable (RenderableType): String or other renderable.
  20. pad (Union[int, Tuple[int]]): Padding for top, right, bottom, and left borders.
  21. May be specified with 1, 2, or 4 integers (CSS style).
  22. style (Union[str, Style], optional): Style for padding characters. Defaults to "none".
  23. expand (bool, optional): Expand padding to fit available width. Defaults to True.
  24. """
  25. def __init__(
  26. self,
  27. renderable: "RenderableType",
  28. pad: "PaddingDimensions" = (0, 0, 0, 0),
  29. *,
  30. style: Union[str, Style] = "none",
  31. expand: bool = True,
  32. ):
  33. self.renderable = renderable
  34. self.top, self.right, self.bottom, self.left = self.unpack(pad)
  35. self.style = style
  36. self.expand = expand
  37. @classmethod
  38. def indent(cls, renderable: "RenderableType", level: int) -> "Padding":
  39. """Make padding instance to render an indent.
  40. Args:
  41. renderable (RenderableType): String or other renderable.
  42. level (int): Number of characters to indent.
  43. Returns:
  44. Padding: A Padding instance.
  45. """
  46. return Padding(renderable, pad=(0, 0, 0, level), expand=False)
  47. @staticmethod
  48. def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]:
  49. """Unpack padding specified in CSS style."""
  50. if isinstance(pad, int):
  51. return (pad, pad, pad, pad)
  52. if len(pad) == 1:
  53. _pad = pad[0]
  54. return (_pad, _pad, _pad, _pad)
  55. if len(pad) == 2:
  56. pad_top, pad_right = cast(Tuple[int, int], pad)
  57. return (pad_top, pad_right, pad_top, pad_right)
  58. if len(pad) == 4:
  59. top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
  60. return (top, right, bottom, left)
  61. raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")
  62. def __repr__(self) -> str:
  63. return f"Padding({self.renderable!r}, ({self.top},{self.right},{self.bottom},{self.left}))"
  64. def __rich_console__(
  65. self, console: "Console", options: "ConsoleOptions"
  66. ) -> "RenderResult":
  67. style = console.get_style(self.style)
  68. if self.expand:
  69. width = options.max_width
  70. else:
  71. width = min(
  72. Measurement.get(console, options, self.renderable).maximum
  73. + self.left
  74. + self.right,
  75. options.max_width,
  76. )
  77. render_options = options.update_width(width - self.left - self.right)
  78. if render_options.height is not None:
  79. render_options = render_options.update_height(
  80. height=render_options.height - self.top - self.bottom
  81. )
  82. lines = console.render_lines(
  83. self.renderable, render_options, style=style, pad=True
  84. )
  85. _Segment = Segment
  86. left = _Segment(" " * self.left, style) if self.left else None
  87. right = (
  88. [_Segment(f'{" " * self.right}', style), _Segment.line()]
  89. if self.right
  90. else [_Segment.line()]
  91. )
  92. blank_line: Optional[List[Segment]] = None
  93. if self.top:
  94. blank_line = [_Segment(f'{" " * width}\n', style)]
  95. yield from blank_line * self.top
  96. if left:
  97. for line in lines:
  98. yield left
  99. yield from line
  100. yield from right
  101. else:
  102. for line in lines:
  103. yield from line
  104. yield from right
  105. if self.bottom:
  106. blank_line = blank_line or [_Segment(f'{" " * width}\n', style)]
  107. yield from blank_line * self.bottom
  108. def __rich_measure__(
  109. self, console: "Console", options: "ConsoleOptions"
  110. ) -> "Measurement":
  111. max_width = options.max_width
  112. extra_width = self.left + self.right
  113. if max_width - extra_width < 1:
  114. return Measurement(max_width, max_width)
  115. measure_min, measure_max = Measurement.get(console, options, self.renderable)
  116. measurement = Measurement(measure_min + extra_width, measure_max + extra_width)
  117. measurement = measurement.with_maximum(max_width)
  118. return measurement
  119. if __name__ == "__main__": # pragma: no cover
  120. from pip._vendor.rich import print
  121. print(Padding("Hello, World", (2, 4), style="on blue"))