// 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, ) -> Result; } pub fn find_markers( printer: &impl Printer, config: &Config, issues: HashSet, file_scanner: &impl FileScanner, ) -> Result { 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 { 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, ) -> Result { 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) -> 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 } }