diff options
Diffstat (limited to 'bin/src/config.rs')
-rw-r--r-- | bin/src/config.rs | 148 |
1 files changed, 104 insertions, 44 deletions
diff --git a/bin/src/config.rs b/bin/src/config.rs index d3944ac..98b41da 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")] | ||
55 | pub conf_path: Option<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.as_ref()) | ||
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")] | ||
105 | pub conf_path: Option<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.as_ref()) | ||
150 | } | ||
128 | } | 151 | } |
129 | 152 | ||
130 | #[derive(Parser, Debug)] | 153 | #[derive(Parser, Debug)] |
@@ -181,6 +204,72 @@ pub struct Explain { | |||
181 | pub target: u32, | 204 | pub target: u32, |
182 | } | 205 | } |
183 | 206 | ||
207 | #[derive(Debug, Copy, Clone)] | ||
208 | pub enum OutFormat { | ||
209 | #[cfg(feature = "json")] | ||
210 | Json, | ||
211 | Errfmt, | ||
212 | StdErr, | ||
213 | } | ||
214 | |||
215 | impl Default for OutFormat { | ||
216 | fn default() -> Self { | ||
217 | OutFormat::StdErr | ||
218 | } | ||
219 | } | ||
220 | |||
221 | impl fmt::Display for OutFormat { | ||
222 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
223 | write!( | ||
224 | f, | ||
225 | "{}", | ||
226 | match self { | ||
227 | #[cfg(feature = "json")] | ||
228 | Self::Json => "json", | ||
229 | Self::Errfmt => "errfmt", | ||
230 | Self::StdErr => "stderr", | ||
231 | } | ||
232 | ) | ||
233 | } | ||
234 | } | ||
235 | |||
236 | impl FromStr for OutFormat { | ||
237 | type Err = &'static str; | ||
238 | |||
239 | fn from_str(value: &str) -> Result<Self, Self::Err> { | ||
240 | match value.to_ascii_lowercase().as_str() { | ||
241 | #[cfg(feature = "json")] | ||
242 | "json" => Ok(Self::Json), | ||
243 | #[cfg(not(feature = "json"))] | ||
244 | "json" => Err("statix was not compiled with the `json` feature flag"), | ||
245 | "errfmt" => Ok(Self::Errfmt), | ||
246 | "stderr" => Ok(Self::StdErr), | ||
247 | _ => Err("unknown output format, try: json, errfmt"), | ||
248 | } | ||
249 | } | ||
250 | } | ||
251 | |||
252 | #[derive(Serialize, Deserialize, Debug)] | ||
253 | pub struct ConfFile { | ||
254 | checks: Vec<String>, | ||
255 | } | ||
256 | |||
257 | impl ConfFile { | ||
258 | pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, ConfigErr> { | ||
259 | let path = path.as_ref(); | ||
260 | let config_file = fs::read_to_string(path).map_err(ConfigErr::InvalidPath)?; | ||
261 | toml::de::from_str(&config_file).map_err(|err| { | ||
262 | let pos = err.line_col(); | ||
263 | let msg = if let Some((line, col)) = pos { | ||
264 | format!("line {}, col {}", line, col) | ||
265 | } else { | ||
266 | "unknown".to_string() | ||
267 | }; | ||
268 | ConfigErr::ConfFileParse(msg) | ||
269 | }) | ||
270 | } | ||
271 | } | ||
272 | |||
184 | fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { | 273 | fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { |
185 | let parts = src.split(','); | 274 | let parts = src.split(','); |
186 | match parts.collect::<Vec<_>>().as_slice() { | 275 | match parts.collect::<Vec<_>>().as_slice() { |
@@ -225,47 +314,18 @@ fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> { | |||
225 | Ok(vfs) | 314 | Ok(vfs) |
226 | } | 315 | } |
227 | 316 | ||
228 | #[derive(Debug, Copy, Clone)] | 317 | fn lints(conf_path: Option<&PathBuf>) -> Result<LintMap, ConfigErr> { |
229 | pub enum OutFormat { | 318 | if let Some(conf_path) = conf_path { |
230 | #[cfg(feature = "json")] | 319 | let config_file = ConfFile::from_path(conf_path)?; |
231 | Json, | 320 | Ok(utils::lint_map_of( |
232 | Errfmt, | 321 | (&*LINTS) |
233 | StdErr, | 322 | .into_iter() |
234 | } | 323 | .filter(|l| config_file.checks.iter().any(|check| check == l.name())) |
235 | 324 | .cloned() | |
236 | impl Default for OutFormat { | 325 | .collect::<Vec<_>>() |
237 | fn default() -> Self { | 326 | .as_slice(), |
238 | OutFormat::StdErr | 327 | )) |
239 | } | 328 | } else { |
240 | } | 329 | Ok(utils::lint_map()) |
241 | |||
242 | impl fmt::Display for OutFormat { | ||
243 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
244 | write!( | ||
245 | f, | ||
246 | "{}", | ||
247 | match self { | ||
248 | #[cfg(feature = "json")] | ||
249 | Self::Json => "json", | ||
250 | Self::Errfmt => "errfmt", | ||
251 | Self::StdErr => "stderr", | ||
252 | } | ||
253 | ) | ||
254 | } | ||
255 | } | ||
256 | |||
257 | impl FromStr for OutFormat { | ||
258 | type Err = &'static str; | ||
259 | |||
260 | fn from_str(value: &str) -> Result<Self, Self::Err> { | ||
261 | match value.to_ascii_lowercase().as_str() { | ||
262 | #[cfg(feature = "json")] | ||
263 | "json" => Ok(Self::Json), | ||
264 | #[cfg(not(feature = "json"))] | ||
265 | "json" => Err("statix was not compiled with the `json` feature flag"), | ||
266 | "errfmt" => Ok(Self::Errfmt), | ||
267 | "stderr" => Ok(Self::StdErr), | ||
268 | _ => Err("unknown output format, try: json, errfmt"), | ||
269 | } | ||
270 | } | 330 | } |
271 | } | 331 | } |