use std::path::Path;
pub const HEAD_MAX: usize = 200;
pub fn scrub_path(s: &str, home: Option<&Path>, cwd: Option<&Path>) -> String {
let mut out = s.to_string();
if let Some(h) = home.and_then(|p| p.to_str()) {
if !h.is_empty() {
out = out.replace(h, "<HOME>");
}
}
if let Some(c) = cwd.and_then(|p| p.to_str()) {
if !c.is_empty() {
out = out.replace(c, "<CWD>");
}
}
out
}
pub fn truncate_head(s: &str, max_chars: usize) -> String {
if s.chars().count() <= max_chars {
return s.to_string();
}
s.chars().take(max_chars).collect()
}
pub fn backtrace_top_k(bt: &str, k: usize, home: Option<&Path>, cwd: Option<&Path>) -> Vec<String> {
bt.lines()
.take(k)
.map(|line| {
let scrubbed = scrub_path(line, home, cwd);
if let Some(idx) = scrubbed.find(" at ") {
let (head, rest) = scrubbed.split_at(idx + 4);
let short = match rest.rsplit_once('/') {
Some((_, tail)) => tail.to_string(),
None => rest.to_string(),
};
format!("{}{}", head, short)
} else {
scrubbed
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn scrubs_home_path() {
let home = PathBuf::from("/Users/lichao");
let s = "panic at /Users/lichao/project/foo.rs:10";
assert_eq!(
scrub_path(s, Some(&home), None),
"panic at <HOME>/project/foo.rs:10"
);
}
#[test]
fn scrubs_cwd_path() {
let cwd = PathBuf::from("/tmp/proj");
let s = "error in /tmp/proj/src/main.rs:3";
assert_eq!(
scrub_path(s, None, Some(&cwd)),
"error in <CWD>/src/main.rs:3"
);
}
#[test]
fn truncate_head_respects_char_boundary() {
let s = "中文字串";
let out = truncate_head(s, 3);
assert_eq!(out, "中文字");
assert!(out.is_char_boundary(out.len()));
}
#[test]
fn truncate_head_returns_full_when_short() {
assert_eq!(truncate_head("hi", 200), "hi");
}
#[test]
fn backtrace_strips_abs_paths_keeps_basename() {
let bt = "\
0: my_crate::fn_a
at /Users/lichao/p/src/a.rs:10
1: my_crate::fn_b
at /Users/lichao/p/src/b.rs:20
2: my_crate::fn_c
at /Users/lichao/p/src/c.rs:30
3: frame4
4: frame5
5: frame6_should_be_dropped";
let frames = backtrace_top_k(bt, 5, Some(Path::new("/Users/lichao")), None);
assert_eq!(frames.len(), 5);
assert!(frames[1].ends_with("a.rs:10"), "got {:?}", frames[1]);
assert!(!frames[1].contains("/Users/lichao"));
}
}