nolo 0.1.0

A CLI tool for discovering and analyzing `TODO` comments across codebases.
Documentation
mod analytics;
mod cli;
pub mod comment;
mod config;
mod discover;
mod error;
mod log;
mod report;

use clap::Parser;

use crate::cli::{CliArgs, CommandType};
use crate::config::Config;
use crate::discover::discover;
use tracing::{debug, trace};

fn main() {
    let args = CliArgs::parse();
    log::init().expect("Failed to initialize logging");

    let config = match Config::load(args.config.as_deref()) {
        Ok(config) => config,
        Err(e) => {
            eprintln!("Error loading configuration: {}", e);
            std::process::exit(1);
        }
    };

    let (command_type, scan_path) = args.get_command_and_path(&config);

    match command_type {
        CommandType::Scan => {
            trace!("Starting nolo TODO comment scan");
            run_scan(&scan_path, &config);
        }
        CommandType::Report => {
            trace!("Starting nolo TODO analytics report");
            let format = args.get_report_format(&config);
            let display_limit = args.get_display_limit(&config);
            run_report(&scan_path, format, display_limit, &config);
        }
    }
}

fn run_scan(scan_path: &std::path::Path, config: &Config) {
    let paths = match discover(scan_path) {
        Ok(paths) => paths,
        Err(e) => {
            eprintln!("Error: {}", e);
            std::process::exit(1);
        }
    };

    let keywords = config.get_keywords();

    for path in paths {
        debug!("Reading content from: {:?}", path);

        let todo_comments =
            comment::extract_todo_comments_with_language_detection(path.clone(), &keywords);
        if !todo_comments.is_empty() {
            debug!("Found {} TODO comments in {:?}", todo_comments.len(), path);
            for todo in todo_comments {
                let relative_path = report::get_relative_path(&path, scan_path);
                println!("{}:{}: {}", relative_path, todo.line, todo.text);
            }
        }
    }
}

fn run_report(
    scan_path: &std::path::Path,
    format: crate::cli::ReportFormat,
    display_limit: Option<usize>,
    config: &Config,
) {
    let mut file_todos = Vec::new();
    let mut total_files = 0;

    let paths = match discover(scan_path) {
        Ok(paths) => paths,
        Err(e) => {
            eprintln!("Error: {}", e);
            std::process::exit(1);
        }
    };

    let keywords = config.get_keywords();

    for path in paths {
        debug!("Reading content from: {:?}", path);
        total_files += 1;

        let todo_comments =
            comment::extract_todo_comments_with_language_detection(path.clone(), &keywords);
        file_todos.push((path, todo_comments));
    }

    let analytics = analytics::analyze_todos(
        file_todos,
        total_files,
        scan_path.to_path_buf(),
        display_limit,
        &keywords,
    );

    match format {
        crate::cli::ReportFormat::Table => {
            report::print_table_report(&analytics);
        }
        crate::cli::ReportFormat::Json => {
            print_json_report(&analytics);
        }
    }
}

fn print_json_report(analytics: &analytics::TodoAnalytics) {
    match serde_json::to_string_pretty(analytics) {
        Ok(json) => println!("{}", json),
        Err(e) => {
            eprintln!("Error serializing to JSON: {}", e);
            std::process::exit(1);
        }
    }
}