use anyhow::Result;
use clap::{Parser, Subcommand};
use context_footprint::adapters::doc_scorer::simple::SimpleDocScorer;
use context_footprint::adapters::fs::reader::FileSourceReader;
use context_footprint::adapters::scip::adapter::ScipDataSourceAdapter;
use context_footprint::adapters::size_function::tiktoken::TiktokenSizeFunction;
use context_footprint::cli;
use context_footprint::domain::builder::GraphBuilder;
use context_footprint::domain::ports::SemanticDataSource;
#[derive(Parser)]
#[command(name = "context-footprint")]
#[command(about = "Analyze code coupling via Context Footprint metric", long_about = None)]
struct Cli {
#[arg(default_value = "index.scip")]
scip_path: String,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Compute {
symbol: String,
},
Stats {
#[arg(short, long)]
include_tests: bool,
},
Top {
#[arg(short, long, default_value = "10")]
limit: usize,
#[arg(short = 't', long, default_value = "all")]
node_type: String,
#[arg(short, long)]
include_tests: bool,
},
Search {
pattern: String,
#[arg(short, long)]
with_cf: bool,
#[arg(short, long)]
limit: Option<usize>,
#[arg(short, long)]
include_tests: bool,
},
Context {
symbol: String,
#[arg(short, long)]
show_boundaries: bool,
#[arg(short, long)]
max_tokens: Option<u32>,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
println!("Loading SCIP index from: {}", cli.scip_path);
let data_source = ScipDataSourceAdapter::new(&cli.scip_path);
let source_reader = FileSourceReader::new();
let size_function = Box::new(TiktokenSizeFunction::new());
let doc_scorer = Box::new(SimpleDocScorer::new());
println!("Building context graph...");
let mut semantic_data = data_source.load()?;
if let Some(scip_parent) = std::path::Path::new(&cli.scip_path).parent() {
let project_root = scip_parent.to_string_lossy().to_string();
if !project_root.is_empty() {
semantic_data.project_root = project_root;
}
}
let builder = GraphBuilder::new(size_function, doc_scorer);
let graph = builder.build(semantic_data.clone(), &source_reader)?;
println!("Graph Summary:");
println!(" Nodes: {}", graph.graph.node_count());
println!(" Edges: {}", graph.graph.edge_count());
println!();
match &cli.command {
Commands::Compute { symbol } => {
cli::compute_cf_for_symbol(&graph, symbol)?;
}
Commands::Stats { include_tests } => {
cli::compute_and_display_cf_stats(&graph, *include_tests)?;
}
Commands::Top {
limit,
node_type,
include_tests,
} => {
cli::display_top_cf_nodes(&graph, *limit, node_type, *include_tests)?;
}
Commands::Search {
pattern,
with_cf,
limit,
include_tests,
} => {
cli::search_symbols(&graph, pattern, *with_cf, *limit, *include_tests)?;
}
Commands::Context {
symbol,
show_boundaries,
max_tokens,
} => {
cli::display_context_code(
&graph,
symbol,
*show_boundaries,
&source_reader,
&semantic_data.project_root,
*max_tokens,
)?;
}
}
Ok(())
}