Skip to main content

st/formatters/
relations_formatter.rs

1//! Relations formatter that works with the standard formatter interface
2//! "Making relations a first-class mode!" - Omni
3
4use crate::formatters::Formatter;
5use crate::relations::RelationAnalyzer;
6use crate::scanner::{FileNode, TreeStats};
7use anyhow::Result;
8use std::io::Write;
9use std::path::Path;
10
11/// Main relations formatter - delegates to text formatter by default
12pub struct RelationsFormatter {
13    filter: Option<String>,
14    focus: Option<std::path::PathBuf>,
15}
16
17impl RelationsFormatter {
18    pub fn new(filter: Option<String>, focus: Option<std::path::PathBuf>) -> Self {
19        Self { filter, focus }
20    }
21}
22
23impl Formatter for RelationsFormatter {
24    fn format(
25        &self,
26        writer: &mut dyn Write,
27        _nodes: &[FileNode],
28        _stats: &TreeStats,
29        root_path: &Path,
30    ) -> Result<()> {
31        // Create relation analyzer
32        let mut analyzer = RelationAnalyzer::new();
33
34        // Analyze the directory
35        eprintln!("๐Ÿ” Analyzing code relationships...");
36        analyzer.analyze_directory(root_path)?;
37
38        // Apply filters if specified
39        if let Some(filter_type) = &self.filter {
40            // In a real implementation, we'd filter the relations
41            eprintln!("๐Ÿ“‹ Filtering by: {}", filter_type);
42        }
43
44        // Get relations based on focus or all
45        let relations: Vec<&crate::relations::FileRelation> = if let Some(focus_file) = &self.focus
46        {
47            // Convert relative path to absolute for matching
48            let abs_focus = if focus_file.is_relative() {
49                root_path.join(focus_file)
50            } else {
51                focus_file.clone()
52            };
53
54            let file_relations = analyzer.get_file_relations(&abs_focus);
55            eprintln!(
56                "๐Ÿ“„ Found {} relationships for {}",
57                file_relations.len(),
58                focus_file.display()
59            );
60            file_relations
61        } else {
62            analyzer.get_relations().iter().collect()
63        };
64
65        // Write header
66        writeln!(writer, "๐Ÿ”— Code Relationship Analysis")?;
67        writeln!(writer, "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”")?;
68        writeln!(writer)?;
69
70        // If no relationships found
71        if relations.is_empty() {
72            if let Some(focus_file) = &self.focus {
73                writeln!(
74                    writer,
75                    "No relationships found for: {}",
76                    focus_file.display()
77                )?;
78            } else {
79                writeln!(writer, "No relationships found in the codebase.")?;
80            }
81            return Ok(());
82        }
83
84        // Group relations by type
85        use crate::relations::RelationType;
86        let mut imports = Vec::new();
87        let mut calls = Vec::new();
88        let mut types = Vec::new();
89        let mut tests = Vec::new();
90        let mut coupled = Vec::new();
91
92        for relation in &relations {
93            match &relation.relation_type {
94                RelationType::Imports => imports.push(relation),
95                RelationType::FunctionCall => calls.push(relation),
96                RelationType::TypeUsage => types.push(relation),
97                RelationType::TestedBy => tests.push(relation),
98                RelationType::Coupled => coupled.push(relation),
99                RelationType::Exports => {} // Skip exports for now
100            }
101        }
102
103        // Display relationships by type
104        if !imports.is_empty() {
105            writeln!(writer, "๐Ÿ“ฆ Imports ({}):", imports.len())?;
106            for rel in imports {
107                writeln!(
108                    writer,
109                    "  {} โ†’ {}",
110                    rel.source.display(),
111                    rel.target.display()
112                )?;
113            }
114            writeln!(writer)?;
115        }
116
117        if !calls.is_empty() {
118            writeln!(writer, "๐Ÿ“ž Function Calls ({}):", calls.len())?;
119            for rel in calls {
120                writeln!(
121                    writer,
122                    "  {} โ†’ {}",
123                    rel.source.display(),
124                    rel.target.display()
125                )?;
126            }
127            writeln!(writer)?;
128        }
129
130        if !types.is_empty() {
131            writeln!(writer, "๐Ÿท๏ธ  Type Usage ({}):", types.len())?;
132            for rel in types {
133                writeln!(
134                    writer,
135                    "  {} โ†’ {}",
136                    rel.source.display(),
137                    rel.target.display()
138                )?;
139            }
140            writeln!(writer)?;
141        }
142
143        if !tests.is_empty() {
144            writeln!(writer, "๐Ÿงช Tests ({}):", tests.len())?;
145            for rel in tests {
146                writeln!(
147                    writer,
148                    "  {} โ†’ {}",
149                    rel.source.display(),
150                    rel.target.display()
151                )?;
152            }
153            writeln!(writer)?;
154        }
155
156        if !coupled.is_empty() {
157            writeln!(writer, "๐Ÿ”— Coupled Changes ({}):", coupled.len())?;
158            for rel in coupled {
159                writeln!(
160                    writer,
161                    "  {} โ†” {}",
162                    rel.source.display(),
163                    rel.target.display()
164                )?;
165            }
166            writeln!(writer)?;
167        }
168
169        // Summary
170        writeln!(writer, "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”")?;
171        writeln!(writer, "Total relationships: {}", relations.len())?;
172        if let Some(focus_file) = &self.focus {
173            writeln!(writer, "Focused on: {}", focus_file.display())?;
174        } else {
175            writeln!(writer, "Files analyzed: {}", root_path.display())?;
176        }
177
178        Ok(())
179    }
180}