use std::path::Path;
use std::sync::Arc;
use crate::db::graph::{GraphDb, QueryExt};
use crate::cli_tools::json_entities_array;
use crate::cli_tools::append_signature_if_present;
pub async fn run_explore_file(
file_path: &str,
repo_name: Option<&str>,
graph_db: &Arc<GraphDb>,
) -> anyhow::Result<(String, serde_json::Value)> {
let (normalized_path, display_path) = if Path::new(file_path).exists() {
let canonical = std::fs::canonicalize(file_path)?;
let canonical_str = canonical.to_string_lossy().to_string();
(canonical_str.clone(), canonical_str)
} else {
(file_path.to_string(), file_path.to_string())
};
let entities = graph_db
.get_file_entities(&normalized_path, repo_name)
.await?;
let outgoing_refs = graph_db
.get_file_outgoing_references(&normalized_path, repo_name)
.await
.unwrap_or_else(|_| serde_json::json!([]));
let result = serde_json::json!({
"entities": entities,
"outgoing_references": outgoing_refs,
});
Ok((display_path, result))
}
pub fn format_file_entities(file_path: &str, result: &serde_json::Value) -> String {
let mut output = format!("# Entities in `{}`\n\n", file_path);
let entities = json_entities_array(result);
let outgoing_refs = result
.as_object()
.and_then(|obj| obj.get("outgoing_references"))
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
if entities.is_empty() && outgoing_refs.is_empty() {
output.push_str("No entities found in this file.\n");
return output;
}
if !entities.is_empty() {
output.push_str(&format!("Found {} entity/entities:\n\n", entities.len()));
let mut classes = Vec::new();
let mut interfaces = Vec::new();
let mut objects = Vec::new();
let mut companions = Vec::new();
let mut methods = Vec::new();
let mut functions = Vec::new();
let mut properties = Vec::new();
let mut python_classes = Vec::new();
let mut python_constants = Vec::new();
let mut python_functions = Vec::new();
let mut python_methods = Vec::new();
let mut python_modules = Vec::new();
let mut rust_structs = Vec::new();
let mut rust_enums = Vec::new();
let mut rust_unions = Vec::new();
let mut rust_traits = Vec::new();
let mut rust_impls = Vec::new();
let mut rust_functions = Vec::new();
let mut rust_methods = Vec::new();
let mut rust_macros = Vec::new();
let mut rust_type_aliases = Vec::new();
let mut rust_constants = Vec::new();
let mut rust_statics = Vec::new();
let mut rust_modules = Vec::new();
let mut build_deps = Vec::new();
let mut build_plugins = Vec::new();
let mut build_tasks = Vec::new();
let mut pipeline_stages = Vec::new();
let mut pipeline_steps = Vec::new();
let mut groovy_classes = Vec::new();
let mut groovy_interfaces = Vec::new();
let mut groovy_traits = Vec::new();
let mut groovy_methods = Vec::new();
let mut groovy_functions = Vec::new();
let mut groovy_enums = Vec::new();
let mut groovy_properties = Vec::new();
let mut cargo_packages = Vec::new();
let mut cargo_features = Vec::new();
let mut workspace_members = Vec::new();
let mut config_properties = Vec::new();
let mut k8s_resources = Vec::new();
let mut helm_charts = Vec::new();
let mut helm_values = Vec::new();
let mut helm_template_vars = Vec::new();
for entity in &entities {
if let Some(kind) = entity.get("kind").and_then(|v| v.as_str()) {
match kind {
"class" | "kotlin_class" => classes.push(entity),
"interface" | "kotlin_interface" => interfaces.push(entity),
"kotlin_object" => objects.push(entity),
"kotlin_companion" => companions.push(entity),
"method" | "kotlin_method" => methods.push(entity),
"function" | "kotlin_function" => functions.push(entity),
"kotlin_property" => properties.push(entity),
"python_class" => python_classes.push(entity),
"python_constant" => python_constants.push(entity),
"python_function" => python_functions.push(entity),
"python_method" => python_methods.push(entity),
"python_module" => python_modules.push(entity),
"rust_struct" => rust_structs.push(entity),
"rust_enum" => rust_enums.push(entity),
"rust_union" => rust_unions.push(entity),
"rust_trait" => rust_traits.push(entity),
"rust_impl" => rust_impls.push(entity),
"rust_function" => rust_functions.push(entity),
"rust_method" => rust_methods.push(entity),
"rust_macro_def" | "rust_macro_invoke" => rust_macros.push(entity),
"rust_type_alias" => rust_type_aliases.push(entity),
"rust_constant" => rust_constants.push(entity),
"rust_static" => rust_statics.push(entity),
"rust_module" => rust_modules.push(entity),
"build_dependency" => build_deps.push(entity),
"build_plugin" => build_plugins.push(entity),
"build_task" => build_tasks.push(entity),
"pipeline_stage" => pipeline_stages.push(entity),
"pipeline_step" => pipeline_steps.push(entity),
"groovy_class" => groovy_classes.push(entity),
"groovy_interface" => groovy_interfaces.push(entity),
"groovy_trait" => groovy_traits.push(entity),
"groovy_method" => groovy_methods.push(entity),
"groovy_function" => groovy_functions.push(entity),
"groovy_enum" => groovy_enums.push(entity),
"groovy_property" => groovy_properties.push(entity),
"cargo_package" => cargo_packages.push(entity),
"cargo_feature" => cargo_features.push(entity),
"workspace_member" => workspace_members.push(entity),
"config_property" => config_properties.push(entity),
"k8s_deployment" | "k8s_service" | "k8s_configmap" | "k8s_secret"
| "k8s_ingress" | "k8s_namespace" | "k8s_resource" => {
k8s_resources.push(entity)
}
"helm_chart" => helm_charts.push(entity),
"helm_value" => helm_values.push(entity),
"helm_template_var" => helm_template_vars.push(entity),
_ => {}
}
}
}
if !classes.is_empty() {
output.push_str("## Classes\n\n");
for entity in classes {
output.push_str(&format_entity_summary(entity));
}
}
if !interfaces.is_empty() {
output.push_str("## Interfaces\n\n");
for entity in interfaces {
output.push_str(&format_entity_summary(entity));
}
}
if !objects.is_empty() {
output.push_str("## Objects (Singletons)\n\n");
for entity in objects {
output.push_str(&format_entity_summary(entity));
}
}
if !companions.is_empty() {
output.push_str("## Companion Objects\n\n");
for entity in companions {
output.push_str(&format_entity_summary(entity));
}
}
if !methods.is_empty() {
output.push_str("## Methods\n\n");
for entity in methods {
output.push_str(&format_entity_summary(entity));
}
}
if !functions.is_empty() {
output.push_str("## Functions\n\n");
for entity in functions {
output.push_str(&format_entity_summary(entity));
}
}
if !properties.is_empty() {
output.push_str("## Properties\n\n");
for entity in properties {
output.push_str(&format_entity_summary(entity));
}
}
if !python_classes.is_empty() {
output.push_str("## Python Classes\n\n");
for entity in python_classes {
output.push_str(&format_entity_summary(entity));
}
}
if !python_constants.is_empty() {
output.push_str("## Python Constants\n\n");
for entity in python_constants {
output.push_str(&format_entity_summary(entity));
}
}
if !python_functions.is_empty() {
output.push_str("## Python Functions\n\n");
for entity in python_functions {
output.push_str(&format_entity_summary(entity));
}
}
if !python_methods.is_empty() {
output.push_str("## Python Methods\n\n");
for entity in python_methods {
output.push_str(&format_entity_summary(entity));
}
}
if !python_modules.is_empty() {
output.push_str("## Python Modules\n\n");
for entity in python_modules {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_structs.is_empty() {
output.push_str("## Structs (Rust)\n\n");
for entity in rust_structs {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_enums.is_empty() {
output.push_str("## Enums (Rust)\n\n");
for entity in rust_enums {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_unions.is_empty() {
output.push_str("## Unions (Rust)\n\n");
for entity in rust_unions {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_traits.is_empty() {
output.push_str("## Traits (Rust)\n\n");
for entity in rust_traits {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_impls.is_empty() {
output.push_str("## Impl Blocks (Rust)\n\n");
for entity in rust_impls {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_functions.is_empty() {
output.push_str("## Functions (Rust)\n\n");
for entity in rust_functions {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_methods.is_empty() {
output.push_str("## Methods (Rust)\n\n");
for entity in rust_methods {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_macros.is_empty() {
output.push_str("## Macros (Rust)\n\n");
for entity in rust_macros {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_type_aliases.is_empty() {
output.push_str("## Type Aliases (Rust)\n\n");
for entity in rust_type_aliases {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_constants.is_empty() {
output.push_str("## Constants (Rust)\n\n");
for entity in rust_constants {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_statics.is_empty() {
output.push_str("## Statics (Rust)\n\n");
for entity in rust_statics {
output.push_str(&format_entity_summary(entity));
}
}
if !rust_modules.is_empty() {
output.push_str("## Modules (Rust)\n\n");
for entity in rust_modules {
output.push_str(&format_entity_summary(entity));
}
}
if !build_deps.is_empty() {
output.push_str("## Dependencies\n\n");
for entity in build_deps {
output.push_str(&format_entity_summary(entity));
}
}
if !build_plugins.is_empty() {
output.push_str("## Plugins\n\n");
for entity in build_plugins {
output.push_str(&format_entity_summary(entity));
}
}
if !build_tasks.is_empty() {
output.push_str("## Tasks\n\n");
for entity in build_tasks {
output.push_str(&format_entity_summary(entity));
}
}
if !pipeline_stages.is_empty() {
output.push_str("## Pipeline Stages\n\n");
for entity in pipeline_stages {
output.push_str(&format_entity_summary(entity));
}
}
if !pipeline_steps.is_empty() {
output.push_str("## Pipeline Steps\n\n");
for entity in pipeline_steps {
output.push_str(&format_entity_summary(entity));
}
}
if !groovy_classes.is_empty() {
output.push_str("## Classes (Groovy)\n\n");
for entity in groovy_classes {
output.push_str(&format_entity_summary(entity));
}
}
if !groovy_interfaces.is_empty() {
output.push_str("## Interfaces (Groovy)\n\n");
for entity in groovy_interfaces {
output.push_str(&format_entity_summary(entity));
}
}
if !groovy_traits.is_empty() {
output.push_str("## Traits (Groovy)\n\n");
for entity in groovy_traits {
output.push_str(&format_entity_summary(entity));
}
}
if !groovy_enums.is_empty() {
output.push_str("## Enums (Groovy)\n\n");
for entity in groovy_enums {
output.push_str(&format_entity_summary(entity));
}
}
if !groovy_methods.is_empty() {
output.push_str("## Methods (Groovy)\n\n");
for entity in groovy_methods {
output.push_str(&format_entity_summary(entity));
}
}
if !groovy_functions.is_empty() {
output.push_str("## Functions (Groovy)\n\n");
for entity in groovy_functions {
output.push_str(&format_entity_summary(entity));
}
}
if !groovy_properties.is_empty() {
output.push_str("## Properties (Groovy)\n\n");
for entity in groovy_properties {
output.push_str(&format_entity_summary(entity));
}
}
if !cargo_packages.is_empty() {
output.push_str("## Cargo Package\n\n");
for entity in cargo_packages {
output.push_str(&format_entity_summary(entity));
}
}
if !cargo_features.is_empty() {
output.push_str("## Cargo Features\n\n");
for entity in cargo_features {
output.push_str(&format_entity_summary(entity));
}
}
if !workspace_members.is_empty() {
output.push_str("## Workspace Members\n\n");
for entity in workspace_members {
output.push_str(&format_entity_summary(entity));
}
}
if !config_properties.is_empty() {
output.push_str("## Configuration Properties\n\n");
for entity in config_properties {
output.push_str(&format_entity_summary(entity));
}
}
if !k8s_resources.is_empty() {
output.push_str("## Kubernetes Resources\n\n");
for entity in k8s_resources {
output.push_str(&format_entity_summary(entity));
}
}
if !helm_charts.is_empty() {
output.push_str("## Helm Chart\n\n");
for entity in helm_charts {
output.push_str(&format_entity_summary(entity));
}
}
if !helm_values.is_empty() {
output.push_str("## Helm Values\n\n");
for entity in helm_values {
output.push_str(&format_entity_summary(entity));
}
}
if !helm_template_vars.is_empty() {
output.push_str("## Template Variables\n\n");
for entity in helm_template_vars {
output.push_str(&format_entity_summary(entity));
}
}
}
if !outgoing_refs.is_empty() {
output.push_str("## Imports / Referenced Types\n\n");
let mut seen = std::collections::HashSet::new();
for entry in &outgoing_refs {
let name = entry.get("name").and_then(|v| v.as_str()).unwrap_or("");
let kind = entry.get("kind").and_then(|v| v.as_str()).unwrap_or("");
let fp = entry
.get("file_path")
.and_then(|v| v.as_str())
.unwrap_or("");
let line_num = entry.get("line").and_then(|v| v.as_i64()).unwrap_or(0);
let key = format!("{}:{}:{}", name, kind, fp);
if seen.insert(key) {
if line_num > 0 && !fp.is_empty() {
output.push_str(&format!("- {} ({}) — {}:{}\n", name, kind, fp, line_num));
} else {
output.push_str(&format!("- {} ({})\n", name, kind));
}
}
}
output.push('\n');
}
output
}
fn format_entity_summary(entity: &serde_json::Value) -> String {
let mut output = String::new();
if let Some(name) = entity.get("name").and_then(|v| v.as_str()) {
output.push_str(&format!("- **`{}`**", name));
if let Some(start_line) = entity.get("start_line").and_then(|v| v.as_i64()) {
output.push_str(&format!(" (line {})", start_line));
}
output.push('\n');
if let Some(decorators_array) = entity.get("decorators").and_then(|v| v.as_array())
&& !decorators_array.is_empty()
{
let decorator_strs: Vec<String> = decorators_array
.iter()
.filter_map(|v| v.as_str())
.map(|s| s.to_string())
.collect();
if !decorator_strs.is_empty() {
output.push_str(&format!(" - Decorators: {}\n", decorator_strs.join(", ")));
}
}
append_signature_if_present(&mut output, entity);
if let Some(docstring) = entity
.get("docstring")
.and_then(|v| v.as_str())
.filter(|s| !s.trim().is_empty())
{
let doc_preview = docstring.lines().next().unwrap_or("");
output.push_str(&format!(" - Doc: {}\n", doc_preview));
}
output.push('\n');
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_format_file_entities_empty() {
let entities = json!([]);
let formatted = format_file_entities("src/main.java", &entities);
assert!(formatted.contains("No entities found in this file"));
}
#[test]
fn test_format_file_entities_single_class() {
let entities = json!([
{
"name": "MyClass",
"kind": "class",
"start_line": 10,
"signature": "public class MyClass"
}
]);
let formatted = format_file_entities("src/main.java", &entities);
assert!(formatted.contains("## Classes"));
assert!(formatted.contains("MyClass"));
assert!(formatted.contains("(line 10)"));
assert!(formatted.contains("public class MyClass"));
}
#[test]
fn test_format_file_entities_multiple_classes() {
let entities = json!([
{
"name": "Class1",
"kind": "class",
"start_line": 10
},
{
"name": "Class2",
"kind": "class",
"start_line": 50
}
]);
let formatted = format_file_entities("src/main.java", &entities);
assert!(formatted.contains("Found 2 entity/entities"));
assert!(formatted.contains("Class1"));
assert!(formatted.contains("Class2"));
}
#[test]
fn test_format_file_entities_groups_by_kind() {
let entities = json!([
{"name": "MyClass", "kind": "class"},
{"name": "MyInterface", "kind": "interface"},
{"name": "myMethod", "kind": "method"},
{"name": "myFunction", "kind": "function"}
]);
let formatted = format_file_entities("src/main.java", &entities);
assert!(formatted.contains("## Classes"));
assert!(formatted.contains("## Interfaces"));
assert!(formatted.contains("## Methods"));
assert!(formatted.contains("## Functions"));
}
#[test]
fn test_format_entity_summary_with_signature() {
let entity = json!({
"name": "myMethod",
"kind": "method",
"start_line": 20,
"signature": "public void myMethod(String param)"
});
let formatted = format_entity_summary(&entity);
assert!(formatted.contains("myMethod"));
assert!(formatted.contains("(line 20)"));
assert!(formatted.contains("public void myMethod(String param)"));
}
#[test]
fn test_format_entity_summary_with_docstring() {
let entity = json!({
"name": "myMethod",
"kind": "method",
"docstring": "First line of doc\nSecond line of doc"
});
let formatted = format_entity_summary(&entity);
assert!(formatted.contains("myMethod"));
assert!(formatted.contains("First line of doc"));
assert!(!formatted.contains("Second line of doc"));
}
#[test]
fn test_format_entity_summary_ignores_whitespace_docstring() {
let entity = json!({
"name": "myMethod",
"kind": "method",
"docstring": " \n \t"
});
let formatted = format_entity_summary(&entity);
assert!(!formatted.contains("- Doc:"));
}
#[test]
fn test_format_entity_summary_without_optional_fields() {
let entity = json!({
"name": "MyClass",
"kind": "class"
});
let formatted = format_entity_summary(&entity);
assert!(formatted.contains("MyClass"));
assert!(!formatted.contains("(line"));
assert!(!formatted.contains("Signature:"));
}
#[test]
fn test_format_file_entities_unknown_kind() {
let entities = json!([
{
"name": "UnknownEntity",
"kind": "unknown_kind"
}
]);
let formatted = format_file_entities("src/main.java", &entities);
assert!(!formatted.contains("UnknownEntity"));
assert!(formatted.contains("Found 1 entity/entities"));
}
#[test]
fn test_format_file_entities_displays_file_path() {
let entities = json!([
{"name": "MyClass", "kind": "class"}
]);
let formatted = format_file_entities("src/main/java/MyClass.java", &entities);
assert!(formatted.contains("src/main/java/MyClass.java"));
}
}