2024-09-19 19:12:41 +01:00
|
|
|
//
|
2024-09-20 14:36:01 +01:00
|
|
|
use std::{collections::HashSet, path::Path};
|
2024-09-19 19:12:41 +01:00
|
|
|
|
2024-09-20 14:36:01 +01:00
|
|
|
use crate::{
|
|
|
|
issues::Issue,
|
2024-09-21 18:44:40 +01:00
|
|
|
model::{Config, Line, Marker},
|
|
|
|
printer::Printer,
|
2024-09-20 14:36:01 +01:00
|
|
|
};
|
2024-09-19 19:12:41 +01:00
|
|
|
use anyhow::Result;
|
2024-09-21 11:35:56 +01:00
|
|
|
use file_format::FileFormat;
|
2024-09-19 20:18:53 +01:00
|
|
|
use ignore::Walk;
|
2024-09-19 19:12:41 +01:00
|
|
|
|
2024-09-21 11:35:56 +01:00
|
|
|
pub trait FileScanner {
|
|
|
|
fn scan_file(
|
|
|
|
&self,
|
|
|
|
path: &Path,
|
|
|
|
config: &Config,
|
2024-09-21 18:44:40 +01:00
|
|
|
printer: &impl Printer,
|
2024-09-21 11:35:56 +01:00
|
|
|
issues: &HashSet<Issue>,
|
2024-09-21 18:44:40 +01:00
|
|
|
) -> Result<u32>;
|
2024-09-21 11:35:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn find_markers(
|
2024-09-21 18:44:40 +01:00
|
|
|
printer: &impl Printer,
|
2024-09-21 11:35:56 +01:00
|
|
|
config: &Config,
|
|
|
|
issues: HashSet<Issue>,
|
|
|
|
file_scanner: &impl FileScanner,
|
2024-09-21 18:44:40 +01:00
|
|
|
) -> Result<u32, anyhow::Error> {
|
|
|
|
let mut errors = 0;
|
2024-09-19 20:18:53 +01:00
|
|
|
for file in Walk::new(config.fs().base()).flatten() {
|
2024-09-19 19:12:41 +01:00
|
|
|
let path = file.path();
|
2024-09-21 11:35:56 +01:00
|
|
|
if is_text_file(config, path)? {
|
2024-09-21 18:44:40 +01:00
|
|
|
errors += file_scanner.scan_file(path, config, printer, &issues)?
|
2024-09-19 19:12:41 +01:00
|
|
|
}
|
|
|
|
}
|
2024-09-21 18:44:40 +01:00
|
|
|
Ok(errors)
|
2024-09-19 19:12:41 +01:00
|
|
|
}
|
|
|
|
|
2024-09-21 11:35:56 +01:00
|
|
|
fn is_text_file(config: &Config, path: &Path) -> Result<bool> {
|
|
|
|
Ok(config.fs().path_is_file(path)?
|
|
|
|
&& FileFormat::from_file(path)?
|
|
|
|
.media_type()
|
|
|
|
.starts_with("text/"))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct DefaultFileScanner;
|
|
|
|
impl FileScanner for DefaultFileScanner {
|
|
|
|
fn scan_file(
|
|
|
|
&self,
|
|
|
|
file: &Path,
|
|
|
|
config: &Config,
|
2024-09-21 18:44:40 +01:00
|
|
|
printer: &impl Printer,
|
2024-09-21 11:35:56 +01:00
|
|
|
issues: &HashSet<Issue>,
|
2024-09-21 18:44:40 +01:00
|
|
|
) -> Result<u32> {
|
2024-09-21 11:35:56 +01:00
|
|
|
let relative_path = file.strip_prefix(config.fs().base())?.to_path_buf();
|
2024-09-21 18:44:40 +01:00
|
|
|
let mut errors = 0;
|
2024-09-21 11:35:56 +01:00
|
|
|
config
|
|
|
|
.fs()
|
|
|
|
.file_read_to_string(file)? // tarpaulin uncovered okay
|
|
|
|
.lines()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(n, line)| {
|
|
|
|
Line::builder()
|
|
|
|
.relative_path(relative_path.clone())
|
|
|
|
.num(n + 1) // line numbers are not 0-based, but enumerate is
|
|
|
|
.value(line.to_owned())
|
|
|
|
.build()
|
|
|
|
})
|
|
|
|
.filter_map(|line| line.into_marker().ok())
|
|
|
|
.filter(|marker| !matches!(marker, Marker::Unmarked))
|
|
|
|
.map(|marker| has_open_issue(marker, issues))
|
2024-09-21 18:44:40 +01:00
|
|
|
.for_each(|marker| match marker {
|
|
|
|
Marker::Invalid(_) => {
|
|
|
|
errors += 1;
|
|
|
|
printer.println(marker.to_string());
|
|
|
|
}
|
|
|
|
Marker::Closed(_, _) => {
|
|
|
|
errors += 1;
|
|
|
|
printer.println(marker.to_string());
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
});
|
|
|
|
if errors > 0 {
|
2024-09-22 09:07:16 +01:00
|
|
|
printer.println(format!(
|
|
|
|
">> {errors} errors in {}\n",
|
|
|
|
relative_path.to_string_lossy()
|
|
|
|
));
|
2024-09-21 18:44:40 +01:00
|
|
|
}
|
2024-09-19 19:12:41 +01:00
|
|
|
|
2024-09-21 18:44:40 +01:00
|
|
|
Ok(errors)
|
2024-09-21 11:35:56 +01:00
|
|
|
}
|
2024-09-19 19:12:41 +01:00
|
|
|
}
|
2024-09-20 14:36:01 +01:00
|
|
|
|
|
|
|
fn has_open_issue(marker: Marker, issues: &HashSet<Issue>) -> Marker {
|
|
|
|
if let Marker::Valid(_, ref issue) = marker {
|
|
|
|
let has_open_issue = issues.contains(issue);
|
|
|
|
if has_open_issue {
|
|
|
|
marker
|
|
|
|
} else {
|
|
|
|
marker.into_closed()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
marker
|
|
|
|
}
|
|
|
|
}
|