1#[cfg(feature = "dir")]
5pub struct Walk {
6 inner: walkdir::IntoIter,
7}
8
9#[cfg(feature = "dir")]
10impl Walk {
11 pub fn new(path: &std::path::Path) -> Self {
12 Self {
13 inner: walkdir::WalkDir::new(path).into_iter(),
14 }
15 }
16}
17
18#[cfg(feature = "dir")]
19impl Iterator for Walk {
20 type Item = Result<std::path::PathBuf, std::io::Error>;
21
22 fn next(&mut self) -> Option<Self::Item> {
23 while let Some(entry) = self.inner.next().map(|e| {
24 e.map(walkdir::DirEntry::into_path)
25 .map_err(std::io::Error::from)
26 }) {
27 if entry.as_ref().ok().and_then(|e| e.file_name())
28 != Some(std::ffi::OsStr::new(".keep"))
29 {
30 return Some(entry);
31 }
32 }
33 None
34 }
35}
36
37#[cfg(feature = "dir")]
43pub fn copy_template(
44 source: impl AsRef<std::path::Path>,
45 dest: impl AsRef<std::path::Path>,
46) -> Result<(), crate::assert::Error> {
47 let source = source.as_ref();
48 let dest = dest.as_ref();
49 let source = canonicalize(source)
50 .map_err(|e| format!("Failed to canonicalize {}: {}", source.display(), e))?;
51 std::fs::create_dir_all(dest)
52 .map_err(|e| format!("Failed to create {}: {}", dest.display(), e))?;
53 let dest = canonicalize(dest)
54 .map_err(|e| format!("Failed to canonicalize {}: {}", dest.display(), e))?;
55
56 for current in Walk::new(&source) {
57 let current = current.map_err(|e| e.to_string())?;
58 let rel = current.strip_prefix(&source).unwrap();
59 let target = dest.join(rel);
60
61 shallow_copy(¤t, &target)?;
62 }
63
64 Ok(())
65}
66
67pub(crate) fn shallow_copy(
69 source: &std::path::Path,
70 dest: &std::path::Path,
71) -> Result<(), crate::assert::Error> {
72 let meta = source
73 .symlink_metadata()
74 .map_err(|e| format!("Failed to read metadata from {}: {}", source.display(), e))?;
75 if meta.is_dir() {
76 std::fs::create_dir_all(dest)
77 .map_err(|e| format!("Failed to create {}: {}", dest.display(), e))?;
78 } else if meta.is_file() {
79 std::fs::copy(source, dest).map_err(|e| {
80 format!(
81 "Failed to copy {} to {}: {}",
82 source.display(),
83 dest.display(),
84 e
85 )
86 })?;
87 copy_stats(&meta, dest).map_err(|e| {
98 format!(
99 "Failed to copy {} metadata to {}: {}",
100 source.display(),
101 dest.display(),
102 e
103 )
104 })?;
105 } else if let Ok(target) = std::fs::read_link(source) {
106 symlink_to_file(dest, &target)
107 .map_err(|e| format!("Failed to create symlink {}: {}", dest.display(), e))?;
108 }
109
110 Ok(())
111}
112
113#[cfg(feature = "dir")]
114fn copy_stats(
115 source_meta: &std::fs::Metadata,
116 dest: &std::path::Path,
117) -> Result<(), std::io::Error> {
118 let src_mtime = filetime::FileTime::from_last_modification_time(source_meta);
119 filetime::set_file_mtime(dest, src_mtime)?;
120
121 Ok(())
122}
123
124#[cfg(not(feature = "dir"))]
125fn copy_stats(
126 _source_meta: &std::fs::Metadata,
127 _dest: &std::path::Path,
128) -> Result<(), std::io::Error> {
129 Ok(())
130}
131
132#[cfg(windows)]
133fn symlink_to_file(link: &std::path::Path, target: &std::path::Path) -> Result<(), std::io::Error> {
134 std::os::windows::fs::symlink_file(target, link)
135}
136
137#[cfg(not(windows))]
138fn symlink_to_file(link: &std::path::Path, target: &std::path::Path) -> Result<(), std::io::Error> {
139 std::os::unix::fs::symlink(target, link)
140}
141
142pub fn resolve_dir(
143 path: impl AsRef<std::path::Path>,
144) -> Result<std::path::PathBuf, std::io::Error> {
145 let path = path.as_ref();
146 let meta = std::fs::symlink_metadata(path)?;
147 if meta.is_dir() {
148 canonicalize(path)
149 } else if meta.is_file() {
150 let target = std::fs::read_to_string(path)?;
152 let target_path = path.parent().unwrap().join(target);
153 resolve_dir(target_path)
154 } else {
155 canonicalize(path)
156 }
157}
158
159pub(crate) fn canonicalize(path: &std::path::Path) -> Result<std::path::PathBuf, std::io::Error> {
160 #[cfg(feature = "dir")]
161 {
162 dunce::canonicalize(path)
163 }
164 #[cfg(not(feature = "dir"))]
165 {
166 Ok(strip_trailing_slash(path).to_owned())
168 }
169}
170
171pub fn strip_trailing_slash(path: &std::path::Path) -> &std::path::Path {
172 path.components().as_path()
173}
174
175pub(crate) fn normalize_path(path: &std::path::Path) -> std::path::PathBuf {
184 use std::path::Component;
185
186 let mut components = path.components().peekable();
187 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
188 components.next();
189 std::path::PathBuf::from(c.as_os_str())
190 } else {
191 std::path::PathBuf::new()
192 };
193
194 for component in components {
195 match component {
196 Component::Prefix(..) => unreachable!(),
197 Component::RootDir => {
198 ret.push(Component::RootDir);
199 }
200 Component::CurDir => {}
201 Component::ParentDir => {
202 if ret.ends_with(Component::ParentDir) {
203 ret.push(Component::ParentDir);
204 } else {
205 let popped = ret.pop();
206 if !popped && !ret.has_root() {
207 ret.push(Component::ParentDir);
208 }
209 }
210 }
211 Component::Normal(c) => {
212 ret.push(c);
213 }
214 }
215 }
216 ret
217}
218
219pub(crate) fn display_relpath(path: impl AsRef<std::path::Path>) -> String {
220 let path = path.as_ref();
221 let relpath = if let Ok(cwd) = std::env::current_dir() {
222 match path.strip_prefix(cwd) {
223 Ok(path) => path,
224 Err(_) => path,
225 }
226 } else {
227 path
228 };
229 relpath.display().to_string()
230}