run_clang_tidy/cli/
utils.rs

1use std::{fs, path};
2
3use color_eyre::{eyre::eyre, eyre::WrapErr};
4
5pub fn path_or_err<P>(path: P) -> eyre::Result<path::PathBuf>
6where
7    P: AsRef<path::Path>,
8{
9    let path_as_buf = path::PathBuf::from(path.as_ref());
10
11    if !path_as_buf.exists() {
12        return Err(eyre!("Path not found or permission denied"))
13            .wrap_err(format!("'{}' is not a path", path_as_buf.to_string_lossy()));
14    }
15
16    Ok(path_as_buf)
17}
18
19pub fn file_or_err<P>(path: P) -> eyre::Result<path::PathBuf>
20where
21    P: AsRef<path::Path>,
22{
23    let path_as_buf = path::PathBuf::from(path.as_ref());
24
25    if !path_as_buf.is_file() {
26        return Err(eyre!("File not found or permission denied"))
27            .wrap_err(format!("'{}' is not a file", path_as_buf.to_string_lossy()));
28    }
29
30    Ok(path_as_buf)
31}
32
33pub fn dir_or_err<P>(path: P) -> eyre::Result<path::PathBuf>
34where
35    P: AsRef<path::Path>,
36{
37    let path_as_buf = path::PathBuf::from(path.as_ref());
38    let meta = fs::metadata(path.as_ref()).wrap_err(format!(
39        "'{}' is not a directory",
40        path_as_buf.to_string_lossy()
41    ))?;
42
43    if !meta.is_dir() {
44        return Err(eyre!("Directory not found")).wrap_err(format!(
45            "'{}' is not a directory",
46            path_as_buf.to_string_lossy()
47        ));
48    }
49
50    Ok(path_as_buf)
51}
52
53pub fn file_with_name<P>(path: P, name: &str) -> eyre::Result<path::PathBuf>
54where
55    P: AsRef<path::Path>,
56{
57    let buf = file_or_err(path.as_ref())?;
58    let name_str = buf.to_string_lossy();
59
60    let file_name = path
61        .as_ref()
62        .file_name()
63        .and_then(std::ffi::OsStr::to_str)
64        .ok_or(eyre!(format!(
65            "Expected file with name '{name}', got '{name_str}'"
66        )))?;
67
68    if file_name.to_lowercase() != name.to_lowercase() {
69        return Err(eyre!(format!(
70            "Expected file with name '{name}', got '{name_str}'"
71        )));
72    }
73    Ok(buf)
74}
75
76pub fn file_with_ext<P>(path: P, ext: &str, strict: bool) -> eyre::Result<path::PathBuf>
77where
78    P: AsRef<path::Path>,
79{
80    let buf = file_or_err(path.as_ref())?;
81    let name = buf.to_string_lossy();
82
83    let file_ext = path
84        .as_ref()
85        .extension()
86        .and_then(std::ffi::OsStr::to_str)
87        .ok_or(eyre!(format!(
88            "Expected file with extension '{ext}', got file '{name}'"
89        )))?;
90
91    let ext_minus = match ext.chars().next() {
92        Some(c) if c == '.' && !strict => &ext[1..],
93        _ => ext,
94    };
95
96    // if ext.starts_with(".") {
97    //     &ext[1..]
98    // }
99
100    if ext_minus.to_lowercase() != file_ext.to_lowercase() {
101        return Err(eyre!(format!(
102            "Expected file extension '{ext_minus}', got '{file_ext}'"
103        )));
104    }
105    Ok(buf)
106}
107
108pub fn file_with_name_or_ext<P>(path: P, name_or_ext: &str) -> eyre::Result<path::PathBuf>
109where
110    P: AsRef<path::Path>,
111{
112    let buf = file_or_err(path.as_ref())?;
113
114    let f_for_name = file_with_name(path.as_ref(), name_or_ext);
115    let f_for_ext = file_with_ext(path.as_ref(), name_or_ext, false);
116
117    match f_for_name {
118        Ok(path) => Ok(path),
119        Err(_) => match f_for_ext {
120            Ok(path) => Ok(path),
121            Err(_) => Err(eyre!(format!(
122                "Expected file with name or extension '{}', got '{}'",
123                name_or_ext,
124                buf.to_string_lossy()
125            ))),
126        },
127    }
128}
129
130pub fn filename_or_exists<P>(path: P, root: Option<P>) -> eyre::Result<path::PathBuf>
131where
132    P: AsRef<path::Path>,
133{
134    if path.as_ref().is_absolute() && !path.as_ref().exists() {
135        return Err(eyre::eyre!(format!(
136            "'{}' does not exist",
137            path.as_ref().to_string_lossy()
138        )));
139    }
140
141    // this is one way to find out of if the specified path is only a file- or directory name
142    // for paths that are provided as pure filenames, no checks are performed since they can be
143    // part of search paths (e.g., for command names)
144    let is_file = path
145        .as_ref()
146        .file_name()
147        .and_then(|file_name| (path.as_ref().as_os_str() == file_name).then_some(true))
148        .is_some();
149
150    if is_file {
151        return Ok(path.as_ref().to_path_buf());
152    }
153
154    if path.as_ref().is_relative() {
155        let full_path = match root {
156            None => path.as_ref().to_path_buf(),
157            Some(root) => {
158                let mut full_path = root.as_ref().to_path_buf();
159                full_path.push(path.as_ref());
160                full_path
161            }
162        };
163        if !full_path.exists() {
164            return Err(eyre::eyre!(format!(
165                "'{}' does not exist",
166                path.as_ref().to_string_lossy()
167            )));
168        }
169        return Ok(full_path);
170    }
171
172    Ok(path.as_ref().to_path_buf())
173}
174
175pub fn filename_or_exists_with_ext<P>(
176    path: P,
177    root: Option<P>,
178    ext: Option<&str>,
179) -> eyre::Result<path::PathBuf>
180where
181    P: AsRef<path::Path>,
182{
183    let path_buf = path.as_ref().to_path_buf();
184    let root_buf = root.map(|p| p.as_ref().to_path_buf());
185
186    let mut checks = vec![filename_or_exists(path_buf, root_buf.clone())];
187
188    // allow to omit the extension, e.g., .exe for Windows
189    if let Some(ext) = ext {
190        let mut try_ext = path.as_ref().to_path_buf();
191        try_ext.set_extension(ext);
192
193        checks.push(filename_or_exists(try_ext, root_buf));
194    }
195
196    // println!("commands to check: {:?}", checks);
197
198    let has_path = checks.iter().find(|result| result.is_ok());
199    if let Some(cmd) = has_path {
200        return Ok(cmd.as_ref().unwrap().as_path().to_path_buf());
201    }
202
203    Err(checks.remove(0).unwrap_err())
204}
205
206pub fn executable_or_exists<P>(path: P, root: Option<P>) -> eyre::Result<path::PathBuf>
207where
208    P: AsRef<path::Path>,
209{
210    let ext = if cfg!(windows) { Some("exe") } else { None };
211    filename_or_exists_with_ext(path, root, ext)
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_path() {
220        let path = path::Path::new("some/path/to/.clang-format");
221        let file_name = path.file_name().and_then(std::ffi::OsStr::to_str).unwrap();
222
223        assert_eq!(".clang-format", file_name.to_lowercase());
224    }
225}