use anyhow::Result;
use magellan::graph::{ExecutionPath, PathEnumerationResult, PathStatistics, SymbolInfo};
use magellan::output::{output_json, JsonResponse, OutputFormat};
use magellan::CodeGraph;
use std::path::PathBuf;
pub fn run_paths(
db_path: PathBuf,
start_symbol_id: String,
end_symbol_id: Option<String>,
max_depth: usize,
max_paths: usize,
output_format: OutputFormat,
) -> Result<()> {
let mut args = vec![
"paths".to_string(),
"--start".to_string(),
start_symbol_id.clone(),
"--max-depth".to_string(),
max_depth.to_string(),
"--max-paths".to_string(),
max_paths.to_string(),
];
if let Some(ref end) = end_symbol_id {
args.push("--end".to_string());
args.push(end.clone());
}
let graph = CodeGraph::open(&db_path)?;
let exec_id = magellan::output::generate_execution_id();
let db_path_str = db_path.to_string_lossy().to_string();
graph.execution_log().start_execution(
&exec_id,
env!("CARGO_PKG_VERSION"),
&args,
None,
&db_path_str,
)?;
let result = graph.enumerate_paths(
&start_symbol_id,
end_symbol_id.as_deref(),
max_depth,
max_paths,
)?;
if output_format == OutputFormat::Json || output_format == OutputFormat::Pretty {
graph
.execution_log()
.finish_execution(&exec_id, "success", None, 0, 0, 0)?;
return output_json_mode(
&start_symbol_id,
end_symbol_id.as_deref(),
result,
&exec_id,
output_format,
);
}
let end_label = end_symbol_id
.as_ref()
.map(|s| format!(" to \"{}\"", s))
.unwrap_or_default();
if result.paths.is_empty() {
println!("No paths found from \"{}\"{}", start_symbol_id, end_label);
} else {
println!("Execution paths from \"{}\"{}:", start_symbol_id, end_label);
println!(" Total paths enumerated: {}", result.total_enumerated);
println!(" Paths returned: {}", result.paths.len());
if result.bounded_hit {
println!(
" Note: Enumeration hit bounds (max_depth={}, max_paths={})",
max_depth, max_paths
);
}
println!("\nStatistics:");
println!(" Average length: {:.2}", result.statistics.avg_length);
println!(" Min length: {}", result.statistics.min_length);
println!(" Max length: {}", result.statistics.max_length);
println!(" Unique symbols: {}", result.statistics.unique_symbols);
println!("\nPaths:");
for (i, path) in result.paths.iter().enumerate() {
println!(" [{}] Length: {}", i + 1, path.length);
for (j, symbol) in path.symbols.iter().enumerate() {
let fqn_display = symbol.fqn.as_deref().unwrap_or("?");
println!(" {}. {} ({})", j + 1, fqn_display, symbol.kind);
}
if i < result.paths.len().saturating_sub(1) {
println!();
}
}
}
graph
.execution_log()
.finish_execution(&exec_id, "success", None, 0, 0, 0)?;
Ok(())
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PathsResponse {
pub start_symbol_id: String,
pub end_symbol_id: Option<String>,
pub config: PathsConfig,
pub paths: Vec<ExecutionPathJson>,
pub total_enumerated: usize,
pub bounded_hit: bool,
pub statistics: PathStatisticsJson,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PathsConfig {
pub max_depth: usize,
pub max_paths: usize,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ExecutionPathJson {
pub symbols: Vec<SymbolInfoJson>,
pub length: usize,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PathStatisticsJson {
pub avg_length: f64,
pub min_length: usize,
pub max_length: usize,
pub unique_symbols: usize,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SymbolInfoJson {
pub symbol_id: Option<String>,
pub fqn: Option<String>,
pub file_path: String,
pub kind: String,
}
impl From<SymbolInfo> for SymbolInfoJson {
fn from(info: SymbolInfo) -> Self {
Self {
symbol_id: info.symbol_id,
fqn: info.fqn,
file_path: info.file_path,
kind: info.kind,
}
}
}
impl From<ExecutionPath> for ExecutionPathJson {
fn from(path: ExecutionPath) -> Self {
Self {
symbols: path.symbols.into_iter().map(SymbolInfoJson::from).collect(),
length: path.length,
}
}
}
impl From<PathStatistics> for PathStatisticsJson {
fn from(stats: PathStatistics) -> Self {
Self {
avg_length: stats.avg_length,
min_length: stats.min_length,
max_length: stats.max_length,
unique_symbols: stats.unique_symbols,
}
}
}
fn output_json_mode(
start_symbol_id: &str,
end_symbol_id: Option<&str>,
result: PathEnumerationResult,
exec_id: &str,
output_format: OutputFormat,
) -> Result<()> {
let paths_json: Vec<ExecutionPathJson> = result
.paths
.into_iter()
.map(ExecutionPathJson::from)
.collect();
let response = PathsResponse {
start_symbol_id: start_symbol_id.to_string(),
end_symbol_id: end_symbol_id.map(|s| s.to_string()),
config: PathsConfig {
max_depth: 0, max_paths: 0, },
paths: paths_json,
total_enumerated: result.total_enumerated,
bounded_hit: result.bounded_hit,
statistics: PathStatisticsJson::from(result.statistics),
};
let json_response = JsonResponse::new(response, exec_id);
output_json(&json_response, output_format)?;
Ok(())
}