use crate::graph::GraphStore;
use anyhow::{Context, Result};
use console::style;
use serde_json;
use std::path::Path;
pub fn run(path: &Path, query: &str, format: &str) -> Result<()> {
let repo_path = path
.canonicalize()
.with_context(|| format!("Path does not exist: {}", path.display()))?;
let db_path = crate::cache::get_graph_db_path(&repo_path);
if !db_path.exists() {
anyhow::bail!(
"No analysis found. Run {} first.",
style("repotoire analyze").cyan()
);
}
let graph = GraphStore::new(&db_path)
.with_context(|| "Failed to open graph database")?;
let json_output = format == "json";
let query_lower = query.to_lowercase();
if query_lower.contains("function") {
let functions = graph.get_functions();
if json_output {
let json: Vec<_> = functions.iter().map(|f| serde_json::json!({
"name": f.name,
"qualified_name": f.qualified_name,
"file": f.file_path,
"line_start": f.line_start,
"line_end": f.line_end,
})).collect();
println!("{}", serde_json::to_string_pretty(&json)?);
} else {
println!("\n{} Functions ({})\n", style("📊").bold(), functions.len());
for func in functions.iter().take(50) {
println!(" {} ({}:{})",
style(&func.qualified_name).cyan(),
&func.file_path,
func.line_start
);
}
if functions.len() > 50 {
println!(" ... and {} more", functions.len() - 50);
}
}
} else if query_lower.contains("class") {
let classes = graph.get_classes();
if json_output {
let json: Vec<_> = classes.iter().map(|c| serde_json::json!({
"name": c.name,
"qualified_name": c.qualified_name,
"file": c.file_path,
"line_start": c.line_start,
"line_end": c.line_end,
})).collect();
println!("{}", serde_json::to_string_pretty(&json)?);
} else {
println!("\n{} Classes ({})\n", style("📊").bold(), classes.len());
for class in classes.iter().take(50) {
println!(" {} ({}:{})",
style(&class.qualified_name).cyan(),
&class.file_path,
class.line_start
);
}
if classes.len() > 50 {
println!(" ... and {} more", classes.len() - 50);
}
}
} else if query_lower.contains("file") {
let files = graph.get_files();
if json_output {
let json: Vec<_> = files.iter().map(|f| serde_json::json!({
"path": f.file_path,
})).collect();
println!("{}", serde_json::to_string_pretty(&json)?);
} else {
println!("\n{} Files ({})\n", style("📊").bold(), files.len());
for file in files.iter().take(50) {
println!(" {}", style(&file.file_path).cyan());
}
if files.len() > 50 {
println!(" ... and {} more", files.len() - 50);
}
}
} else if query_lower.contains("call") {
let calls = graph.get_calls();
if json_output {
let json: Vec<_> = calls.iter().map(|(from, to)| serde_json::json!({
"from": from,
"to": to,
})).collect();
println!("{}", serde_json::to_string_pretty(&json)?);
} else {
println!("\n{} Call Edges ({})\n", style("📊").bold(), calls.len());
for (from, to) in calls.iter().take(50) {
println!(" {} -> {}", style(from).cyan(), style(to).green());
}
if calls.len() > 50 {
println!(" ... and {} more", calls.len() - 50);
}
}
} else if query_lower.contains("import") {
let imports = graph.get_imports();
if json_output {
let json: Vec<_> = imports.iter().map(|(from, to)| serde_json::json!({
"from": from,
"to": to,
})).collect();
println!("{}", serde_json::to_string_pretty(&json)?);
} else {
println!("\n{} Import Edges ({})\n", style("📊").bold(), imports.len());
for (from, to) in imports.iter().take(50) {
println!(" {} -> {}", style(from).cyan(), style(to).green());
}
if imports.len() > 50 {
println!(" ... and {} more", imports.len() - 50);
}
}
} else if query_lower == "stats" {
return stats(path);
} else {
println!("{}", style("Supported queries:").bold());
println!(" - functions: List all functions");
println!(" - classes: List all classes");
println!(" - files: List all files");
println!(" - calls: List call edges");
println!(" - imports: List import edges");
println!(" - stats: Show graph statistics");
println!("\nNote: Cypher queries are not supported. You can also run 'repotoire stats' directly.");
}
Ok(())
}
pub fn stats(path: &Path) -> Result<()> {
let repo_path = path
.canonicalize()
.with_context(|| format!("Path does not exist: {}", path.display()))?;
let stats_path = crate::cache::get_graph_stats_path(&repo_path);
if stats_path.exists() {
let stats_json = std::fs::read_to_string(&stats_path)
.with_context(|| "Failed to read graph stats")?;
let stats: serde_json::Value = serde_json::from_str(&stats_json)
.with_context(|| "Failed to parse graph stats")?;
println!("\n{} Graph Statistics\n", style("📊").bold());
println!(" {}: {}", style("Files").cyan(),
style(stats["total_files"].as_u64().unwrap_or(0)).bold());
println!(" {}: {}", style("Functions").cyan(),
style(stats["total_functions"].as_u64().unwrap_or(0)).bold());
println!(" {}: {}", style("Classes").cyan(),
style(stats["total_classes"].as_u64().unwrap_or(0)).bold());
let calls = stats["calls"].as_u64().unwrap_or(0);
let imports = stats["imports"].as_u64().unwrap_or(0);
let total_edges = stats["total_edges"].as_u64().unwrap_or(0);
let contains = total_edges.saturating_sub(calls + imports);
println!();
println!(" {} edges: {}", style("CALLS").cyan(), style(calls).bold());
println!(" {} edges: {}", style("IMPORTS").cyan(), style(imports).bold());
println!(" {} edges: {}", style("CONTAINS").cyan(), style(contains).bold());
println!();
println!(" Total nodes: {}", style(stats["total_nodes"].as_u64().unwrap_or(0)).bold());
println!(" Total edges: {}", style(total_edges).bold());
return Ok(());
}
let db_path = crate::cache::get_graph_db_path(&repo_path);
if !db_path.exists() {
anyhow::bail!(
"No analysis found. Run {} first.",
style("repotoire analyze").cyan()
);
}
let graph = GraphStore::new(&db_path)
.with_context(|| "Failed to open graph database")?;
println!("\n{} Graph Statistics\n", style("📊").bold());
let stats = graph.stats();
println!(" {}: {}", style("Files").cyan(), style(stats.get("total_files").copied().unwrap_or(0)).bold());
println!(" {}: {}", style("Functions").cyan(), style(stats.get("total_functions").copied().unwrap_or(0)).bold());
println!(" {}: {}", style("Classes").cyan(), style(stats.get("total_classes").copied().unwrap_or(0)).bold());
let calls = graph.get_calls().len();
let imports = graph.get_imports().len();
let contains = graph.edge_count() - calls - imports;
println!();
println!(" {} edges: {}", style("CALLS").cyan(), style(calls).bold());
println!(" {} edges: {}", style("IMPORTS").cyan(), style(imports).bold());
println!(" {} edges: {}", style("CONTAINS").cyan(), style(contains).bold());
println!();
println!(" Total nodes: {}", style(graph.node_count()).bold());
println!(" Total edges: {}", style(graph.edge_count()).bold());
Ok(())
}