use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; use kxio::fs::DirItem; use regex::Regex; fn main() -> Result<()> { println!("Forgejo TODO Checker!"); let config = Config { 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(), prefix_pattern: regex::Regex::new(r"(#|//)\s*(TODO|FIXME)").context("prefix regex")?, issue_pattern: regex::Regex::new(r"( |)(\(|\(#)(?P\d+)(\))") .context("issue regex")?, }; println!("Repo: {}", config.repo); println!("Prefix: {}", config.prefix_pattern); println!("Issues: {}", config.issue_pattern); // TODO: scan files in workdir // TODO: ignore files listed in .rgignore .ignore or .gitignore 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)? // .filter_map(Result::ok) // .map(|e| e.path()) // .for_each(|e| println!("{e:?}")); // Collect open issues: // let limit = 10; // let page = 0; // let api = format!( // "{ci_server_url}/api/v1/repos/{ci_repo}/issues?state=open&limit=${limit}&page=${page}" // ); // TODO: add authentication when provided // let issues = ureq::get(&api).call()?.into_string()?; // TODO: parse issues to get list of open issue numbers // TODO: loop over list of expected issues and drop any where they do exist and are open // TODO: if remaining list is not empty - add all to error list // // TODO: if error list is empty - exit okay // TODO: if error list is not empty - log erros and exit not okay 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, }