fix rustdoc lint warnings
[ouch.git] / src / utils / fs.rs
blob96f5523b91cda3bacaa42b7c96c15f3a13ae9d98
1 //! Filesystem utility functions.
3 use std::{
4     env,
5     fs::ReadDir,
6     io::{Read, Write},
7     path::{Path, PathBuf},
8 };
10 use fs_err as fs;
12 use super::{to_utf, user_wants_to_overwrite};
13 use crate::{extension::Extension, info, QuestionPolicy};
15 /// Checks if given path points to an empty directory.
16 pub fn dir_is_empty(dir_path: &Path) -> bool {
17     let is_empty = |mut rd: ReadDir| rd.next().is_none();
19     dir_path.read_dir().map(is_empty).unwrap_or_default()
22 /// Remove `path` asking the user to overwrite if necessary.
23 ///
24 /// * `Ok(true)` means the path is clear,
25 /// * `Ok(false)` means the user doesn't want to overwrite
26 /// * `Err(_)` is an error
27 pub fn clear_path(path: &Path, question_policy: QuestionPolicy) -> crate::Result<bool> {
28     if path.exists() && !user_wants_to_overwrite(path, question_policy)? {
29         return Ok(false);
30     }
32     if path.is_dir() {
33         fs::remove_dir_all(path)?;
34     } else if path.is_file() {
35         fs::remove_file(path)?;
36     }
38     Ok(true)
41 /// Creates a directory at the path, if there is nothing there.
42 pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
43     if !path.exists() {
44         fs::create_dir_all(path)?;
45         // creating a directory is an important change to the file system we
46         // should always inform the user about
47         info!(accessible, "directory {} created.", to_utf(path));
48     }
49     Ok(())
52 /// Returns current directory, but before change the process' directory to the
53 /// one that contains the file pointed to by `filename`.
54 pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
55     let previous_location = env::current_dir()?;
57     let parent = filename.parent().ok_or(crate::Error::CompressingRootFolder)?;
58     env::set_current_dir(parent)?;
60     Ok(previous_location)
63 /// Try to detect the file extension by looking for known magic strings
64 /// Source: <https://en.wikipedia.org/wiki/List_of_file_signatures>
65 pub fn try_infer_extension(path: &Path) -> Option<Extension> {
66     fn is_zip(buf: &[u8]) -> bool {
67         buf.len() >= 3
68             && buf[..=1] == [0x50, 0x4B]
69             && (buf[2..=3] == [0x3, 0x4] || buf[2..=3] == [0x5, 0x6] || buf[2..=3] == [0x7, 0x8])
70     }
71     fn is_tar(buf: &[u8]) -> bool {
72         buf.len() > 261 && buf[257..=261] == [0x75, 0x73, 0x74, 0x61, 0x72]
73     }
74     fn is_gz(buf: &[u8]) -> bool {
75         buf.starts_with(&[0x1F, 0x8B, 0x8])
76     }
77     fn is_bz2(buf: &[u8]) -> bool {
78         buf.starts_with(&[0x42, 0x5A, 0x68])
79     }
80     fn is_xz(buf: &[u8]) -> bool {
81         buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])
82     }
83     fn is_lz4(buf: &[u8]) -> bool {
84         buf.starts_with(&[0x04, 0x22, 0x4D, 0x18])
85     }
86     fn is_sz(buf: &[u8]) -> bool {
87         buf.starts_with(&[0xFF, 0x06, 0x00, 0x00, 0x73, 0x4E, 0x61, 0x50, 0x70, 0x59])
88     }
89     fn is_zst(buf: &[u8]) -> bool {
90         buf.starts_with(&[0x28, 0xB5, 0x2F, 0xFD])
91     }
93     let buf = {
94         let mut buf = [0; 270];
96         // Error cause will be ignored, so use std::fs instead of fs_err
97         let result = std::fs::File::open(&path).map(|mut file| file.read(&mut buf));
99         // In case of file open or read failure, could not infer a extension
100         if result.is_err() {
101             return None;
102         }
103         buf
104     };
106     use crate::extension::CompressionFormat::*;
107     if is_zip(&buf) {
108         Some(Extension::new(&[Zip], "zip"))
109     } else if is_tar(&buf) {
110         Some(Extension::new(&[Tar], "tar"))
111     } else if is_gz(&buf) {
112         Some(Extension::new(&[Gzip], "gz"))
113     } else if is_bz2(&buf) {
114         Some(Extension::new(&[Bzip], "bz2"))
115     } else if is_xz(&buf) {
116         Some(Extension::new(&[Lzma], "xz"))
117     } else if is_lz4(&buf) {
118         Some(Extension::new(&[Lz4], "lz4"))
119     } else if is_sz(&buf) {
120         Some(Extension::new(&[Snappy], "sz"))
121     } else if is_zst(&buf) {
122         Some(Extension::new(&[Zstd], "zst"))
123     } else {
124         None
125     }
128 /// Returns true if a path is a symlink.
129 /// This is the same as the nightly <https://doc.rust-lang.org/std/path/struct.Path.html#method.is_symlink>
130 /// Useful to detect broken symlinks when compressing. (So we can safely ignore them)
131 pub fn is_symlink(path: &Path) -> bool {
132     fs::symlink_metadata(path)
133         .map(|m| m.file_type().is_symlink())
134         .unwrap_or(false)