From 708bcb0b911431bf7fbdde5afc5296c7e196c19d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 18 Sep 2024 07:44:34 +0100 Subject: [PATCH] feat: scan for TODO and FIXME markers --- Cargo.toml | 1 + src/main.rs | 153 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 137 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 844f56c..08ea434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" anyhow = "1.0" regex = "1.10" ureq = "2.10" +kxio = "1.2" diff --git a/src/main.rs b/src/main.rs index c5a2ca7..94f3a39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,18 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -use anyhow::Context; +use anyhow::{Context, Result}; +use kxio::fs::DirItem; use regex::Regex; -struct Config { - workdir: PathBuf, - repo: String, - server: String, - auth_token: Option, - prefix_pattern: Regex, - issue_pattern: Regex, -} - -fn main() -> anyhow::Result<()> { +fn main() -> Result<()> { println!("Forgejo TODO Checker!"); let config = Config { - workdir: std::env::var("GITHUB_WORKSPACE") - .context("GITHUB_WORKSPACE")? - .into(), + fs: kxio::fs::new( + std::env::var("GITHUB_WORKSPACE") + .context("GITHUB_WORKSPACE")? + .into(), + ), repo: std::env::var("GITHUB_REPOSITORY").context("GITHUB_REPOSITORY")?, server: std::env::var("GITHUB_SERVER_URL").context("GITHUB_SERVER_URL")?, auth_token: std::env::var("REPO_TOKEN").ok(), @@ -33,8 +27,12 @@ fn main() -> anyhow::Result<()> { // TODO: scan files in workdir // TODO: ignore files listed in .rgignore .ignore or .gitignore - // TODO: scan for malformed TODO and FIXME comments (e.g. no issue number) - add to error list - // TODO: build list of expected open issues with file locations where found + + let mut found_markers = FoundMarkers::default(); + + scan_files(&config, &mut found_markers)?; + + println!("{found_markers:?}"); // list files in current directory to get a feel for what we have access to // std::fs::read_dir(workdir)? @@ -60,3 +58,124 @@ fn main() -> anyhow::Result<()> { Ok(()) } + +struct Config { + fs: kxio::fs::FileSystem, + repo: String, + server: String, + auth_token: Option, + prefix_pattern: Regex, + issue_pattern: Regex, +} + +#[derive(Debug, Default)] +struct FoundMarkers { + markers: Vec, + issue_markers: Vec, +} +impl FoundMarkers { + fn add_marker(&mut self, marker: Marker) { + self.markers.push(marker); + } + fn add_issue_marker(&mut self, issue_marker: IssueMarker) { + self.issue_markers.push(issue_marker); + } +} + +fn scan_files(config: &Config, found_markers: &mut FoundMarkers) -> Result<()> { + scan_dir(config.fs.base(), config, found_markers) +} + +fn scan_dir(dir: &Path, config: &Config, found_markers: &mut FoundMarkers) -> Result<()> { + let read_dir = config.fs.dir_read(dir)?; + for entry in read_dir { + match entry? { + DirItem::File(file) => scan_file(&file, config, found_markers), + DirItem::Dir(dir) => scan_dir(&dir, config, found_markers), + DirItem::SymLink(_) | DirItem::Fifo(_) | DirItem::Unsupported(_) => Ok(()), + }?; + } + Ok(()) +} + +fn scan_file(path: &Path, config: &Config, found_markers: &mut FoundMarkers) -> Result<()> { + config + .fs + .file_read_to_string(path)? + .lines() + .enumerate() + .filter_map(|(n, line)| prefix_match(n, line, path, config)) + .for_each(|marker| { + if let Some(issue) = config + .issue_pattern + .find(&marker.line.value) + .map(|issue| issue.as_str()) + .and_then(|issue| issue.parse::().ok()) + { + found_markers.add_issue_marker(marker.into_issue_marker(issue)); + } else { + found_markers.add_marker(marker); + } + }); + Ok(()) +} + +fn prefix_match(num: usize, line: &str, path: &Path, config: &Config) -> Option { + let find = config.prefix_pattern.find(line)?; + let marker_type = match find.as_str() { + "TODO" => Some(MarkerType::Todo), + "FIXME" => Some(MarkerType::Fixme), + _ => None, + }?; + Some(Marker { + marker_type, + file: path.to_path_buf(), + line: Line { + num, + value: line.to_owned(), + }, + }) +} + +/// What type of comment +#[derive(Debug)] +enum MarkerType { + Todo, + Fixme, +} + +#[derive(Debug)] +struct Line { + num: usize, + value: String, +} + +/// Represents a TODO or FIXME comment that doesn't have any issue number +#[derive(Debug)] +struct Marker { + /// What type of marker + marker_type: MarkerType, + + /// Path of the file + file: PathBuf, + + /// The line from the file + line: Line, +} +impl Marker { + fn into_issue_marker(self, issue: usize) -> IssueMarker { + IssueMarker { + marker: self, + issue, + } + } +} + +#[derive(Debug)] +struct IssueMarker { + /// The marker + marker: Marker, + + /// The issue number + issue: usize, +}