diff options
Diffstat (limited to 'bin/src/config.rs')
-rw-r--r-- | bin/src/config.rs | 168 |
1 files changed, 122 insertions, 46 deletions
diff --git a/bin/src/config.rs b/bin/src/config.rs index d3944ac..b6310e6 100644 --- a/bin/src/config.rs +++ b/bin/src/config.rs | |||
@@ -1,8 +1,15 @@ | |||
1 | use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr}; | 1 | use std::{ |
2 | default::Default, | ||
3 | fmt, fs, | ||
4 | path::{Path, PathBuf}, | ||
5 | str::FromStr, | ||
6 | }; | ||
2 | 7 | ||
3 | use crate::{dirs, err::ConfigErr}; | 8 | use crate::{dirs, err::ConfigErr, utils, LintMap}; |
4 | 9 | ||
5 | use clap::Parser; | 10 | use clap::Parser; |
11 | use lib::LINTS; | ||
12 | use serde::{Deserialize, Serialize}; | ||
6 | use vfs::ReadOnlyVfs; | 13 | use vfs::ReadOnlyVfs; |
7 | 14 | ||
8 | #[derive(Parser, Debug)] | 15 | #[derive(Parser, Debug)] |
@@ -43,6 +50,10 @@ pub struct Check { | |||
43 | #[clap(short = 'o', long, default_value_t, parse(try_from_str))] | 50 | #[clap(short = 'o', long, default_value_t, parse(try_from_str))] |
44 | pub format: OutFormat, | 51 | pub format: OutFormat, |
45 | 52 | ||
53 | /// Path to statix.toml | ||
54 | #[clap(short = 'c', long = "config", default_value = ".")] | ||
55 | pub conf_path: PathBuf, | ||
56 | |||
46 | /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout | 57 | /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout |
47 | #[clap(short, long = "stdin")] | 58 | #[clap(short, long = "stdin")] |
48 | pub streaming: bool, | 59 | pub streaming: bool, |
@@ -65,6 +76,10 @@ impl Check { | |||
65 | vfs(files.collect::<Vec<_>>()) | 76 | vfs(files.collect::<Vec<_>>()) |
66 | } | 77 | } |
67 | } | 78 | } |
79 | |||
80 | pub fn lints(&self) -> Result<LintMap, ConfigErr> { | ||
81 | lints(&self.conf_path) | ||
82 | } | ||
68 | } | 83 | } |
69 | 84 | ||
70 | #[derive(Parser, Debug)] | 85 | #[derive(Parser, Debug)] |
@@ -85,6 +100,10 @@ pub struct Fix { | |||
85 | #[clap(short, long = "dry-run")] | 100 | #[clap(short, long = "dry-run")] |
86 | pub diff_only: bool, | 101 | pub diff_only: bool, |
87 | 102 | ||
103 | /// Path to statix.toml | ||
104 | #[clap(short = 'c', long = "config", default_value = ".")] | ||
105 | pub conf_path: PathBuf, | ||
106 | |||
88 | /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout | 107 | /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout |
89 | #[clap(short, long = "stdin")] | 108 | #[clap(short, long = "stdin")] |
90 | pub streaming: bool, | 109 | pub streaming: bool, |
@@ -125,6 +144,10 @@ impl Fix { | |||
125 | FixOut::Write | 144 | FixOut::Write |
126 | } | 145 | } |
127 | } | 146 | } |
147 | |||
148 | pub fn lints(&self) -> Result<LintMap, ConfigErr> { | ||
149 | lints(&self.conf_path) | ||
150 | } | ||
128 | } | 151 | } |
129 | 152 | ||
130 | #[derive(Parser, Debug)] | 153 | #[derive(Parser, Debug)] |
@@ -181,50 +204,6 @@ pub struct Explain { | |||
181 | pub target: u32, | 204 | pub target: u32, |
182 | } | 205 | } |
183 | 206 | ||
184 | fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { | ||
185 | let parts = src.split(','); | ||
186 | match parts.collect::<Vec<_>>().as_slice() { | ||
187 | [line, col] => { | ||
188 | let l = line | ||
189 | .parse::<usize>() | ||
190 | .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?; | ||
191 | let c = col | ||
192 | .parse::<usize>() | ||
193 | .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?; | ||
194 | Ok((l, c)) | ||
195 | } | ||
196 | _ => Err(ConfigErr::InvalidPosition(src.to_owned())), | ||
197 | } | ||
198 | } | ||
199 | |||
200 | fn parse_warning_code(src: &str) -> Result<u32, ConfigErr> { | ||
201 | let mut char_stream = src.chars(); | ||
202 | let severity = char_stream | ||
203 | .next() | ||
204 | .ok_or_else(|| ConfigErr::InvalidWarningCode(src.to_owned()))? | ||
205 | .to_ascii_lowercase(); | ||
206 | match severity { | ||
207 | 'w' => char_stream | ||
208 | .collect::<String>() | ||
209 | .parse::<u32>() | ||
210 | .map_err(|_| ConfigErr::InvalidWarningCode(src.to_owned())), | ||
211 | _ => Ok(0), | ||
212 | } | ||
213 | } | ||
214 | |||
215 | fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> { | ||
216 | let mut vfs = ReadOnlyVfs::default(); | ||
217 | for file in files.iter() { | ||
218 | if let Ok(data) = fs::read_to_string(&file) { | ||
219 | let _id = vfs.alloc_file_id(&file); | ||
220 | vfs.set_file_contents(&file, data.as_bytes()); | ||
221 | } else { | ||
222 | println!("{} contains non-utf8 content", file.display()); | ||
223 | }; | ||
224 | } | ||
225 | Ok(vfs) | ||
226 | } | ||
227 | |||
228 | #[derive(Debug, Copy, Clone)] | 207 | #[derive(Debug, Copy, Clone)] |
229 | pub enum OutFormat { | 208 | pub enum OutFormat { |
230 | #[cfg(feature = "json")] | 209 | #[cfg(feature = "json")] |
@@ -269,3 +248,100 @@ impl FromStr for OutFormat { | |||
269 | } | 248 | } |
270 | } | 249 | } |
271 | } | 250 | } |
251 | |||
252 | #[derive(Serialize, Deserialize, Debug)] | ||
253 | pub struct ConfFile { | ||
254 | disabled: Vec<String>, | ||
255 | } | ||
256 | |||
257 | impl Default for ConfFile { | ||
258 | fn default() -> Self { | ||
259 | let disabled = vec![]; | ||
260 | Self { disabled } | ||
261 | } | ||
262 | } | ||
263 | |||
264 | impl ConfFile { | ||
265 | pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, ConfigErr> { | ||
266 | let path = path.as_ref(); | ||
267 | let config_file = fs::read_to_string(path).map_err(ConfigErr::InvalidPath)?; | ||
268 | toml::de::from_str(&config_file).map_err(|err| { | ||
269 | let pos = err.line_col(); | ||
270 | let msg = if let Some((line, col)) = pos { | ||
271 | format!("line {}, col {}", line, col) | ||
272 | } else { | ||
273 | "unknown".to_string() | ||
274 | }; | ||
275 | ConfigErr::ConfFileParse(msg) | ||
276 | }) | ||
277 | } | ||
278 | pub fn discover<P: AsRef<Path>>(path: P) -> Result<Self, ConfigErr> { | ||
279 | let cannonical_path = fs::canonicalize(path.as_ref()).map_err(ConfigErr::InvalidPath)?; | ||
280 | for p in cannonical_path.ancestors() { | ||
281 | let statix_toml_path = p.with_file_name("statix.toml"); | ||
282 | if statix_toml_path.exists() { | ||
283 | return Self::from_path(statix_toml_path); | ||
284 | }; | ||
285 | } | ||
286 | Ok(Self::default()) | ||
287 | } | ||
288 | pub fn dump(&self) -> String { | ||
289 | toml::ser::to_string_pretty(&self).unwrap() | ||
290 | } | ||
291 | } | ||
292 | |||
293 | fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { | ||
294 | let parts = src.split(','); | ||
295 | match parts.collect::<Vec<_>>().as_slice() { | ||
296 | [line, col] => { | ||
297 | let do_parse = |val: &str| { | ||
298 | val.parse::<usize>() | ||
299 | .map_err(|_| ConfigErr::InvalidPosition(src.to_owned())) | ||
300 | }; | ||
301 | let l = do_parse(line)?; | ||
302 | let c = do_parse(col)?; | ||
303 | Ok((l, c)) | ||
304 | } | ||
305 | _ => Err(ConfigErr::InvalidPosition(src.to_owned())), | ||
306 | } | ||
307 | } | ||
308 | |||
309 | fn parse_warning_code(src: &str) -> Result<u32, ConfigErr> { | ||
310 | let mut char_stream = src.chars(); | ||
311 | let severity = char_stream | ||
312 | .next() | ||
313 | .ok_or_else(|| ConfigErr::InvalidWarningCode(src.to_owned()))? | ||
314 | .to_ascii_lowercase(); | ||
315 | match severity { | ||
316 | 'w' => char_stream | ||
317 | .collect::<String>() | ||
318 | .parse::<u32>() | ||
319 | .map_err(|_| ConfigErr::InvalidWarningCode(src.to_owned())), | ||
320 | _ => Ok(0), | ||
321 | } | ||
322 | } | ||
323 | |||
324 | fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> { | ||
325 | let mut vfs = ReadOnlyVfs::default(); | ||
326 | for file in files.iter() { | ||
327 | if let Ok(data) = fs::read_to_string(&file) { | ||
328 | let _id = vfs.alloc_file_id(&file); | ||
329 | vfs.set_file_contents(&file, data.as_bytes()); | ||
330 | } else { | ||
331 | println!("{} contains non-utf8 content", file.display()); | ||
332 | }; | ||
333 | } | ||
334 | Ok(vfs) | ||
335 | } | ||
336 | |||
337 | fn lints(conf_path: &PathBuf) -> Result<LintMap, ConfigErr> { | ||
338 | let config_file = ConfFile::discover(conf_path)?; | ||
339 | Ok(utils::lint_map_of( | ||
340 | (&*LINTS) | ||
341 | .into_iter() | ||
342 | .filter(|l| !config_file.disabled.iter().any(|check| check == l.name())) | ||
343 | .cloned() | ||
344 | .collect::<Vec<_>>() | ||
345 | .as_slice(), | ||
346 | )) | ||
347 | } | ||