codesearch 0.1.9

A fast, intelligent CLI tool with multiple search modes (regex, fuzzy, semantic), code analysis, and dead code detection for popular programming languages
Documentation
//! Utility Command Handlers
//!
//! Handles utility commands like duplicates, deadcode, circular, health, index, watch, etc.

use crate::circular;
use crate::deadcode;
use crate::duplicates;
use crate::health;
use crate::index;
use crate::watcher;
use colored::*;
use serde_json;
use std::path::Path;
use std::sync::Arc;

/// Handle the duplicates command
///
/// # Arguments
///
/// * `path` - The directory to analyze
/// * `extensions` - Optional list of file extensions to filter by
/// * `exclude` - Optional list of directories to exclude
/// * `min_lines` - Minimum number of lines for a duplicate block
/// * `similarity` - Minimum similarity threshold (0.0-1.0)
///
/// # Returns
///
/// Result indicating success or error
pub fn handle_duplicates_command(
    path: &Path,
    extensions: Option<Vec<String>>,
    exclude: Option<Vec<String>>,
    min_lines: usize,
    similarity: f64,
) -> Result<(), Box<dyn std::error::Error>> {
    duplicates::detect_duplicates(
        path,
        extensions.as_deref(),
        exclude.as_deref(),
        min_lines,
        similarity,
    )?;
    Ok(())
}

/// Handle the deadcode command
///
/// # Arguments
///
/// * `path` - The directory to analyze
/// * `extensions` - Optional list of file extensions to filter by
/// * `exclude` - Optional list of directories to exclude
///
/// # Returns
///
/// Result indicating success or error
pub fn handle_deadcode_command(
    path: &Path,
    extensions: Option<Vec<String>>,
    exclude: Option<Vec<String>>,
) -> Result<(), Box<dyn std::error::Error>> {
    deadcode::detect_dead_code(path, extensions.as_deref(), exclude.as_deref())?;
    Ok(())
}

/// Handle the circular command
///
/// # Arguments
///
/// * `path` - The directory to analyze
/// * `extensions` - Optional list of file extensions to filter by
/// * `exclude` - Optional list of directories to exclude
///
/// # Returns
///
/// Result indicating success or error
pub fn handle_circular_command(
    path: &Path,
    extensions: Option<Vec<String>>,
    exclude: Option<Vec<String>>,
) -> Result<(), Box<dyn std::error::Error>> {
    circular::detect_circular_calls(path, extensions.as_deref(), exclude.as_deref())?;
    Ok(())
}

/// Handle the health command
///
/// # Arguments
///
/// * `path` - The directory to analyze
/// * `extensions` - Optional list of file extensions to filter by
/// * `exclude` - Optional list of directories to exclude
/// * `format` - Output format (text/json)
/// * `fail_under` - Optional threshold for health score (exit with error if below)
///
/// # Returns
///
/// Result indicating success or error
pub fn handle_health_command(
    path: &Path,
    extensions: Option<Vec<String>>,
    exclude: Option<Vec<String>>,
    format: &str,
    fail_under: Option<u8>,
) -> Result<(), Box<dyn std::error::Error>> {
    let report = health::scan_health(path, extensions.as_deref(), exclude.as_deref())?;

    if format == "json" {
        println!("{}", serde_json::to_string_pretty(&report)?);
    } else {
        health::print_health_report(&report);
    }

    if let Some(threshold) = fail_under {
        if report.score < threshold {
            std::process::exit(1);
        }
    }

    Ok(())
}

/// Handle the index command
///
/// # Arguments
///
/// * `path` - The directory to index
/// * `extensions` - Optional list of file extensions to filter by
/// * `exclude` - Optional list of directories to exclude
/// * `index_file` - Path to save the index file
///
/// # Returns
///
/// Result indicating success or error
pub fn handle_index_command(
    path: &Path,
    extensions: Option<Vec<String>>,
    exclude: Option<Vec<String>>,
    index_file: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("{}", "Building code index...".cyan().bold());
    let index = Arc::new(index::CodeIndex::new(index_file.to_path_buf()));
    index.index_directory(path, extensions.as_deref(), exclude.as_deref())?;
    index.save()?;

    let stats = index.get_stats();
    println!("\n{}", "Index Statistics:".green().bold());
    println!("  Total files: {}", stats.total_files);
    println!("  Total lines: {}", stats.total_lines);
    println!("  Total functions: {}", stats.total_functions);
    println!("  Total classes: {}", stats.total_classes);
    println!(
        "\n{}",
        format!("Index saved to: {}", index_file.display()).green()
    );

    Ok(())
}

/// Handle the watch command
///
/// # Arguments
///
/// * `path` - The directory to watch
/// * `extensions` - Optional list of file extensions to filter by
/// * `index_file` - Path to the index file
///
/// # Returns
///
/// Result indicating success or error
pub fn handle_watch_command(
    path: &Path,
    extensions: Option<Vec<String>>,
    index_file: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("{}", "Starting file watcher...".cyan().bold());
    let index = Arc::new(index::CodeIndex::new(index_file.to_path_buf()));
    watcher::start_watching(path.to_path_buf(), index, extensions)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::tempdir;

    #[test]
    fn test_handle_duplicates_command() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("test.rs"), "fn test() {}").unwrap();

        let result =
            handle_duplicates_command(dir.path(), Some(vec!["rs".to_string()]), None, 5, 0.8);

        assert!(result.is_ok());
    }

    #[test]
    fn test_handle_deadcode_command() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("test.rs"), "fn test() {}").unwrap();

        let result = handle_deadcode_command(dir.path(), Some(vec!["rs".to_string()]), None);

        assert!(result.is_ok());
    }

    #[test]
    fn test_handle_circular_command() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("test.rs"), "fn test() {}").unwrap();

        let result = handle_circular_command(dir.path(), Some(vec!["rs".to_string()]), None);

        assert!(result.is_ok());
    }

    #[test]
    fn test_handle_health_command() {
        let dir = tempdir().unwrap();
        fs::write(dir.path().join("test.rs"), "fn test() {}").unwrap();

        let result =
            handle_health_command(dir.path(), Some(vec!["rs".to_string()]), None, "text", None);

        assert!(result.is_ok());
    }
}