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

107 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

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 

191ASCII: Box = Box( 

192 """\ 

193+--+ 

194| || 

195|-+| 

196| || 

197|-+| 

198|-+| 

199| || 

200+--+ 

201""", 

202 ascii=True, 

203) 

204 

205ASCII2: Box = Box( 

206 """\ 

207+-++ 

208| || 

209+-++ 

210| || 

211+-++ 

212+-++ 

213| || 

214+-++ 

215""", 

216 ascii=True, 

217) 

218 

219ASCII_DOUBLE_HEAD: Box = Box( 

220 """\ 

221+-++ 

222| || 

223+=++ 

224| || 

225+-++ 

226+-++ 

227| || 

228+-++ 

229""", 

230 ascii=True, 

231) 

232 

233SQUARE: Box = Box( 

234 """\ 

235┌─┬┐ 

236│ ││ 

237├─┼┤ 

238│ ││ 

239├─┼┤ 

240├─┼┤ 

241│ ││ 

242└─┴┘ 

243""" 

244) 

245 

246SQUARE_DOUBLE_HEAD: Box = Box( 

247 """\ 

248┌─┬┐ 

249│ ││ 

250╞═╪╡ 

251│ ││ 

252├─┼┤ 

253├─┼┤ 

254│ ││ 

255└─┴┘ 

256""" 

257) 

258 

259MINIMAL: Box = Box( 

260 """\ 

261 

262 

263╶─┼╴ 

264 

265╶─┼╴ 

266╶─┼╴ 

267 

268 

269""" 

270) 

271 

272 

273MINIMAL_HEAVY_HEAD: Box = Box( 

274 """\ 

275 

276 

277╺━┿╸ 

278 

279╶─┼╴ 

280╶─┼╴ 

281 

282 

283""" 

284) 

285 

286MINIMAL_DOUBLE_HEAD: Box = Box( 

287 """\ 

288 

289 

290 ═╪  

291 

292 ─┼  

293 ─┼  

294 

295 

296""" 

297) 

298 

299 

300SIMPLE: Box = Box( 

301 """\ 

302  

303  

304 ──  

305  

306  

307 ──  

308  

309  

310""" 

311) 

312 

313SIMPLE_HEAD: Box = Box( 

314 """\ 

315  

316  

317 ──  

318  

319  

320  

321  

322  

323""" 

324) 

325 

326 

327SIMPLE_HEAVY: Box = Box( 

328 """\ 

329  

330  

331 ━━  

332  

333  

334 ━━  

335  

336  

337""" 

338) 

339 

340 

341HORIZONTALS: Box = Box( 

342 """\ 

343 ──  

344  

345 ──  

346  

347 ──  

348 ──  

349  

350 ──  

351""" 

352) 

353 

354ROUNDED: Box = Box( 

355 """\ 

356╭─┬╮ 

357│ ││ 

358├─┼┤ 

359│ ││ 

360├─┼┤ 

361├─┼┤ 

362│ ││ 

363╰─┴╯ 

364""" 

365) 

366 

367HEAVY: Box = Box( 

368 """\ 

369┏━┳┓ 

370┃ ┃┃ 

371┣━╋┫ 

372┃ ┃┃ 

373┣━╋┫ 

374┣━╋┫ 

375┃ ┃┃ 

376┗━┻┛ 

377""" 

378) 

379 

380HEAVY_EDGE: Box = Box( 

381 """\ 

382┏━┯┓ 

383┃ │┃ 

384┠─┼┨ 

385┃ │┃ 

386┠─┼┨ 

387┠─┼┨ 

388┃ │┃ 

389┗━┷┛ 

390""" 

391) 

392 

393HEAVY_HEAD: Box = Box( 

394 """\ 

395┏━┳┓ 

396┃ ┃┃ 

397┡━╇┩ 

398│ ││ 

399├─┼┤ 

400├─┼┤ 

401│ ││ 

402└─┴┘ 

403""" 

404) 

405 

406DOUBLE: Box = Box( 

407 """\ 

408╔═╦╗ 

409║ ║║ 

410╠═╬╣ 

411║ ║║ 

412╠═╬╣ 

413╠═╬╣ 

414║ ║║ 

415╚═╩╝ 

416""" 

417) 

418 

419DOUBLE_EDGE: Box = Box( 

420 """\ 

421╔═╤╗ 

422║ │║ 

423╟─┼╢ 

424║ │║ 

425╟─┼╢ 

426╟─┼╢ 

427║ │║ 

428╚═╧╝ 

429""" 

430) 

431 

432MARKDOWN: Box = Box( 

433 """\ 

434  

435| || 

436|-|| 

437| || 

438|-|| 

439|-|| 

440| || 

441  

442""", 

443 ascii=True, 

444) 

445 

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

447LEGACY_WINDOWS_SUBSTITUTIONS = { 

448 ROUNDED: SQUARE, 

449 MINIMAL_HEAVY_HEAD: MINIMAL, 

450 SIMPLE_HEAVY: SIMPLE, 

451 HEAVY: SQUARE, 

452 HEAVY_EDGE: SQUARE, 

453 HEAVY_HEAD: SQUARE, 

454} 

455 

456# Map headed boxes to their headerless equivalents 

457PLAIN_HEADED_SUBSTITUTIONS = { 

458 HEAVY_HEAD: SQUARE, 

459 SQUARE_DOUBLE_HEAD: SQUARE, 

460 MINIMAL_DOUBLE_HEAD: MINIMAL, 

461 MINIMAL_HEAVY_HEAD: MINIMAL, 

462 ASCII_DOUBLE_HEAD: ASCII2, 

463} 

464 

465 

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

467 

468 from rich.columns import Columns 

469 from rich.panel import Panel 

470 

471 from . import box as box 

472 from .console import Console 

473 from .table import Table 

474 from .text import Text 

475 

476 console = Console(record=True) 

477 

478 BOXES = [ 

479 "ASCII", 

480 "ASCII2", 

481 "ASCII_DOUBLE_HEAD", 

482 "SQUARE", 

483 "SQUARE_DOUBLE_HEAD", 

484 "MINIMAL", 

485 "MINIMAL_HEAVY_HEAD", 

486 "MINIMAL_DOUBLE_HEAD", 

487 "SIMPLE", 

488 "SIMPLE_HEAD", 

489 "SIMPLE_HEAVY", 

490 "HORIZONTALS", 

491 "ROUNDED", 

492 "HEAVY", 

493 "HEAVY_EDGE", 

494 "HEAVY_HEAD", 

495 "DOUBLE", 

496 "DOUBLE_EDGE", 

497 "MARKDOWN", 

498 ] 

499 

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

501 console.print() 

502 

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

504 for box_name in sorted(BOXES): 

505 table = Table( 

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

507 ) 

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

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

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

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

512 table.box = getattr(box, box_name) 

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

514 columns.add_renderable(table) 

515 console.print(columns) 

516 

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