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

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

104 statements  

1from typing import TYPE_CHECKING, Iterable, List, Literal 

2 

3 

4from ._loop import loop_last 

5 

6if TYPE_CHECKING: 

7 from rich.console import ConsoleOptions 

8 

9 

10class Box: 

11 """Defines characters to render boxes. 

12 

13 ┌─┬┐ top 

14 │ ││ head 

15 ├─┼┤ head_row 

16 │ ││ mid 

17 ├─┼┤ row 

18 ├─┼┤ foot_row 

19 │ ││ foot 

20 └─┴┘ bottom 

21 

22 Args: 

23 box (str): Characters making up box. 

24 ascii (bool, optional): True if this box uses ascii characters only. Default is False. 

25 """ 

26 

27 def __init__(self, box: str, *, ascii: bool = False) -> None: 

28 self._box = box 

29 self.ascii = ascii 

30 line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines() 

31 # top 

32 self.top_left, self.top, self.top_divider, self.top_right = iter(line1) 

33 # head 

34 self.head_left, _, self.head_vertical, self.head_right = iter(line2) 

35 # head_row 

36 ( 

37 self.head_row_left, 

38 self.head_row_horizontal, 

39 self.head_row_cross, 

40 self.head_row_right, 

41 ) = iter(line3) 

42 

43 # mid 

44 self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4) 

45 # row 

46 self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5) 

47 # foot_row 

48 ( 

49 self.foot_row_left, 

50 self.foot_row_horizontal, 

51 self.foot_row_cross, 

52 self.foot_row_right, 

53 ) = iter(line6) 

54 # foot 

55 self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7) 

56 # bottom 

57 self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter( 

58 line8 

59 ) 

60 

61 def __repr__(self) -> str: 

62 return "Box(...)" 

63 

64 def __str__(self) -> str: 

65 return self._box 

66 

67 def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box": 

68 """Substitute this box for another if it won't render due to platform issues. 

69 

70 Args: 

71 options (ConsoleOptions): Console options used in rendering. 

72 safe (bool, optional): Substitute this for another Box if there are known problems 

73 displaying on the platform (currently only relevant on Windows). Default is True. 

74 

75 Returns: 

76 Box: A different Box or the same Box. 

77 """ 

78 box = self 

79 if options.legacy_windows and safe: 

80 box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box) 

81 if options.ascii_only and not box.ascii: 

82 box = ASCII 

83 return box 

84 

85 def get_plain_headed_box(self) -> "Box": 

86 """If this box uses special characters for the borders of the header, then 

87 return the equivalent box that does not. 

88 

89 Returns: 

90 Box: The most similar Box that doesn't use header-specific box characters. 

91 If the current Box already satisfies this criterion, then it's returned. 

92 """ 

93 return PLAIN_HEADED_SUBSTITUTIONS.get(self, self) 

94 

95 def get_top(self, widths: Iterable[int]) -> str: 

96 """Get the top of a simple box. 

97 

98 Args: 

99 widths (List[int]): Widths of columns. 

100 

101 Returns: 

102 str: A string of box characters. 

103 """ 

104 

105 parts: List[str] = [] 

106 append = parts.append 

107 append(self.top_left) 

108 for last, width in loop_last(widths): 

109 append(self.top * width) 

110 if not last: 

111 append(self.top_divider) 

112 append(self.top_right) 

113 return "".join(parts) 

114 

115 def get_row( 

116 self, 

117 widths: Iterable[int], 

118 level: Literal["head", "row", "foot", "mid"] = "row", 

119 edge: bool = True, 

120 ) -> str: 

121 """Get the top of a simple box. 

122 

123 Args: 

124 width (List[int]): Widths of columns. 

125 

126 Returns: 

127 str: A string of box characters. 

128 """ 

129 if level == "head": 

130 left = self.head_row_left 

131 horizontal = self.head_row_horizontal 

132 cross = self.head_row_cross 

133 right = self.head_row_right 

134 elif level == "row": 

135 left = self.row_left 

136 horizontal = self.row_horizontal 

137 cross = self.row_cross 

138 right = self.row_right 

139 elif level == "mid": 

140 left = self.mid_left 

141 horizontal = " " 

142 cross = self.mid_vertical 

143 right = self.mid_right 

144 elif level == "foot": 

145 left = self.foot_row_left 

146 horizontal = self.foot_row_horizontal 

147 cross = self.foot_row_cross 

148 right = self.foot_row_right 

149 else: 

150 raise ValueError("level must be 'head', 'row' or 'foot'") 

151 

152 parts: List[str] = [] 

153 append = parts.append 

154 if edge: 

155 append(left) 

156 for last, width in loop_last(widths): 

157 append(horizontal * width) 

158 if not last: 

159 append(cross) 

160 if edge: 

161 append(right) 

162 return "".join(parts) 

163 

164 def get_bottom(self, widths: Iterable[int]) -> str: 

165 """Get the bottom of a simple box. 

166 

167 Args: 

168 widths (List[int]): Widths of columns. 

169 

170 Returns: 

171 str: A string of box characters. 

172 """ 

173 

174 parts: List[str] = [] 

175 append = parts.append 

176 append(self.bottom_left) 

177 for last, width in loop_last(widths): 

178 append(self.bottom * width) 

179 if not last: 

180 append(self.bottom_divider) 

181 append(self.bottom_right) 

182 return "".join(parts) 

183 

184 

185# fmt: off 

186ASCII: Box = Box( 

187 "+--+\n" 

188 "| ||\n" 

189 "|-+|\n" 

190 "| ||\n" 

191 "|-+|\n" 

192 "|-+|\n" 

193 "| ||\n" 

194 "+--+\n", 

195 ascii=True, 

196) 

197 

198ASCII2: Box = Box( 

199 "+-++\n" 

200 "| ||\n" 

201 "+-++\n" 

202 "| ||\n" 

203 "+-++\n" 

204 "+-++\n" 

205 "| ||\n" 

206 "+-++\n", 

207 ascii=True, 

208) 

209 

210ASCII_DOUBLE_HEAD: Box = Box( 

211 "+-++\n" 

212 "| ||\n" 

213 "+=++\n" 

214 "| ||\n" 

215 "+-++\n" 

216 "+-++\n" 

217 "| ||\n" 

218 "+-++\n", 

219 ascii=True, 

220) 

221 

222SQUARE: Box = Box( 

223 "┌─┬┐\n" 

224 "│ ││\n" 

225 "├─┼┤\n" 

226 "│ ││\n" 

227 "├─┼┤\n" 

228 "├─┼┤\n" 

229 "│ ││\n" 

230 "└─┴┘\n" 

231) 

232 

233SQUARE_DOUBLE_HEAD: Box = Box( 

234 "┌─┬┐\n" 

235 "│ ││\n" 

236 "╞═╪╡\n" 

237 "│ ││\n" 

238 "├─┼┤\n" 

239 "├─┼┤\n" 

240 "│ ││\n" 

241 "└─┴┘\n" 

242) 

243 

244MINIMAL: Box = Box( 

245 " ╷ \n" 

246 " │ \n" 

247 "╶─┼╴\n" 

248 " │ \n" 

249 "╶─┼╴\n" 

250 "╶─┼╴\n" 

251 " │ \n" 

252 " ╵ \n" 

253) 

254 

255 

256MINIMAL_HEAVY_HEAD: Box = Box( 

257 " ╷ \n" 

258 " │ \n" 

259 "╺━┿╸\n" 

260 " │ \n" 

261 "╶─┼╴\n" 

262 "╶─┼╴\n" 

263 " │ \n" 

264 " ╵ \n" 

265) 

266 

267MINIMAL_DOUBLE_HEAD: Box = Box( 

268 " ╷ \n" 

269 " │ \n" 

270 " ═╪ \n" 

271 " │ \n" 

272 " ─┼ \n" 

273 " ─┼ \n" 

274 " │ \n" 

275 " ╵ \n" 

276) 

277 

278 

279SIMPLE: Box = Box( 

280 " \n" 

281 " \n" 

282 " ── \n" 

283 " \n" 

284 " \n" 

285 " ── \n" 

286 " \n" 

287 " \n" 

288) 

289 

290SIMPLE_HEAD: Box = Box( 

291 " \n" 

292 " \n" 

293 " ── \n" 

294 " \n" 

295 " \n" 

296 " \n" 

297 " \n" 

298 " \n" 

299) 

300 

301 

302SIMPLE_HEAVY: Box = Box( 

303 " \n" 

304 " \n" 

305 " ━━ \n" 

306 " \n" 

307 " \n" 

308 " ━━ \n" 

309 " \n" 

310 " \n" 

311) 

312 

313 

314HORIZONTALS: Box = Box( 

315 " ── \n" 

316 " \n" 

317 " ── \n" 

318 " \n" 

319 " ── \n" 

320 " ── \n" 

321 " \n" 

322 " ── \n" 

323) 

324 

325ROUNDED: Box = Box( 

326 "╭─┬╮\n" 

327 "│ ││\n" 

328 "├─┼┤\n" 

329 "│ ││\n" 

330 "├─┼┤\n" 

331 "├─┼┤\n" 

332 "│ ││\n" 

333 "╰─┴╯\n" 

334) 

335 

336HEAVY: Box = Box( 

337 "┏━┳┓\n" 

338 "┃ ┃┃\n" 

339 "┣━╋┫\n" 

340 "┃ ┃┃\n" 

341 "┣━╋┫\n" 

342 "┣━╋┫\n" 

343 "┃ ┃┃\n" 

344 "┗━┻┛\n" 

345) 

346 

347HEAVY_EDGE: Box = Box( 

348 "┏━┯┓\n" 

349 "┃ │┃\n" 

350 "┠─┼┨\n" 

351 "┃ │┃\n" 

352 "┠─┼┨\n" 

353 "┠─┼┨\n" 

354 "┃ │┃\n" 

355 "┗━┷┛\n" 

356) 

357 

358HEAVY_HEAD: Box = Box( 

359 "┏━┳┓\n" 

360 "┃ ┃┃\n" 

361 "┡━╇┩\n" 

362 "│ ││\n" 

363 "├─┼┤\n" 

364 "├─┼┤\n" 

365 "│ ││\n" 

366 "└─┴┘\n" 

367) 

368 

369DOUBLE: Box = Box( 

370 "╔═╦╗\n" 

371 "║ ║║\n" 

372 "╠═╬╣\n" 

373 "║ ║║\n" 

374 "╠═╬╣\n" 

375 "╠═╬╣\n" 

376 "║ ║║\n" 

377 "╚═╩╝\n" 

378) 

379 

380DOUBLE_EDGE: Box = Box( 

381 "╔═╤╗\n" 

382 "║ │║\n" 

383 "╟─┼╢\n" 

384 "║ │║\n" 

385 "╟─┼╢\n" 

386 "╟─┼╢\n" 

387 "║ │║\n" 

388 "╚═╧╝\n" 

389) 

390 

391MARKDOWN: Box = Box( 

392 " \n" 

393 "| ||\n" 

394 "|-||\n" 

395 "| ||\n" 

396 "|-||\n" 

397 "|-||\n" 

398 "| ||\n" 

399 " \n", 

400 ascii=True, 

401) 

402# fmt: on 

403 

404# Map Boxes that don't render with raster fonts on to equivalent that do 

405LEGACY_WINDOWS_SUBSTITUTIONS = { 

406 ROUNDED: SQUARE, 

407 MINIMAL_HEAVY_HEAD: MINIMAL, 

408 SIMPLE_HEAVY: SIMPLE, 

409 HEAVY: SQUARE, 

410 HEAVY_EDGE: SQUARE, 

411 HEAVY_HEAD: SQUARE, 

412} 

413 

414# Map headed boxes to their headerless equivalents 

415PLAIN_HEADED_SUBSTITUTIONS = { 

416 HEAVY_HEAD: SQUARE, 

417 SQUARE_DOUBLE_HEAD: SQUARE, 

418 MINIMAL_DOUBLE_HEAD: MINIMAL, 

419 MINIMAL_HEAVY_HEAD: MINIMAL, 

420 ASCII_DOUBLE_HEAD: ASCII2, 

421} 

422 

423 

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

425 from rich.columns import Columns 

426 from rich.panel import Panel 

427 

428 from . import box as box 

429 from .console import Console 

430 from .table import Table 

431 from .text import Text 

432 

433 console = Console(record=True) 

434 

435 BOXES = [ 

436 "ASCII", 

437 "ASCII2", 

438 "ASCII_DOUBLE_HEAD", 

439 "SQUARE", 

440 "SQUARE_DOUBLE_HEAD", 

441 "MINIMAL", 

442 "MINIMAL_HEAVY_HEAD", 

443 "MINIMAL_DOUBLE_HEAD", 

444 "SIMPLE", 

445 "SIMPLE_HEAD", 

446 "SIMPLE_HEAVY", 

447 "HORIZONTALS", 

448 "ROUNDED", 

449 "HEAVY", 

450 "HEAVY_EDGE", 

451 "HEAVY_HEAD", 

452 "DOUBLE", 

453 "DOUBLE_EDGE", 

454 "MARKDOWN", 

455 ] 

456 

457 console.print(Panel("[bold green]Box Constants", style="green"), justify="center") 

458 console.print() 

459 

460 columns = Columns(expand=True, padding=2) 

461 for box_name in sorted(BOXES): 

462 table = Table( 

463 show_footer=True, style="dim", border_style="not dim", expand=True 

464 ) 

465 table.add_column("Header 1", "Footer 1") 

466 table.add_column("Header 2", "Footer 2") 

467 table.add_row("Cell", "Cell") 

468 table.add_row("Cell", "Cell") 

469 table.box = getattr(box, box_name) 

470 table.title = Text(f"box.{box_name}", style="magenta") 

471 columns.add_renderable(table) 

472 console.print(columns) 

473 

474 # console.save_svg("box.svg")