use anyhow::Result;
use std::path::PathBuf;
use magellan::graph::AstNode;
use magellan::output::{generate_execution_id, output_json, JsonResponse, OutputFormat};
use magellan::CodeGraph;
fn normalize_user_kind(kind: &str) -> String {
let normalized = match kind {
"Function" => "function_item",
"Struct" => "struct_item",
"Enum" => "enum_item",
"Trait" => "trait_item",
"Impl" => "impl_item",
"Module" | "Mod" => "mod_item",
"If" => "if_expression",
"Match" => "match_expression",
"While" => "while_expression",
"For" => "for_expression",
"Loop" => "loop_expression",
"Return" => "return_expression",
"Break" => "break_expression",
"Continue" => "continue_expression",
"Block" => "block",
"Call" => "call_expression",
"Assign" => "assignment_expression",
"Let" => "let_statement",
"Const" => "const_item",
"Static" => "static_item",
"Attribute" => "attribute_item",
"Class" => "class_definition",
"Interface" => "interface_definition",
"fn" => "function_item",
"mod" => "mod_item",
"struct" => "struct_item",
"enum" => "enum_item",
"trait" => "trait_item",
"impl" => "impl_item",
"const" => "const_item",
"static" => "static_item",
"if" => "if_expression",
"match" => "match_expression",
"while" => "while_expression",
"for" => "for_expression",
"loop" => "loop_expression",
"return" => "return_expression",
"break" => "break_expression",
"continue" => "continue_expression",
"let" => "let_statement",
_ => kind,
};
normalized.to_string()
}
pub fn run_ast_command(
db_path: PathBuf,
file_path: String,
position: Option<usize>,
output_format: OutputFormat,
) -> Result<()> {
let graph = CodeGraph::open(&db_path)?;
let exec_id = generate_execution_id();
if let Some(pos) = position {
match graph.get_ast_node_at_position(&file_path, pos)? {
Some(node) => match output_format {
OutputFormat::Json | OutputFormat::Pretty => {
let response = JsonResponse::new(
serde_json::json!({
"file_path": file_path,
"position": pos,
"node": node,
}),
&exec_id,
);
output_json(&response, output_format)?;
}
OutputFormat::Human => {
println!("AST node at position {} in {}:", pos, file_path);
print_node_tree(&graph, &node, 0)?;
}
},
None => {
eprintln!("No AST node found at position {} in {}", pos, file_path);
std::process::exit(1);
}
}
} else {
let nodes = graph.get_ast_nodes_by_file(&file_path)?;
if nodes.is_empty() {
eprintln!("No AST nodes found for file: {}", file_path);
std::process::exit(1);
}
match output_format {
OutputFormat::Json | OutputFormat::Pretty => {
let response = JsonResponse::new(
serde_json::json!({
"file_path": file_path,
"count": nodes.len(),
"nodes": nodes,
}),
&exec_id,
);
output_json(&response, output_format)?;
}
OutputFormat::Human => {
println!("AST nodes for {} ({} nodes):", file_path, nodes.len());
for node_with_text in nodes {
print_node_tree(&graph, &node_with_text.node, 0)?;
}
}
}
}
Ok(())
}
pub fn run_find_ast_command(
db_path: PathBuf,
kind: String,
output_format: OutputFormat,
) -> Result<()> {
let graph = CodeGraph::open(&db_path)?;
let exec_id = generate_execution_id();
let normalized_kind = normalize_user_kind(&kind);
let nodes = graph.get_ast_nodes_by_kind(&normalized_kind)?;
if nodes.is_empty() {
if normalized_kind != kind {
eprintln!(
"No AST nodes found with kind '{}' (normalized to '{}')",
kind, normalized_kind
);
} else {
eprintln!("No AST nodes found with kind '{}'", kind);
}
std::process::exit(1);
}
match output_format {
OutputFormat::Json | OutputFormat::Pretty => {
let response = JsonResponse::new(
serde_json::json!({
"kind": kind,
"count": nodes.len(),
"nodes": nodes,
}),
&exec_id,
);
output_json(&response, output_format)?;
}
OutputFormat::Human => {
println!("Found {} AST nodes with kind '{}':", nodes.len(), kind);
for node in nodes {
println!(" - {} @ {}:{}", node.kind, node.byte_start, node.byte_end);
}
}
}
Ok(())
}
fn print_node_tree(graph: &CodeGraph, node: &AstNode, indent: usize) -> Result<()> {
let prefix = " ".repeat(indent);
let connector = if indent == 0 { "" } else { "└── " };
println!(
"{}{}{} ({}:{})",
prefix, connector, node.kind, node.byte_start, node.byte_end
);
if let Some(node_id) = node.id {
let children = graph.get_ast_children(node_id)?;
for child in children {
print_node_tree(graph, &child, indent + 1)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_node_tree_basic() {
let node = AstNode {
id: None,
parent_id: None,
kind: "Function".to_string(),
byte_start: 0,
byte_end: 100,
};
assert_eq!(node.kind, "Function");
assert_eq!(node.byte_start, 0);
assert_eq!(node.byte_end, 100);
}
#[test]
fn test_magellan_ast_command_structure() {
let _human_format = OutputFormat::Human;
let _json_format = OutputFormat::Json;
let _pretty_format = OutputFormat::Pretty;
let _test_node = AstNode {
id: Some(1),
parent_id: None,
kind: "function_item".to_string(),
byte_start: 0,
byte_end: 50,
};
assert!(true);
}
#[test]
fn test_magellan_find_ast_command_structure() {
assert!(true);
}
#[test]
fn test_normalize_user_kind_titlecase() {
assert_eq!(normalize_user_kind("Function"), "function_item");
assert_eq!(normalize_user_kind("Struct"), "struct_item");
assert_eq!(normalize_user_kind("Enum"), "enum_item");
assert_eq!(normalize_user_kind("Trait"), "trait_item");
assert_eq!(normalize_user_kind("Impl"), "impl_item");
assert_eq!(normalize_user_kind("Module"), "mod_item");
assert_eq!(normalize_user_kind("Mod"), "mod_item");
assert_eq!(normalize_user_kind("If"), "if_expression");
assert_eq!(normalize_user_kind("Match"), "match_expression");
assert_eq!(normalize_user_kind("While"), "while_expression");
assert_eq!(normalize_user_kind("For"), "for_expression");
assert_eq!(normalize_user_kind("Loop"), "loop_expression");
assert_eq!(normalize_user_kind("Return"), "return_expression");
assert_eq!(normalize_user_kind("Break"), "break_expression");
assert_eq!(normalize_user_kind("Continue"), "continue_expression");
assert_eq!(normalize_user_kind("Block"), "block");
assert_eq!(normalize_user_kind("Call"), "call_expression");
assert_eq!(normalize_user_kind("Assign"), "assignment_expression");
assert_eq!(normalize_user_kind("Let"), "let_statement");
assert_eq!(normalize_user_kind("Const"), "const_item");
assert_eq!(normalize_user_kind("Static"), "static_item");
assert_eq!(normalize_user_kind("Attribute"), "attribute_item");
}
#[test]
fn test_normalize_user_kind_snake_case() {
assert_eq!(normalize_user_kind("fn"), "function_item");
assert_eq!(normalize_user_kind("mod"), "mod_item");
assert_eq!(normalize_user_kind("struct"), "struct_item");
assert_eq!(normalize_user_kind("enum"), "enum_item");
assert_eq!(normalize_user_kind("trait"), "trait_item");
assert_eq!(normalize_user_kind("impl"), "impl_item");
assert_eq!(normalize_user_kind("const"), "const_item");
assert_eq!(normalize_user_kind("static"), "static_item");
assert_eq!(normalize_user_kind("if"), "if_expression");
assert_eq!(normalize_user_kind("match"), "match_expression");
assert_eq!(normalize_user_kind("while"), "while_expression");
assert_eq!(normalize_user_kind("for"), "for_expression");
assert_eq!(normalize_user_kind("loop"), "loop_expression");
assert_eq!(normalize_user_kind("return"), "return_expression");
assert_eq!(normalize_user_kind("break"), "break_expression");
assert_eq!(normalize_user_kind("continue"), "continue_expression");
assert_eq!(normalize_user_kind("let"), "let_statement");
}
#[test]
fn test_normalize_user_kind_passthrough() {
assert_eq!(normalize_user_kind("function_item"), "function_item");
assert_eq!(normalize_user_kind("if_expression"), "if_expression");
assert_eq!(normalize_user_kind("block"), "block");
assert_eq!(normalize_user_kind("call_expression"), "call_expression");
assert_eq!(normalize_user_kind("unknown_kind"), "unknown_kind");
}
}