//! JSON and table rendering. use anyhow::Result; use serde_json::Value; use crate::message::MsgSummary; pub fn print_json(v: &Value) -> Result<()> { println!("{}", serde_json::to_string_pretty(v)?); Ok(()) } /// Truncates to `max` characters, ellipsizing multi-byte text safely. pub fn truncate(s: &str, max: usize) -> String { if s.chars().count() <= max { s.to_string() } else { let cut: String = s.chars().take(max.saturating_sub(1)).collect(); format!("{cut}…") } } pub fn message_table(items: &[MsgSummary]) -> String { let mut out = String::new(); out.push_str(&format!( "{:>6} {:2} {:22} {:32} {}\n", "UID", "", "DATE", "FROM", "SUBJECT" )); for m in items { let unread = if m.flags.iter().any(|f| f == "\\Seen") { " " } else { "●" }; out.push_str(&format!( "{:>6} {:2} {:22} {:32} {}\n", m.uid, unread, truncate(m.date.as_deref().unwrap_or("-"), 22), truncate(m.from.as_deref().unwrap_or("-"), 32), truncate(m.subject.as_deref().unwrap_or("-"), 60), )); } out } #[cfg(test)] mod tests { use super::*; #[test] fn truncate_keeps_short_strings() { assert_eq!(truncate("hola", 10), "hola"); assert_eq!(truncate("hola", 4), "hola"); } #[test] fn truncate_ellipsizes_long_strings() { assert_eq!(truncate("abcdefgh", 5), "abcd…"); } #[test] fn truncate_is_multibyte_safe() { // Would panic with byte-based slicing. assert_eq!(truncate("ñandú y cóndor", 6), "ñandú…"); assert_eq!(truncate("🚀🚀🚀🚀", 3), "🚀🚀…"); } fn sample(flags: &[&str]) -> MsgSummary { MsgSummary { uid: 7, flags: flags.iter().map(|s| s.to_string()).collect(), size: None, date: Some("Fri, 17 Jul 2026 10:00:00 -0300".into()), from: Some("Alice ".into()), to: None, subject: Some("Hello".into()), } } #[test] fn table_marks_unread_messages() { let unread = message_table(&[sample(&[])]); assert!(unread.contains('●'), "unread marker missing:\n{unread}"); let read = message_table(&[sample(&["\\Seen"])]); assert!( !read.contains('●'), "read message should have no marker:\n{read}" ); } #[test] fn table_contains_uid_and_subject() { let t = message_table(&[sample(&[])]); assert!(t.contains(" 7")); assert!(t.contains("Hello")); } }