run_clang_tidy/cli/
utils.rs1use 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_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 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 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 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}