aurora-context 0.1.0

Session history, TODO finder, and code review
Documentation
use std::fs;
use std::io::{BufRead, BufReader};

use aurora_core::{AuroraError, AuroraResult, Pipeline, Value};
use regex::Regex;
use walkdir::WalkDir;

pub fn find_todos(path: &str) -> AuroraResult<Pipeline> {
    let pattern = r"(TODO|FIXME|HACK|XXX|BUG|OPTIMIZE|NOTE)[: ]?(.*)";
    let re = Regex::new(pattern)
        .map_err(|e| AuroraError::ContextError(format!("regex: {e}")))?;

    let headers = vec![
        "file".to_string(),
        "line".to_string(),
        "tag".to_string(),
        "comment".to_string(),
    ];

    let mut rows: Vec<Vec<Value>> = Vec::new();

    for entry in WalkDir::new(path)
        .follow_links(false)
        .into_iter()
        .filter_entry(|e| {
            let name = e.file_name().to_string_lossy();
            name != "target" && name != ".git" && name != "node_modules" && !name.starts_with('.')
        })
        .filter_map(|e| e.ok())
    {
        if !entry.file_type().is_file() {
            continue;
        }

        let file_path = entry.path().to_string_lossy().to_string();

        let file = match fs::File::open(entry.path()) {
            Ok(f) => f,
            Err(_) => continue,
        };

        let reader = BufReader::new(file);
        for (line_no, line) in reader.lines().enumerate() {
            let line = match line {
                Ok(l) => l,
                Err(_) => continue,
            };

            for cap in re.captures_iter(&line) {
                let tag = cap.get(1)
                    .map(|m| m.as_str().to_string())
                    .unwrap_or_default();
                let comment = cap.get(2)
                    .map(|m| m.as_str().trim().to_string())
                    .unwrap_or_default();

                rows.push(vec![
                    Value::String(file_path.clone()),
                    Value::Int((line_no + 1) as i64),
                    Value::String(tag),
                    Value::String(comment),
                ]);
            }
        }
    }

    Ok(Pipeline::table(headers, rows))
}