Compare commits

...

2 commits

Author SHA1 Message Date
708bcb0b91 feat: scan for TODO and FIXME markers
Some checks failed
Test / test (push) Failing after 1m25s
2024-09-18 11:10:05 +01:00
eea2e14fb0 feat: collect env into Config and note planned operations
All checks were successful
Test / test (push) Successful in 3s
2024-09-17 19:42:33 +01:00
2 changed files with 172 additions and 17 deletions

View file

@ -6,3 +6,5 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
regex = "1.10" regex = "1.10"
ureq = "2.10"
kxio = "1.2"

View file

@ -1,28 +1,181 @@
use anyhow::Context; use std::path::{Path, PathBuf};
fn main() -> anyhow::Result<()> { use anyhow::{Context, Result};
use kxio::fs::DirItem;
use regex::Regex;
fn main() -> Result<()> {
println!("Forgejo TODO Checker!"); println!("Forgejo TODO Checker!");
let ci_repo = std::env::var("GITHUB_REPOSITORY").context("GITHUB_REPOSITORY")?; let config = Config {
let ci_server_url = std::env::var("GITHUB_SERVER_URL").context("GITHUB_SERVER_URL")?; fs: kxio::fs::new(
let ci_repo_url = format!("{ci_server_url}/{ci_repo}"); 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<ISSUE_NUMBER>\d+)(\))")
.context("issue regex")?,
};
// optional arg for private repos println!("Repo: {}", config.repo);
let repo_token = std::env::var("REPO_TOKEN").ok(); println!("Prefix: {}", config.prefix_pattern);
println!("Issues: {}", config.issue_pattern);
// Regex to match a todo or fixme comment // TODO: scan files in workdir
let prefix = regex::Regex::new(r"(#|//)\s*(TODO|FIXME)").context("prefix regex")?; // TODO: ignore files listed in .rgignore .ignore or .gitignore
// Regex to match the rest of the line to find an issue number
let issue =
regex::Regex::new(r"( |)(\(|\(#)(?P<ISSUE_NUMBER>\d+)(\))").context("issue regex")?;
println!("[ci_repo:{ci_repo}][ci_repo_url:{ci_repo_url}][repo_token:{repo_token:?}][prefix:{prefix}][issue:{issue}]"); 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 // list files in current directory to get a feel for what we have access to
std::fs::read_dir(".")? // std::fs::read_dir(workdir)?
.filter_map(Result::ok) // .filter_map(Result::ok)
.map(|e| e.path()) // .map(|e| e.path())
.for_each(|e| println!("{e:?}")); // .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(()) Ok(())
} }
struct Config {
fs: kxio::fs::FileSystem,
repo: String,
server: String,
auth_token: Option<String>,
prefix_pattern: Regex,
issue_pattern: Regex,
}
#[derive(Debug, Default)]
struct FoundMarkers {
markers: Vec<Marker>,
issue_markers: Vec<IssueMarker>,
}
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::<usize>().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<Marker> {
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,
}