libscilo/config/instantiated_config/
lints.rs1use std::{cmp::Ordering, path::PathBuf};
4use std::path::Path;
5
6use log::debug;
7use regex::Regex;
8use walkdir::DirEntry;
9
10use crate::{InstantiatedConfig, LintError, fs::directory_walker};
11
12impl InstantiatedConfig {
13 pub fn execute_lints(self) -> Result<(), LintError> {
15 for lint in self.lints.iter() {
16 lint.check(&self)?;
17 }
18 Ok(())
19 }
20
21 pub(crate) fn check_root_dirs(&self) -> Result<(), LintError> {
23 let root_dirs = self.root_dirs.clone();
24 for dir in root_dirs.into_iter().filter_map(|d| d) {
25 dir_exists(dir.as_path())?;
26 }
27
28 Ok(())
29 }
30
31 pub(crate) fn check_root_files(&self) -> Result<(), LintError> {
33 for file in &self.root_files {
34 file_exists(file)?;
35 }
36
37 Ok(())
38 }
39
40 pub(crate) fn check_code_subdir_workflows(&self) -> Result<(), LintError> {
42 if let (Some(code), Some(workflows)) = (&self.root_dirs.code, &self.workflow_names) {
43 debug!("Checking for workflow files in {}", code.display());
44 let workflows: Vec<&str> = workflows.iter().map(String::as_str).collect();
45 let walker = directory_walker(code.as_path(), &self.ignored);
46 for entry in walker {
47 debug!("Checking for workflow file in {}", entry.path().display());
48 let mut subdir_workflow = false;
49
50 for workflow in &workflows {
51 let workflow_path = entry.path().join(workflow);
52 debug!("Checking for {}", workflow_path.display());
53 if workflow_path.exists() {
54 subdir_workflow = true;
55 }
56 }
57
58 if !subdir_workflow {
59 return Err(LintError::SubdirectoryMissingWorkflow(
60 entry.path().to_path_buf(),
61 ));
62 }
63 }
64 }
65
66 Ok(())
67 }
68
69 pub(crate) fn check_code_subdir_readmes(&self) -> Result<(), LintError> {
71 if let (Some(code), Some(readmes)) = (&self.root_dirs.code, &self.readme_names) {
72 let readmes = readmes.iter().map(String::as_str).collect();
74 subdir_readmes(code, readmes, &self.ignored)
75 } else {
76 Ok(())
77 }
78 }
79
80 pub(crate) fn check_data_subdir_readmes(&self) -> Result<(), LintError> {
82 if let (Some(data), Some(readmes)) = (&self.root_dirs.data, &self.readme_names) {
83 let readmes = readmes.iter().map(String::as_str).collect();
85 subdir_readmes(data, readmes, &self.ignored)
86 } else {
87 Ok(())
88 }
89 }
90
91 pub(crate) fn check_code_results_subdir_regex(&self) -> Result<(), LintError> {
94 if let Some(re) = &self.code_results_subdir_regex {
95 if let Some(code) = &self.root_dirs.code {
96 subdir_regex(code, re, &self.ignored)?;
97 }
98 if let Some(results) = &self.root_dirs.results {
99 subdir_regex(results, re, &self.ignored)?;
100 }
101 }
102
103 Ok(())
104 }
105
106 pub(crate) fn check_code_results_subdir_pairing(&self) -> Result<(), LintError> {
110 if let (Some(code), Some(results)) = (&self.root_dirs.code, &self.root_dirs.results) {
111 let mut code_walker = directory_walker(code, &self.ignored);
112 let mut results_walker = directory_walker(results, &self.ignored);
113
114 let mut code_subdir_opt = code_walker.next();
115 let mut results_subdir_opt = results_walker.next();
116
117 loop {
118 match (code_subdir_opt, results_subdir_opt) {
121 (None, None) => break,
122 (Some(subdir), None) => {
123 return missing_results_subdir(code, results, subdir);
124 }
125 (None, Some(subdir)) => {
126 return missing_code_subdir(code, results, subdir);
127 }
128 (Some(code_subdir), Some(results_subdir)) => {
129 match code_subdir.file_name().cmp(results_subdir.file_name()) {
130 Ordering::Equal => {
131 code_subdir_opt = code_walker.next();
133 results_subdir_opt = results_walker.next();
134 }
135 Ordering::Less => {
136 return missing_results_subdir(code, results, code_subdir);
137 }
138 Ordering::Greater => {
139 return missing_code_subdir(code, results, results_subdir);
140 }
141 }
142 }
143 }
144 }
145 }
146
147 Ok(())
148 }
149}
150
151pub fn dir_exists(path: &Path) -> Result<(), LintError> {
155 if path.exists() && path.is_dir() {
156 Ok(())
157 } else {
158 Err(LintError::RequiredDirectoryDoesNotExist(path.to_path_buf()))
159 }
160}
161
162fn file_exists(path: &Path) -> Result<(), LintError> {
166 if path.exists() && path.is_file() {
167 Ok(())
168 } else {
169 Err(LintError::RequiredFileDoesNotExist(path.to_path_buf()))
170 }
171}
172
173fn subdir_readmes(path: &Path, readmes: Vec<&str>, ignored: &Vec<PathBuf>) -> Result<(), LintError> {
175 debug!("Checking for READMEs in {}", path.display());
176 let walker = directory_walker(path, ignored);
177 for entry in walker {
178 debug!("Checking for README in {}", entry.path().display());
179 let mut subdir_readme = false;
180
181 for readme in &readmes {
182 let readme_path = entry.path().join(readme);
183 debug!("Checking for {}", readme_path.display());
184 if readme_path.exists() {
185 subdir_readme = true;
186 break
187 }
188 }
189
190 if !subdir_readme {
191 return Err(LintError::SubdirectoryMissingReadme(
192 entry.path().to_path_buf(),
193 ));
194 }
195 }
196
197 Ok(())
198}
199
200fn subdir_regex(path: &Path, re: &Regex, ignored: &Vec<PathBuf>) -> Result<(), LintError> {
202 let walker = directory_walker(path, ignored);
203 for entry in walker {
204 if let Some(dir_name) = entry.path().file_name() {
205 if let Some(dir_str) = dir_name.to_str() {
206 if !re.is_match_at(dir_str, 0) {
207 return Err(LintError::CodeResultsSubdirectoryRegexMismatch(
208 entry.into_path(),
209 re.to_string(),
210 ));
211 }
212 }
213 }
214 }
215 Ok(())
216}
217
218fn missing_results_subdir(code: &Path, results: &Path, subdir: DirEntry) -> Result<(), LintError> {
220 let existing = code.join(subdir.file_name()).to_path_buf();
221 let missing = results.join(subdir.file_name()).to_path_buf();
222
223 Err(LintError::CodeSubdirectoryMissingInResults {
224 code: existing,
225 results: missing,
226 })
227}
228
229fn missing_code_subdir(code: &Path, results: &Path, subdir: DirEntry) -> Result<(), LintError> {
231 let existing = results.join(subdir.file_name()).to_path_buf();
232 let missing = code.join(subdir.file_name()).to_path_buf();
233
234 Err(LintError::ResultsSubdirectoryMissingInCode {
235 code: missing,
236 results: existing,
237 })
238}