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

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

122 statements  

1from typing import TYPE_CHECKING, Optional 

2 

3from .align import AlignMethod 

4from .box import ROUNDED, Box 

5from .cells import cell_len 

6from .jupyter import JupyterMixin 

7from .measure import Measurement, measure_renderables 

8from .padding import Padding, PaddingDimensions 

9from .segment import Segment 

10from .style import Style, StyleType 

11from .text import Text, TextType 

12 

13if TYPE_CHECKING: 

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

15 

16 

17class Panel(JupyterMixin): 

18 """A console renderable that draws a border around its contents. 

19 

20 Example: 

21 >>> console.print(Panel("Hello, World!")) 

22 

23 Args: 

24 renderable (RenderableType): A console renderable object. 

25 box (Box): A Box instance that defines the look of the border (see :ref:`appendix_box`. Defaults to box.ROUNDED. 

26 title (Optional[TextType], optional): Optional title displayed in panel header. Defaults to None. 

27 title_align (AlignMethod, optional): Alignment of title. Defaults to "center". 

28 subtitle (Optional[TextType], optional): Optional subtitle displayed in panel footer. Defaults to None. 

29 subtitle_align (AlignMethod, optional): Alignment of subtitle. Defaults to "center". 

30 safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. 

31 expand (bool, optional): If True the panel will stretch to fill the console width, otherwise it will be sized to fit the contents. Defaults to True. 

32 style (str, optional): The style of the panel (border and contents). Defaults to "none". 

33 border_style (str, optional): The style of the border. Defaults to "none". 

34 width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect. 

35 height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect. 

36 padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0. 

37 highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False. 

38 """ 

39 

40 def __init__( 

41 self, 

42 renderable: "RenderableType", 

43 box: Box = ROUNDED, 

44 *, 

45 title: Optional[TextType] = None, 

46 title_align: AlignMethod = "center", 

47 subtitle: Optional[TextType] = None, 

48 subtitle_align: AlignMethod = "center", 

49 safe_box: Optional[bool] = None, 

50 expand: bool = True, 

51 style: StyleType = "none", 

52 border_style: StyleType = "none", 

53 width: Optional[int] = None, 

54 height: Optional[int] = None, 

55 padding: PaddingDimensions = (0, 1), 

56 highlight: bool = False, 

57 ) -> None: 

58 self.renderable = renderable 

59 self.box = box 

60 self.title = title 

61 self.title_align: AlignMethod = title_align 

62 self.subtitle = subtitle 

63 self.subtitle_align = subtitle_align 

64 self.safe_box = safe_box 

65 self.expand = expand 

66 self.style = style 

67 self.border_style = border_style 

68 self.width = width 

69 self.height = height 

70 self.padding = padding 

71 self.highlight = highlight 

72 

73 @classmethod 

74 def fit( 

75 cls, 

76 renderable: "RenderableType", 

77 box: Box = ROUNDED, 

78 *, 

79 title: Optional[TextType] = None, 

80 title_align: AlignMethod = "center", 

81 subtitle: Optional[TextType] = None, 

82 subtitle_align: AlignMethod = "center", 

83 safe_box: Optional[bool] = None, 

84 style: StyleType = "none", 

85 border_style: StyleType = "none", 

86 width: Optional[int] = None, 

87 height: Optional[int] = None, 

88 padding: PaddingDimensions = (0, 1), 

89 highlight: bool = False, 

90 ) -> "Panel": 

91 """An alternative constructor that sets expand=False.""" 

92 return cls( 

93 renderable, 

94 box, 

95 title=title, 

96 title_align=title_align, 

97 subtitle=subtitle, 

98 subtitle_align=subtitle_align, 

99 safe_box=safe_box, 

100 style=style, 

101 border_style=border_style, 

102 width=width, 

103 height=height, 

104 padding=padding, 

105 highlight=highlight, 

106 expand=False, 

107 ) 

108 

109 @property 

110 def _title(self) -> Optional[Text]: 

111 if self.title: 

112 title_text = ( 

113 Text.from_markup(self.title) 

114 if isinstance(self.title, str) 

115 else self.title.copy() 

116 ) 

117 title_text.end = "" 

118 title_text.plain = title_text.plain.replace("\n", " ") 

119 title_text.no_wrap = True 

120 title_text.expand_tabs() 

121 title_text.pad(1) 

122 return title_text 

123 return None 

124 

125 @property 

126 def _subtitle(self) -> Optional[Text]: 

127 if self.subtitle: 

128 subtitle_text = ( 

129 Text.from_markup(self.subtitle) 

130 if isinstance(self.subtitle, str) 

131 else self.subtitle.copy() 

132 ) 

133 subtitle_text.end = "" 

134 subtitle_text.plain = subtitle_text.plain.replace("\n", " ") 

135 subtitle_text.no_wrap = True 

136 subtitle_text.expand_tabs() 

137 subtitle_text.pad(1) 

138 return subtitle_text 

139 return None 

140 

141 def __rich_console__( 

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

143 ) -> "RenderResult": 

144 _padding = Padding.unpack(self.padding) 

145 renderable = ( 

146 Padding(self.renderable, _padding) if any(_padding) else self.renderable 

147 ) 

148 style = console.get_style(self.style) 

149 partial_border_style = console.get_style(self.border_style) 

150 border_style = style + partial_border_style 

151 width = ( 

152 options.max_width 

153 if self.width is None 

154 else min(options.max_width, self.width) 

155 ) 

156 

157 safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box 

158 box = self.box.substitute(options, safe=safe_box) 

159 

160 def align_text( 

161 text: Text, width: int, align: str, character: str, style: Style 

162 ) -> Text: 

163 """Gets new aligned text. 

164 

165 Args: 

166 text (Text): Title or subtitle text. 

167 width (int): Desired width. 

168 align (str): Alignment. 

169 character (str): Character for alignment. 

170 style (Style): Border style 

171 

172 Returns: 

173 Text: New text instance 

174 """ 

175 text = text.copy() 

176 text.truncate(width) 

177 excess_space = width - cell_len(text.plain) 

178 if text.style: 

179 text.stylize(console.get_style(text.style)) 

180 

181 if excess_space: 

182 if align == "left": 

183 return Text.assemble( 

184 text, 

185 (character * excess_space, style), 

186 no_wrap=True, 

187 end="", 

188 ) 

189 elif align == "center": 

190 left = excess_space // 2 

191 return Text.assemble( 

192 (character * left, style), 

193 text, 

194 (character * (excess_space - left), style), 

195 no_wrap=True, 

196 end="", 

197 ) 

198 else: 

199 return Text.assemble( 

200 (character * excess_space, style), 

201 text, 

202 no_wrap=True, 

203 end="", 

204 ) 

205 return text 

206 

207 title_text = self._title 

208 if title_text is not None: 

209 title_text.stylize_before(partial_border_style) 

210 

211 child_width = ( 

212 width - 2 

213 if self.expand 

214 else console.measure( 

215 renderable, options=options.update_width(width - 2) 

216 ).maximum 

217 ) 

218 child_height = self.height or options.height or None 

219 if child_height: 

220 child_height -= 2 

221 if title_text is not None: 

222 child_width = min( 

223 options.max_width - 2, max(child_width, title_text.cell_len + 2) 

224 ) 

225 

226 width = child_width + 2 

227 child_options = options.update( 

228 width=child_width, height=child_height, highlight=self.highlight 

229 ) 

230 lines = console.render_lines(renderable, child_options, style=style) 

231 

232 line_start = Segment(box.mid_left, border_style) 

233 line_end = Segment(f"{box.mid_right}", border_style) 

234 new_line = Segment.line() 

235 if title_text is None or width <= 4: 

236 yield Segment(box.get_top([width - 2]), border_style) 

237 else: 

238 title_text = align_text( 

239 title_text, 

240 width - 4, 

241 self.title_align, 

242 box.top, 

243 border_style, 

244 ) 

245 yield Segment(box.top_left + box.top, border_style) 

246 yield from console.render(title_text, child_options.update_width(width - 4)) 

247 yield Segment(box.top + box.top_right, border_style) 

248 

249 yield new_line 

250 for line in lines: 

251 yield line_start 

252 yield from line 

253 yield line_end 

254 yield new_line 

255 

256 subtitle_text = self._subtitle 

257 if subtitle_text is not None: 

258 subtitle_text.stylize_before(partial_border_style) 

259 

260 if subtitle_text is None or width <= 4: 

261 yield Segment(box.get_bottom([width - 2]), border_style) 

262 else: 

263 subtitle_text = align_text( 

264 subtitle_text, 

265 width - 4, 

266 self.subtitle_align, 

267 box.bottom, 

268 border_style, 

269 ) 

270 yield Segment(box.bottom_left + box.bottom, border_style) 

271 yield from console.render( 

272 subtitle_text, child_options.update_width(width - 4) 

273 ) 

274 yield Segment(box.bottom + box.bottom_right, border_style) 

275 

276 yield new_line 

277 

278 def __rich_measure__( 

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

280 ) -> "Measurement": 

281 _title = self._title 

282 _, right, _, left = Padding.unpack(self.padding) 

283 padding = left + right 

284 renderables = [self.renderable, _title] if _title else [self.renderable] 

285 

286 if self.width is None: 

287 width = ( 

288 measure_renderables( 

289 console, 

290 options.update_width(options.max_width - padding - 2), 

291 renderables, 

292 ).maximum 

293 + padding 

294 + 2 

295 ) 

296 else: 

297 width = self.width 

298 return Measurement(width, width) 

299 

300 

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

302 from .console import Console 

303 

304 c = Console() 

305 

306 from .box import DOUBLE, ROUNDED 

307 from .padding import Padding 

308 

309 p = Panel( 

310 "Hello, World!", 

311 title="rich.Panel", 

312 style="white on blue", 

313 box=DOUBLE, 

314 padding=1, 

315 ) 

316 

317 c.print() 

318 c.print(p)