#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] pub enum BoxStyle {
Single,
Double,
Rounded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] pub struct BoxChars {
pub horizontal: char,
pub vertical: char,
pub top_left: char,
pub top_right: char,
pub bottom_left: char,
pub bottom_right: char,
}
impl BoxStyle {
#[must_use]
#[allow(dead_code)] pub const fn chars(self) -> BoxChars {
match self {
Self::Single => BoxChars {
horizontal: '─',
vertical: '│',
top_left: '┌',
top_right: '┐',
bottom_left: '└',
bottom_right: '┘',
},
Self::Double => BoxChars {
horizontal: '═',
vertical: '║',
top_left: '╔',
top_right: '╗',
bottom_left: '╚',
bottom_right: '╝',
},
Self::Rounded => BoxChars {
horizontal: '─',
vertical: '│',
top_left: '╭',
top_right: '╮',
bottom_left: '╰',
bottom_right: '╯',
},
}
}
#[must_use]
#[allow(dead_code)] pub const fn from_corner(ch: char) -> Option<Self> {
match ch {
'┌' | '┐' | '└' | '┘' => Some(Self::Single),
'╔' | '╗' | '╚' | '╝' => Some(Self::Double),
'╭' | '╮' | '╰' | '╯' => Some(Self::Rounded),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Box {
pub top_left: (usize, usize),
pub bottom_right: (usize, usize),
pub style: BoxStyle,
pub parent_idx: Option<usize>,
pub child_indices: Vec<usize>,
}
impl Box {
#[allow(dead_code)] #[must_use]
pub const fn width(&self) -> usize {
self.bottom_right.1 - self.top_left.1 + 1
}
#[allow(dead_code)] #[must_use]
pub const fn height(&self) -> usize {
self.bottom_right.0 - self.top_left.0 + 1
}
#[allow(dead_code)] #[must_use]
pub const fn contains_interior(&self, row: usize, col: usize) -> bool {
row > self.top_left.0
&& row < self.bottom_right.0
&& col > self.top_left.1
&& col < self.bottom_right.1
}
#[allow(dead_code)] #[must_use]
pub const fn contains_border(&self, row: usize, col: usize) -> bool {
(row == self.top_left.0 || row == self.bottom_right.0)
&& col >= self.top_left.1
&& col <= self.bottom_right.1
|| (col == self.top_left.1 || col == self.bottom_right.1)
&& row >= self.top_left.0
&& row <= self.bottom_right.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] pub enum ArrowType {
Standard,
Double,
Long,
Dashed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] pub struct ArrowChars {
pub line: char,
pub arrowhead_right: char,
pub arrowhead_left: char,
}
impl ArrowType {
#[must_use]
#[allow(dead_code)] pub const fn chars(self) -> ArrowChars {
match self {
Self::Standard => ArrowChars {
line: '─',
arrowhead_right: '→',
arrowhead_left: '←',
},
Self::Double => ArrowChars {
line: '═',
arrowhead_right: '⇒',
arrowhead_left: '⇐',
},
Self::Long => ArrowChars {
line: '─',
arrowhead_right: '⟶',
arrowhead_left: '⟵',
},
Self::Dashed => ArrowChars {
line: '·',
arrowhead_right: '>',
arrowhead_left: '<',
},
}
}
#[must_use]
#[allow(dead_code)] pub const fn from_char(ch: char) -> Option<Self> {
match ch {
'→' | '←' => Some(Self::Standard),
'⇒' | '⇐' => Some(Self::Double),
'⟶' | '⟵' => Some(Self::Long),
'>' | '<' => Some(Self::Dashed),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)] pub struct HorizontalArrow {
pub row: usize,
pub start_col: usize,
pub end_col: usize,
pub arrow_type: ArrowType,
pub rightward: bool,
pub arrow_char: Option<char>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)] pub struct VerticalArrow {
pub col: usize,
pub start_row: usize,
pub end_row: usize,
pub arrow_type: ArrowType,
pub downward: bool,
pub arrow_char: Option<char>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)] pub struct TextRow {
pub row: usize,
pub start_col: usize,
pub end_col: usize,
pub content: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)] pub enum Segment {
Horizontal {
row: usize,
start_col: usize,
end_col: usize,
},
Vertical {
col: usize,
start_row: usize,
end_row: usize,
},
}
impl Segment {
#[allow(dead_code)] #[must_use]
pub fn length(&self) -> usize {
match self {
Self::Horizontal {
start_col, end_col, ..
} => end_col - start_col + 1,
Self::Vertical {
start_row, end_row, ..
} => end_row - start_row + 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)] pub struct ConnectionLine {
pub segments: Vec<Segment>,
pub from_box: Option<usize>,
pub to_box: Option<usize>,
}
#[allow(dead_code)] const SINGLE_LINE_HORIZ: char = '─';
#[allow(dead_code)] const SINGLE_LINE_VERT: char = '│';
#[allow(dead_code)] const SINGLE_LINE_CORNERS: &[char] = &['┌', '┐', '└', '┘'];
#[allow(dead_code)] const DOUBLE_LINE_HORIZ: char = '═';
#[allow(dead_code)] const DOUBLE_LINE_VERT: char = '║';
#[allow(dead_code)] const DOUBLE_LINE_CORNERS: &[char] = &['╔', '╗', '╚', '╝'];
#[allow(dead_code)] const ROUNDED_HORIZ: char = '─';
#[allow(dead_code)] const ROUNDED_VERT: char = '│';
#[allow(dead_code)] const ROUNDED_CORNERS: &[char] = &['╭', '╮', '╰', '╯'];
#[allow(dead_code)] const fn is_box_char(ch: char) -> bool {
matches!(
ch,
'─' | '│'
| '┌'
| '┐'
| '└'
| '┘'
| '├'
| '┤'
| '┼'
| '┬'
| '┴'
| '┃'
| '═'
| '║'
| '╔'
| '╗'
| '╚'
| '╝'
| '╭'
| '╮'
| '╰'
| '╯'
)
}
#[allow(dead_code)] const fn is_box_corner(ch: char) -> bool {
matches!(
ch,
'┌' | '┐' | '└' | '┘' | '╔' | '╗' | '╚' | '╝' | '╭' | '╮' | '╰' | '╯'
)
}
#[allow(dead_code)] const fn is_double_line_corner(ch: char) -> bool {
matches!(ch, '╔' | '╗' | '╚' | '╝')
}
#[allow(dead_code)] const fn is_rounded_corner(ch: char) -> bool {
matches!(ch, '╭' | '╮' | '╰' | '╯')
}
#[allow(dead_code)] const fn is_vertical_arrow_char(ch: char) -> bool {
matches!(ch, '↓' | '↑' | '⇓' | '⇑' | '⟱' | '⟰')
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] pub enum LabelAttachment {
Box(usize),
HorizontalArrow(usize),
VerticalArrow(usize),
ConnectionLine(usize),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)] pub struct Label {
pub row: usize,
pub col: usize,
pub content: String,
pub attached_to: LabelAttachment,
pub offset: (isize, isize),
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)] pub struct PrimitiveInventory {
pub boxes: Vec<Box>,
pub horizontal_arrows: Vec<HorizontalArrow>,
pub vertical_arrows: Vec<VerticalArrow>,
pub text_rows: Vec<TextRow>,
pub connection_lines: Vec<ConnectionLine>,
pub labels: Vec<Label>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_box_dimensions() {
let b = Box {
top_left: (0, 0),
bottom_right: (3, 5),
style: BoxStyle::Single,
parent_idx: None,
child_indices: Vec::new(),
};
assert_eq!(b.width(), 6);
assert_eq!(b.height(), 4);
}
#[test]
fn test_box_contains_interior() {
let b = Box {
top_left: (0, 0),
bottom_right: (3, 5),
style: BoxStyle::Single,
parent_idx: None,
child_indices: Vec::new(),
};
assert!(b.contains_interior(1, 1));
assert!(b.contains_interior(2, 3));
assert!(!b.contains_interior(0, 1)); assert!(!b.contains_interior(3, 1)); assert!(!b.contains_interior(1, 0)); }
#[test]
fn test_box_contains_border() {
let b = Box {
top_left: (0, 0),
bottom_right: (3, 5),
style: BoxStyle::Single,
parent_idx: None,
child_indices: Vec::new(),
};
assert!(b.contains_border(0, 0)); assert!(b.contains_border(0, 3)); assert!(b.contains_border(3, 5)); assert!(!b.contains_border(1, 1)); }
#[test]
fn test_horizontal_arrow() {
let arr = HorizontalArrow {
row: 5,
start_col: 2,
end_col: 8,
arrow_type: ArrowType::Standard,
rightward: true,
arrow_char: Some('→'),
};
assert_eq!(arr.row, 5);
assert_eq!(arr.start_col, 2);
}
#[test]
fn test_vertical_arrow() {
let arr = VerticalArrow {
col: 3,
start_row: 1,
end_row: 6,
arrow_type: ArrowType::Standard,
downward: true,
arrow_char: None,
};
assert_eq!(arr.col, 3);
assert_eq!(arr.start_row, 1);
}
#[test]
fn test_text_row() {
let tr = TextRow {
row: 2,
start_col: 5,
end_col: 10,
content: "Hello".to_string(),
};
assert_eq!(tr.content, "Hello");
}
#[test]
fn test_is_box_char() {
assert!(is_box_char('─'));
assert!(is_box_char('│'));
assert!(is_box_char('┌'));
assert!(!is_box_char('a'));
assert!(!is_box_char(' '));
}
#[test]
fn test_is_box_corner() {
assert!(is_box_corner('┌'));
assert!(is_box_corner('┐'));
assert!(is_box_corner('└'));
assert!(is_box_corner('┘'));
assert!(!is_box_corner('─'));
assert!(!is_box_corner('│'));
}
#[test]
fn test_box_style_single_chars() {
let chars = BoxStyle::Single.chars();
assert_eq!(chars.horizontal, '─');
assert_eq!(chars.vertical, '│');
assert_eq!(chars.top_left, '┌');
assert_eq!(chars.top_right, '┐');
assert_eq!(chars.bottom_left, '└');
assert_eq!(chars.bottom_right, '┘');
}
#[test]
fn test_box_style_double_chars() {
let chars = BoxStyle::Double.chars();
assert_eq!(chars.horizontal, '═');
assert_eq!(chars.vertical, '║');
assert_eq!(chars.top_left, '╔');
assert_eq!(chars.top_right, '╗');
assert_eq!(chars.bottom_left, '╚');
assert_eq!(chars.bottom_right, '╝');
}
#[test]
fn test_box_style_rounded_chars() {
let chars = BoxStyle::Rounded.chars();
assert_eq!(chars.horizontal, '─');
assert_eq!(chars.vertical, '│');
assert_eq!(chars.top_left, '╭');
assert_eq!(chars.top_right, '╮');
assert_eq!(chars.bottom_left, '╰');
assert_eq!(chars.bottom_right, '╯');
}
#[test]
fn test_is_box_char_double_line() {
assert!(is_box_char('═'));
assert!(is_box_char('║'));
assert!(is_box_char('╔'));
assert!(is_box_char('╗'));
assert!(is_box_char('╚'));
assert!(is_box_char('╝'));
}
#[test]
fn test_is_box_char_rounded() {
assert!(is_box_char('╭'));
assert!(is_box_char('╮'));
assert!(is_box_char('╰'));
assert!(is_box_char('╯'));
}
#[test]
fn test_is_box_corner_all_styles() {
assert!(is_box_corner('┌'));
assert!(is_box_corner('┐'));
assert!(is_box_corner('└'));
assert!(is_box_corner('┘'));
assert!(is_box_corner('╔'));
assert!(is_box_corner('╗'));
assert!(is_box_corner('╚'));
assert!(is_box_corner('╝'));
assert!(is_box_corner('╭'));
assert!(is_box_corner('╮'));
assert!(is_box_corner('╰'));
assert!(is_box_corner('╯'));
}
#[test]
fn test_is_double_line_corner() {
assert!(is_double_line_corner('╔'));
assert!(is_double_line_corner('╗'));
assert!(is_double_line_corner('╚'));
assert!(is_double_line_corner('╝'));
assert!(!is_double_line_corner('┌'));
assert!(!is_double_line_corner('╭'));
}
#[test]
fn test_is_rounded_corner() {
assert!(is_rounded_corner('╭'));
assert!(is_rounded_corner('╮'));
assert!(is_rounded_corner('╰'));
assert!(is_rounded_corner('╯'));
assert!(!is_rounded_corner('┌'));
assert!(!is_rounded_corner('╔'));
}
#[test]
fn test_box_style_from_single_corner() {
assert_eq!(BoxStyle::from_corner('┌'), Some(BoxStyle::Single));
assert_eq!(BoxStyle::from_corner('┐'), Some(BoxStyle::Single));
assert_eq!(BoxStyle::from_corner('└'), Some(BoxStyle::Single));
assert_eq!(BoxStyle::from_corner('┘'), Some(BoxStyle::Single));
}
#[test]
fn test_box_style_from_double_corner() {
assert_eq!(BoxStyle::from_corner('╔'), Some(BoxStyle::Double));
assert_eq!(BoxStyle::from_corner('╗'), Some(BoxStyle::Double));
assert_eq!(BoxStyle::from_corner('╚'), Some(BoxStyle::Double));
assert_eq!(BoxStyle::from_corner('╝'), Some(BoxStyle::Double));
}
#[test]
fn test_box_style_from_rounded_corner() {
assert_eq!(BoxStyle::from_corner('╭'), Some(BoxStyle::Rounded));
assert_eq!(BoxStyle::from_corner('╮'), Some(BoxStyle::Rounded));
assert_eq!(BoxStyle::from_corner('╰'), Some(BoxStyle::Rounded));
assert_eq!(BoxStyle::from_corner('╯'), Some(BoxStyle::Rounded));
}
#[test]
fn test_box_style_from_non_corner() {
assert_eq!(BoxStyle::from_corner('─'), None);
assert_eq!(BoxStyle::from_corner('│'), None);
assert_eq!(BoxStyle::from_corner('a'), None);
}
#[test]
fn test_box_has_style_field() {
let b = Box {
top_left: (0, 0),
bottom_right: (3, 5),
style: BoxStyle::Single,
parent_idx: None,
child_indices: Vec::new(),
};
assert_eq!(b.style, BoxStyle::Single);
}
#[test]
fn test_box_style_preserved() {
let b = Box {
top_left: (1, 2),
bottom_right: (5, 8),
style: BoxStyle::Double,
parent_idx: None,
child_indices: Vec::new(),
};
assert_eq!(b.style, BoxStyle::Double);
}
#[test]
fn test_arrow_type_standard_chars() {
let chars = ArrowType::Standard.chars();
assert_eq!(chars.line, '─');
assert_eq!(chars.arrowhead_right, '→');
assert_eq!(chars.arrowhead_left, '←');
}
#[test]
fn test_arrow_type_double_chars() {
let chars = ArrowType::Double.chars();
assert_eq!(chars.line, '═');
assert_eq!(chars.arrowhead_right, '⇒');
assert_eq!(chars.arrowhead_left, '⇐');
}
#[test]
fn test_arrow_type_long_chars() {
let chars = ArrowType::Long.chars();
assert_eq!(chars.line, '─');
assert_eq!(chars.arrowhead_right, '⟶');
assert_eq!(chars.arrowhead_left, '⟵');
}
#[test]
fn test_arrow_type_dashed_chars() {
let chars = ArrowType::Dashed.chars();
assert_eq!(chars.line, '·');
assert_eq!(chars.arrowhead_right, '>');
assert_eq!(chars.arrowhead_left, '<');
}
#[test]
fn test_arrow_type_from_char() {
assert_eq!(ArrowType::from_char('→'), Some(ArrowType::Standard));
assert_eq!(ArrowType::from_char('←'), Some(ArrowType::Standard));
assert_eq!(ArrowType::from_char('⇒'), Some(ArrowType::Double));
assert_eq!(ArrowType::from_char('⇐'), Some(ArrowType::Double));
assert_eq!(ArrowType::from_char('⟶'), Some(ArrowType::Long));
assert_eq!(ArrowType::from_char('⟵'), Some(ArrowType::Long));
assert_eq!(ArrowType::from_char('a'), None);
}
#[test]
fn test_arrow_type_is_vertical_arrow_char() {
assert!(is_vertical_arrow_char('↓'));
assert!(is_vertical_arrow_char('↑'));
assert!(is_vertical_arrow_char('⇓'));
assert!(is_vertical_arrow_char('⇑'));
assert!(is_vertical_arrow_char('⟱'));
assert!(is_vertical_arrow_char('⟰'));
assert!(!is_vertical_arrow_char('→'));
assert!(!is_vertical_arrow_char('a'));
}
#[test]
fn test_horizontal_arrow_has_type_field() {
let arr = HorizontalArrow {
row: 5,
start_col: 2,
end_col: 8,
arrow_type: ArrowType::Standard,
rightward: true,
arrow_char: None,
};
assert_eq!(arr.arrow_type, ArrowType::Standard);
assert!(arr.rightward);
}
#[test]
fn test_horizontal_arrow_leftward() {
let arr = HorizontalArrow {
row: 5,
start_col: 8,
end_col: 2,
arrow_type: ArrowType::Double,
rightward: false,
arrow_char: Some('⇐'),
};
assert_eq!(arr.arrow_type, ArrowType::Double);
assert!(!arr.rightward);
}
#[test]
fn test_vertical_arrow_has_type_field() {
let arr = VerticalArrow {
col: 3,
start_row: 1,
end_row: 6,
arrow_type: ArrowType::Long,
downward: true,
arrow_char: Some('↓'),
};
assert_eq!(arr.arrow_type, ArrowType::Long);
assert!(arr.downward);
}
#[test]
fn test_vertical_arrow_upward() {
let arr = VerticalArrow {
col: 3,
start_row: 6,
end_row: 1,
arrow_type: ArrowType::Dashed,
downward: false,
arrow_char: Some('↑'),
};
assert_eq!(arr.arrow_type, ArrowType::Dashed);
assert!(!arr.downward);
}
#[test]
fn test_segment_horizontal() {
let seg = Segment::Horizontal {
row: 5,
start_col: 2,
end_col: 8,
};
match seg {
Segment::Horizontal {
row,
start_col,
end_col,
} => {
assert_eq!(row, 5);
assert_eq!(start_col, 2);
assert_eq!(end_col, 8);
}
Segment::Vertical { .. } => panic!("Expected Horizontal segment"),
}
}
#[test]
fn test_segment_vertical() {
let seg = Segment::Vertical {
col: 3,
start_row: 1,
end_row: 6,
};
match seg {
Segment::Vertical {
col,
start_row,
end_row,
} => {
assert_eq!(col, 3);
assert_eq!(start_row, 1);
assert_eq!(end_row, 6);
}
Segment::Horizontal { .. } => panic!("Expected Vertical segment"),
}
}
#[test]
fn test_connection_line_basic() {
let conn = ConnectionLine {
segments: vec![
Segment::Horizontal {
row: 2,
start_col: 0,
end_col: 5,
},
Segment::Vertical {
col: 5,
start_row: 2,
end_row: 5,
},
],
from_box: Some(0),
to_box: Some(1),
};
assert_eq!(conn.segments.len(), 2);
assert_eq!(conn.from_box, Some(0));
assert_eq!(conn.to_box, Some(1));
}
#[test]
fn test_connection_line_unattached() {
let conn = ConnectionLine {
segments: vec![Segment::Horizontal {
row: 3,
start_col: 0,
end_col: 10,
}],
from_box: None,
to_box: None,
};
assert_eq!(conn.segments.len(), 1);
assert!(conn.from_box.is_none());
assert!(conn.to_box.is_none());
}
#[test]
fn test_connection_line_single_segment() {
let conn = ConnectionLine {
segments: vec![Segment::Vertical {
col: 2,
start_row: 0,
end_row: 4,
}],
from_box: Some(0),
to_box: None,
};
assert_eq!(conn.segments.len(), 1);
assert_eq!(conn.from_box, Some(0));
}
#[test]
fn test_label_attachment_box() {
let attachment = LabelAttachment::Box(0);
assert_eq!(attachment, LabelAttachment::Box(0));
}
#[test]
fn test_label_attachment_horizontal_arrow() {
let attachment = LabelAttachment::HorizontalArrow(2);
assert_eq!(attachment, LabelAttachment::HorizontalArrow(2));
}
#[test]
fn test_label_attachment_vertical_arrow() {
let attachment = LabelAttachment::VerticalArrow(1);
assert_eq!(attachment, LabelAttachment::VerticalArrow(1));
}
#[test]
fn test_label_attachment_connection_line() {
let attachment = LabelAttachment::ConnectionLine(3);
assert_eq!(attachment, LabelAttachment::ConnectionLine(3));
}
#[test]
fn test_label_basic() {
let label = Label {
row: 5,
col: 10,
content: "Input".to_string(),
attached_to: LabelAttachment::Box(0),
offset: (-1, 2),
};
assert_eq!(label.row, 5);
assert_eq!(label.col, 10);
assert_eq!(label.content, "Input");
assert_eq!(label.offset, (-1, 2));
}
#[test]
fn test_label_with_arrow_attachment() {
let label = Label {
row: 3,
col: 8,
content: "Process".to_string(),
attached_to: LabelAttachment::HorizontalArrow(1),
offset: (0, -3),
};
assert_eq!(label.attached_to, LabelAttachment::HorizontalArrow(1));
assert_eq!(label.offset, (0, -3));
}
#[test]
fn test_label_negative_offsets() {
let label = Label {
row: 2,
col: 4,
content: "Start".to_string(),
attached_to: LabelAttachment::Box(1),
offset: (-2, -1),
};
assert!(label.offset.0 < 0);
assert!(label.offset.1 < 0);
}
#[test]
fn test_label_large_offset() {
let label = Label {
row: 10,
col: 20,
content: "Far away".to_string(),
attached_to: LabelAttachment::ConnectionLine(2),
offset: (5, 8),
};
assert_eq!(label.offset, (5, 8));
}
}