// Shared utilities use crate::store; use std::fs; use std::path::PathBuf; /// Ensure a subdirectory of the memory dir exists and return its path. pub fn memory_subdir(name: &str) -> Result { let dir = store::memory_dir().join(name); fs::create_dir_all(&dir) .map_err(|e| format!("create {}: {}", dir.display(), e))?; Ok(dir) } /// Truncate text to `max_len` bytes at a char boundary, appending `suffix`. /// Returns the original string if it's already short enough. pub fn truncate(text: &str, max_len: usize, suffix: &str) -> String { if text.len() <= max_len { text.to_string() } else { let end = text.floor_char_boundary(max_len); format!("{}{}", &text[..end], suffix) } } /// Take the first `n` chars from a string. pub fn first_n_chars(s: &str, n: usize) -> String { s.chars().take(n).collect() } /// Parse a timestamp string to unix epoch seconds. /// Handles: "2026-03-05T19:56:00", "2026-03-05T19:56", "2026-03-05 19:56:00", "2026-03-05 19:56" pub fn parse_timestamp_to_epoch(ts: &str) -> Option { use chrono::{Local, NaiveDateTime, TimeZone}; let formats = ["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"]; for fmt in &formats { if let Ok(ndt) = NaiveDateTime::parse_from_str(ts, fmt) { if let Some(dt) = Local.from_local_datetime(&ndt).earliest() { return Some(dt.timestamp()); } } } None }