pub const MAX_TUI_NODES: usize = 500;
pub const DEFAULT_VISIBLE_NODES: usize = 20;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NodeShape {
#[default]
Circle,
Diamond,
Square,
Triangle,
Star,
}
impl NodeShape {
#[must_use]
pub fn unicode(&self) -> char {
match self {
Self::Circle => '●',
Self::Diamond => '◆',
Self::Square => '■',
Self::Triangle => '▲',
Self::Star => '★',
}
}
#[must_use]
pub fn ascii(&self) -> char {
match self {
Self::Circle => 'o',
Self::Diamond => '<',
Self::Square => '#',
Self::Triangle => '^',
Self::Star => '*',
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NodeStatus {
#[default]
Healthy,
Warning,
Error,
Info,
Neutral,
}
impl NodeStatus {
#[must_use]
pub fn shape(&self) -> NodeShape {
match self {
Self::Healthy => NodeShape::Circle,
Self::Warning => NodeShape::Triangle,
Self::Error => NodeShape::Diamond,
Self::Info => NodeShape::Star,
Self::Neutral => NodeShape::Square,
}
}
#[must_use]
pub fn color_code(&self) -> &'static str {
match self {
Self::Healthy => "\x1b[32m", Self::Warning => "\x1b[33m", Self::Error => "\x1b[31m", Self::Info => "\x1b[36m", Self::Neutral => "\x1b[90m", }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Position {
pub x: f32,
pub y: f32,
}
impl Position {
#[must_use]
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
#[must_use]
pub fn distance(&self, other: &Self) -> f32 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct Node<T> {
pub id: String,
pub data: T,
pub status: NodeStatus,
pub label: Option<String>,
pub position: Position,
pub importance: f32,
}
impl<T> Node<T> {
pub fn new(id: impl Into<String>, data: T) -> Self {
Self {
id: id.into(),
data,
status: NodeStatus::default(),
label: None,
position: Position::default(),
importance: 1.0,
}
}
#[must_use]
pub fn with_status(mut self, status: NodeStatus) -> Self {
self.status = status;
self
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
#[must_use]
pub fn with_importance(mut self, importance: f32) -> Self {
self.importance = importance;
self
}
}
#[derive(Debug, Clone)]
pub struct Edge<E> {
pub from: String,
pub to: String,
pub data: E,
pub weight: f32,
pub label: Option<String>,
}
impl<E> Edge<E> {
pub fn new(from: impl Into<String>, to: impl Into<String>, data: E) -> Self {
Self { from: from.into(), to: to.into(), data, weight: 1.0, label: None }
}
#[must_use]
pub fn with_weight(mut self, weight: f32) -> Self {
self.weight = weight;
self
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_shape_default() {
assert_eq!(NodeShape::default(), NodeShape::Circle);
}
#[test]
fn test_node_shape_unicode() {
assert_eq!(NodeShape::Circle.unicode(), '●');
assert_eq!(NodeShape::Diamond.unicode(), '◆');
assert_eq!(NodeShape::Square.unicode(), '■');
assert_eq!(NodeShape::Triangle.unicode(), '▲');
assert_eq!(NodeShape::Star.unicode(), '★');
}
#[test]
fn test_node_shape_ascii() {
assert_eq!(NodeShape::Circle.ascii(), 'o');
assert_eq!(NodeShape::Diamond.ascii(), '<');
assert_eq!(NodeShape::Square.ascii(), '#');
assert_eq!(NodeShape::Triangle.ascii(), '^');
assert_eq!(NodeShape::Star.ascii(), '*');
}
#[test]
fn test_node_status_default() {
assert_eq!(NodeStatus::default(), NodeStatus::Healthy);
}
#[test]
fn test_node_status_shape() {
assert_eq!(NodeStatus::Healthy.shape(), NodeShape::Circle);
assert_eq!(NodeStatus::Warning.shape(), NodeShape::Triangle);
assert_eq!(NodeStatus::Error.shape(), NodeShape::Diamond);
assert_eq!(NodeStatus::Info.shape(), NodeShape::Star);
assert_eq!(NodeStatus::Neutral.shape(), NodeShape::Square);
}
#[test]
fn test_node_status_color_code() {
assert!(NodeStatus::Healthy.color_code().contains("32m"));
assert!(NodeStatus::Warning.color_code().contains("33m"));
assert!(NodeStatus::Error.color_code().contains("31m"));
assert!(NodeStatus::Info.color_code().contains("36m"));
assert!(NodeStatus::Neutral.color_code().contains("90m"));
}
#[test]
fn test_position_default() {
let pos = Position::default();
assert_eq!(pos.x, 0.0);
assert_eq!(pos.y, 0.0);
}
#[test]
fn test_position_new() {
let pos = Position::new(10.0, 20.0);
assert_eq!(pos.x, 10.0);
assert_eq!(pos.y, 20.0);
}
#[test]
fn test_position_distance() {
let p1 = Position::new(0.0, 0.0);
let p2 = Position::new(3.0, 4.0);
assert!((p1.distance(&p2) - 5.0).abs() < 0.001);
}
#[test]
fn test_position_distance_to_self() {
let p = Position::new(5.0, 5.0);
assert!((p.distance(&p)).abs() < 0.001);
}
#[test]
fn test_node_new() {
let node = Node::new("test", 42);
assert_eq!(node.id, "test");
assert_eq!(node.data, 42);
assert_eq!(node.status, NodeStatus::Healthy);
assert!(node.label.is_none());
assert_eq!(node.importance, 1.0);
}
#[test]
fn test_node_with_status() {
let node = Node::new("test", ()).with_status(NodeStatus::Error);
assert_eq!(node.status, NodeStatus::Error);
}
#[test]
fn test_node_with_label() {
let node = Node::new("test", ()).with_label("My Label");
assert_eq!(node.label, Some("My Label".to_string()));
}
#[test]
fn test_node_with_importance() {
let node = Node::new("test", ()).with_importance(0.5);
assert_eq!(node.importance, 0.5);
}
#[test]
fn test_node_builder_chain() {
let node = Node::new("test", "data")
.with_status(NodeStatus::Warning)
.with_label("Label")
.with_importance(0.75);
assert_eq!(node.status, NodeStatus::Warning);
assert_eq!(node.label, Some("Label".to_string()));
assert_eq!(node.importance, 0.75);
}
#[test]
fn test_edge_new() {
let edge = Edge::new("A", "B", 100);
assert_eq!(edge.from, "A");
assert_eq!(edge.to, "B");
assert_eq!(edge.data, 100);
assert_eq!(edge.weight, 1.0);
assert!(edge.label.is_none());
}
#[test]
fn test_edge_with_weight() {
let edge = Edge::new("A", "B", ()).with_weight(2.5);
assert_eq!(edge.weight, 2.5);
}
#[test]
fn test_edge_with_label() {
let edge = Edge::new("A", "B", ()).with_label("depends_on");
assert_eq!(edge.label, Some("depends_on".to_string()));
}
#[test]
fn test_edge_builder_chain() {
let edge =
Edge::new("source", "target", "edge_data").with_weight(3.0).with_label("relation");
assert_eq!(edge.from, "source");
assert_eq!(edge.to, "target");
assert_eq!(edge.weight, 3.0);
assert_eq!(edge.label, Some("relation".to_string()));
}
#[test]
fn test_constants() {
assert_eq!(MAX_TUI_NODES, 500);
assert_eq!(DEFAULT_VISIBLE_NODES, 20);
}
}