aboutsummaryrefslogtreecommitdiff
path: root/bin/src/config.rs
diff options
context:
space:
mode:
Diffstat (limited to 'bin/src/config.rs')
-rw-r--r--bin/src/config.rs168
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 @@
1use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr}; 1use std::{
2 default::Default,
3 fmt, fs,
4 path::{Path, PathBuf},
5 str::FromStr,
6};
2 7
3use crate::{dirs, err::ConfigErr}; 8use crate::{dirs, err::ConfigErr, utils, LintMap};
4 9
5use clap::Parser; 10use clap::Parser;
11use lib::LINTS;
12use serde::{Deserialize, Serialize};
6use vfs::ReadOnlyVfs; 13use 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
184fn 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
200fn 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
215fn 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)]
229pub enum OutFormat { 208pub 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)]
253pub struct ConfFile {
254 disabled: Vec<String>,
255}
256
257impl Default for ConfFile {
258 fn default() -> Self {
259 let disabled = vec![];
260 Self { disabled }
261 }
262}
263
264impl 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
293fn 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
309fn 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
324fn 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
337fn 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}