use core::str::FromStr;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum MergeStrategy {
#[default]
Replace,
Exact,
Fuzzy,
}
impl MergeStrategy {
pub fn merge<'a>(self, prev: &'a str, next: &'a str) -> &'a str {
if self == Self::Replace {
return next;
}
match (BorderSymbol::from_str(prev), BorderSymbol::from_str(next)) {
(Ok(prev_symbol), Ok(next_symbol)) => prev_symbol
.merge(next_symbol, self)
.try_into()
.unwrap_or(next),
(Err(_), Ok(_)) => prev,
(_, Err(_)) => next,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct BorderSymbol {
right: LineStyle,
up: LineStyle,
left: LineStyle,
down: LineStyle,
}
impl BorderSymbol {
#[must_use]
const fn new(right: LineStyle, up: LineStyle, left: LineStyle, down: LineStyle) -> Self {
Self {
right,
up,
left,
down,
}
}
#[must_use]
fn fuzzy(mut self, other: Self) -> Self {
#[allow(clippy::enum_glob_use)]
use LineStyle::*;
if !self.is_straight() {
self = self
.replace(DoubleDash, Plain)
.replace(TripleDash, Plain)
.replace(QuadrupleDash, Plain)
.replace(DoubleDashThick, Thick)
.replace(TripleDashThick, Thick)
.replace(QuadrupleDashThick, Thick);
}
if !self.is_corner() {
self = self.replace(Rounded, Plain);
}
if self.contains(Double) && self.contains(Thick) {
if other.contains(Double) {
self = self.replace(Thick, Double);
} else {
self = self.replace(Double, Thick);
}
}
if <&str>::try_from(self).is_err() {
if other.contains(Double) {
self = self.replace(Plain, Double);
} else {
self = self.replace(Double, Plain);
}
}
self
}
fn is_straight(self) -> bool {
use LineStyle::Nothing;
(self.up == self.down && self.left == self.right)
&& (self.up == Nothing || self.left == Nothing)
}
fn is_corner(self) -> bool {
use LineStyle::Nothing;
match (self.up, self.right, self.down, self.left) {
(up, right, Nothing, Nothing) => up == right,
(Nothing, right, down, Nothing) => right == down,
(Nothing, Nothing, down, left) => down == left,
(up, Nothing, Nothing, left) => up == left,
_ => false,
}
}
fn contains(self, style: LineStyle) -> bool {
self.up == style || self.right == style || self.down == style || self.left == style
}
#[must_use]
fn replace(mut self, from: LineStyle, to: LineStyle) -> Self {
self.up = if self.up == from { to } else { self.up };
self.right = if self.right == from { to } else { self.right };
self.down = if self.down == from { to } else { self.down };
self.left = if self.left == from { to } else { self.left };
self
}
fn merge(self, other: Self, strategy: MergeStrategy) -> Self {
let exact_result = Self::new(
self.right.merge(other.right),
self.up.merge(other.up),
self.left.merge(other.left),
self.down.merge(other.down),
);
match strategy {
MergeStrategy::Replace => other,
MergeStrategy::Fuzzy => exact_result.fuzzy(other),
MergeStrategy::Exact => exact_result,
}
}
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
enum BorderSymbolError {
#[error("cannot parse &str `{0}` to BorderSymbol")]
CannotParse(alloc::string::String),
#[error("cannot convert BorderSymbol `{0:#?}` to &str: no such symbol exists")]
Unrepresentable(BorderSymbol),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LineStyle {
Nothing,
Plain,
Rounded,
Double,
Thick,
DoubleDash,
DoubleDashThick,
TripleDash,
TripleDashThick,
QuadrupleDash,
QuadrupleDashThick,
}
impl LineStyle {
#[must_use]
pub fn merge(self, other: Self) -> Self {
if other == Self::Nothing { self } else { other }
}
}
macro_rules! define_symbols {
(
$( $symbol:expr => ($right:ident, $up:ident, $left:ident, $down:ident) ),* $(,)?
) => {
impl FromStr for BorderSymbol {
type Err = BorderSymbolError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use LineStyle::*;
use alloc::string::ToString;
match s {
$( $symbol => Ok(Self::new($right, $up, $left, $down)) ),* ,
_ => Err(BorderSymbolError::CannotParse(s.to_string())),
}
}
}
impl TryFrom<BorderSymbol> for &'static str {
type Error = BorderSymbolError;
fn try_from(value: BorderSymbol) -> Result<Self, Self::Error> {
use LineStyle::*;
match (value.right, value.up, value.left, value.down) {
$( ($right, $up, $left, $down) => Ok($symbol) ),* ,
_ => Err(BorderSymbolError::Unrepresentable(value)),
}
}
}
};
}
define_symbols!(
"─" => (Plain, Nothing, Plain, Nothing),
"━" => (Thick, Nothing, Thick, Nothing),
"│" => (Nothing, Plain, Nothing, Plain),
"┃" => (Nothing, Thick, Nothing, Thick),
"┄" => (TripleDash, Nothing, TripleDash, Nothing),
"┅" => (TripleDashThick, Nothing, TripleDashThick, Nothing),
"┆" => (Nothing, TripleDash, Nothing, TripleDash),
"┇" => (Nothing, TripleDashThick, Nothing, TripleDashThick),
"┈" => (QuadrupleDash, Nothing, QuadrupleDash, Nothing),
"┉" => (QuadrupleDashThick, Nothing, QuadrupleDashThick, Nothing),
"┊" => (Nothing, QuadrupleDash, Nothing, QuadrupleDash),
"┋" => (Nothing, QuadrupleDashThick, Nothing, QuadrupleDashThick),
"┌" => (Plain, Nothing, Nothing, Plain),
"┍" => (Thick, Nothing, Nothing, Plain),
"┎" => (Plain, Nothing, Nothing, Thick),
"┏" => (Thick, Nothing, Nothing, Thick),
"┐" => (Nothing, Nothing, Plain, Plain),
"┑" => (Nothing, Nothing, Thick, Plain),
"┒" => (Nothing, Nothing, Plain, Thick),
"┓" => (Nothing, Nothing, Thick, Thick),
"└" => (Plain, Plain, Nothing, Nothing),
"┕" => (Thick, Plain, Nothing, Nothing),
"┖" => (Plain, Thick, Nothing, Nothing),
"┗" => (Thick, Thick, Nothing, Nothing),
"┘" => (Nothing, Plain, Plain, Nothing),
"┙" => (Nothing, Plain, Thick, Nothing),
"┚" => (Nothing, Thick, Plain, Nothing),
"┛" => (Nothing, Thick, Thick, Nothing),
"├" => (Plain, Plain, Nothing, Plain),
"┝" => (Thick, Plain, Nothing, Plain),
"┞" => (Plain, Thick, Nothing, Plain),
"┟" => (Plain, Plain, Nothing, Thick),
"┠" => (Plain, Thick, Nothing, Thick),
"┡" => (Thick, Thick, Nothing, Plain),
"┢" => (Thick, Plain, Nothing, Thick),
"┣" => (Thick, Thick, Nothing, Thick),
"┤" => (Nothing, Plain, Plain, Plain),
"┥" => (Nothing, Plain, Thick, Plain),
"┦" => (Nothing, Thick, Plain, Plain),
"┧" => (Nothing, Plain, Plain, Thick),
"┨" => (Nothing, Thick, Plain, Thick),
"┩" => (Nothing, Thick, Thick, Plain),
"┪" => (Nothing, Plain, Thick, Thick),
"┫" => (Nothing, Thick, Thick, Thick),
"┬" => (Plain, Nothing, Plain, Plain),
"┭" => (Plain, Nothing, Thick, Plain),
"┮" => (Thick, Nothing, Plain, Plain),
"┯" => (Thick, Nothing, Thick, Plain),
"┰" => (Plain, Nothing, Plain, Thick),
"┱" => (Plain, Nothing, Thick, Thick),
"┲" => (Thick, Nothing, Plain, Thick),
"┳" => (Thick, Nothing, Thick, Thick),
"┴" => (Plain, Plain, Plain, Nothing),
"┵" => (Plain, Plain, Thick, Nothing),
"┶" => (Thick, Plain, Plain, Nothing),
"┷" => (Thick, Plain, Thick, Nothing),
"┸" => (Plain, Thick, Plain, Nothing),
"┹" => (Plain, Thick, Thick, Nothing),
"┺" => (Thick, Thick, Plain, Nothing),
"┻" => (Thick, Thick, Thick, Nothing),
"┼" => (Plain, Plain, Plain, Plain),
"┽" => (Plain, Plain, Thick, Plain),
"┾" => (Thick, Plain, Plain, Plain),
"┿" => (Thick, Plain, Thick, Plain),
"╀" => (Plain, Thick, Plain, Plain),
"╁" => (Plain, Plain, Plain, Thick),
"╂" => (Plain, Thick, Plain, Thick),
"╃" => (Plain, Thick, Thick, Plain),
"╄" => (Thick, Thick, Plain, Plain),
"╅" => (Plain, Plain, Thick, Thick),
"╆" => (Thick, Plain, Plain, Thick),
"╇" => (Thick, Thick, Thick, Plain),
"╈" => (Thick, Plain, Thick, Thick),
"╉" => (Plain, Thick, Thick, Thick),
"╊" => (Thick, Thick, Plain, Thick),
"╋" => (Thick, Thick, Thick, Thick),
"╌" => (DoubleDash, Nothing, DoubleDash, Nothing),
"╍" => (DoubleDashThick, Nothing, DoubleDashThick, Nothing),
"╎" => (Nothing, DoubleDash, Nothing, DoubleDash),
"╏" => (Nothing, DoubleDashThick, Nothing, DoubleDashThick),
"═" => (Double, Nothing, Double, Nothing),
"║" => (Nothing, Double, Nothing, Double),
"╒" => (Double, Nothing, Nothing, Plain),
"╓" => (Plain, Nothing, Nothing, Double),
"╔" => (Double, Nothing, Nothing, Double),
"╕" => (Nothing, Nothing, Double, Plain),
"╖" => (Nothing, Nothing, Plain, Double),
"╗" => (Nothing, Nothing, Double, Double),
"╘" => (Double, Plain, Nothing, Nothing),
"╙" => (Plain, Double, Nothing, Nothing),
"╚" => (Double, Double, Nothing, Nothing),
"╛" => (Nothing, Plain, Double, Nothing),
"╜" => (Nothing, Double, Plain, Nothing),
"╝" => (Nothing, Double, Double, Nothing),
"╞" => (Double, Plain, Nothing, Plain),
"╟" => (Plain, Double, Nothing, Double),
"╠" => (Double, Double, Nothing, Double),
"╡" => (Nothing, Plain, Double, Plain),
"╢" => (Nothing, Double, Plain, Double),
"╣" => (Nothing, Double, Double, Double),
"╤" => (Double, Nothing, Double, Plain),
"╥" => (Plain, Nothing, Plain, Double),
"╦" => (Double, Nothing, Double, Double),
"╧" => (Double, Plain, Double, Nothing),
"╨" => (Plain, Double, Plain, Nothing),
"╩" => (Double, Double, Double, Nothing),
"╪" => (Double, Plain, Double, Plain),
"╫" => (Plain, Double, Plain, Double),
"╬" => (Double, Double, Double, Double),
"╭" => (Rounded, Nothing, Nothing, Rounded),
"╮" => (Nothing, Nothing, Rounded, Rounded),
"╯" => (Nothing, Rounded, Rounded, Nothing),
"╰" => (Rounded, Rounded, Nothing, Nothing),
"╴" => (Nothing, Nothing, Plain, Nothing),
"╵" => (Nothing, Plain, Nothing, Nothing),
"╶" => (Plain, Nothing, Nothing, Nothing),
"╷" => (Nothing, Nothing, Nothing, Plain),
"╸" => (Nothing, Nothing, Thick, Nothing),
"╹" => (Nothing, Thick, Nothing, Nothing),
"╺" => (Thick, Nothing, Nothing, Nothing),
"╻" => (Nothing, Nothing, Nothing, Thick),
"╼" => (Thick, Nothing, Plain, Nothing),
"╽" => (Nothing, Plain, Nothing, Thick),
"╾" => (Plain, Nothing, Thick, Nothing),
"╿" => (Nothing, Thick, Nothing, Plain),
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replace_merge_strategy() {
let strategy = MergeStrategy::Replace;
let symbols = [
"─", "━", "│", "┃", "┄", "┅", "┆", "┇", "┈", "┉", "┊", "┋", "┌", "┍", "┎", "┏", "┐",
"┑", "┒", "┓", "└", "┕", "┖", "┗", "┘", "┙", "┚", "┛", "├", "┝", "┞", "┟", "┠", "┡",
"┢", "┣", "┤", "┥", "┦", "┧", "┨", "┩", "┪", "┫", "┬", "┭", "┮", "┯", "┰", "┱", "┲",
"┳", "┴", "┵", "┶", "┷", "┸", "┹", "┺", "┻", "┼", "┽", "┾", "┿", "╀", "╁", "╂", "╃",
"╄", "╅", "╆", "╇", "╈", "╉", "╊", "╋", "╌", "╍", "╎", "╏", "═", "║", "╒", "╓", "╔",
"╕", "╖", "╗", "╘", "╙", "╚", "╛", "╜", "╝", "╞", "╟", "╠", "╡", "╢", "╣", "╤", "╥",
"╦", "╧", "╨", "╩", "╪", "╫", "╬", "╭", "╮", "╯", "╰", "╴", "╵", "╶", "╷", "╸", "╹",
"╺", "╻", "╼", "╽", "╾", "╿", " ", "a", "b",
];
for a in symbols {
for b in symbols {
assert_eq!(strategy.merge(a, b), b);
}
}
}
#[test]
fn exact_merge_strategy() {
let strategy = MergeStrategy::Exact;
assert_eq!(strategy.merge("┆", "─"), "─");
assert_eq!(strategy.merge("┏", "┆"), "┆");
assert_eq!(strategy.merge("╎", "┉"), "┉");
assert_eq!(strategy.merge("╎", "┉"), "┉");
assert_eq!(strategy.merge("┋", "┋"), "┋");
assert_eq!(strategy.merge("╷", "╶"), "┌");
assert_eq!(strategy.merge("╭", "┌"), "┌");
assert_eq!(strategy.merge("│", "┕"), "┝");
assert_eq!(strategy.merge("┏", "│"), "┝");
assert_eq!(strategy.merge("│", "┏"), "┢");
assert_eq!(strategy.merge("╽", "┕"), "┢");
assert_eq!(strategy.merge("│", "─"), "┼");
assert_eq!(strategy.merge("┘", "┌"), "┼");
assert_eq!(strategy.merge("┵", "┝"), "┿");
assert_eq!(strategy.merge("│", "━"), "┿");
assert_eq!(strategy.merge("┵", "╞"), "╞");
assert_eq!(strategy.merge(" ", "╠"), " ");
assert_eq!(strategy.merge("╠", " "), " ");
assert_eq!(strategy.merge("╎", "╧"), "╧");
assert_eq!(strategy.merge("╛", "╒"), "╪");
assert_eq!(strategy.merge("│", "═"), "╪");
assert_eq!(strategy.merge("╤", "╧"), "╪");
assert_eq!(strategy.merge("╡", "╞"), "╪");
assert_eq!(strategy.merge("┌", "╭"), "╭");
assert_eq!(strategy.merge("┘", "╭"), "╭");
assert_eq!(strategy.merge("┌", "a"), "a");
assert_eq!(strategy.merge("a", "╭"), "a");
assert_eq!(strategy.merge("a", "b"), "b");
}
#[test]
fn fuzzy_merge_strategy() {
let strategy = MergeStrategy::Fuzzy;
assert_eq!(strategy.merge("┄", "╴"), "─");
assert_eq!(strategy.merge("│", "┆"), "┆");
assert_eq!(strategy.merge(" ", "┉"), " ");
assert_eq!(strategy.merge("┋", "┋"), "┋");
assert_eq!(strategy.merge("╷", "╶"), "┌");
assert_eq!(strategy.merge("╭", "┌"), "┌");
assert_eq!(strategy.merge("│", "┕"), "┝");
assert_eq!(strategy.merge("┏", "│"), "┝");
assert_eq!(strategy.merge("┏", "┆"), "┝");
assert_eq!(strategy.merge("│", "┏"), "┢");
assert_eq!(strategy.merge("╽", "┕"), "┢");
assert_eq!(strategy.merge("│", "─"), "┼");
assert_eq!(strategy.merge("┆", "─"), "┼");
assert_eq!(strategy.merge("┘", "┌"), "┼");
assert_eq!(strategy.merge("┘", "╭"), "┼");
assert_eq!(strategy.merge("╎", "┉"), "┿");
assert_eq!(strategy.merge(" ", "╠"), " ");
assert_eq!(strategy.merge("╠", " "), " ");
assert_eq!(strategy.merge("┵", "╞"), "╪");
assert_eq!(strategy.merge("╛", "╒"), "╪");
assert_eq!(strategy.merge("│", "═"), "╪");
assert_eq!(strategy.merge("╤", "╧"), "╪");
assert_eq!(strategy.merge("╡", "╞"), "╪");
assert_eq!(strategy.merge("╎", "╧"), "╪");
assert_eq!(strategy.merge("┌", "╭"), "╭");
assert_eq!(strategy.merge("┌", "a"), "a");
assert_eq!(strategy.merge("a", "╭"), "a");
assert_eq!(strategy.merge("a", "b"), "b");
}
}