1 use std::ffi::OsStr;
2 use std::path::Path;
3 
4 use 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)]
is_hidden(dent: &DirEntry) -> bool12 pub 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)]
is_hidden(dent: &DirEntry) -> bool29 pub 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)))]
is_hidden(dent: &DirEntry) -> bool52 pub 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)]
strip_prefix<'a, P: AsRef<Path> + ?Sized>( prefix: &'a P, path: &'a Path, ) -> Option<&'a Path>64 pub 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))]
strip_prefix<'a, P: AsRef<Path> + ?Sized>( prefix: &'a P, path: &'a Path, ) -> Option<&'a Path>83 pub 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)]
is_file_name<P: AsRef<Path>>(path: P) -> bool93 pub 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))]
is_file_name<P: AsRef<Path>>(path: P) -> bool104 pub 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)]
file_name<'a, P: AsRef<Path> + ?Sized>( path: &'a P, ) -> Option<&'a OsStr>113 pub 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))]
file_name<'a, P: AsRef<Path> + ?Sized>( path: &'a P, ) -> Option<&'a OsStr>138 pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
139     path: &'a P,
140 ) -> Option<&'a OsStr> {
141     path.as_ref().file_name()
142 }
143