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.rs170
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 @@
1use std::{default::Default, fs, path::PathBuf, str::FromStr};
2
3use clap::Clap;
4use globset::{GlobBuilder, GlobSetBuilder};
5use vfs::ReadOnlyVfs;
6
7use 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]>")]
12pub 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]>")]
34pub enum SubCommand {
35 /// Find and fix issues raised by statix
36 Fix(Fix),
37}
38
39#[derive(Clap, Debug)]
40pub 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)]
47pub enum OutFormat {
48 Json,
49 Errfmt,
50 StdErr,
51}
52
53impl Default for OutFormat {
54 fn default() -> Self {
55 OutFormat::StdErr
56 }
57}
58
59impl 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)]
73pub struct LintConfig {
74 pub files: Vec<PathBuf>,
75 pub format: OutFormat,
76}
77
78impl 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
116mod 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}