forgejo-todo-checker/src/scanner.rs
Paul Campbell daf560318d
All checks were successful
Test / build (map[name:nightly]) (push) Successful in 4m37s
Test / build (map[name:stable]) (push) Successful in 5m27s
chore: replace anyhow with color_eyre
2025-01-04 20:38:14 +00:00

109 lines
3 KiB
Rust

//
use std::{collections::HashSet, path::Path};
use crate::{
issues::Issue,
model::{Config, Line, Marker},
printer::Printer,
};
use color_eyre::{eyre::Context as _, Result};
use file_format::FileFormat;
use ignore::Walk;
pub trait FileScanner {
fn scan_file(
&self,
path: &Path,
config: &Config,
printer: &impl Printer,
issues: &HashSet<Issue>,
) -> Result<u32>;
}
pub fn find_markers(
printer: &impl Printer,
config: &Config,
issues: HashSet<Issue>,
file_scanner: &impl FileScanner,
) -> Result<u32> {
let mut errors = 0;
for file in Walk::new(config.fs().base()).flatten() {
let path = file.path();
if is_text_file(config, path).context("is text file")? {
errors += file_scanner
.scan_file(path, config, printer, &issues)
.context("scan file")?
}
}
Ok(errors)
}
fn is_text_file(config: &Config, path: &Path) -> Result<bool> {
Ok(config.fs().path(path).is_file()?
&& FileFormat::from_file(path)?
.media_type()
.starts_with("text/"))
}
pub struct DefaultFileScanner;
impl FileScanner for DefaultFileScanner {
fn scan_file(
&self,
file: &Path,
config: &Config,
printer: &impl Printer,
issues: &HashSet<Issue>,
) -> Result<u32> {
let relative_path = file.strip_prefix(config.fs().base())?.to_path_buf();
let mut errors = 0;
config
.fs()
.file(file)
.reader()?
.to_string() // 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))
.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 {
printer.println(format!(
">> {errors} errors in {}\n",
relative_path.to_string_lossy()
));
}
Ok(errors)
}
}
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
}
}