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

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

119 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, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`. 

26 Defaults to box.ROUNDED. 

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

28 expand (bool, optional): If True the panel will stretch to fill the console 

29 width, otherwise it will be sized to fit the contents. Defaults to True. 

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

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

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

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

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

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

36 """ 

37 

38 def __init__( 

39 self, 

40 renderable: "RenderableType", 

41 box: Box = ROUNDED, 

42 *, 

43 title: Optional[TextType] = None, 

44 title_align: AlignMethod = "center", 

45 subtitle: Optional[TextType] = None, 

46 subtitle_align: AlignMethod = "center", 

47 safe_box: Optional[bool] = None, 

48 expand: bool = True, 

49 style: StyleType = "none", 

50 border_style: StyleType = "none", 

51 width: Optional[int] = None, 

52 height: Optional[int] = None, 

53 padding: PaddingDimensions = (0, 1), 

54 highlight: bool = False, 

55 ) -> None: 

56 self.renderable = renderable 

57 self.box = box 

58 self.title = title 

59 self.title_align: AlignMethod = title_align 

60 self.subtitle = subtitle 

61 self.subtitle_align = subtitle_align 

62 self.safe_box = safe_box 

63 self.expand = expand 

64 self.style = style 

65 self.border_style = border_style 

66 self.width = width 

67 self.height = height 

68 self.padding = padding 

69 self.highlight = highlight 

70 

71 @classmethod 

72 def fit( 

73 cls, 

74 renderable: "RenderableType", 

75 box: Box = ROUNDED, 

76 *, 

77 title: Optional[TextType] = None, 

78 title_align: AlignMethod = "center", 

79 subtitle: Optional[TextType] = None, 

80 subtitle_align: AlignMethod = "center", 

81 safe_box: Optional[bool] = None, 

82 style: StyleType = "none", 

83 border_style: StyleType = "none", 

84 width: Optional[int] = None, 

85 height: Optional[int] = None, 

86 padding: PaddingDimensions = (0, 1), 

87 highlight: bool = False, 

88 ) -> "Panel": 

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

90 return cls( 

91 renderable, 

92 box, 

93 title=title, 

94 title_align=title_align, 

95 subtitle=subtitle, 

96 subtitle_align=subtitle_align, 

97 safe_box=safe_box, 

98 style=style, 

99 border_style=border_style, 

100 width=width, 

101 height=height, 

102 padding=padding, 

103 highlight=highlight, 

104 expand=False, 

105 ) 

106 

107 @property 

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

109 if self.title: 

110 title_text = ( 

111 Text.from_markup(self.title) 

112 if isinstance(self.title, str) 

113 else self.title.copy() 

114 ) 

115 title_text.end = "" 

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

117 title_text.no_wrap = True 

118 title_text.expand_tabs() 

119 title_text.pad(1) 

120 return title_text 

121 return None 

122 

123 @property 

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

125 if self.subtitle: 

126 subtitle_text = ( 

127 Text.from_markup(self.subtitle) 

128 if isinstance(self.subtitle, str) 

129 else self.subtitle.copy() 

130 ) 

131 subtitle_text.end = "" 

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

133 subtitle_text.no_wrap = True 

134 subtitle_text.expand_tabs() 

135 subtitle_text.pad(1) 

136 return subtitle_text 

137 return None 

138 

139 def __rich_console__( 

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

141 ) -> "RenderResult": 

142 _padding = Padding.unpack(self.padding) 

143 renderable = ( 

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

145 ) 

146 style = console.get_style(self.style) 

147 border_style = style + console.get_style(self.border_style) 

148 width = ( 

149 options.max_width 

150 if self.width is None 

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

152 ) 

153 

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

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

156 

157 def align_text( 

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

159 ) -> Text: 

160 """Gets new aligned text. 

161 

162 Args: 

163 text (Text): Title or subtitle text. 

164 width (int): Desired width. 

165 align (str): Alignment. 

166 character (str): Character for alignment. 

167 style (Style): Border style 

168 

169 Returns: 

170 Text: New text instance 

171 """ 

172 text = text.copy() 

173 text.truncate(width) 

174 excess_space = width - cell_len(text.plain) 

175 if excess_space: 

176 if align == "left": 

177 return Text.assemble( 

178 text, 

179 (character * excess_space, style), 

180 no_wrap=True, 

181 end="", 

182 ) 

183 elif align == "center": 

184 left = excess_space // 2 

185 return Text.assemble( 

186 (character * left, style), 

187 text, 

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

189 no_wrap=True, 

190 end="", 

191 ) 

192 else: 

193 return Text.assemble( 

194 (character * excess_space, style), 

195 text, 

196 no_wrap=True, 

197 end="", 

198 ) 

199 return text 

200 

201 title_text = self._title 

202 if title_text is not None: 

203 title_text.stylize_before(border_style) 

204 

205 child_width = ( 

206 width - 2 

207 if self.expand 

208 else console.measure( 

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

210 ).maximum 

211 ) 

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

213 if child_height: 

214 child_height -= 2 

215 if title_text is not None: 

216 child_width = min( 

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

218 ) 

219 

220 width = child_width + 2 

221 child_options = options.update( 

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

223 ) 

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

225 

226 line_start = Segment(box.mid_left, border_style) 

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

228 new_line = Segment.line() 

229 if title_text is None or width <= 4: 

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

231 else: 

232 title_text = align_text( 

233 title_text, 

234 width - 4, 

235 self.title_align, 

236 box.top, 

237 border_style, 

238 ) 

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

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

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

242 

243 yield new_line 

244 for line in lines: 

245 yield line_start 

246 yield from line 

247 yield line_end 

248 yield new_line 

249 

250 subtitle_text = self._subtitle 

251 if subtitle_text is not None: 

252 subtitle_text.stylize_before(border_style) 

253 

254 if subtitle_text is None or width <= 4: 

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

256 else: 

257 subtitle_text = align_text( 

258 subtitle_text, 

259 width - 4, 

260 self.subtitle_align, 

261 box.bottom, 

262 border_style, 

263 ) 

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

265 yield from console.render( 

266 subtitle_text, child_options.update_width(width - 4) 

267 ) 

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

269 

270 yield new_line 

271 

272 def __rich_measure__( 

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

274 ) -> "Measurement": 

275 _title = self._title 

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

277 padding = left + right 

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

279 

280 if self.width is None: 

281 width = ( 

282 measure_renderables( 

283 console, 

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

285 renderables, 

286 ).maximum 

287 + padding 

288 + 2 

289 ) 

290 else: 

291 width = self.width 

292 return Measurement(width, width) 

293 

294 

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

296 from .console import Console 

297 

298 c = Console() 

299 

300 from .box import DOUBLE, ROUNDED 

301 from .padding import Padding 

302 

303 p = Panel( 

304 "Hello, World!", 

305 title="rich.Panel", 

306 style="white on blue", 

307 box=DOUBLE, 

308 padding=1, 

309 ) 

310 

311 c.print() 

312 c.print(p)