]>
Commit | Line | Data |
---|---|---|
dc9dc135 XL |
1 | use crate::errors::*; |
2 | use std::convert::Into; | |
7cac9316 | 3 | use std::fs::{self, File}; |
416331ca | 4 | use std::io::Write; |
83c7162d | 5 | use std::path::{Component, Path, PathBuf}; |
7cac9316 | 6 | |
94222f64 | 7 | /// Naively replaces any path separator with a forward-slash '/' |
83c7162d XL |
8 | pub fn normalize_path(path: &str) -> String { |
9 | use std::path::is_separator; | |
10 | path.chars() | |
11 | .map(|ch| if is_separator(ch) { '/' } else { ch }) | |
12 | .collect::<String>() | |
13 | } | |
14 | ||
15 | /// Write the given data to a file, creating it first if necessary | |
9fa01778 | 16 | pub fn write_file<P: AsRef<Path>>(build_dir: &Path, filename: P, content: &[u8]) -> Result<()> { |
83c7162d XL |
17 | let path = build_dir.join(filename); |
18 | ||
dc9dc135 | 19 | create_file(&path)?.write_all(content).map_err(Into::into) |
83c7162d XL |
20 | } |
21 | ||
041b39d2 XL |
22 | /// Takes a path and returns a path containing just enough `../` to point to |
23 | /// the root of the given path. | |
7cac9316 | 24 | /// |
041b39d2 XL |
25 | /// This is mostly interesting for a relative path to point back to the |
26 | /// directory from where the path starts. | |
7cac9316 | 27 | /// |
ea8adc8c | 28 | /// ```rust |
ea8adc8c XL |
29 | /// # use std::path::Path; |
30 | /// # use mdbook::utils::fs::path_to_root; | |
ea8adc8c XL |
31 | /// let path = Path::new("some/relative/path"); |
32 | /// assert_eq!(path_to_root(path), "../../"); | |
7cac9316 XL |
33 | /// ``` |
34 | /// | |
041b39d2 XL |
35 | /// **note:** it's not very fool-proof, if you find a situation where |
36 | /// it doesn't return the correct path. | |
e74abb32 XL |
37 | /// Consider [submitting a new issue](https://github.com/rust-lang/mdBook/issues) |
38 | /// or a [pull-request](https://github.com/rust-lang/mdBook/pulls) to improve it. | |
ea8adc8c | 39 | pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String { |
2c00a5a8 | 40 | debug!("path_to_root"); |
7cac9316 XL |
41 | // Remove filename and add "../" for every directory |
42 | ||
ea8adc8c | 43 | path.into() |
7cac9316 XL |
44 | .parent() |
45 | .expect("") | |
46 | .components() | |
47 | .fold(String::new(), |mut s, c| { | |
48 | match c { | |
49 | Component::Normal(_) => s.push_str("../"), | |
50 | _ => { | |
2c00a5a8 XL |
51 | debug!("Other path component... {:?}", c); |
52 | } | |
7cac9316 XL |
53 | } |
54 | s | |
55 | }) | |
56 | } | |
57 | ||
041b39d2 XL |
58 | /// This function creates a file and returns it. But before creating the file |
59 | /// it checks every directory in the path to see if it exists, | |
60 | /// and if it does not it will be created. | |
ea8adc8c | 61 | pub fn create_file(path: &Path) -> Result<File> { |
2c00a5a8 | 62 | debug!("Creating {}", path.display()); |
7cac9316 XL |
63 | |
64 | // Construct path | |
65 | if let Some(p) = path.parent() { | |
2c00a5a8 | 66 | trace!("Parent directory is: {:?}", p); |
7cac9316 | 67 | |
041b39d2 | 68 | fs::create_dir_all(p)?; |
7cac9316 XL |
69 | } |
70 | ||
dc9dc135 | 71 | File::create(path).map_err(Into::into) |
7cac9316 XL |
72 | } |
73 | ||
74 | /// Removes all the content of a directory but not the directory itself | |
ea8adc8c | 75 | pub fn remove_dir_content(dir: &Path) -> Result<()> { |
041b39d2 | 76 | for item in fs::read_dir(dir)? { |
7cac9316 XL |
77 | if let Ok(item) = item { |
78 | let item = item.path(); | |
79 | if item.is_dir() { | |
041b39d2 | 80 | fs::remove_dir_all(item)?; |
7cac9316 | 81 | } else { |
041b39d2 | 82 | fs::remove_file(item)?; |
7cac9316 XL |
83 | } |
84 | } | |
85 | } | |
86 | Ok(()) | |
87 | } | |
88 | ||
041b39d2 XL |
89 | /// Copies all files of a directory to another one except the files |
90 | /// with the extensions given in the `ext_blacklist` array | |
2c00a5a8 XL |
91 | pub fn copy_files_except_ext( |
92 | from: &Path, | |
93 | to: &Path, | |
94 | recursive: bool, | |
f9f354fc | 95 | avoid_dir: Option<&PathBuf>, |
2c00a5a8 XL |
96 | ext_blacklist: &[&str], |
97 | ) -> Result<()> { | |
98 | debug!( | |
f9f354fc | 99 | "Copying all files from {} to {} (blacklist: {:?}), avoiding {:?}", |
2c00a5a8 XL |
100 | from.display(), |
101 | to.display(), | |
f9f354fc XL |
102 | ext_blacklist, |
103 | avoid_dir | |
2c00a5a8 XL |
104 | ); |
105 | ||
7cac9316 XL |
106 | // Check that from and to are different |
107 | if from == to { | |
108 | return Ok(()); | |
109 | } | |
2c00a5a8 | 110 | |
041b39d2 XL |
111 | for entry in fs::read_dir(from)? { |
112 | let entry = entry?; | |
5869c6ff XL |
113 | let metadata = entry |
114 | .path() | |
115 | .metadata() | |
116 | .with_context(|| format!("Failed to read {:?}", entry.path()))?; | |
7cac9316 XL |
117 | |
118 | // If the entry is a dir and the recursive option is enabled, call itself | |
119 | if metadata.is_dir() && recursive { | |
120 | if entry.path() == to.to_path_buf() { | |
121 | continue; | |
122 | } | |
7cac9316 | 123 | |
f9f354fc XL |
124 | if let Some(avoid) = avoid_dir { |
125 | if entry.path() == *avoid { | |
126 | continue; | |
127 | } | |
128 | } | |
129 | ||
7cac9316 XL |
130 | // check if output dir already exists |
131 | if !to.join(entry.file_name()).exists() { | |
041b39d2 | 132 | fs::create_dir(&to.join(entry.file_name()))?; |
7cac9316 XL |
133 | } |
134 | ||
2c00a5a8 XL |
135 | copy_files_except_ext( |
136 | &from.join(entry.file_name()), | |
137 | &to.join(entry.file_name()), | |
138 | true, | |
f9f354fc | 139 | avoid_dir, |
2c00a5a8 XL |
140 | ext_blacklist, |
141 | )?; | |
7cac9316 | 142 | } else if metadata.is_file() { |
7cac9316 XL |
143 | // Check if it is in the blacklist |
144 | if let Some(ext) = entry.path().extension() { | |
145 | if ext_blacklist.contains(&ext.to_str().unwrap()) { | |
146 | continue; | |
147 | } | |
148 | } | |
2c00a5a8 XL |
149 | debug!( |
150 | "creating path for file: {:?}", | |
151 | &to.join( | |
152 | entry | |
153 | .path() | |
154 | .file_name() | |
155 | .expect("a file should have a file name...") | |
156 | ) | |
157 | ); | |
158 | ||
159 | debug!( | |
160 | "Copying {:?} to {:?}", | |
161 | entry.path(), | |
162 | &to.join( | |
163 | entry | |
164 | .path() | |
165 | .file_name() | |
166 | .expect("a file should have a file name...") | |
167 | ) | |
168 | ); | |
169 | fs::copy( | |
170 | entry.path(), | |
171 | &to.join( | |
172 | entry | |
173 | .path() | |
174 | .file_name() | |
175 | .expect("a file should have a file name..."), | |
176 | ), | |
177 | )?; | |
7cac9316 XL |
178 | } |
179 | } | |
180 | Ok(()) | |
181 | } | |
182 | ||
f035d41b XL |
183 | pub fn get_404_output_file(input_404: &Option<String>) -> String { |
184 | input_404 | |
185 | .as_ref() | |
186 | .unwrap_or(&"404.md".to_string()) | |
187 | .replace(".md", ".html") | |
188 | } | |
189 | ||
7cac9316 XL |
190 | #[cfg(test)] |
191 | mod tests { | |
7cac9316 | 192 | use super::copy_files_except_ext; |
fc512014 XL |
193 | use std::{fs, io::Result, path::Path}; |
194 | ||
195 | #[cfg(target_os = "windows")] | |
196 | fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> { | |
197 | std::os::windows::fs::symlink_file(src, dst) | |
198 | } | |
199 | ||
200 | #[cfg(not(target_os = "windows"))] | |
201 | fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> { | |
202 | std::os::unix::fs::symlink(src, dst) | |
203 | } | |
7cac9316 XL |
204 | |
205 | #[test] | |
206 | fn copy_files_except_ext_test() { | |
83c7162d | 207 | let tmp = match tempfile::TempDir::new() { |
7cac9316 | 208 | Ok(t) => t, |
dc9dc135 | 209 | Err(e) => panic!("Could not create a temp dir: {}", e), |
7cac9316 XL |
210 | }; |
211 | ||
212 | // Create a couple of files | |
dc9dc135 XL |
213 | if let Err(err) = fs::File::create(&tmp.path().join("file.txt")) { |
214 | panic!("Could not create file.txt: {}", err); | |
7cac9316 | 215 | } |
dc9dc135 XL |
216 | if let Err(err) = fs::File::create(&tmp.path().join("file.md")) { |
217 | panic!("Could not create file.md: {}", err); | |
7cac9316 | 218 | } |
dc9dc135 XL |
219 | if let Err(err) = fs::File::create(&tmp.path().join("file.png")) { |
220 | panic!("Could not create file.png: {}", err); | |
7cac9316 | 221 | } |
dc9dc135 XL |
222 | if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir")) { |
223 | panic!("Could not create sub_dir: {}", err); | |
7cac9316 | 224 | } |
dc9dc135 XL |
225 | if let Err(err) = fs::File::create(&tmp.path().join("sub_dir/file.png")) { |
226 | panic!("Could not create sub_dir/file.png: {}", err); | |
7cac9316 | 227 | } |
dc9dc135 XL |
228 | if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir_exists")) { |
229 | panic!("Could not create sub_dir_exists: {}", err); | |
7cac9316 | 230 | } |
dc9dc135 XL |
231 | if let Err(err) = fs::File::create(&tmp.path().join("sub_dir_exists/file.txt")) { |
232 | panic!("Could not create sub_dir_exists/file.txt: {}", err); | |
7cac9316 | 233 | } |
fc512014 XL |
234 | if let Err(err) = symlink( |
235 | &tmp.path().join("file.png"), | |
236 | &tmp.path().join("symlink.png"), | |
237 | ) { | |
238 | panic!("Could not symlink file.png: {}", err); | |
239 | } | |
7cac9316 XL |
240 | |
241 | // Create output dir | |
dc9dc135 XL |
242 | if let Err(err) = fs::create_dir(&tmp.path().join("output")) { |
243 | panic!("Could not create output: {}", err); | |
7cac9316 | 244 | } |
dc9dc135 XL |
245 | if let Err(err) = fs::create_dir(&tmp.path().join("output/sub_dir_exists")) { |
246 | panic!("Could not create output/sub_dir_exists: {}", err); | |
7cac9316 XL |
247 | } |
248 | ||
dc9dc135 | 249 | if let Err(e) = |
f9f354fc | 250 | copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, None, &["md"]) |
dc9dc135 XL |
251 | { |
252 | panic!("Error while executing the function:\n{:?}", e); | |
7cac9316 XL |
253 | } |
254 | ||
255 | // Check if the correct files where created | |
256 | if !(&tmp.path().join("output/file.txt")).exists() { | |
257 | panic!("output/file.txt should exist") | |
258 | } | |
259 | if (&tmp.path().join("output/file.md")).exists() { | |
260 | panic!("output/file.md should not exist") | |
261 | } | |
262 | if !(&tmp.path().join("output/file.png")).exists() { | |
263 | panic!("output/file.png should exist") | |
264 | } | |
265 | if !(&tmp.path().join("output/sub_dir/file.png")).exists() { | |
266 | panic!("output/sub_dir/file.png should exist") | |
267 | } | |
268 | if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() { | |
269 | panic!("output/sub_dir/file.png should exist") | |
270 | } | |
fc512014 XL |
271 | if !(&tmp.path().join("output/symlink.png")).exists() { |
272 | panic!("output/symlink.png should exist") | |
273 | } | |
7cac9316 XL |
274 | } |
275 | } |