cruxlines 0.1.0

Ranks symbol definitions by cross-file references using tree-sitter.
Documentation
use std::path::PathBuf;
use std::process;
use clap::Parser;

mod cli_io;

use cruxlines::{cruxlines, OutputRow};
use cli_io::{gather_inputs, CliIoError};

#[derive(Debug, Parser)]
struct Cli {
    #[arg(short = 'u', long = "references")]
    references: bool,
    #[arg(required = true)]
    files: Vec<PathBuf>,
}

fn main() {
    let cli = Cli::parse();
    let cwd = match std::env::current_dir() {
        Ok(cwd) => cwd,
        Err(err) => {
            eprintln!("cruxlines: failed to read current dir: {err}");
            process::exit(1);
        }
    };
    let inputs = match gather_inputs(cli.files) {
        Ok(inputs) => inputs,
        Err(err) => {
            report_error(err);
            process::exit(1);
        }
    };
    let output_rows = cruxlines(inputs);

    for row in &output_rows {
        print_row(&row, cli.references, &cwd);
    }

}

fn print_row(row: &OutputRow, include_references: bool, cwd: &std::path::Path) {
    if include_references {
        println!(
            "{:.6}\t{:.6}\t{:.6}\t{}\t{}:{}:{}{}",
            row.rank,
            row.local_score,
            row.file_rank,
            row.definition.name,
            display_path(&row.definition.path, cwd),
            row.definition.line,
            row.definition.column,
            format_usage_list(&row.references, cwd)
        );
    } else {
        println!(
            "{:.6}\t{:.6}\t{:.6}\t{}\t{}:{}:{}",
            row.rank,
            row.local_score,
            row.file_rank,
            row.definition.name,
            display_path(&row.definition.path, cwd),
            row.definition.line,
            row.definition.column
        );
    }
}

fn format_usage_list(usages: &[cruxlines::Location], cwd: &std::path::Path) -> String {
    let mut out = String::new();
    for usage in usages {
        out.push('\t');
        out.push_str(&format!(
            "{}:{}:{}",
            display_path(&usage.path, cwd),
            usage.line,
            usage.column
        ));
    }
    out
}

fn display_path(path: &std::path::Path, cwd: &std::path::Path) -> String {
    match path.strip_prefix(cwd) {
        Ok(rel) => rel.display().to_string(),
        Err(_) => path.display().to_string(),
    }
}

fn report_error(err: CliIoError) {
    match err {
        CliIoError::CurrentDir(source) => {
            eprintln!("cruxlines: failed to read current dir: {source}");
        }
        CliIoError::ReadFile { path, source } => {
            eprintln!("cruxlines: failed to read {}: {source}", path.display());
        }
    }
}