diff options
| author | Elvis Claros Castro <elvis@claros.ar> | 2026-09-26 18:47:40 -0300 |
|---|---|---|
| committer | Elvis Claros Castro <elvis@claros.ar> | 2026-09-26 18:47:40 -0300 |
| commit | d9738b4be2add82ff955f061c79316b0dc91768f (patch) | |
| tree | e0f31bb9f72dff315fc475f9e9f30322b69602ff /src/output.rs | |
| download | posta-main.tar.gz posta-main.zip | |
Diffstat (limited to 'src/output.rs')
| -rw-r--r-- | src/output.rs | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/src/output.rs b/src/output.rs new file mode 100644 index 0000000..9cc21b2 --- /dev/null +++ b/src/output.rs @@ -0,0 +1,98 @@ +//! 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 <alice@example.com>".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")); + } +} |