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.rs213
1 files changed, 119 insertions, 94 deletions
diff --git a/bin/src/config.rs b/bin/src/config.rs
index 6d7bc49..202ff6c 100644
--- a/bin/src/config.rs
+++ b/bin/src/config.rs
@@ -15,118 +15,74 @@ use crate::err::ConfigErr;
15#[derive(Clap, Debug)] 15#[derive(Clap, Debug)]
16#[clap(version = "0.1.0", author = "Akshay <[email protected]>")] 16#[clap(version = "0.1.0", author = "Akshay <[email protected]>")]
17pub struct Opts { 17pub struct Opts {
18 /// File or directory to run statix on 18 #[clap(subcommand)]
19 #[clap(default_value = ".")] 19 pub cmd: SubCommand,
20 pub target: String, 20}
21
22#[derive(Clap, Debug)]
23pub enum SubCommand {
24 /// Lints and suggestions for the nix programming language
25 Check(Check),
26 /// Find and fix issues raised by statix-check
27 Fix(Fix),
28 /// Fix exactly one issue at provided position
29 Single(Single),
30}
31
32#[derive(Clap, Debug)]
33pub struct Check {
34 /// File or directory to run check on
35 #[clap(default_value = ".", parse(from_os_str))]
36 target: PathBuf,
21 37
22 /// Globs of file patterns to skip 38 /// Globs of file patterns to skip
23 #[clap(short, long)] 39 #[clap(short, long)]
24 pub ignore: Vec<String>, 40 ignore: Vec<String>,
25 41
26 /// Output format. 42 /// Output format.
27 /// Supported values: errfmt, json (on feature flag only) 43 /// Supported values: errfmt, json (on feature flag only)
28 #[clap(short = 'o', long)] 44 #[clap(short = 'o', long, default_value = "OutFormat::StdErr")]
29 format: Option<OutFormat>, 45 pub format: OutFormat,
30
31 /// Find and fix issues raised by statix
32 #[clap(short = 'f', long)]
33 pub fix: bool,
34
35 /// Do not fix files in place, display a diff instead
36 #[clap(short = 'd', long = "dry-run")]
37 diff_only: bool,
38}
39
40
41#[derive(Debug, Copy, Clone)]
42pub enum OutFormat {
43 #[cfg(feature = "json")]
44 Json,
45 Errfmt,
46 StdErr,
47} 46}
48 47
49impl Default for OutFormat { 48impl Check {
50 fn default() -> Self { 49 pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> {
51 OutFormat::StdErr 50 let files = walk_with_ignores(&self.ignore, &self.target)?;
51 vfs(files)
52 } 52 }
53} 53}
54 54
55impl FromStr for OutFormat { 55#[derive(Clap, Debug)]
56 type Err = &'static str; 56pub struct Fix {
57 /// File or directory to run fix on
58 #[clap(default_value = ".", parse(from_os_str))]
59 target: PathBuf,
57 60
58 fn from_str(value: &str) -> Result<Self, Self::Err> { 61 /// Globs of file patterns to skip
59 match value.to_ascii_lowercase().as_str() { 62 #[clap(short, long)]
60 #[cfg(feature = "json")] "json" => Ok(Self::Json), 63 ignore: Vec<String>,
61 "errfmt" => Ok(Self::Errfmt),
62 "stderr" => Ok(Self::StdErr),
63 "json" => Err("statix was not compiled with the `json` feature flag"),
64 _ => Err("unknown output format, try: json, errfmt"),
65 }
66 }
67}
68 64
69#[derive(Debug)] 65 /// Do not fix files in place, display a diff instead
70pub struct LintConfig { 66 #[clap(short, long = "dry-run")]
71 pub files: Vec<PathBuf>, 67 pub diff_only: bool,
72 pub format: OutFormat,
73} 68}
74 69
75impl LintConfig { 70impl Fix {
76 pub fn from_opts(opts: Opts) -> Result<Self, ConfigErr> {
77 let ignores = build_ignore_set(&opts.ignore).map_err(|err| {
78 ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone())
79 })?;
80
81 let files = walk_nix_files(&opts.target)?
82 .filter(|path| !ignores.is_match(path))
83 .collect();
84
85 Ok(Self {
86 files,
87 format: opts.format.unwrap_or_default(),
88 })
89 }
90
91 pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> { 71 pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> {
92 let mut vfs = ReadOnlyVfs::default(); 72 let files = walk_with_ignores(&self.ignore, &self.target)?;
93 for file in self.files.iter() { 73 vfs(files)
94 let _id = vfs.alloc_file_id(&file);
95 let data = fs::read_to_string(&file).map_err(ConfigErr::InvalidPath)?;
96 vfs.set_file_contents(&file, data.as_bytes());
97 }
98 Ok(vfs)
99 } 74 }
100} 75}
101 76
102pub struct FixConfig { 77#[derive(Clap, Debug)]
103 pub files: Vec<PathBuf>, 78pub struct Single {
104 pub diff_only: bool, 79 /// File to run single-fix on
105} 80 #[clap(default_value = ".", parse(from_os_str))]
106 81 pub target: PathBuf,
107impl FixConfig { 82
108 pub fn from_opts(opts: Opts) -> Result<Self, ConfigErr> { 83 /// Position to attempt a fix at
109 let ignores = build_ignore_set(&opts.ignore).map_err(|err| { 84 #[clap(short, long, parse(try_from_str = parse_line_col))]
110 ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone()) 85 pub position: (usize, usize),
111 })?;
112
113 let files = walk_nix_files(&opts.target)?
114 .filter(|path| !ignores.is_match(path))
115 .collect();
116
117 let diff_only = opts.diff_only;
118 Ok(Self { files, diff_only })
119 }
120
121 pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> {
122 let mut vfs = ReadOnlyVfs::default();
123 for file in self.files.iter() {
124 let _id = vfs.alloc_file_id(&file);
125 let data = fs::read_to_string(&file).map_err(ConfigErr::InvalidPath)?;
126 vfs.set_file_contents(&file, data.as_bytes());
127 }
128 Ok(vfs)
129 }
130} 86}
131 87
132mod dirs { 88mod dirs {
@@ -185,7 +141,23 @@ mod dirs {
185 } 141 }
186} 142}
187 143
188fn build_ignore_set(ignores: &Vec<String>) -> Result<GlobSet, GlobError> { 144fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> {
145 let parts = src.split(",");
146 match parts.collect::<Vec<_>>().as_slice() {
147 [line, col] => {
148 let l = line
149 .parse::<usize>()
150 .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?;
151 let c = col
152 .parse::<usize>()
153 .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?;
154 Ok((l, c))
155 }
156 _ => Err(ConfigErr::InvalidPosition(src.to_owned())),
157 }
158}
159
160fn build_ignore_set(ignores: &[String]) -> Result<GlobSet, GlobError> {
189 let mut set = GlobSetBuilder::new(); 161 let mut set = GlobSetBuilder::new();
190 for pattern in ignores { 162 for pattern in ignores {
191 let glob = GlobBuilder::new(&pattern).build()?; 163 let glob = GlobBuilder::new(&pattern).build()?;
@@ -198,3 +170,56 @@ fn walk_nix_files<P: AsRef<Path>>(target: P) -> Result<impl Iterator<Item = Path
198 let walker = dirs::Walker::new(target)?; 170 let walker = dirs::Walker::new(target)?;
199 Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix"))) 171 Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix")))
200} 172}
173
174fn walk_with_ignores<P: AsRef<Path>>(
175 ignores: &[String],
176 target: P,
177) -> Result<Vec<PathBuf>, ConfigErr> {
178 let ignores = build_ignore_set(ignores).map_err(|err| {
179 ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone())
180 })?;
181
182 Ok(walk_nix_files(&target)?
183 .filter(|path| !ignores.is_match(path))
184 .collect())
185}
186
187fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> {
188 let mut vfs = ReadOnlyVfs::default();
189 for file in files.iter() {
190 let _id = vfs.alloc_file_id(&file);
191 let data = fs::read_to_string(&file).map_err(ConfigErr::InvalidPath)?;
192 vfs.set_file_contents(&file, data.as_bytes());
193 }
194 Ok(vfs)
195
196}
197
198#[derive(Debug, Copy, Clone)]
199pub enum OutFormat {
200 #[cfg(feature = "json")]
201 Json,
202 Errfmt,
203 StdErr,
204}
205
206impl Default for OutFormat {
207 fn default() -> Self {
208 OutFormat::StdErr
209 }
210}
211
212impl FromStr for OutFormat {
213 type Err = &'static str;
214
215 fn from_str(value: &str) -> Result<Self, Self::Err> {
216 match value.to_ascii_lowercase().as_str() {
217 #[cfg(feature = "json")]
218 "json" => Ok(Self::Json),
219 "errfmt" => Ok(Self::Errfmt),
220 "stderr" => Ok(Self::StdErr),
221 "json" => Err("statix was not compiled with the `json` feature flag"),
222 _ => Err("unknown output format, try: json, errfmt"),
223 }
224 }
225}