// Port of rich/box.py.
///|
/// Defines characters to render boxes.
///
/// ┌─┬┐ top
/// │ ││ head
/// ├─┼┤ head_row
/// │ ││ mid
/// ├─┼┤ row
/// ├─┼┤ foot_row
/// │ ││ foot
/// └─┴┘ bottom
pub struct Box {
/// Name of a built-in box (used for substitutions), or "".
name : String
box_ : String
ascii : Bool
top_left : String
top : String
top_divider : String
top_right : String
head_left : String
head_vertical : String
head_right : String
head_row_left : String
head_row_horizontal : String
head_row_cross : String
head_row_right : String
mid_left : String
mid_vertical : String
mid_right : String
row_left : String
row_horizontal : String
row_cross : String
row_right : String
foot_row_left : String
foot_row_horizontal : String
foot_row_cross : String
foot_row_right : String
foot_left : String
foot_vertical : String
foot_right : String
bottom_left : String
bottom : String
bottom_divider : String
bottom_right : String
} derive(Eq, Debug)
///|
/// Create a box from 8 lines of 4 characters.
pub fn Box::new(
box_ : String,
ascii? : Bool = false,
name? : String = "",
) -> Box raise RichError {
let lines = py_splitlines(box_)
guard lines.length() == 8 else { raise ValueError("box must have 8 lines") }
let chars = lines.map(l => l.iter().map(c => c.to_string()).collect())
for line in chars {
guard line.length() == 4 else {
raise ValueError("box lines must have 4 characters")
}
}
{
name,
box_,
ascii,
top_left: chars[0][0],
top: chars[0][1],
top_divider: chars[0][2],
top_right: chars[0][3],
head_left: chars[1][0],
head_vertical: chars[1][2],
head_right: chars[1][3],
head_row_left: chars[2][0],
head_row_horizontal: chars[2][1],
head_row_cross: chars[2][2],
head_row_right: chars[2][3],
mid_left: chars[3][0],
mid_vertical: chars[3][2],
mid_right: chars[3][3],
row_left: chars[4][0],
row_horizontal: chars[4][1],
row_cross: chars[4][2],
row_right: chars[4][3],
foot_row_left: chars[5][0],
foot_row_horizontal: chars[5][1],
foot_row_cross: chars[5][2],
foot_row_right: chars[5][3],
foot_left: chars[6][0],
foot_vertical: chars[6][2],
foot_right: chars[6][3],
bottom_left: chars[7][0],
bottom: chars[7][1],
bottom_divider: chars[7][2],
bottom_right: chars[7][3],
}
}
///|
fn builtin_box(name : String, box_ : String, ascii? : Bool = false) -> Box {
Box::new(box_, ascii~, name~) catch {
_ => abort("bad box")
}
}
///|
pub impl Show for Box with fn output(self, logger) {
logger.write_string(self.box_)
}
///|
/// Upstream `repr(box)`.
pub fn Box::repr(_self : Box) -> String {
"Box(...)"
}
///|
/// Substitute this box for another if it won't render due to platform
/// issues.
pub fn Box::substitute(
self : Box,
options : ConsoleOptions,
safe? : Bool = true,
) -> Box {
let mut box_ = self
if options.legacy_windows && safe {
box_ = match box_.name {
"ROUNDED" => box_square
"MINIMAL_HEAVY_HEAD" => box_minimal
"SIMPLE_HEAVY" => box_simple
"HEAVY" => box_square
"HEAVY_EDGE" => box_square
"HEAVY_HEAD" => box_square
_ => box_
}
}
if options.ascii_only() && !box_.ascii {
box_ = box_ascii
}
box_
}
///|
/// If this box uses special characters for the borders of the header,
/// return the equivalent box that does not.
pub fn Box::get_plain_headed_box(self : Box) -> Box {
match self.name {
"HEAVY_HEAD" => box_square
"SQUARE_DOUBLE_HEAD" => box_square
"MINIMAL_DOUBLE_HEAD" => box_minimal
"MINIMAL_HEAVY_HEAD" => box_minimal
"ASCII_DOUBLE_HEAD" => box_ascii2
_ => self
}
}
///|
/// Get the top of a simple box.
pub fn Box::get_top(self : Box, widths : ArrayView[Int]) -> String {
let parts = StringBuilder()
parts.write_string(self.top_left)
for i, width in widths {
parts.write_string(self.top.repeat(width))
if i != widths.length() - 1 {
parts.write_string(self.top_divider)
}
}
parts.write_string(self.top_right)
parts.to_string()
}
///|
/// Row levels for `Box::get_row`.
pub(all) enum RowLevel {
Head
Row
Foot
Mid
} derive(Eq, Debug)
///|
/// Get the top of a simple box.
pub fn Box::get_row(
self : Box,
widths : ArrayView[Int],
level? : RowLevel = Row,
edge? : Bool = true,
) -> String {
let (left, horizontal, cross, right) = match level {
Head =>
(
self.head_row_left,
self.head_row_horizontal,
self.head_row_cross,
self.head_row_right,
)
Row => (self.row_left, self.row_horizontal, self.row_cross, self.row_right)
Mid => (self.mid_left, " ", self.mid_vertical, self.mid_right)
Foot =>
(
self.foot_row_left,
self.foot_row_horizontal,
self.foot_row_cross,
self.foot_row_right,
)
}
let parts = StringBuilder()
if edge {
parts.write_string(left)
}
for i, width in widths {
parts.write_string(horizontal.repeat(width))
if i != widths.length() - 1 {
parts.write_string(cross)
}
}
if edge {
parts.write_string(right)
}
parts.to_string()
}
///|
/// Get the bottom of a simple box.
pub fn Box::get_bottom(self : Box, widths : ArrayView[Int]) -> String {
let parts = StringBuilder()
parts.write_string(self.bottom_left)
for i, width in widths {
parts.write_string(self.bottom.repeat(width))
if i != widths.length() - 1 {
parts.write_string(self.bottom_divider)
}
}
parts.write_string(self.bottom_right)
parts.to_string()
}
///|
pub let box_ascii : Box = builtin_box(
"ASCII",
"+--+\n| ||\n|-+|\n| ||\n|-+|\n|-+|\n| ||\n+--+\n",
ascii=true,
)
///|
pub let box_ascii2 : Box = builtin_box(
"ASCII2",
"+-++\n| ||\n+-++\n| ||\n+-++\n+-++\n| ||\n+-++\n",
ascii=true,
)
///|
pub let box_ascii_double_head : Box = builtin_box(
"ASCII_DOUBLE_HEAD",
"+-++\n| ||\n+=++\n| ||\n+-++\n+-++\n| ||\n+-++\n",
ascii=true,
)
///|
pub let box_square : Box = builtin_box(
"SQUARE", "┌─┬┐\n│ ││\n├─┼┤\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n└─┴┘\n",
)
///|
pub let box_square_double_head : Box = builtin_box(
"SQUARE_DOUBLE_HEAD", "┌─┬┐\n│ ││\n╞═╪╡\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n└─┴┘\n",
)
///|
pub let box_minimal : Box = builtin_box(
"MINIMAL", " ╷ \n │ \n╶─┼╴\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ \n",
)
///|
pub let box_minimal_heavy_head : Box = builtin_box(
"MINIMAL_HEAVY_HEAD", " ╷ \n │ \n╺━┿╸\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ \n",
)
///|
pub let box_minimal_double_head : Box = builtin_box(
"MINIMAL_DOUBLE_HEAD", " ╷ \n │ \n ═╪ \n │ \n ─┼ \n ─┼ \n │ \n ╵ \n",
)
///|
pub let box_simple : Box = builtin_box(
"SIMPLE", " \n \n ── \n \n \n ── \n \n \n",
)
///|
pub let box_simple_head : Box = builtin_box(
"SIMPLE_HEAD", " \n \n ── \n \n \n \n \n \n",
)
///|
pub let box_simple_heavy : Box = builtin_box(
"SIMPLE_HEAVY", " \n \n ━━ \n \n \n ━━ \n \n \n",
)
///|
pub let box_horizontals : Box = builtin_box(
"HORIZONTALS", " ── \n \n ── \n \n ── \n ── \n \n ── \n",
)
///|
pub let box_rounded : Box = builtin_box(
"ROUNDED", "╭─┬╮\n│ ││\n├─┼┤\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n╰─┴╯\n",
)
///|
pub let box_heavy : Box = builtin_box(
"HEAVY", "┏━┳┓\n┃ ┃┃\n┣━╋┫\n┃ ┃┃\n┣━╋┫\n┣━╋┫\n┃ ┃┃\n┗━┻┛\n",
)
///|
pub let box_heavy_edge : Box = builtin_box(
"HEAVY_EDGE", "┏━┯┓\n┃ │┃\n┠─┼┨\n┃ │┃\n┠─┼┨\n┠─┼┨\n┃ │┃\n┗━┷┛\n",
)
///|
pub let box_heavy_head : Box = builtin_box(
"HEAVY_HEAD", "┏━┳┓\n┃ ┃┃\n┡━╇┩\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n└─┴┘\n",
)
///|
pub let box_double : Box = builtin_box(
"DOUBLE", "╔═╦╗\n║ ║║\n╠═╬╣\n║ ║║\n╠═╬╣\n╠═╬╣\n║ ║║\n╚═╩╝\n",
)
///|
pub let box_double_edge : Box = builtin_box(
"DOUBLE_EDGE", "╔═╤╗\n║ │║\n╟─┼╢\n║ │║\n╟─┼╢\n╟─┼╢\n║ │║\n╚═╧╝\n",
)
///|
pub let box_markdown : Box = builtin_box(
"MARKDOWN",
" \n| ||\n|-||\n| ||\n|-||\n|-||\n| ||\n \n",
ascii=true,
)