diff options
author | Akshay <[email protected]> | 2021-10-19 11:28:46 +0100 |
---|---|---|
committer | Akshay <[email protected]> | 2021-10-19 11:28:46 +0100 |
commit | 214e3bc4b675b08ce4df76b1373f4548949e67ee (patch) | |
tree | db6584a360d69bc9759e2b8059a013ba9e8db5e1 /bin/src/config.rs | |
parent | 0076b3a37dcca0e11afd05dc98174f646cdb8fa9 (diff) |
fully flesh out CLI
Diffstat (limited to 'bin/src/config.rs')
-rw-r--r-- | bin/src/config.rs | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/bin/src/config.rs b/bin/src/config.rs new file mode 100644 index 0000000..f2cf29d --- /dev/null +++ b/bin/src/config.rs | |||
@@ -0,0 +1,170 @@ | |||
1 | use std::{default::Default, fs, path::PathBuf, str::FromStr}; | ||
2 | |||
3 | use clap::Clap; | ||
4 | use globset::{GlobBuilder, GlobSetBuilder}; | ||
5 | use vfs::ReadOnlyVfs; | ||
6 | |||
7 | use crate::err::ConfigErr; | ||
8 | |||
9 | /// Static analysis and linting for the nix programming language | ||
10 | #[derive(Clap, Debug)] | ||
11 | #[clap(version = "0.1.0", author = "Akshay <[email protected]>")] | ||
12 | pub struct Opts { | ||
13 | /// File or directory to run statix on | ||
14 | #[clap(default_value = ".")] | ||
15 | target: String, | ||
16 | |||
17 | // /// Path to statix config | ||
18 | // #[clap(short, long, default_value = ".statix.toml")] | ||
19 | // config: String, | ||
20 | /// Regex of file patterns to not lint | ||
21 | #[clap(short, long)] | ||
22 | ignore: Vec<String>, | ||
23 | |||
24 | /// Output format. Supported values: json, errfmt | ||
25 | #[clap(short = 'o', long)] | ||
26 | format: Option<OutFormat>, | ||
27 | |||
28 | #[clap(subcommand)] | ||
29 | pub subcmd: Option<SubCommand>, | ||
30 | } | ||
31 | |||
32 | #[derive(Clap, Debug)] | ||
33 | #[clap(version = "0.1.0", author = "Akshay <[email protected]>")] | ||
34 | pub enum SubCommand { | ||
35 | /// Find and fix issues raised by statix | ||
36 | Fix(Fix), | ||
37 | } | ||
38 | |||
39 | #[derive(Clap, Debug)] | ||
40 | pub struct Fix { | ||
41 | /// Do not write to files, display a diff instead | ||
42 | #[clap(short = 'd', long = "dry-run")] | ||
43 | diff_only: bool, | ||
44 | } | ||
45 | |||
46 | #[derive(Debug, Copy, Clone)] | ||
47 | pub enum OutFormat { | ||
48 | Json, | ||
49 | Errfmt, | ||
50 | StdErr, | ||
51 | } | ||
52 | |||
53 | impl Default for OutFormat { | ||
54 | fn default() -> Self { | ||
55 | OutFormat::StdErr | ||
56 | } | ||
57 | } | ||
58 | |||
59 | impl FromStr for OutFormat { | ||
60 | type Err = &'static str; | ||
61 | |||
62 | fn from_str(value: &str) -> Result<Self, Self::Err> { | ||
63 | match value.to_ascii_lowercase().as_str() { | ||
64 | "json" => Ok(Self::Json), | ||
65 | "errfmt" => Ok(Self::Errfmt), | ||
66 | "stderr" => Ok(Self::StdErr), | ||
67 | _ => Err("unknown output format, try: json, errfmt"), | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | #[derive(Debug)] | ||
73 | pub struct LintConfig { | ||
74 | pub files: Vec<PathBuf>, | ||
75 | pub format: OutFormat, | ||
76 | } | ||
77 | |||
78 | impl LintConfig { | ||
79 | pub fn from_opts(opts: Opts) -> Result<Self, ConfigErr> { | ||
80 | let ignores = { | ||
81 | let mut set = GlobSetBuilder::new(); | ||
82 | for pattern in opts.ignore { | ||
83 | let glob = GlobBuilder::new(&pattern).build().map_err(|err| { | ||
84 | ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone()) | ||
85 | })?; | ||
86 | set.add(glob); | ||
87 | } | ||
88 | set.build().map_err(|err| { | ||
89 | ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone()) | ||
90 | }) | ||
91 | }?; | ||
92 | |||
93 | let walker = dirs::Walker::new(opts.target).map_err(ConfigErr::InvalidPath)?; | ||
94 | |||
95 | let files = walker | ||
96 | .filter(|path| matches!(path.extension(), Some(e) if e == "nix")) | ||
97 | .filter(|path| !ignores.is_match(path)) | ||
98 | .collect(); | ||
99 | Ok(Self { | ||
100 | files, | ||
101 | format: opts.format.unwrap_or_default(), | ||
102 | }) | ||
103 | } | ||
104 | |||
105 | pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> { | ||
106 | let mut vfs = ReadOnlyVfs::default(); | ||
107 | for file in self.files.iter() { | ||
108 | let _id = vfs.alloc_file_id(&file); | ||
109 | let data = fs::read_to_string(&file).map_err(ConfigErr::InvalidPath)?; | ||
110 | vfs.set_file_contents(&file, data.as_bytes()); | ||
111 | } | ||
112 | Ok(vfs) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | mod dirs { | ||
117 | use std::{ | ||
118 | fs, | ||
119 | io::{self, Error, ErrorKind}, | ||
120 | path::{Path, PathBuf}, | ||
121 | }; | ||
122 | |||
123 | #[derive(Default, Debug)] | ||
124 | pub struct Walker { | ||
125 | dirs: Vec<PathBuf>, | ||
126 | files: Vec<PathBuf>, | ||
127 | } | ||
128 | |||
129 | impl Walker { | ||
130 | pub fn new<P: AsRef<Path>>(target: P) -> io::Result<Self> { | ||
131 | let target = target.as_ref().to_path_buf(); | ||
132 | if !target.exists() { | ||
133 | Err(Error::new( | ||
134 | ErrorKind::NotFound, | ||
135 | format!("file not found: {}", target.display()), | ||
136 | )) | ||
137 | } else if target.is_dir() { | ||
138 | Ok(Self { | ||
139 | dirs: vec![target], | ||
140 | ..Default::default() | ||
141 | }) | ||
142 | } else { | ||
143 | Ok(Self { | ||
144 | files: vec![target], | ||
145 | ..Default::default() | ||
146 | }) | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | impl Iterator for Walker { | ||
152 | type Item = PathBuf; | ||
153 | fn next(&mut self) -> Option<Self::Item> { | ||
154 | if let Some(dir) = self.dirs.pop() { | ||
155 | if dir.is_dir() { | ||
156 | for entry in fs::read_dir(dir).ok()? { | ||
157 | let entry = entry.ok()?; | ||
158 | let path = entry.path(); | ||
159 | if path.is_dir() { | ||
160 | self.dirs.push(path); | ||
161 | } else if path.is_file() { | ||
162 | self.files.push(path); | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | self.files.pop() | ||
168 | } | ||
169 | } | ||
170 | } | ||