1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Copyright (c) 2022 FernOfSigma.
// This software is licensed under the MIT license.

//! Shortens UNIX-like paths.

use std::path::{Path, PathBuf};
use itertools::Itertools;

/// Takes a reference to a [`Path`] and shortens all but its last component to their
/// first characters. Dots (`.`) are left as is since they might refer to hidden or
/// relative paths.
///
/// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
///
/// # Example
/// ```rust
/// use spat::shorten;
///
/// fn main() {
///     let shortened = shorten("/path/to/something");
///     assert_eq!(shortened.as_os_str(), "/p/t/something");
/// }
/// ```
pub fn shorten(path: impl AsRef<Path>) -> PathBuf{
    let mut components = path.as_ref().components().collect::<Vec<_>>();
    let basename = components.pop();

    let mut shortened = components.into_iter()
        .map(|component| component.as_os_str().to_string_lossy())
        .map(|s| s.chars().take_while_inclusive(|c| c == &'.').collect::<String>())
        .collect::<PathBuf>();

    shortened.extend(basename);
    shortened
}

#[cfg(test)]
mod tests {
    use super::*;

    fn compare(s1: &str, s2: &str) {
        assert_eq!(shorten(s1).as_os_str(), s2);
    }

    #[test]
    fn symbols() {
        compare("~", "~");
        compare("/", "/");
        compare(".", ".");
    }

    #[test]
    fn standard() {
        compare("~/sigma/dev/rust/", "~/s/d/rust");
        compare("/home/sigma/dev/rust/", "/h/s/d/rust");
        compare("home/sigma/dev/rust/", "h/s/d/rust");
    }

    #[test]
    fn hidden() {
        compare("~/.cargo/bin/", "~/.c/bin");
    }

    #[test]
    fn dots() {
        compare("./music/../videos/1.mkv", "./m/../v/1.mkv");
    }
}