chj_rustbin/
path_util.rs

1/// Get the file extension from a path given as bytes (since there is
2/// no path from CStr to PathBuf, right?). If the extension can't be
3/// decoded as UTF-8, returns None. Invalid encoding in other parts of
4/// the path does not matter, though (as long as the `path_separator`
5/// and `b'.'` are still encoded as such).
6pub fn extension_from_ascii_or_utf8_bytes(
7    path: &[u8],
8    path_separator: u8,
9) -> Option<&str> {
10    let dot_pos = path.iter().rposition(|b| *b == b'.')?;
11    if let Some(separator_pos) = path.iter().rposition(|b| *b == path_separator)
12    {
13        if !(separator_pos + 1 < dot_pos) {
14            return None;
15        }
16    } else {
17        if dot_pos < 1 {
18            return None;
19        }
20    }
21    let slice = &path[dot_pos + 1..];
22    if slice.is_empty() {
23        None
24    } else {
25        std::str::from_utf8(slice).ok()
26    }
27}
28
29#[test]
30fn t_extension_from_ascii_or_utf8_bytes() {
31    fn t(s: &str) -> Option<&str> {
32        extension_from_ascii_or_utf8_bytes(s.as_bytes(), b'/')
33    }
34    assert_eq!(t("foo.bar"), Some("bar"));
35    assert_eq!(t("foobar"), None);
36    assert_eq!(t("foo/bar.xz"), Some("xz"));
37    assert_eq!(t("foo.bar/xz"), None);
38    assert_eq!(t("foo.bar\\xz"), Some("bar\\xz")); // Well..
39    assert_eq!(t("foo/bar."), None);
40    assert_eq!(t(".bah"), None);
41    assert_eq!(t("a.b"), Some("b"));
42    assert_eq!(t("ah/.b"), None);
43    assert_eq!(t("ah/.b.a"), Some("a"));
44    assert_eq!(t("ah/a.b"), Some("b"));
45    let mut bs = [0x61, 0x2f, 0x61, 0x2e, 0x62];
46    let e = extension_from_ascii_or_utf8_bytes;
47    assert_eq!(e(&bs, b'/'), Some("b"));
48    bs[1] = 0x90;
49    assert_eq!(e(&bs, b'/'), Some("b"));
50    bs[1] = 0xf0;
51    assert_eq!(e(&bs, b'/'), Some("b"));
52    bs[4] = 0x42;
53    assert_eq!(e(&bs, b'/'), Some("B"));
54    bs[4] = 0xf0;
55    assert_eq!(e(&bs, b'/'), None);
56}