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

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

107 statements  

1import sys 

2from typing import TYPE_CHECKING, Iterable, List 

3 

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

5 from typing import Literal 

6else: 

7 from typing_extensions import Literal # pragma: no cover 

8 

9 

10from ._loop import loop_last 

11 

12if TYPE_CHECKING: 

13 from rich.console import ConsoleOptions 

14 

15 

16class Box: 

17 """Defines characters to render boxes. 

18 

19 ┌─┬┐ top 

20 │ ││ head 

21 ├─┼┤ head_row 

22 │ ││ mid 

23 ├─┼┤ row 

24 ├─┼┤ foot_row 

25 │ ││ foot 

26 └─┴┘ bottom 

27 

28 Args: 

29 box (str): Characters making up box. 

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

31 """ 

32 

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

34 self._box = box 

35 self.ascii = ascii 

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

37 # top 

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

39 # head 

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

41 # head_row 

42 ( 

43 self.head_row_left, 

44 self.head_row_horizontal, 

45 self.head_row_cross, 

46 self.head_row_right, 

47 ) = iter(line3) 

48 

49 # mid 

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

51 # row 

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

53 # foot_row 

54 ( 

55 self.foot_row_left, 

56 self.foot_row_horizontal, 

57 self.foot_row_cross, 

58 self.foot_row_right, 

59 ) = iter(line6) 

60 # foot 

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

62 # bottom 

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

64 line8 

65 ) 

66 

67 def __repr__(self) -> str: 

68 return "Box(...)" 

69 

70 def __str__(self) -> str: 

71 return self._box 

72 

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

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

75 

76 Args: 

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

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

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

80 

81 Returns: 

82 Box: A different Box or the same Box. 

83 """ 

84 box = self 

85 if options.legacy_windows and safe: 

86 box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box) 

87 if options.ascii_only and not box.ascii: 

88 box = ASCII 

89 return box 

90 

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

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

93 return the equivalent box that does not. 

94 

95 Returns: 

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

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

98 """ 

99 return PLAIN_HEADED_SUBSTITUTIONS.get(self, self) 

100 

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

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

103 

104 Args: 

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

106 

107 Returns: 

108 str: A string of box characters. 

109 """ 

110 

111 parts: List[str] = [] 

112 append = parts.append 

113 append(self.top_left) 

114 for last, width in loop_last(widths): 

115 append(self.top * width) 

116 if not last: 

117 append(self.top_divider) 

118 append(self.top_right) 

119 return "".join(parts) 

120 

121 def get_row( 

122 self, 

123 widths: Iterable[int], 

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

125 edge: bool = True, 

126 ) -> str: 

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

128 

129 Args: 

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

131 

132 Returns: 

133 str: A string of box characters. 

134 """ 

135 if level == "head": 

136 left = self.head_row_left 

137 horizontal = self.head_row_horizontal 

138 cross = self.head_row_cross 

139 right = self.head_row_right 

140 elif level == "row": 

141 left = self.row_left 

142 horizontal = self.row_horizontal 

143 cross = self.row_cross 

144 right = self.row_right 

145 elif level == "mid": 

146 left = self.mid_left 

147 horizontal = " " 

148 cross = self.mid_vertical 

149 right = self.mid_right 

150 elif level == "foot": 

151 left = self.foot_row_left 

152 horizontal = self.foot_row_horizontal 

153 cross = self.foot_row_cross 

154 right = self.foot_row_right 

155 else: 

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

157 

158 parts: List[str] = [] 

159 append = parts.append 

160 if edge: 

161 append(left) 

162 for last, width in loop_last(widths): 

163 append(horizontal * width) 

164 if not last: 

165 append(cross) 

166 if edge: 

167 append(right) 

168 return "".join(parts) 

169 

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

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

172 

173 Args: 

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

175 

176 Returns: 

177 str: A string of box characters. 

178 """ 

179 

180 parts: List[str] = [] 

181 append = parts.append 

182 append(self.bottom_left) 

183 for last, width in loop_last(widths): 

184 append(self.bottom * width) 

185 if not last: 

186 append(self.bottom_divider) 

187 append(self.bottom_right) 

188 return "".join(parts) 

189 

190 

191# fmt: off 

192ASCII: Box = Box( 

193 "+--+\n" 

194 "| ||\n" 

195 "|-+|\n" 

196 "| ||\n" 

197 "|-+|\n" 

198 "|-+|\n" 

199 "| ||\n" 

200 "+--+\n", 

201 ascii=True, 

202) 

203 

204ASCII2: Box = Box( 

205 "+-++\n" 

206 "| ||\n" 

207 "+-++\n" 

208 "| ||\n" 

209 "+-++\n" 

210 "+-++\n" 

211 "| ||\n" 

212 "+-++\n", 

213 ascii=True, 

214) 

215 

216ASCII_DOUBLE_HEAD: Box = Box( 

217 "+-++\n" 

218 "| ||\n" 

219 "+=++\n" 

220 "| ||\n" 

221 "+-++\n" 

222 "+-++\n" 

223 "| ||\n" 

224 "+-++\n", 

225 ascii=True, 

226) 

227 

228SQUARE: Box = Box( 

229 "┌─┬┐\n" 

230 "│ ││\n" 

231 "├─┼┤\n" 

232 "│ ││\n" 

233 "├─┼┤\n" 

234 "├─┼┤\n" 

235 "│ ││\n" 

236 "└─┴┘\n" 

237) 

238 

239SQUARE_DOUBLE_HEAD: Box = Box( 

240 "┌─┬┐\n" 

241 "│ ││\n" 

242 "╞═╪╡\n" 

243 "│ ││\n" 

244 "├─┼┤\n" 

245 "├─┼┤\n" 

246 "│ ││\n" 

247 "└─┴┘\n" 

248) 

249 

250MINIMAL: Box = Box( 

251 " ╷ \n" 

252 " │ \n" 

253 "╶─┼╴\n" 

254 " │ \n" 

255 "╶─┼╴\n" 

256 "╶─┼╴\n" 

257 " │ \n" 

258 " ╵ \n" 

259) 

260 

261 

262MINIMAL_HEAVY_HEAD: Box = Box( 

263 " ╷ \n" 

264 " │ \n" 

265 "╺━┿╸\n" 

266 " │ \n" 

267 "╶─┼╴\n" 

268 "╶─┼╴\n" 

269 " │ \n" 

270 " ╵ \n" 

271) 

272 

273MINIMAL_DOUBLE_HEAD: Box = Box( 

274 " ╷ \n" 

275 " │ \n" 

276 " ═╪ \n" 

277 " │ \n" 

278 " ─┼ \n" 

279 " ─┼ \n" 

280 " │ \n" 

281 " ╵ \n" 

282) 

283 

284 

285SIMPLE: Box = Box( 

286 " \n" 

287 " \n" 

288 " ── \n" 

289 " \n" 

290 " \n" 

291 " ── \n" 

292 " \n" 

293 " \n" 

294) 

295 

296SIMPLE_HEAD: Box = Box( 

297 " \n" 

298 " \n" 

299 " ── \n" 

300 " \n" 

301 " \n" 

302 " \n" 

303 " \n" 

304 " \n" 

305) 

306 

307 

308SIMPLE_HEAVY: Box = Box( 

309 " \n" 

310 " \n" 

311 " ━━ \n" 

312 " \n" 

313 " \n" 

314 " ━━ \n" 

315 " \n" 

316 " \n" 

317) 

318 

319 

320HORIZONTALS: Box = Box( 

321 " ── \n" 

322 " \n" 

323 " ── \n" 

324 " \n" 

325 " ── \n" 

326 " ── \n" 

327 " \n" 

328 " ── \n" 

329) 

330 

331ROUNDED: Box = Box( 

332 "╭─┬╮\n" 

333 "│ ││\n" 

334 "├─┼┤\n" 

335 "│ ││\n" 

336 "├─┼┤\n" 

337 "├─┼┤\n" 

338 "│ ││\n" 

339 "╰─┴╯\n" 

340) 

341 

342HEAVY: Box = Box( 

343 "┏━┳┓\n" 

344 "┃ ┃┃\n" 

345 "┣━╋┫\n" 

346 "┃ ┃┃\n" 

347 "┣━╋┫\n" 

348 "┣━╋┫\n" 

349 "┃ ┃┃\n" 

350 "┗━┻┛\n" 

351) 

352 

353HEAVY_EDGE: Box = Box( 

354 "┏━┯┓\n" 

355 "┃ │┃\n" 

356 "┠─┼┨\n" 

357 "┃ │┃\n" 

358 "┠─┼┨\n" 

359 "┠─┼┨\n" 

360 "┃ │┃\n" 

361 "┗━┷┛\n" 

362) 

363 

364HEAVY_HEAD: Box = Box( 

365 "┏━┳┓\n" 

366 "┃ ┃┃\n" 

367 "┡━╇┩\n" 

368 "│ ││\n" 

369 "├─┼┤\n" 

370 "├─┼┤\n" 

371 "│ ││\n" 

372 "└─┴┘\n" 

373) 

374 

375DOUBLE: Box = Box( 

376 "╔═╦╗\n" 

377 "║ ║║\n" 

378 "╠═╬╣\n" 

379 "║ ║║\n" 

380 "╠═╬╣\n" 

381 "╠═╬╣\n" 

382 "║ ║║\n" 

383 "╚═╩╝\n" 

384) 

385 

386DOUBLE_EDGE: Box = Box( 

387 "╔═╤╗\n" 

388 "║ │║\n" 

389 "╟─┼╢\n" 

390 "║ │║\n" 

391 "╟─┼╢\n" 

392 "╟─┼╢\n" 

393 "║ │║\n" 

394 "╚═╧╝\n" 

395) 

396 

397MARKDOWN: Box = Box( 

398 " \n" 

399 "| ||\n" 

400 "|-||\n" 

401 "| ||\n" 

402 "|-||\n" 

403 "|-||\n" 

404 "| ||\n" 

405 " \n", 

406 ascii=True, 

407) 

408# fmt: on 

409 

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

411LEGACY_WINDOWS_SUBSTITUTIONS = { 

412 ROUNDED: SQUARE, 

413 MINIMAL_HEAVY_HEAD: MINIMAL, 

414 SIMPLE_HEAVY: SIMPLE, 

415 HEAVY: SQUARE, 

416 HEAVY_EDGE: SQUARE, 

417 HEAVY_HEAD: SQUARE, 

418} 

419 

420# Map headed boxes to their headerless equivalents 

421PLAIN_HEADED_SUBSTITUTIONS = { 

422 HEAVY_HEAD: SQUARE, 

423 SQUARE_DOUBLE_HEAD: SQUARE, 

424 MINIMAL_DOUBLE_HEAD: MINIMAL, 

425 MINIMAL_HEAVY_HEAD: MINIMAL, 

426 ASCII_DOUBLE_HEAD: ASCII2, 

427} 

428 

429 

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

431 from rich.columns import Columns 

432 from rich.panel import Panel 

433 

434 from . import box as box 

435 from .console import Console 

436 from .table import Table 

437 from .text import Text 

438 

439 console = Console(record=True) 

440 

441 BOXES = [ 

442 "ASCII", 

443 "ASCII2", 

444 "ASCII_DOUBLE_HEAD", 

445 "SQUARE", 

446 "SQUARE_DOUBLE_HEAD", 

447 "MINIMAL", 

448 "MINIMAL_HEAVY_HEAD", 

449 "MINIMAL_DOUBLE_HEAD", 

450 "SIMPLE", 

451 "SIMPLE_HEAD", 

452 "SIMPLE_HEAVY", 

453 "HORIZONTALS", 

454 "ROUNDED", 

455 "HEAVY", 

456 "HEAVY_EDGE", 

457 "HEAVY_HEAD", 

458 "DOUBLE", 

459 "DOUBLE_EDGE", 

460 "MARKDOWN", 

461 ] 

462 

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

464 console.print() 

465 

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

467 for box_name in sorted(BOXES): 

468 table = Table( 

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

470 ) 

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

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

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

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

475 table.box = getattr(box, box_name) 

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

477 columns.add_renderable(table) 

478 console.print(columns) 

479 

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