Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/align.py: 20%

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

127 statements  

1from itertools import chain 

2from typing import TYPE_CHECKING, Iterable, Optional, Literal 

3 

4from .constrain import Constrain 

5from .jupyter import JupyterMixin 

6from .measure import Measurement 

7from .segment import Segment 

8from .style import StyleType 

9 

10if TYPE_CHECKING: 

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

12 

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

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

15 

16 

17class Align(JupyterMixin): 

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

19 

20 Args: 

21 renderable (RenderableType): A console renderable. 

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

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

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

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

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

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

28 

29 Raises: 

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

31 

32 Example: 

33 .. code-block:: python 

34 

35 from rich.console import Console 

36 from rich.align import Align 

37 from rich.panel import Panel 

38 

39 console = Console() 

40 # Create a panel 20 characters wide 

41 p = Panel("Hello, [b]World[/b]!", style="on green", width=20) 

42 

43 # Renders the panel centered in the terminal 

44 console.print(Align(p, align="center")) 

45 """ 

46 

47 def __init__( 

48 self, 

49 renderable: "RenderableType", 

50 align: AlignMethod = "left", 

51 style: Optional[StyleType] = None, 

52 *, 

53 vertical: Optional[VerticalAlignMethod] = None, 

54 pad: bool = True, 

55 width: Optional[int] = None, 

56 height: Optional[int] = None, 

57 ) -> None: 

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

59 raise ValueError( 

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

61 ) 

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

63 raise ValueError( 

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

65 ) 

66 self.renderable = renderable 

67 self.align = align 

68 self.style = style 

69 self.vertical = vertical 

70 self.pad = pad 

71 self.width = width 

72 self.height = height 

73 

74 def __repr__(self) -> str: 

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

76 

77 @classmethod 

78 def left( 

79 cls, 

80 renderable: "RenderableType", 

81 style: Optional[StyleType] = None, 

82 *, 

83 vertical: Optional[VerticalAlignMethod] = None, 

84 pad: bool = True, 

85 width: Optional[int] = None, 

86 height: Optional[int] = None, 

87 ) -> "Align": 

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

89 return cls( 

90 renderable, 

91 "left", 

92 style=style, 

93 vertical=vertical, 

94 pad=pad, 

95 width=width, 

96 height=height, 

97 ) 

98 

99 @classmethod 

100 def center( 

101 cls, 

102 renderable: "RenderableType", 

103 style: Optional[StyleType] = None, 

104 *, 

105 vertical: Optional[VerticalAlignMethod] = None, 

106 pad: bool = True, 

107 width: Optional[int] = None, 

108 height: Optional[int] = None, 

109 ) -> "Align": 

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

111 return cls( 

112 renderable, 

113 "center", 

114 style=style, 

115 vertical=vertical, 

116 pad=pad, 

117 width=width, 

118 height=height, 

119 ) 

120 

121 @classmethod 

122 def right( 

123 cls, 

124 renderable: "RenderableType", 

125 style: Optional[StyleType] = None, 

126 *, 

127 vertical: Optional[VerticalAlignMethod] = None, 

128 pad: bool = True, 

129 width: Optional[int] = None, 

130 height: Optional[int] = None, 

131 ) -> "Align": 

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

133 return cls( 

134 renderable, 

135 "right", 

136 style=style, 

137 vertical=vertical, 

138 pad=pad, 

139 width=width, 

140 height=height, 

141 ) 

142 

143 def __rich_console__( 

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

145 ) -> "RenderResult": 

146 align = self.align 

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

148 rendered = console.render( 

149 Constrain( 

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

151 ), 

152 options.update(height=None), 

153 ) 

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

155 width, height = Segment.get_shape(lines) 

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

157 new_line = Segment.line() 

158 excess_space = options.max_width - width 

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

160 

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

162 if excess_space <= 0: 

163 # Exact fit 

164 for line in lines: 

165 yield from line 

166 yield new_line 

167 

168 elif align == "left": 

169 # Pad on the right 

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

171 for line in lines: 

172 yield from line 

173 if pad: 

174 yield pad 

175 yield new_line 

176 

177 elif align == "center": 

178 # Pad left and right 

179 left = excess_space // 2 

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

181 pad_right = ( 

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

183 ) 

184 for line in lines: 

185 if left: 

186 yield pad 

187 yield from line 

188 if pad_right: 

189 yield pad_right 

190 yield new_line 

191 

192 elif align == "right": 

193 # Padding on left 

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

195 for line in lines: 

196 yield pad 

197 yield from line 

198 yield new_line 

199 

200 blank_line = ( 

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

202 if self.pad 

203 else Segment("\n") 

204 ) 

205 

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

207 if count > 0: 

208 for _ in range(count): 

209 yield blank_line 

210 

211 vertical_height = self.height or options.height 

212 iter_segments: Iterable[Segment] 

213 if self.vertical and vertical_height is not None: 

214 if self.vertical == "top": 

215 bottom_space = vertical_height - height 

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

217 elif self.vertical == "middle": 

218 top_space = (vertical_height - height) // 2 

219 bottom_space = vertical_height - top_space - height 

220 iter_segments = chain( 

221 blank_lines(top_space), 

222 generate_segments(), 

223 blank_lines(bottom_space), 

224 ) 

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

226 top_space = vertical_height - height 

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

228 else: 

229 iter_segments = generate_segments() 

230 if self.style: 

231 style = console.get_style(self.style) 

232 iter_segments = Segment.apply_style(iter_segments, style) 

233 yield from iter_segments 

234 

235 def __rich_measure__( 

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

237 ) -> Measurement: 

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

239 return measurement 

240 

241 

242class VerticalCenter(JupyterMixin): 

243 """Vertically aligns a renderable. 

244 

245 Warn: 

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

247 `vertical="middle"`. 

248 

249 Args: 

250 renderable (RenderableType): A renderable object. 

251 style (StyleType, optional): An optional style to apply to the background. Defaults to None. 

252 """ 

253 

254 def __init__( 

255 self, 

256 renderable: "RenderableType", 

257 style: Optional[StyleType] = None, 

258 ) -> None: 

259 self.renderable = renderable 

260 self.style = style 

261 

262 def __repr__(self) -> str: 

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

264 

265 def __rich_console__( 

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

267 ) -> "RenderResult": 

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

269 lines = console.render_lines( 

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

271 ) 

272 width, _height = Segment.get_shape(lines) 

273 new_line = Segment.line() 

274 height = options.height or options.size.height 

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

276 bottom_space = height - top_space - len(lines) 

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

278 

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

280 for _ in range(count): 

281 yield blank_line 

282 yield new_line 

283 

284 if top_space > 0: 

285 yield from blank_lines(top_space) 

286 for line in lines: 

287 yield from line 

288 yield new_line 

289 if bottom_space > 0: 

290 yield from blank_lines(bottom_space) 

291 

292 def __rich_measure__( 

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

294 ) -> Measurement: 

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

296 return measurement 

297 

298 

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

300 from rich.console import Console, Group 

301 from rich.highlighter import ReprHighlighter 

302 from rich.panel import Panel 

303 

304 highlighter = ReprHighlighter() 

305 console = Console() 

306 

307 panel = Panel( 

308 Group( 

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

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

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

312 ), 

313 width=60, 

314 style="on dark_blue", 

315 title="Align", 

316 ) 

317 

318 console.print( 

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

320 )