syncable_cli/analyzer/display/
helpers.rs

1//! Helper functions for display formatting
2
3use crate::analyzer::{
4    ProjectCategory, ArchitecturePattern, DetectedTechnology, 
5    TechnologyCategory, LibraryType, DockerAnalysis, OrchestrationPattern
6};
7use crate::analyzer::display::BoxDrawer;
8use colored::*;
9
10/// Get emoji for project category
11pub fn get_category_emoji(category: &ProjectCategory) -> &'static str {
12    match category {
13        ProjectCategory::Frontend => "🌐",
14        ProjectCategory::Backend => "βš™οΈ",
15        ProjectCategory::Api => "πŸ”Œ",
16        ProjectCategory::Service => "πŸš€",
17        ProjectCategory::Library => "πŸ“š",
18        ProjectCategory::Tool => "πŸ”§",
19        ProjectCategory::Documentation => "πŸ“–",
20        ProjectCategory::Infrastructure => "πŸ—οΈ",
21        ProjectCategory::Unknown => "❓",
22    }
23}
24
25/// Format project category name
26pub fn format_project_category(category: &ProjectCategory) -> &'static str {
27    match category {
28        ProjectCategory::Frontend => "Frontend",
29        ProjectCategory::Backend => "Backend",
30        ProjectCategory::Api => "API",
31        ProjectCategory::Service => "Service",
32        ProjectCategory::Library => "Library",
33        ProjectCategory::Tool => "Tool",
34        ProjectCategory::Documentation => "Documentation",
35        ProjectCategory::Infrastructure => "Infrastructure",
36        ProjectCategory::Unknown => "Unknown",
37    }
38}
39
40/// Display architecture description
41pub fn display_architecture_description(pattern: &ArchitecturePattern) {
42    match pattern {
43        ArchitecturePattern::Monolithic => {
44            println!("   πŸ“¦ This is a single, self-contained application");
45        }
46        ArchitecturePattern::Fullstack => {
47            println!("   🌐 This is a full-stack application with separate frontend and backend");
48        }
49        ArchitecturePattern::Microservices => {
50            println!("   πŸ”— This is a microservices architecture with multiple independent services");
51        }
52        ArchitecturePattern::ApiFirst => {
53            println!("   πŸ”Œ This is an API-first architecture focused on service interfaces");
54        }
55        ArchitecturePattern::EventDriven => {
56            println!("   πŸ“‘ This is an event-driven architecture with decoupled components");
57        }
58        ArchitecturePattern::Mixed => {
59            println!("   πŸ”€ This is a mixed architecture combining multiple patterns");
60        }
61    }
62}
63
64/// Helper function for displaying architecture description - returns string
65pub fn display_architecture_description_to_string(pattern: &ArchitecturePattern) -> String {
66    match pattern {
67        ArchitecturePattern::Monolithic => {
68            "   πŸ“¦ This is a single, self-contained application\n".to_string()
69        }
70        ArchitecturePattern::Fullstack => {
71            "   🌐 This is a full-stack application with separate frontend and backend\n".to_string()
72        }
73        ArchitecturePattern::Microservices => {
74            "   πŸ”— This is a microservices architecture with multiple independent services\n".to_string()
75        }
76        ArchitecturePattern::ApiFirst => {
77            "   πŸ”Œ This is an API-first architecture focused on service interfaces\n".to_string()
78        }
79        ArchitecturePattern::EventDriven => {
80            "   πŸ“‘ This is an event-driven architecture with decoupled components\n".to_string()
81        }
82        ArchitecturePattern::Mixed => {
83            "   πŸ”€ This is a mixed architecture combining multiple patterns\n".to_string()
84        }
85    }
86}
87
88/// Get main technologies for display
89pub fn get_main_technologies(technologies: &[DetectedTechnology]) -> String {
90    let primary = technologies.iter().find(|t| t.is_primary);
91    let frameworks: Vec<_> = technologies.iter()
92        .filter(|t| matches!(t.category, TechnologyCategory::FrontendFramework | TechnologyCategory::MetaFramework))
93        .take(2)
94        .collect();
95    
96    let mut result = Vec::new();
97    
98    if let Some(p) = primary {
99        result.push(p.name.clone());
100    }
101    
102    for f in frameworks {
103        if Some(&f.name) != primary.map(|p| &p.name) {
104            result.push(f.name.clone());
105        }
106    }
107    
108    if result.is_empty() {
109        "-".to_string()
110    } else {
111        result.join(", ")
112    }
113}
114
115/// Add confidence score as a progress bar to the box drawer
116pub fn add_confidence_bar_to_drawer(score: f32, box_drawer: &mut BoxDrawer) {
117    let percentage = (score * 100.0) as u8;
118    let bar_width = 20;
119    let filled = ((score * bar_width as f32) as usize).min(bar_width);
120    
121    let bar = format!("{}{}",
122        "β–ˆ".repeat(filled).green(),
123        "β–‘".repeat(bar_width - filled).dimmed()
124    );
125    
126    let color = if percentage >= 80 {
127        "green"
128    } else if percentage >= 60 {
129        "yellow"
130    } else {
131        "red"
132    };
133    
134    let confidence_info = format!("{} {}", bar, format!("{:.0}%", percentage).color(color));
135    box_drawer.add_line("Confidence:", &confidence_info, true);
136}
137
138/// Helper function for legacy detailed technology display
139pub fn display_technologies_detailed_legacy(technologies: &[DetectedTechnology]) {
140    // Group technologies by category
141    let mut by_category: std::collections::HashMap<&TechnologyCategory, Vec<&DetectedTechnology>> = std::collections::HashMap::new();
142    
143    for tech in technologies {
144        by_category.entry(&tech.category).or_insert_with(Vec::new).push(tech);
145    }
146    
147    // Find and display primary technology
148    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
149        println!("\nπŸ› οΈ  Technology Stack:");
150        println!("   🎯 PRIMARY: {} (confidence: {:.1}%)", primary.name, primary.confidence * 100.0);
151        println!("      Architecture driver for this project");
152    }
153    
154    // Display categories in order
155    let categories = [
156        (TechnologyCategory::MetaFramework, "πŸ—οΈ  Meta-Frameworks"),
157        (TechnologyCategory::BackendFramework, "πŸ–₯️  Backend Frameworks"),
158        (TechnologyCategory::FrontendFramework, "🎨 Frontend Frameworks"),
159        (TechnologyCategory::Library(LibraryType::UI), "🎨 UI Libraries"),
160        (TechnologyCategory::Library(LibraryType::Utility), "πŸ“š Core Libraries"),
161        (TechnologyCategory::BuildTool, "πŸ”¨ Build Tools"),
162        (TechnologyCategory::PackageManager, "πŸ“¦ Package Managers"),
163        (TechnologyCategory::Database, "πŸ—ƒοΈ  Database & ORM"),
164        (TechnologyCategory::Runtime, "⚑ Runtimes"),
165        (TechnologyCategory::Testing, "πŸ§ͺ Testing"),
166    ];
167    
168    for (category, label) in &categories {
169        if let Some(techs) = by_category.get(category) {
170            if !techs.is_empty() {
171                println!("\n   {}:", label);
172                for tech in techs {
173                    println!("      β€’ {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
174                    if let Some(version) = &tech.version {
175                        println!("        Version: {}", version);
176                    }
177                }
178            }
179        }
180    }
181    
182    // Handle other Library types separately
183    for (cat, techs) in &by_category {
184        match cat {
185            TechnologyCategory::Library(lib_type) => {
186                let label = match lib_type {
187                    LibraryType::StateManagement => "πŸ”„ State Management",
188                    LibraryType::DataFetching => "πŸ”ƒ Data Fetching",
189                    LibraryType::Routing => "πŸ—ΊοΈ  Routing",
190                    LibraryType::Styling => "🎨 Styling",
191                    LibraryType::HttpClient => "🌐 HTTP Clients",
192                    LibraryType::Authentication => "πŸ” Authentication",
193                    LibraryType::Other(_) => "πŸ“¦ Other Libraries",
194                    _ => continue, // Skip already handled UI and Utility
195                };
196                
197                // Only print if not already handled above
198                if !matches!(lib_type, LibraryType::UI | LibraryType::Utility) && !techs.is_empty() {
199                    println!("\n   {}:", label);
200                    for tech in techs {
201                        println!("      β€’ {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
202                        if let Some(version) = &tech.version {
203                            println!("        Version: {}", version);
204                        }
205                    }
206                }
207            }
208            _ => {} // Other categories already handled in the array
209        }
210    }
211}
212
213/// Helper function for legacy detailed technology display - returns string
214pub fn display_technologies_detailed_legacy_to_string(technologies: &[DetectedTechnology]) -> String {
215    let mut output = String::new();
216    
217    // Group technologies by category
218    let mut by_category: std::collections::HashMap<&TechnologyCategory, Vec<&DetectedTechnology>> = std::collections::HashMap::new();
219    
220    for tech in technologies {
221        by_category.entry(&tech.category).or_insert_with(Vec::new).push(tech);
222    }
223    
224    // Find and display primary technology
225    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
226        output.push_str("\nπŸ› οΈ  Technology Stack:\n");
227        output.push_str(&format!("   🎯 PRIMARY: {} (confidence: {:.1}%)\n", primary.name, primary.confidence * 100.0));
228        output.push_str("      Architecture driver for this project\n");
229    }
230    
231    // Display categories in order
232    let categories = [
233        (TechnologyCategory::MetaFramework, "πŸ—οΈ  Meta-Frameworks"),
234        (TechnologyCategory::BackendFramework, "πŸ–₯️  Backend Frameworks"),
235        (TechnologyCategory::FrontendFramework, "🎨 Frontend Frameworks"),
236        (TechnologyCategory::Library(LibraryType::UI), "🎨 UI Libraries"),
237        (TechnologyCategory::Library(LibraryType::Utility), "πŸ“š Core Libraries"),
238        (TechnologyCategory::BuildTool, "πŸ”¨ Build Tools"),
239        (TechnologyCategory::PackageManager, "πŸ“¦ Package Managers"),
240        (TechnologyCategory::Database, "πŸ—ƒοΈ  Database & ORM"),
241        (TechnologyCategory::Runtime, "⚑ Runtimes"),
242        (TechnologyCategory::Testing, "πŸ§ͺ Testing"),
243    ];
244    
245    for (category, label) in &categories {
246        if let Some(techs) = by_category.get(category) {
247            if !techs.is_empty() {
248                output.push_str(&format!("\n   {}:\n", label));
249                for tech in techs {
250                    output.push_str(&format!("      β€’ {} (confidence: {:.1}%)\n", tech.name, tech.confidence * 100.0));
251                    if let Some(version) = &tech.version {
252                        output.push_str(&format!("        Version: {}\n", version));
253                    }
254                }
255            }
256        }
257    }
258    
259    // Handle other Library types separately
260    for (cat, techs) in &by_category {
261        match cat {
262            TechnologyCategory::Library(lib_type) => {
263                let label = match lib_type {
264                    LibraryType::StateManagement => "πŸ”„ State Management",
265                    LibraryType::DataFetching => "πŸ”ƒ Data Fetching",
266                    LibraryType::Routing => "πŸ—ΊοΈ  Routing",
267                    LibraryType::Styling => "🎨 Styling",
268                    LibraryType::HttpClient => "🌐 HTTP Clients",
269                    LibraryType::Authentication => "πŸ” Authentication",
270                    LibraryType::Other(_) => "πŸ“¦ Other Libraries",
271                    _ => continue, // Skip already handled UI and Utility
272                };
273                
274                // Only print if not already handled above
275                if !matches!(lib_type, LibraryType::UI | LibraryType::Utility) && !techs.is_empty() {
276                    output.push_str(&format!("\n   {}:\n", label));
277                    for tech in techs {
278                        output.push_str(&format!("      β€’ {} (confidence: {:.1}%)\n", tech.name, tech.confidence * 100.0));
279                        if let Some(version) = &tech.version {
280                            output.push_str(&format!("        Version: {}\n", version));
281                        }
282                    }
283                }
284            }
285            _ => {} // Other categories already handled in the array
286        }
287    }
288    
289    output
290}
291
292/// Helper function for legacy Docker analysis display
293pub fn display_docker_analysis_detailed_legacy(docker_analysis: &DockerAnalysis) {
294    println!("\n   🐳 Docker Infrastructure Analysis:");
295    
296    // Dockerfiles
297    if !docker_analysis.dockerfiles.is_empty() {
298        println!("      πŸ“„ Dockerfiles ({}):", docker_analysis.dockerfiles.len());
299        for dockerfile in &docker_analysis.dockerfiles {
300            println!("         β€’ {}", dockerfile.path.display());
301            if let Some(env) = &dockerfile.environment {
302                println!("           Environment: {}", env);
303            }
304            if let Some(base_image) = &dockerfile.base_image {
305                println!("           Base image: {}", base_image);
306            }
307            if !dockerfile.exposed_ports.is_empty() {
308                println!("           Exposed ports: {}", 
309                    dockerfile.exposed_ports.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", "));
310            }
311            if dockerfile.is_multistage {
312                println!("           Multi-stage build: {} stages", dockerfile.build_stages.len());
313            }
314            println!("           Instructions: {}", dockerfile.instruction_count);
315        }
316    }
317    
318    // Compose files
319    if !docker_analysis.compose_files.is_empty() {
320        println!("      πŸ“‹ Compose Files ({}):", docker_analysis.compose_files.len());
321        for compose_file in &docker_analysis.compose_files {
322            println!("         β€’ {}", compose_file.path.display());
323            if let Some(env) = &compose_file.environment {
324                println!("           Environment: {}", env);
325            }
326            if let Some(version) = &compose_file.version {
327                println!("           Version: {}", version);
328            }
329            if !compose_file.service_names.is_empty() {
330                println!("           Services: {}", compose_file.service_names.join(", "));
331            }
332            if !compose_file.networks.is_empty() {
333                println!("           Networks: {}", compose_file.networks.join(", "));
334            }
335            if !compose_file.volumes.is_empty() {
336                println!("           Volumes: {}", compose_file.volumes.join(", "));
337            }
338        }
339    }
340    
341    // Rest of the detailed Docker display...
342    println!("      πŸ—οΈ  Orchestration Pattern: {:?}", docker_analysis.orchestration_pattern);
343    match docker_analysis.orchestration_pattern {
344        OrchestrationPattern::SingleContainer => {
345            println!("         Simple containerized application");
346        }
347        OrchestrationPattern::DockerCompose => {
348            println!("         Multi-service Docker Compose setup");
349        }
350        OrchestrationPattern::Microservices => {
351            println!("         Microservices architecture with service discovery");
352        }
353        OrchestrationPattern::EventDriven => {
354            println!("         Event-driven architecture with message queues");
355        }
356        OrchestrationPattern::ServiceMesh => {
357            println!("         Service mesh for advanced service communication");
358        }
359        OrchestrationPattern::Mixed => {
360            println!("         Mixed/complex orchestration pattern");
361        }
362    }
363}
364
365/// Helper function for legacy Docker analysis display - returns string
366pub fn display_docker_analysis_detailed_legacy_to_string(docker_analysis: &DockerAnalysis) -> String {
367    let mut output = String::new();
368    
369    output.push_str("\n   🐳 Docker Infrastructure Analysis:\n");
370    
371    // Dockerfiles
372    if !docker_analysis.dockerfiles.is_empty() {
373        output.push_str(&format!("      πŸ“„ Dockerfiles ({}):\n", docker_analysis.dockerfiles.len()));
374        for dockerfile in &docker_analysis.dockerfiles {
375            output.push_str(&format!("         β€’ {}\n", dockerfile.path.display()));
376            if let Some(env) = &dockerfile.environment {
377                output.push_str(&format!("           Environment: {}\n", env));
378            }
379            if let Some(base_image) = &dockerfile.base_image {
380                output.push_str(&format!("           Base image: {}\n", base_image));
381            }
382            if !dockerfile.exposed_ports.is_empty() {
383                output.push_str(&format!("           Exposed ports: {}\n", 
384                    dockerfile.exposed_ports.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", ")));
385            }
386            if dockerfile.is_multistage {
387                output.push_str(&format!("           Multi-stage build: {} stages\n", dockerfile.build_stages.len()));
388            }
389            output.push_str(&format!("           Instructions: {}\n", dockerfile.instruction_count));
390        }
391    }
392    
393    // Compose files
394    if !docker_analysis.compose_files.is_empty() {
395        output.push_str(&format!("      πŸ“‹ Compose Files ({}):\n", docker_analysis.compose_files.len()));
396        for compose_file in &docker_analysis.compose_files {
397            output.push_str(&format!("         β€’ {}\n", compose_file.path.display()));
398            if let Some(env) = &compose_file.environment {
399                output.push_str(&format!("           Environment: {}\n", env));
400            }
401            if let Some(version) = &compose_file.version {
402                output.push_str(&format!("           Version: {}\n", version));
403            }
404            if !compose_file.service_names.is_empty() {
405                output.push_str(&format!("           Services: {}\n", compose_file.service_names.join(", ")));
406            }
407            if !compose_file.networks.is_empty() {
408                output.push_str(&format!("           Networks: {}\n", compose_file.networks.join(", ")));
409            }
410            if !compose_file.volumes.is_empty() {
411                output.push_str(&format!("           Volumes: {}\n", compose_file.volumes.join(", ")));
412            }
413        }
414    }
415    
416    // Rest of the detailed Docker display...
417    output.push_str(&format!("      πŸ—οΈ  Orchestration Pattern: {:?}\n", docker_analysis.orchestration_pattern));
418    match docker_analysis.orchestration_pattern {
419        OrchestrationPattern::SingleContainer => {
420            output.push_str("         Simple containerized application\n");
421        }
422        OrchestrationPattern::DockerCompose => {
423            output.push_str("         Multi-service Docker Compose setup\n");
424        }
425        OrchestrationPattern::Microservices => {
426            output.push_str("         Microservices architecture with service discovery\n");
427        }
428        OrchestrationPattern::EventDriven => {
429            output.push_str("         Event-driven architecture with message queues\n");
430        }
431        OrchestrationPattern::ServiceMesh => {
432            output.push_str("         Service mesh for advanced service communication\n");
433        }
434        OrchestrationPattern::Mixed => {
435            output.push_str("         Mixed/complex orchestration pattern\n");
436        }
437    }
438    
439    output
440}