use std::borrow::Cow;
fn is_absolute_windows_path(s: &str) -> bool {
if s.len() > 2 && &s[..2] == "\\\\" {
return true;
}
let mut char_iter = s.chars();
let (fc, sc, tc) = (char_iter.next(), char_iter.next(), char_iter.next());
match fc.unwrap_or_default() {
'A'..='Z' | 'a'..='z' => {
if sc == Some(':') && tc.map_or(false, |tc| tc == '\\' || tc == '/') {
return true;
}
}
_ => (),
}
false
}
fn is_absolute_unix_path(s: &str) -> bool {
s.starts_with('/')
}
pub fn join_path(base: &str, other: &str) -> String {
if base == "" || is_absolute_windows_path(other) || is_absolute_unix_path(other) {
return other.into();
}
if other == "" {
return base.into();
}
let win_abs = is_absolute_windows_path(base);
let unix_abs = is_absolute_unix_path(base);
let win_style = win_abs || (!unix_abs && base.contains('\\'));
if win_style {
format!(
"{}\\{}",
base.trim_end_matches(&['\\', '/'][..]),
other.trim_start_matches(&['\\', '/'][..])
)
} else {
format!(
"{}/{}",
base.trim_end_matches('/'),
other.trim_start_matches('/')
)
}
}
pub fn split_path_bytes(path: &[u8]) -> (Option<&[u8]>, &[u8]) {
let path = match path.iter().rposition(|b| *b != b'\\' && *b != b'/') {
Some(cutoff) => &path[..=cutoff],
None => path,
};
let split_char = if !path.starts_with(b"/") && path.contains(&b'\\') {
b'\\'
} else {
b'/'
};
match path.iter().rposition(|b| *b == split_char) {
Some(0) => (Some(&path[..1]), &path[1..]),
Some(pos) => (Some(&path[..pos]), &path[pos + 1..]),
None => (None, path),
}
}
pub fn split_path(path: &str) -> (Option<&str>, &str) {
let (dir, name) = split_path_bytes(path.as_bytes());
unsafe {
(
dir.map(|b| std::str::from_utf8_unchecked(b)),
std::str::from_utf8_unchecked(name),
)
}
}
pub fn shorten_path(path: &str, length: usize) -> Cow<'_, str> {
if path.len() <= length {
return Cow::Borrowed(path);
} else if length <= 10 {
if length > 3 {
return Cow::Owned(format!("{}...", &path[..length - 3]));
}
return Cow::Borrowed(&path[..length]);
}
let mut rv = String::new();
let mut last_idx = 0;
let mut piece_iter = path.match_indices(&['\\', '/'][..]);
let mut final_sep = "/";
let max_len = length - 4;
while let Some((idx, sep)) = piece_iter.next() {
let slice = &path[last_idx..idx + sep.len()];
rv.push_str(slice);
let done = last_idx > 0;
last_idx = idx + sep.len();
final_sep = sep;
if done {
break;
}
}
let mut final_length = rv.len() as i64;
let mut rest = vec![];
let mut next_idx = path.len();
while let Some((idx, _)) = piece_iter.next_back() {
if idx <= last_idx {
break;
}
let slice = &path[idx + 1..next_idx];
if final_length + (slice.len() as i64) > max_len as i64 {
break;
}
rest.push(slice);
next_idx = idx + 1;
final_length += slice.len() as i64;
}
if rv.len() > max_len || rest.is_empty() {
let basename = path.rsplit(&['\\', '/'][..]).next().unwrap();
if basename.len() > max_len {
return Cow::Owned(format!("...{}", &basename[basename.len() - max_len + 1..]));
} else {
return Cow::Owned(format!("...{}{}", final_sep, basename));
}
}
rest.reverse();
rv.push_str("...");
rv.push_str(final_sep);
for item in rest {
rv.push_str(&item);
}
Cow::Owned(rv)
}
#[test]
fn test_join_path() {
assert_eq!(join_path("C:\\a", "b"), "C:\\a\\b");
assert_eq!(join_path("C:/a", "b"), "C:/a\\b");
assert_eq!(join_path("C:\\a", "b\\c"), "C:\\a\\b\\c");
assert_eq!(join_path("C:/a", "C:\\b"), "C:\\b");
assert_eq!(join_path("a\\b\\c", "d\\e"), "a\\b\\c\\d\\e");
assert_eq!(join_path("\\\\UNC\\", "a"), "\\\\UNC\\a");
assert_eq!(join_path("/a/b", "c"), "/a/b/c");
assert_eq!(join_path("/a/b", "c/d"), "/a/b/c/d");
assert_eq!(join_path("/a/b", "/c/d/e"), "/c/d/e");
assert_eq!(join_path("a/b/", "c"), "a/b/c");
}
#[test]
fn test_shorten_path() {
assert_eq!(shorten_path("/foo/bar/baz/blah/blafasel", 6), "/fo...");
assert_eq!(shorten_path("/foo/bar/baz/blah/blafasel", 2), "/f");
assert_eq!(
shorten_path("/foo/bar/baz/blah/blafasel", 21),
"/foo/.../blafasel"
);
assert_eq!(
shorten_path("/foo/bar/baz/blah/blafasel", 22),
"/foo/.../blah/blafasel"
);
assert_eq!(
shorten_path("C:\\bar\\baz\\blah\\blafasel", 20),
"C:\\bar\\...\\blafasel"
);
assert_eq!(
shorten_path("/foo/blar/baz/blah/blafasel", 27),
"/foo/blar/baz/blah/blafasel"
);
assert_eq!(
shorten_path("/foo/blar/baz/blah/blafasel", 26),
"/foo/.../baz/blah/blafasel"
);
assert_eq!(
shorten_path("/foo/b/baz/blah/blafasel", 23),
"/foo/.../blah/blafasel"
);
assert_eq!(shorten_path("/foobarbaz/blahblah", 16), ".../blahblah");
assert_eq!(shorten_path("/foobarbazblahblah", 12), "...lahblah");
assert_eq!(shorten_path("", 0), "");
}
#[test]
fn test_split_path() {
assert_eq!(split_path("C:\\a\\b"), (Some("C:\\a"), "b"));
assert_eq!(split_path("C:/a\\b"), (Some("C:/a"), "b"));
assert_eq!(split_path("C:\\a\\b\\c"), (Some("C:\\a\\b"), "c"));
assert_eq!(split_path("a\\b\\c\\d\\e"), (Some("a\\b\\c\\d"), "e"));
assert_eq!(split_path("\\\\UNC\\a"), (Some("\\\\UNC"), "a"));
assert_eq!(split_path("/a/b/c"), (Some("/a/b"), "c"));
assert_eq!(split_path("/a/b/c/d"), (Some("/a/b/c"), "d"));
assert_eq!(split_path("a/b/c"), (Some("a/b"), "c"));
assert_eq!(split_path("a"), (None, "a"));
assert_eq!(split_path("a/"), (None, "a"));
assert_eq!(split_path("/a"), (Some("/"), "a"));
assert_eq!(split_path(""), (None, ""));
}
#[test]
fn test_split_path_bytes() {
assert_eq!(
split_path_bytes(&b"C:\\a\\b"[..]),
(Some(&b"C:\\a"[..]), &b"b"[..])
);
assert_eq!(
split_path_bytes(&b"C:/a\\b"[..]),
(Some(&b"C:/a"[..]), &b"b"[..])
);
assert_eq!(
split_path_bytes(&b"C:\\a\\b\\c"[..]),
(Some(&b"C:\\a\\b"[..]), &b"c"[..])
);
assert_eq!(
split_path_bytes(&b"a\\b\\c\\d\\e"[..]),
(Some(&b"a\\b\\c\\d"[..]), &b"e"[..])
);
assert_eq!(
split_path_bytes(&b"\\\\UNC\\a"[..]),
(Some(&b"\\\\UNC"[..]), &b"a"[..])
);
assert_eq!(
split_path_bytes(&b"/a/b/c"[..]),
(Some(&b"/a/b"[..]), &b"c"[..])
);
assert_eq!(
split_path_bytes(&b"/a/b/c/d"[..]),
(Some(&b"/a/b/c"[..]), &b"d"[..])
);
assert_eq!(
split_path_bytes(&b"a/b/c"[..]),
(Some(&b"a/b"[..]), &b"c"[..])
);
assert_eq!(split_path_bytes(&b"a"[..]), (None, &b"a"[..]));
assert_eq!(split_path_bytes(&b"a/"[..]), (None, &b"a"[..]));
assert_eq!(split_path_bytes(&b"/a"[..]), (Some(&b"/"[..]), &b"a"[..]));
assert_eq!(split_path_bytes(&b""[..]), (None, &b""[..]));
}