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