1pub fn longest_backtick_run(s: &str) -> usize {
12 let mut max = 0;
13 let mut cur = 0;
14 for ch in s.chars() {
15 if ch == '`' {
16 cur += 1;
17 max = max.max(cur);
18 } else {
19 cur = 0;
20 }
21 }
22 max
23}
24
25pub fn md_code_block(commands: &[String]) -> String {
29 let max_run = commands
30 .iter()
31 .map(|c| longest_backtick_run(c))
32 .max()
33 .unwrap_or(0);
34 let fence = "`".repeat(max_run.max(2) + 1);
35 let mut out = String::new();
36 out.push_str(&fence);
37 out.push_str("bash\n");
38 for c in commands {
39 out.push_str(c);
40 out.push('\n');
41 }
42 out.push_str(&fence);
43 out.push('\n');
44 out
45}
46
47pub fn md_inline_code(s: &str) -> String {
49 let ticks = "`".repeat(longest_backtick_run(s) + 1);
50 let pad = if s.starts_with('`') || s.ends_with('`') {
51 " "
52 } else {
53 ""
54 };
55 format!("{ticks}{pad}{s}{pad}{ticks}")
56}
57
58pub fn md_table_cell_text(s: &str) -> String {
60 s.replace('|', "\\|").replace(['\n', '\r'], " ")
61}
62
63pub fn md_table_cell_code(s: &str) -> String {
66 let oneline = s.replace(['\n', '\r'], " ");
67 let escaped = oneline.replace('|', "\\|");
68 md_inline_code(&escaped)
69}
70
71pub fn display_home(path: &str) -> String {
73 if let Some(home) = dirs::home_dir() {
74 if let Some(home_str) = home.to_str() {
75 if let Some(rest) = path.strip_prefix(home_str) {
76 let rest = rest.trim_start_matches('/');
77 if rest.is_empty() {
78 return "~".to_string();
79 }
80 return format!("~/{rest}");
81 }
82 }
83 }
84 path.to_string()
85}
86
87pub fn opt_home(value: &Option<String>) -> String {
89 value
90 .as_deref()
91 .filter(|s| !s.is_empty())
92 .map(display_home)
93 .unwrap_or_else(|| "-".to_string())
94}
95
96pub fn opt(value: &Option<String>) -> String {
98 value
99 .as_deref()
100 .filter(|s| !s.is_empty())
101 .unwrap_or("-")
102 .to_string()
103}
104
105pub fn time_part(created_at: &str) -> &str {
107 created_at.split_whitespace().nth(1).unwrap_or(created_at)
108}
109
110pub fn short_datetime(created_at: &str) -> &str {
112 created_at.get(..16).unwrap_or(created_at)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn inline_code_protege_les_backticks() {
121 assert_eq!(md_inline_code("ls"), "`ls`");
122 assert_eq!(md_inline_code("echo `date`"), "`` echo `date` ``");
123 }
124
125 #[test]
126 fn code_block_choisit_une_cloture_assez_longue() {
127 let block = md_code_block(&["echo ```x```".to_string()]);
128 assert!(block.starts_with("````bash\n"));
129 assert!(block.trim_end().ends_with("````"));
130 }
131
132 #[test]
133 fn table_cell_echappe_les_pipes() {
134 let cell = md_table_cell_code("grep -E 'a|b'");
135 assert!(cell.contains("\\|"));
136 assert!(!cell.contains("a|b"));
137 }
138
139 #[test]
140 fn horaire_et_date_courte() {
141 assert_eq!(time_part("2026-06-23 10:12:01"), "10:12:01");
142 assert_eq!(short_datetime("2026-06-23 10:12:01"), "2026-06-23 10:12");
143 }
144}