Coverage for /run/media/veganeco/water/status600.com/pypi/reptilian_climates/modules_series_2/ships/fields/gardens_pip/rich/align.py: 22%

1 import sys

2 from itertools import chain

3 from typing import TYPE_CHECKING, Iterable, Optional

4

5 if sys.version_info >= (3, 8):

6 from typing import Literal

7 else:

8 from typing_extensions import Literal # pragma: no cover

9

10 from .constrain import Constrain

11 from .jupyter import JupyterMixin

12 from .measure import Measurement

13 from .segment import Segment

14 from .style import StyleType

15

16 if TYPE_CHECKING:

17 from .console import Console, ConsoleOptions, RenderableType, RenderResult

18

19 AlignMethod = Literal["left", "center", "right"]

20 VerticalAlignMethod = Literal["top", "middle", "bottom"]

21

22

23 class Align(JupyterMixin):

24 """Align a renderable by adding spaces if necessary.

25

26 Args:

27 renderable (RenderableType): A console renderable.

28 align (AlignMethod): One of "left", "center", or "right""

29 style (StyleType, optional): An optional style to apply to the background.

30 vertical (Optional[VerticalAlignMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None.

31 pad (bool, optional): Pad the right with spaces. Defaults to True.

32 width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.

33 height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None.

34

35 Raises:

36 ValueError: if ``align`` is not one of the expected values.

37 """

38

39 def __init__(

40 self,

41 renderable: "RenderableType",

42 align: AlignMethod = "left",

43 style: Optional[StyleType] = None,

44 *,

45 vertical: Optional[VerticalAlignMethod] = None,

46 pad: bool = True,

47 width: Optional[int] = None,

48 height: Optional[int] = None,

49 ) -> None:

50 if align not in ("left", "center", "right"):

51 raise ValueError(

52 f'invalid value for align, expected "left", "center", or "right" (not {align!r})'

53 )

54 if vertical is not None and vertical not in ("top", "middle", "bottom"):

55 raise ValueError(

56 f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})'

57 )

58 self.renderable = renderable

59 self.align = align

60 self.style = style

61 self.vertical = vertical

62 self.pad = pad

63 self.width = width

64 self.height = height

65

66 def __repr__(self) -> str:

67 return f"Align({self.renderable!r}, {self.align!r})"

68

69 @classmethod

70 def left(

71 cls,

72 renderable: "RenderableType",

73 style: Optional[StyleType] = None,

74 *,

75 vertical: Optional[VerticalAlignMethod] = None,

76 pad: bool = True,

77 width: Optional[int] = None,

78 height: Optional[int] = None,

79 ) -> "Align":

80 """Align a renderable to the left."""

81 return cls(

82 renderable,

83 "left",

84 style=style,

85 vertical=vertical,

86 pad=pad,

87 width=width,

88 height=height,

89 )

90

91 @classmethod

92 def center(

93 cls,

94 renderable: "RenderableType",

95 style: Optional[StyleType] = None,

96 *,

97 vertical: Optional[VerticalAlignMethod] = None,

98 pad: bool = True,

99 width: Optional[int] = None,

100 height: Optional[int] = None,

101 ) -> "Align":

102 """Align a renderable to the center."""

103 return cls(

104 renderable,

105 "center",

106 style=style,

107 vertical=vertical,

108 pad=pad,

109 width=width,

110 height=height,

111 )

112

113 @classmethod

114 def right(

115 cls,

116 renderable: "RenderableType",

117 style: Optional[StyleType] = None,

118 *,

119 vertical: Optional[VerticalAlignMethod] = None,

120 pad: bool = True,

121 width: Optional[int] = None,

122 height: Optional[int] = None,

123 ) -> "Align":

124 """Align a renderable to the right."""

125 return cls(

126 renderable,

127 "right",

128 style=style,

129 vertical=vertical,

130 pad=pad,

131 width=width,

132 height=height,

133 )

134

135 def __rich_console__(

136 self, console: "Console", options: "ConsoleOptions"

137 ) -> "RenderResult":

138 align = self.align

139 width = console.measure(self.renderable, options=options).maximum

140 rendered = console.render(

141 Constrain(

142 self.renderable, width if self.width is None else min(width, self.width)

143 ),

144 options.update(height=None),

145 )

146 lines = list(Segment.split_lines(rendered))

147 width, height = Segment.get_shape(lines)

148 lines = Segment.set_shape(lines, width, height)

149 new_line = Segment.line()

150 excess_space = options.max_width - width

151 style = console.get_style(self.style) if self.style is not None else None

152

153 def generate_segments() -> Iterable[Segment]:

154 if excess_space <= 0:

155 # Exact fit

156 for line in lines:

157 yield from line

158 yield new_line

159

160 elif align == "left":

161 # Pad on the right

162 pad = Segment(" " * excess_space, style) if self.pad else None

163 for line in lines:

164 yield from line

165 if pad:

166 yield pad

167 yield new_line

168

169 elif align == "center":

170 # Pad left and right

171 left = excess_space // 2

172 pad = Segment(" " * left, style)

173 pad_right = (

174 Segment(" " * (excess_space - left), style) if self.pad else None

175 )

176 for line in lines:

177 if left:

178 yield pad

179 yield from line

180 if pad_right:

181 yield pad_right

182 yield new_line

183

184 elif align == "right":

185 # Padding on left

186 pad = Segment(" " * excess_space, style)

187 for line in lines:

188 yield pad

189 yield from line

190 yield new_line

191

192 blank_line = (

193 Segment(f"{' ' * (self.width or options.max_width)}\n", style)

194 if self.pad

195 else Segment("\n")

196 )

197

198 def blank_lines(count: int) -> Iterable[Segment]:

199 if count > 0:

200 for _ in range(count):

201 yield blank_line

202

203 vertical_height = self.height or options.height

204 iter_segments: Iterable[Segment]

205 if self.vertical and vertical_height is not None:

206 if self.vertical == "top":

207 bottom_space = vertical_height - height

208 iter_segments = chain(generate_segments(), blank_lines(bottom_space))

209 elif self.vertical == "middle":

210 top_space = (vertical_height - height) // 2

211 bottom_space = vertical_height - top_space - height

212 iter_segments = chain(

213 blank_lines(top_space),

214 generate_segments(),

215 blank_lines(bottom_space),

216 )

217 else: # self.vertical == "bottom":

218 top_space = vertical_height - height

219 iter_segments = chain(blank_lines(top_space), generate_segments())

220 else:

221 iter_segments = generate_segments()

222 if self.style:

223 style = console.get_style(self.style)

224 iter_segments = Segment.apply_style(iter_segments, style)

225 yield from iter_segments

226

227 def __rich_measure__(

228 self, console: "Console", options: "ConsoleOptions"

229 ) -> Measurement:

230 measurement = Measurement.get(console, options, self.renderable)

231 return measurement

232

233

234 class VerticalCenter(JupyterMixin):

235 """Vertically aligns a renderable.

236

237 Warn:

238 This class is deprecated and may be removed in a future version. Use Align class with

239 `vertical="middle"`.

240

241 Args:

242 renderable (RenderableType): A renderable object.

243 """

244

245 def __init__(

246 self,

247 renderable: "RenderableType",

248 style: Optional[StyleType] = None,

249 ) -> None:

250 self.renderable = renderable

251 self.style = style

252

253 def __repr__(self) -> str:

254 return f"VerticalCenter({self.renderable!r})"

255

256 def __rich_console__(

257 self, console: "Console", options: "ConsoleOptions"

258 ) -> "RenderResult":

259 style = console.get_style(self.style) if self.style is not None else None

260 lines = console.render_lines(

261 self.renderable, options.update(height=None), pad=False

262 )

263 width, _height = Segment.get_shape(lines)

264 new_line = Segment.line()

265 height = options.height or options.size.height

266 top_space = (height - len(lines)) // 2

267 bottom_space = height - top_space - len(lines)

268 blank_line = Segment(f"{' ' * width}", style)

269

270 def blank_lines(count: int) -> Iterable[Segment]:

271 for _ in range(count):

272 yield blank_line

273 yield new_line

274

275 if top_space > 0:

276 yield from blank_lines(top_space)

277 for line in lines:

278 yield from line

279 yield new_line

280 if bottom_space > 0:

281 yield from blank_lines(bottom_space)

282

283 def __rich_measure__(

284 self, console: "Console", options: "ConsoleOptions"

285 ) -> Measurement:

286 measurement = Measurement.get(console, options, self.renderable)

287 return measurement

288

289

290 if __name__ == "__main__": # pragma: no cover

291 from rich.console import Console, Group

292 from rich.highlighter import ReprHighlighter

293 from rich.panel import Panel

294

295 highlighter = ReprHighlighter()

296 console = Console()

297

298 panel = Panel(

299 Group(

300 Align.left(highlighter("align='left'")),

301 Align.center(highlighter("align='center'")),

302 Align.right(highlighter("align='right'")),

303 ),

304 width=60,

305 style="on dark_blue",

306 title="Align",

307 )

308

309 console.print(

310 Align.center(panel, vertical="middle", style="on red", height=console.height)

311 )