1use std::path::Path;
4
5pub trait PathExt {
6 fn short_path(&self, home: &Self) -> String;
7}
8
9impl PathExt for Path {
10 fn short_path(&self, home: &Self) -> String {
11 if home != Self::new("/") {
12 if self == home {
13 return "~".into();
14 }
15 if let Ok(rel) = self.strip_prefix(home) {
16 let names = path_dir_names(rel);
17 return if names.is_empty() {
18 "~".into()
19 } else {
20 format!("~/{}", abbrev_path_dirs(&names))
21 };
22 }
23 }
24
25 let names = path_dir_names(self);
26 if names.is_empty() {
27 "/".into()
28 } else {
29 format!("/{}", abbrev_path_dirs(&names))
30 }
31 }
32}
33
34fn path_dir_names(path: &Path) -> Vec<String> {
35 path.components()
36 .filter_map(|component| match component {
37 std::path::Component::Normal(segment) => Some(segment.to_string_lossy().into_owned()),
38 std::path::Component::Prefix(_)
39 | std::path::Component::RootDir
40 | std::path::Component::CurDir
41 | std::path::Component::ParentDir => None,
42 })
43 .collect()
44}
45
46fn abbrev_path_dirs(names: &[String]) -> String {
47 match names.len() {
48 0 => String::new(),
49 1 => names.first().cloned().unwrap_or_default(),
50 total => {
51 let mut out = String::new();
52 for (idx, name) in names.iter().enumerate() {
53 if idx > 0 {
54 out.push('/');
55 }
56 let is_last = idx == total.saturating_sub(1);
57 if is_last {
58 out.push_str(name);
59 } else {
60 out.push(name.chars().next().unwrap_or('ยท'));
61 }
62 }
63 out
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use test_that::prelude::*;
71
72 use super::*;
73
74 #[test]
75 fn test_short_path_under_home_abbreviates_parent_directories() {
76 let home = Path::new("/home/user");
77
78 assert_that!(
79 Path::new("/home/user/src/pkg/myproject").short_path(home),
80 eq("~/s/p/myproject")
81 );
82 }
83
84 #[test]
85 fn test_short_path_many_dirs_abbreviates_all_but_last() {
86 let home = Path::new("/home/user");
87
88 assert_that!(
89 Path::new("/home/user/one/two/three/four/five").short_path(home),
90 eq("~/o/t/t/f/five")
91 );
92 }
93
94 #[test]
95 fn test_short_path_outside_home_renders_absolute_abbrev() {
96 let home = Path::new("/home/user");
97
98 assert_that!(Path::new("/opt/pkg/foo").short_path(home), eq("/o/p/foo"));
99 }
100}