use anyhow::Result;
use magellan::output::{output_json, JsonResponse, OutputFormat};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveredDb {
pub path: String,
pub project_name: String,
pub file_count: Option<i64>,
pub symbol_count: Option<i64>,
}
pub fn run_registry_scan(root: PathBuf, output_format: OutputFormat) -> Result<()> {
let discovered = scan_for_databases(&root)?;
match output_format {
OutputFormat::Json | OutputFormat::Pretty => {
let response = JsonResponse::new(discovered, "registry-scan");
output_json(&response, output_format)?;
}
OutputFormat::Human => {
if discovered.is_empty() {
println!("No Magellan databases found under {}", root.display());
} else {
println!(
"Found {} database(s) under {}:\n",
discovered.len(),
root.display()
);
for db in &discovered {
println!(" {}: {}", db.project_name, db.path);
if let (Some(files), Some(symbols)) = (db.file_count, db.symbol_count) {
println!(" {} files, {} symbols", files, symbols);
}
}
}
}
}
Ok(())
}
fn scan_for_databases(root: &Path) -> Result<Vec<DiscoveredDb>> {
let mut databases = Vec::new();
if !root.exists() {
anyhow::bail!("Directory does not exist: {}", root.display());
}
collect_magellan_dbs(root, &mut databases)?;
databases.sort_by(|a, b| a.project_name.cmp(&b.project_name));
Ok(databases)
}
fn collect_magellan_dbs(dir: &Path, databases: &mut Vec<DiscoveredDb>) -> Result<()> {
let magellan_dir = dir.join(".magellan");
if magellan_dir.is_dir() {
if let Ok(entries) = std::fs::read_dir(&magellan_dir) {
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
if path.extension().map_or(false, |ext| ext == "db") {
if let Some(db_info) = discover_database(&path)? {
databases.push(db_info);
}
}
}
}
}
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_dir() {
let name = path.file_name().unwrap_or_default().to_string_lossy();
if name != ".git" && !name.starts_with('.') {
collect_magellan_dbs(&path, databases)?;
}
}
}
}
Ok(())
}
fn discover_database(db_path: &Path) -> Result<Option<DiscoveredDb>> {
let project_name = db_path
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let mut file_count = None;
let mut symbol_count = None;
if let Ok(conn) = rusqlite::Connection::open(db_path) {
if let Ok(count) = conn.query_row(
"SELECT COUNT(*) FROM graph_entities WHERE kind = 'File'",
[],
|row| row.get::<_, i64>(0),
) {
file_count = Some(count);
}
if let Ok(count) = conn.query_row(
"SELECT COUNT(*) FROM graph_entities WHERE kind = 'Symbol'",
[],
|row| row.get::<_, i64>(0),
) {
symbol_count = Some(count);
}
}
Ok(Some(DiscoveredDb {
path: db_path.to_string_lossy().to_string(),
project_name,
file_count,
symbol_count,
}))
}
pub fn run_registry_list(root: PathBuf, output_format: OutputFormat) -> Result<()> {
let discovered = scan_for_databases(&root)?;
match output_format {
OutputFormat::Json | OutputFormat::Pretty => {
let response = JsonResponse::new(discovered, "registry-list");
output_json(&response, output_format)?;
}
OutputFormat::Human => {
if discovered.is_empty() {
println!("No databases found. Run 'magellan registry scan --root <dir>' first.");
} else {
println!(
"{:<30} {:<50} {:>10} {:>10}",
"Project", "Path", "Files", "Symbols"
);
println!("{}", "-".repeat(100));
for db in &discovered {
let files = db.file_count.unwrap_or(-1);
let symbols = db.symbol_count.unwrap_or(-1);
println!(
"{:<30} {:<50} {:>10} {:>10}",
db.project_name, db.path, files, symbols
);
}
}
}
}
Ok(())
}