use std::borrow::Cow;
#[inline]
pub fn truncate(s: &str, max: usize) -> Cow<'_, str> {
let char_count = s.chars().count();
if char_count <= max {
Cow::Borrowed(s)
} else if max == 0 {
Cow::Borrowed("")
} else if max == 1 {
Cow::Borrowed("…")
} else {
let truncated: String = s.chars().take(max - 1).collect();
Cow::Owned(format!("{truncated}…"))
}
}
pub fn truncate_middle(s: &str, max: usize) -> Cow<'_, str> {
let char_count = s.chars().count();
if char_count <= max {
return Cow::Borrowed(s);
}
if max <= 3 {
return truncate(s, max);
}
let start_len = (max - 1) / 3; let end_len = max - 1 - start_len;
let start: String = s.chars().take(start_len).collect();
let end: String = s.chars().skip(char_count - end_len).collect();
Cow::Owned(format!("{start}…{end}"))
}
pub fn truncate_with<'a>(s: &'a str, max: usize, ellipsis: &str) -> Cow<'a, str> {
let char_count = s.chars().count();
let ellipsis_len = ellipsis.chars().count();
if char_count <= max {
Cow::Borrowed(s)
} else if max <= ellipsis_len {
Cow::Owned(ellipsis.chars().take(max).collect())
} else {
let truncated: String = s.chars().take(max - ellipsis_len).collect();
Cow::Owned(format!("{truncated}{ellipsis}"))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HealthStatus {
Healthy,
Warning,
Critical,
Unknown,
}
impl HealthStatus {
#[inline]
pub const fn symbol(&self) -> &'static str {
match self {
Self::Healthy => "✓",
Self::Warning => "⚠",
Self::Critical => "✗",
Self::Unknown => "?",
}
}
pub fn colored_symbol(&self) -> &'static str {
match self {
Self::Healthy => "\x1b[32m✓\x1b[0m", Self::Warning => "\x1b[33m⚠\x1b[0m", Self::Critical => "\x1b[31m✗\x1b[0m", Self::Unknown => "\x1b[90m?\x1b[0m", }
}
#[inline]
pub const fn label(&self) -> &'static str {
match self {
Self::Healthy => "Healthy",
Self::Warning => "Warning",
Self::Critical => "Critical",
Self::Unknown => "Unknown",
}
}
pub fn from_percentage(pct: f64) -> Self {
if pct >= 80.0 {
Self::Healthy
} else if pct >= 50.0 {
Self::Warning
} else {
Self::Critical
}
}
pub fn from_score(score: u32, max: u32) -> Self {
if max == 0 {
return Self::Unknown;
}
let pct = (score as f64 / max as f64) * 100.0;
Self::from_percentage(pct)
}
}
impl std::fmt::Display for HealthStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.symbol())
}
}
#[derive(Debug, Clone)]
pub struct EmptyState {
pub icon: Option<String>,
pub title: String,
pub hint: Option<String>,
pub center_vertical: bool,
}
impl EmptyState {
pub fn new(title: impl Into<String>) -> Self {
Self {
icon: None,
title: title.into(),
hint: None,
center_vertical: true,
}
}
pub fn icon(mut self, icon: impl Into<String>) -> Self {
self.icon = Some(icon.into());
self
}
pub fn hint(mut self, hint: impl Into<String>) -> Self {
self.hint = Some(hint.into());
self
}
pub fn top_aligned(mut self) -> Self {
self.center_vertical = false;
self
}
pub fn render_lines(&self, available_height: u16) -> (Vec<String>, u16) {
let mut lines = Vec::new();
if let Some(ref icon) = self.icon {
lines.push(icon.clone());
lines.push(String::new()); }
lines.push(self.title.clone());
if let Some(ref hint) = self.hint {
lines.push(String::new()); lines.push(hint.clone());
}
let y_offset = if self.center_vertical {
let content_height = lines.len() as u16;
if available_height > content_height {
(available_height - content_height) / 2
} else {
0
}
} else {
1 };
(lines, y_offset)
}
}
impl Default for EmptyState {
fn default() -> Self {
Self::new("No data available")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate_short() {
assert_eq!(truncate("Hello", 10), "Hello");
assert_eq!(truncate("", 5), "");
}
#[test]
fn test_truncate_exact() {
assert_eq!(truncate("Hello", 5), "Hello");
}
#[test]
fn test_truncate_long() {
assert_eq!(truncate("Hello World", 8), "Hello W…");
assert_eq!(truncate("Hello World", 6), "Hello…");
assert_eq!(truncate("Hello World", 1), "…");
assert_eq!(truncate("Hello World", 0), "");
}
#[test]
fn test_truncate_middle() {
assert_eq!(truncate_middle("/home/user/path", 20), "/home/user/path");
assert_eq!(
truncate_middle("/home/user/long/path/file.rs", 15),
"/hom…th/file.rs"
);
}
#[test]
fn test_health_status_symbol() {
assert_eq!(HealthStatus::Healthy.symbol(), "✓");
assert_eq!(HealthStatus::Warning.symbol(), "⚠");
assert_eq!(HealthStatus::Critical.symbol(), "✗");
assert_eq!(HealthStatus::Unknown.symbol(), "?");
}
#[test]
fn test_health_from_percentage() {
assert_eq!(HealthStatus::from_percentage(100.0), HealthStatus::Healthy);
assert_eq!(HealthStatus::from_percentage(80.0), HealthStatus::Healthy);
assert_eq!(HealthStatus::from_percentage(79.0), HealthStatus::Warning);
assert_eq!(HealthStatus::from_percentage(50.0), HealthStatus::Warning);
assert_eq!(HealthStatus::from_percentage(49.0), HealthStatus::Critical);
assert_eq!(HealthStatus::from_percentage(0.0), HealthStatus::Critical);
}
#[test]
fn test_health_from_score() {
assert_eq!(HealthStatus::from_score(20, 20), HealthStatus::Healthy);
assert_eq!(HealthStatus::from_score(16, 20), HealthStatus::Healthy);
assert_eq!(HealthStatus::from_score(15, 20), HealthStatus::Warning);
assert_eq!(HealthStatus::from_score(10, 20), HealthStatus::Warning);
assert_eq!(HealthStatus::from_score(9, 20), HealthStatus::Critical);
assert_eq!(HealthStatus::from_score(0, 0), HealthStatus::Unknown);
}
#[test]
fn test_empty_state_render() {
let empty = EmptyState::new("No data").icon("📊").hint("Try refreshing");
let (lines, offset) = empty.render_lines(20);
assert_eq!(lines.len(), 5); assert!(offset > 0); }
#[test]
fn test_empty_state_top_aligned() {
let empty = EmptyState::new("No data").top_aligned();
let (_, offset) = empty.render_lines(20);
assert_eq!(offset, 1);
}
#[test]
fn test_truncate_unicode() {
assert_eq!(truncate("你好世界", 3), "你好…");
assert_eq!(truncate("日本語", 5), "日本語");
}
#[test]
fn test_truncate_middle_short() {
assert_eq!(truncate_middle("abc", 10), "abc");
assert_eq!(truncate_middle("abcdefgh", 3), "ab…");
assert_eq!(truncate_middle("abcdefgh", 2), "a…");
}
#[test]
fn test_truncate_with_custom_ellipsis() {
assert_eq!(truncate_with("Hello World", 10, "..."), "Hello W...");
assert_eq!(truncate_with("Hello", 10, "..."), "Hello");
assert_eq!(truncate_with("Hello World", 2, "..."), "..");
}
#[test]
fn test_truncate_with_empty_ellipsis() {
assert_eq!(truncate_with("Hello World", 5, ""), "Hello");
}
#[test]
fn test_health_status_label() {
assert_eq!(HealthStatus::Healthy.label(), "Healthy");
assert_eq!(HealthStatus::Warning.label(), "Warning");
assert_eq!(HealthStatus::Critical.label(), "Critical");
assert_eq!(HealthStatus::Unknown.label(), "Unknown");
}
#[test]
fn test_health_status_colored_symbol() {
let healthy = HealthStatus::Healthy.colored_symbol();
assert!(healthy.contains("\x1b[32m")); assert!(healthy.contains("✓"));
let warning = HealthStatus::Warning.colored_symbol();
assert!(warning.contains("\x1b[33m")); assert!(warning.contains("⚠"));
let critical = HealthStatus::Critical.colored_symbol();
assert!(critical.contains("\x1b[31m")); assert!(critical.contains("✗"));
let unknown = HealthStatus::Unknown.colored_symbol();
assert!(unknown.contains("\x1b[90m")); assert!(unknown.contains("?"));
}
#[test]
fn test_health_status_display() {
assert_eq!(format!("{}", HealthStatus::Healthy), "✓");
assert_eq!(format!("{}", HealthStatus::Warning), "⚠");
assert_eq!(format!("{}", HealthStatus::Critical), "✗");
assert_eq!(format!("{}", HealthStatus::Unknown), "?");
}
#[test]
fn test_empty_state_default() {
let empty = EmptyState::default();
assert_eq!(empty.title, "No data available");
assert!(empty.icon.is_none());
assert!(empty.hint.is_none());
assert!(empty.center_vertical);
}
#[test]
fn test_empty_state_no_icon_no_hint() {
let empty = EmptyState::new("Test message");
let (lines, _) = empty.render_lines(10);
assert_eq!(lines.len(), 1); assert_eq!(lines[0], "Test message");
}
#[test]
fn test_empty_state_with_icon_only() {
let empty = EmptyState::new("Test message").icon("🔍");
let (lines, _) = empty.render_lines(10);
assert_eq!(lines.len(), 3); assert_eq!(lines[0], "🔍");
assert_eq!(lines[1], "");
assert_eq!(lines[2], "Test message");
}
#[test]
fn test_empty_state_with_hint_only() {
let empty = EmptyState::new("Test message").hint("Try again");
let (lines, _) = empty.render_lines(10);
assert_eq!(lines.len(), 3); assert_eq!(lines[0], "Test message");
assert_eq!(lines[1], "");
assert_eq!(lines[2], "Try again");
}
#[test]
fn test_empty_state_render_small_height() {
let empty = EmptyState::new("Title").icon("📊").hint("Hint");
let (lines, offset) = empty.render_lines(3); assert_eq!(lines.len(), 5);
assert_eq!(offset, 0); }
#[test]
fn test_truncate_middle_exact_boundary() {
let result = truncate_middle("abcdefghij", 4);
assert!(result.len() <= 4 || result.chars().count() <= 4);
}
#[test]
fn test_health_from_percentage_edge_cases() {
assert_eq!(HealthStatus::from_percentage(80.0), HealthStatus::Healthy);
assert_eq!(HealthStatus::from_percentage(79.999), HealthStatus::Warning);
assert_eq!(HealthStatus::from_percentage(50.0), HealthStatus::Warning);
assert_eq!(
HealthStatus::from_percentage(49.999),
HealthStatus::Critical
);
}
#[test]
fn test_empty_state_builder_chain() {
let empty = EmptyState::new("Test")
.icon("🔧")
.hint("Fix it")
.top_aligned();
assert_eq!(empty.title, "Test");
assert_eq!(empty.icon, Some("🔧".to_string()));
assert_eq!(empty.hint, Some("Fix it".to_string()));
assert!(!empty.center_vertical);
}
}