rusty_files/utils/
path.rs

1use std::path::{Path, PathBuf};
2
3pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
4    dunce::canonicalize(path.as_ref()).unwrap_or_else(|_| path.as_ref().to_path_buf())
5}
6
7pub fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
8    let path = path.as_ref();
9
10    if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
11        if name.starts_with('.') && name != "." && name != ".." {
12            return true;
13        }
14    }
15
16    #[cfg(windows)]
17    {
18        use std::os::windows::fs::MetadataExt;
19        if let Ok(metadata) = path.metadata() {
20            const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2;
21            return (metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN) != 0;
22        }
23    }
24
25    false
26}
27
28pub fn get_path_depth<P: AsRef<Path>>(path: P) -> usize {
29    path.as_ref().components().count()
30}
31
32pub fn get_relative_path<P: AsRef<Path>>(base: P, target: P) -> Option<PathBuf> {
33    let base = normalize_path(base);
34    let target = normalize_path(target);
35
36    pathdiff::diff_paths(&target, &base)
37}
38
39pub fn is_same_file<P: AsRef<Path>>(path1: P, path2: P) -> bool {
40    let path1 = normalize_path(path1);
41    let path2 = normalize_path(path2);
42
43    path1 == path2
44}
45
46pub fn get_file_name<P: AsRef<Path>>(path: P) -> String {
47    path.as_ref()
48        .file_name()
49        .and_then(|n| n.to_str())
50        .unwrap_or("")
51        .to_string()
52}
53
54pub fn get_file_stem<P: AsRef<Path>>(path: P) -> String {
55    path.as_ref()
56        .file_stem()
57        .and_then(|n| n.to_str())
58        .unwrap_or("")
59        .to_string()
60}
61
62pub fn get_extension<P: AsRef<Path>>(path: P) -> Option<String> {
63    path.as_ref()
64        .extension()
65        .and_then(|e| e.to_str())
66        .map(|s| s.to_string())
67}
68
69pub fn join_paths<P: AsRef<Path>>(base: P, paths: &[P]) -> PathBuf {
70    let mut result = base.as_ref().to_path_buf();
71    for path in paths {
72        result.push(path.as_ref());
73    }
74    result
75}
76
77pub fn ensure_parent_exists<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
78    if let Some(parent) = path.as_ref().parent() {
79        std::fs::create_dir_all(parent)?;
80    }
81    Ok(())
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_is_hidden_unix() {
90        assert!(is_hidden(".hidden"));
91        assert!(is_hidden("/path/.hidden"));
92        assert!(!is_hidden("visible"));
93        assert!(!is_hidden("/path/visible"));
94    }
95
96    #[test]
97    fn test_get_path_depth() {
98        assert_eq!(get_path_depth("/"), 1);
99        assert_eq!(get_path_depth("/path/to/file"), 4);
100    }
101
102    #[test]
103    fn test_get_file_name() {
104        assert_eq!(get_file_name("/path/to/file.txt"), "file.txt");
105        assert_eq!(get_file_name("file.txt"), "file.txt");
106    }
107
108    #[test]
109    fn test_get_file_stem() {
110        assert_eq!(get_file_stem("/path/to/file.txt"), "file");
111        assert_eq!(get_file_stem("file.txt"), "file");
112    }
113
114    #[test]
115    fn test_get_extension() {
116        assert_eq!(get_extension("/path/to/file.txt"), Some("txt".to_string()));
117        assert_eq!(get_extension("file.rs"), Some("rs".to_string()));
118        assert_eq!(get_extension("file"), None);
119    }
120}