ignore/
pathutil.rs

1use std::ffi::OsStr;
2use std::path::Path;
3
4use crate::walk::DirEntry;
5
6/// Returns true if and only if this entry is considered to be hidden.
7///
8/// This only returns true if the base name of the path starts with a `.`.
9///
10/// On Unix, this implements a more optimized check.
11#[cfg(unix)]
12pub fn is_hidden(dent: &DirEntry) -> bool {
13    use std::os::unix::ffi::OsStrExt;
14
15    if let Some(name) = file_name(dent.path()) {
16        name.as_bytes().get(0) == Some(&b'.')
17    } else {
18        false
19    }
20}
21
22/// Returns true if and only if this entry is considered to be hidden.
23///
24/// On Windows, this returns true if one of the following is true:
25///
26/// * The base name of the path starts with a `.`.
27/// * The file attributes have the `HIDDEN` property set.
28#[cfg(windows)]
29pub fn is_hidden(dent: &DirEntry) -> bool {
30    use std::os::windows::fs::MetadataExt;
31    use winapi_util::file;
32
33    // This looks like we're doing an extra stat call, but on Windows, the
34    // directory traverser reuses the metadata retrieved from each directory
35    // entry and stores it on the DirEntry itself. So this is "free."
36    if let Ok(md) = dent.metadata() {
37        if file::is_hidden(md.file_attributes() as u64) {
38            return true;
39        }
40    }
41    if let Some(name) = file_name(dent.path()) {
42        name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
43    } else {
44        false
45    }
46}
47
48/// Returns true if and only if this entry is considered to be hidden.
49///
50/// This only returns true if the base name of the path starts with a `.`.
51#[cfg(not(any(unix, windows)))]
52pub fn is_hidden(dent: &DirEntry) -> bool {
53    if let Some(name) = file_name(dent.path()) {
54        name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
55    } else {
56        false
57    }
58}
59
60/// Strip `prefix` from the `path` and return the remainder.
61///
62/// If `path` doesn't have a prefix `prefix`, then return `None`.
63#[cfg(unix)]
64pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
65    prefix: &'a P,
66    path: &'a Path,
67) -> Option<&'a Path> {
68    use std::os::unix::ffi::OsStrExt;
69
70    let prefix = prefix.as_ref().as_os_str().as_bytes();
71    let path = path.as_os_str().as_bytes();
72    if prefix.len() > path.len() || prefix != &path[0..prefix.len()] {
73        None
74    } else {
75        Some(&Path::new(OsStr::from_bytes(&path[prefix.len()..])))
76    }
77}
78
79/// Strip `prefix` from the `path` and return the remainder.
80///
81/// If `path` doesn't have a prefix `prefix`, then return `None`.
82#[cfg(not(unix))]
83pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
84    prefix: &'a P,
85    path: &'a Path,
86) -> Option<&'a Path> {
87    path.strip_prefix(prefix).ok()
88}
89
90/// Returns true if this file path is just a file name. i.e., Its parent is
91/// the empty string.
92#[cfg(unix)]
93pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
94    use memchr::memchr;
95    use std::os::unix::ffi::OsStrExt;
96
97    let path = path.as_ref().as_os_str().as_bytes();
98    memchr(b'/', path).is_none()
99}
100
101/// Returns true if this file path is just a file name. i.e., Its parent is
102/// the empty string.
103#[cfg(not(unix))]
104pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
105    path.as_ref().parent().map(|p| p.as_os_str().is_empty()).unwrap_or(false)
106}
107
108/// The final component of the path, if it is a normal file.
109///
110/// If the path terminates in ., .., or consists solely of a root of prefix,
111/// file_name will return None.
112#[cfg(unix)]
113pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
114    path: &'a P,
115) -> Option<&'a OsStr> {
116    use memchr::memrchr;
117    use std::os::unix::ffi::OsStrExt;
118
119    let path = path.as_ref().as_os_str().as_bytes();
120    if path.is_empty() {
121        return None;
122    } else if path.len() == 1 && path[0] == b'.' {
123        return None;
124    } else if path.last() == Some(&b'.') {
125        return None;
126    } else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {
127        return None;
128    }
129    let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
130    Some(OsStr::from_bytes(&path[last_slash..]))
131}
132
133/// The final component of the path, if it is a normal file.
134///
135/// If the path terminates in ., .., or consists solely of a root of prefix,
136/// file_name will return None.
137#[cfg(not(unix))]
138pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
139    path: &'a P,
140) -> Option<&'a OsStr> {
141    path.as_ref().file_name()
142}