yazi/yazi-fs/src/path/clean.rs
2025-11-19 18:07:04 +08:00

96 lines
3 KiB
Rust

use std::path::PathBuf;
use yazi_shared::{loc::LocBuf, path::PathDyn, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
pub fn clean_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlBuf {
let cow: UrlCow = url.into();
let (path, uri, urn) = clean_path_impl(
cow.loc(),
cow.base().components().count() - 1,
cow.trail().components().count() - 1,
);
match cow.as_url() {
Url::Regular(_) => UrlBuf::Regular(path.into()),
Url::Search { domain, .. } => UrlBuf::Search {
loc: LocBuf::<PathBuf>::with(path, uri, urn).expect("create Loc from cleaned path"),
domain: domain.intern(),
},
Url::Archive { domain, .. } => UrlBuf::Archive {
loc: LocBuf::<PathBuf>::with(path, uri, urn).expect("create Loc from cleaned path"),
domain: domain.intern(),
},
Url::Sftp { domain, .. } => UrlBuf::Sftp {
loc: LocBuf::<PathBuf>::with(path, uri, urn).expect("create Loc from cleaned path"),
domain: domain.intern(),
},
}
}
fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBuf, usize, usize) {
use std::path::Component::*;
let mut out = vec![];
let mut uri_count = 0;
let mut urn_count = 0;
macro_rules! push {
($i:ident, $c:ident) => {{
out.push($c);
if $i >= base {
uri_count += 1;
}
if $i >= trail {
urn_count += 1;
}
}};
}
for (i, c) in path.components().enumerate() {
match c {
CurDir => {}
ParentDir => match out.last() {
Some(RootDir) => {}
Some(Normal(_)) => _ = out.pop(),
None | Some(CurDir) | Some(ParentDir) | Some(Prefix(_)) => push!(i, c),
},
c => push!(i, c),
}
}
(if out.is_empty() { PathBuf::from(".") } else { out.iter().collect() }, uri_count, urn_count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clean_url() -> anyhow::Result<()> {
yazi_shared::init_tests();
let cases = [
// CurDir
("archive://:3//./tmp/test.zip/foo/bar", "archive://:3//tmp/test.zip/foo/bar"),
("archive://:3//tmp/./test.zip/foo/bar", "archive://:3//tmp/test.zip/foo/bar"),
("archive://:3//tmp/./test.zip/./foo/bar", "archive://:3//tmp/test.zip/foo/bar"),
("archive://:3//tmp/./test.zip/./foo/./bar/.", "archive://:3//tmp/test.zip/foo/bar"),
// ParentDir
("archive://:3:2//../../tmp/test.zip/foo/bar", "archive://:3:2//tmp/test.zip/foo/bar"),
("archive://:3:2//tmp/../../test.zip/foo/bar", "archive://:3:2//test.zip/foo/bar"),
("archive://:4:2//tmp/test.zip/../../foo/bar", "archive://:2:2//foo/bar"),
("archive://:5:2//tmp/test.zip/../../foo/bar", "archive://:3:2//foo/bar"),
("archive://:4:4//tmp/test.zip/foo/bar/../../", "archive://:1:1//tmp/test.zip"),
("archive://:5:4//tmp/test.zip/foo/bar/../../", "archive://:2:1//tmp/test.zip"),
("archive://:4:4//tmp/test.zip/foo/bar/../../../", "archive:////tmp"),
];
for (input, expected) in cases {
let input: UrlBuf = input.parse()?;
#[cfg(unix)]
assert_eq!(format!("{:?}", clean_url(input)), expected);
#[cfg(windows)]
assert_eq!(format!("{:?}", clean_url(input)).replace(r"\", "/"), expected.replace(r"\", "/"));
}
Ok(())
}
}