syncable_cli/analyzer/display/
helpers.rs

1//! Helper functions for display formatting
2
3use crate::analyzer::display::BoxDrawer;
4use crate::analyzer::{
5    ArchitecturePattern, DetectedTechnology, DockerAnalysis, LibraryType, OrchestrationPattern,
6    ProjectCategory, TechnologyCategory,
7};
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!(
51                "   πŸ”— This is a microservices architecture with multiple independent services"
52            );
53        }
54        ArchitecturePattern::ApiFirst => {
55            println!("   πŸ”Œ This is an API-first architecture focused on service interfaces");
56        }
57        ArchitecturePattern::EventDriven => {
58            println!("   πŸ“‘ This is an event-driven architecture with decoupled components");
59        }
60        ArchitecturePattern::Mixed => {
61            println!("   πŸ”€ This is a mixed architecture combining multiple patterns");
62        }
63    }
64}
65
66/// Helper function for displaying architecture description - returns string
67pub fn display_architecture_description_to_string(pattern: &ArchitecturePattern) -> String {
68    match pattern {
69        ArchitecturePattern::Monolithic => {
70            "   πŸ“¦ This is a single, self-contained application\n".to_string()
71        }
72        ArchitecturePattern::Fullstack => {
73            "   🌐 This is a full-stack application with separate frontend and backend\n"
74                .to_string()
75        }
76        ArchitecturePattern::Microservices => {
77            "   πŸ”— This is a microservices architecture with multiple independent services\n"
78                .to_string()
79        }
80        ArchitecturePattern::ApiFirst => {
81            "   πŸ”Œ This is an API-first architecture focused on service interfaces\n".to_string()
82        }
83        ArchitecturePattern::EventDriven => {
84            "   πŸ“‘ This is an event-driven architecture with decoupled components\n".to_string()
85        }
86        ArchitecturePattern::Mixed => {
87            "   πŸ”€ This is a mixed architecture combining multiple patterns\n".to_string()
88        }
89    }
90}
91
92/// Get main technologies for display
93pub fn get_main_technologies(technologies: &[DetectedTechnology]) -> String {
94    let primary = technologies.iter().find(|t| t.is_primary);
95    let frameworks: Vec<_> = technologies
96        .iter()
97        .filter(|t| {
98            matches!(
99                t.category,
100                TechnologyCategory::FrontendFramework | TechnologyCategory::MetaFramework
101            )
102        })
103        .take(2)
104        .collect();
105
106    let mut result = Vec::new();
107
108    if let Some(p) = primary {
109        result.push(p.name.clone());
110    }
111
112    for f in frameworks {
113        if Some(&f.name) != primary.map(|p| &p.name) {
114            result.push(f.name.clone());
115        }
116    }
117
118    if result.is_empty() {
119        "-".to_string()
120    } else {
121        result.join(", ")
122    }
123}
124
125/// Add confidence score as a progress bar to the box drawer
126pub fn add_confidence_bar_to_drawer(score: f32, box_drawer: &mut BoxDrawer) {
127    let percentage = (score * 100.0) as u8;
128    let bar_width = 20;
129    let filled = ((score * bar_width as f32) as usize).min(bar_width);
130
131    let bar = format!(
132        "{}{}",
133        "β–ˆ".repeat(filled).green(),
134        "β–‘".repeat(bar_width - filled).dimmed()
135    );
136
137    let color = if percentage >= 80 {
138        "green"
139    } else if percentage >= 60 {
140        "yellow"
141    } else {
142        "red"
143    };
144
145    let confidence_info = format!("{} {}", bar, format!("{:.0}%", percentage).color(color));
146    box_drawer.add_line("Confidence:", &confidence_info, true);
147}
148
149/// Helper function for legacy detailed technology display
150pub fn display_technologies_detailed_legacy(technologies: &[DetectedTechnology]) {
151    // Group technologies by category
152    let mut by_category: std::collections::HashMap<&TechnologyCategory, Vec<&DetectedTechnology>> =
153        std::collections::HashMap::new();
154
155    for tech in technologies {
156        by_category
157            .entry(&tech.category)
158            .or_insert_with(Vec::new)
159            .push(tech);
160    }
161
162    // Find and display primary technology
163    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
164        println!("\nπŸ› οΈ  Technology Stack:");
165        println!(
166            "   🎯 PRIMARY: {} (confidence: {:.1}%)",
167            primary.name,
168            primary.confidence * 100.0
169        );
170        println!("      Architecture driver for this project");
171    }
172
173    // Display categories in order
174    let categories = [
175        (TechnologyCategory::MetaFramework, "πŸ—οΈ  Meta-Frameworks"),
176        (
177            TechnologyCategory::BackendFramework,
178            "πŸ–₯️  Backend Frameworks",
179        ),
180        (
181            TechnologyCategory::FrontendFramework,
182            "🎨 Frontend Frameworks",
183        ),
184        (
185            TechnologyCategory::Library(LibraryType::UI),
186            "🎨 UI Libraries",
187        ),
188        (
189            TechnologyCategory::Library(LibraryType::Utility),
190            "πŸ“š Core Libraries",
191        ),
192        (TechnologyCategory::BuildTool, "πŸ”¨ Build Tools"),
193        (TechnologyCategory::PackageManager, "πŸ“¦ Package Managers"),
194        (TechnologyCategory::Database, "πŸ—ƒοΈ  Database & ORM"),
195        (TechnologyCategory::Runtime, "⚑ Runtimes"),
196        (TechnologyCategory::Testing, "πŸ§ͺ Testing"),
197    ];
198
199    for (category, label) in &categories {
200        if let Some(techs) = by_category.get(category) {
201            if !techs.is_empty() {
202                println!("\n   {}:", label);
203                for tech in techs {
204                    println!(
205                        "      β€’ {} (confidence: {:.1}%)",
206                        tech.name,
207                        tech.confidence * 100.0
208                    );
209                    if let Some(version) = &tech.version {
210                        println!("        Version: {}", version);
211                    }
212                }
213            }
214        }
215    }
216
217    // Handle other Library types separately
218    for (cat, techs) in &by_category {
219        match cat {
220            TechnologyCategory::Library(lib_type) => {
221                let label = match lib_type {
222                    LibraryType::StateManagement => "πŸ”„ State Management",
223                    LibraryType::DataFetching => "πŸ”ƒ Data Fetching",
224                    LibraryType::Routing => "πŸ—ΊοΈ  Routing",
225                    LibraryType::Styling => "🎨 Styling",
226                    LibraryType::HttpClient => "🌐 HTTP Clients",
227                    LibraryType::Authentication => "πŸ” Authentication",
228                    LibraryType::Other(_) => "πŸ“¦ Other Libraries",
229                    _ => continue, // Skip already handled UI and Utility
230                };
231
232                // Only print if not already handled above
233                if !matches!(lib_type, LibraryType::UI | LibraryType::Utility) && !techs.is_empty()
234                {
235                    println!("\n   {}:", label);
236                    for tech in techs {
237                        println!(
238                            "      β€’ {} (confidence: {:.1}%)",
239                            tech.name,
240                            tech.confidence * 100.0
241                        );
242                        if let Some(version) = &tech.version {
243                            println!("        Version: {}", version);
244                        }
245                    }
246                }
247            }
248            _ => {} // Other categories already handled in the array
249        }
250    }
251}
252
253/// Helper function for legacy detailed technology display - returns string
254pub fn display_technologies_detailed_legacy_to_string(
255    technologies: &[DetectedTechnology],
256) -> String {
257    let mut output = String::new();
258
259    // Group technologies by category
260    let mut by_category: std::collections::HashMap<&TechnologyCategory, Vec<&DetectedTechnology>> =
261        std::collections::HashMap::new();
262
263    for tech in technologies {
264        by_category
265            .entry(&tech.category)
266            .or_insert_with(Vec::new)
267            .push(tech);
268    }
269
270    // Find and display primary technology
271    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
272        output.push_str("\nπŸ› οΈ  Technology Stack:\n");
273        output.push_str(&format!(
274            "   🎯 PRIMARY: {} (confidence: {:.1}%)\n",
275            primary.name,
276            primary.confidence * 100.0
277        ));
278        output.push_str("      Architecture driver for this project\n");
279    }
280
281    // Display categories in order
282    let categories = [
283        (TechnologyCategory::MetaFramework, "πŸ—οΈ  Meta-Frameworks"),
284        (
285            TechnologyCategory::BackendFramework,
286            "πŸ–₯️  Backend Frameworks",
287        ),
288        (
289            TechnologyCategory::FrontendFramework,
290            "🎨 Frontend Frameworks",
291        ),
292        (
293            TechnologyCategory::Library(LibraryType::UI),
294            "🎨 UI Libraries",
295        ),
296        (
297            TechnologyCategory::Library(LibraryType::Utility),
298            "πŸ“š Core Libraries",
299        ),
300        (TechnologyCategory::BuildTool, "πŸ”¨ Build Tools"),
301        (TechnologyCategory::PackageManager, "πŸ“¦ Package Managers"),
302        (TechnologyCategory::Database, "πŸ—ƒοΈ  Database & ORM"),
303        (TechnologyCategory::Runtime, "⚑ Runtimes"),
304        (TechnologyCategory::Testing, "πŸ§ͺ Testing"),
305    ];
306
307    for (category, label) in &categories {
308        if let Some(techs) = by_category.get(category) {
309            if !techs.is_empty() {
310                output.push_str(&format!("\n   {}:\n", label));
311                for tech in techs {
312                    output.push_str(&format!(
313                        "      β€’ {} (confidence: {:.1}%)\n",
314                        tech.name,
315                        tech.confidence * 100.0
316                    ));
317                    if let Some(version) = &tech.version {
318                        output.push_str(&format!("        Version: {}\n", version));
319                    }
320                }
321            }
322        }
323    }
324
325    // Handle other Library types separately
326    for (cat, techs) in &by_category {
327        match cat {
328            TechnologyCategory::Library(lib_type) => {
329                let label = match lib_type {
330                    LibraryType::StateManagement => "πŸ”„ State Management",
331                    LibraryType::DataFetching => "πŸ”ƒ Data Fetching",
332                    LibraryType::Routing => "πŸ—ΊοΈ  Routing",
333                    LibraryType::Styling => "🎨 Styling",
334                    LibraryType::HttpClient => "🌐 HTTP Clients",
335                    LibraryType::Authentication => "πŸ” Authentication",
336                    LibraryType::Other(_) => "πŸ“¦ Other Libraries",
337                    _ => continue, // Skip already handled UI and Utility
338                };
339
340                // Only print if not already handled above
341                if !matches!(lib_type, LibraryType::UI | LibraryType::Utility) && !techs.is_empty()
342                {
343                    output.push_str(&format!("\n   {}:\n", label));
344                    for tech in techs {
345                        output.push_str(&format!(
346                            "      β€’ {} (confidence: {:.1}%)\n",
347                            tech.name,
348                            tech.confidence * 100.0
349                        ));
350                        if let Some(version) = &tech.version {
351                            output.push_str(&format!("        Version: {}\n", version));
352                        }
353                    }
354                }
355            }
356            _ => {} // Other categories already handled in the array
357        }
358    }
359
360    output
361}
362
363/// Helper function for legacy Docker analysis display
364pub fn display_docker_analysis_detailed_legacy(docker_analysis: &DockerAnalysis) {
365    println!("\n   🐳 Docker Infrastructure Analysis:");
366
367    // Dockerfiles
368    if !docker_analysis.dockerfiles.is_empty() {
369        println!(
370            "      πŸ“„ Dockerfiles ({}):",
371            docker_analysis.dockerfiles.len()
372        );
373        for dockerfile in &docker_analysis.dockerfiles {
374            println!("         β€’ {}", dockerfile.path.display());
375            if let Some(env) = &dockerfile.environment {
376                println!("           Environment: {}", env);
377            }
378            if let Some(base_image) = &dockerfile.base_image {
379                println!("           Base image: {}", base_image);
380            }
381            if !dockerfile.exposed_ports.is_empty() {
382                println!(
383                    "           Exposed ports: {}",
384                    dockerfile
385                        .exposed_ports
386                        .iter()
387                        .map(|p| p.to_string())
388                        .collect::<Vec<_>>()
389                        .join(", ")
390                );
391            }
392            if dockerfile.is_multistage {
393                println!(
394                    "           Multi-stage build: {} stages",
395                    dockerfile.build_stages.len()
396                );
397            }
398            println!("           Instructions: {}", dockerfile.instruction_count);
399        }
400    }
401
402    // Compose files
403    if !docker_analysis.compose_files.is_empty() {
404        println!(
405            "      πŸ“‹ Compose Files ({}):",
406            docker_analysis.compose_files.len()
407        );
408        for compose_file in &docker_analysis.compose_files {
409            println!("         β€’ {}", compose_file.path.display());
410            if let Some(env) = &compose_file.environment {
411                println!("           Environment: {}", env);
412            }
413            if let Some(version) = &compose_file.version {
414                println!("           Version: {}", version);
415            }
416            if !compose_file.service_names.is_empty() {
417                println!(
418                    "           Services: {}",
419                    compose_file.service_names.join(", ")
420                );
421            }
422            if !compose_file.networks.is_empty() {
423                println!("           Networks: {}", compose_file.networks.join(", "));
424            }
425            if !compose_file.volumes.is_empty() {
426                println!("           Volumes: {}", compose_file.volumes.join(", "));
427            }
428        }
429    }
430
431    // Rest of the detailed Docker display...
432    println!(
433        "      πŸ—οΈ  Orchestration Pattern: {:?}",
434        docker_analysis.orchestration_pattern
435    );
436    match docker_analysis.orchestration_pattern {
437        OrchestrationPattern::SingleContainer => {
438            println!("         Simple containerized application");
439        }
440        OrchestrationPattern::DockerCompose => {
441            println!("         Multi-service Docker Compose setup");
442        }
443        OrchestrationPattern::Microservices => {
444            println!("         Microservices architecture with service discovery");
445        }
446        OrchestrationPattern::EventDriven => {
447            println!("         Event-driven architecture with message queues");
448        }
449        OrchestrationPattern::ServiceMesh => {
450            println!("         Service mesh for advanced service communication");
451        }
452        OrchestrationPattern::Mixed => {
453            println!("         Mixed/complex orchestration pattern");
454        }
455    }
456}
457
458/// Helper function for legacy Docker analysis display - returns string
459pub fn display_docker_analysis_detailed_legacy_to_string(
460    docker_analysis: &DockerAnalysis,
461) -> String {
462    let mut output = String::new();
463
464    output.push_str("\n   🐳 Docker Infrastructure Analysis:\n");
465
466    // Dockerfiles
467    if !docker_analysis.dockerfiles.is_empty() {
468        output.push_str(&format!(
469            "      πŸ“„ Dockerfiles ({}):\n",
470            docker_analysis.dockerfiles.len()
471        ));
472        for dockerfile in &docker_analysis.dockerfiles {
473            output.push_str(&format!("         β€’ {}\n", dockerfile.path.display()));
474            if let Some(env) = &dockerfile.environment {
475                output.push_str(&format!("           Environment: {}\n", env));
476            }
477            if let Some(base_image) = &dockerfile.base_image {
478                output.push_str(&format!("           Base image: {}\n", base_image));
479            }
480            if !dockerfile.exposed_ports.is_empty() {
481                output.push_str(&format!(
482                    "           Exposed ports: {}\n",
483                    dockerfile
484                        .exposed_ports
485                        .iter()
486                        .map(|p| p.to_string())
487                        .collect::<Vec<_>>()
488                        .join(", ")
489                ));
490            }
491            if dockerfile.is_multistage {
492                output.push_str(&format!(
493                    "           Multi-stage build: {} stages\n",
494                    dockerfile.build_stages.len()
495                ));
496            }
497            output.push_str(&format!(
498                "           Instructions: {}\n",
499                dockerfile.instruction_count
500            ));
501        }
502    }
503
504    // Compose files
505    if !docker_analysis.compose_files.is_empty() {
506        output.push_str(&format!(
507            "      πŸ“‹ Compose Files ({}):\n",
508            docker_analysis.compose_files.len()
509        ));
510        for compose_file in &docker_analysis.compose_files {
511            output.push_str(&format!("         β€’ {}\n", compose_file.path.display()));
512            if let Some(env) = &compose_file.environment {
513                output.push_str(&format!("           Environment: {}\n", env));
514            }
515            if let Some(version) = &compose_file.version {
516                output.push_str(&format!("           Version: {}\n", version));
517            }
518            if !compose_file.service_names.is_empty() {
519                output.push_str(&format!(
520                    "           Services: {}\n",
521                    compose_file.service_names.join(", ")
522                ));
523            }
524            if !compose_file.networks.is_empty() {
525                output.push_str(&format!(
526                    "           Networks: {}\n",
527                    compose_file.networks.join(", ")
528                ));
529            }
530            if !compose_file.volumes.is_empty() {
531                output.push_str(&format!(
532                    "           Volumes: {}\n",
533                    compose_file.volumes.join(", ")
534                ));
535            }
536        }
537    }
538
539    // Rest of the detailed Docker display...
540    output.push_str(&format!(
541        "      πŸ—οΈ  Orchestration Pattern: {:?}\n",
542        docker_analysis.orchestration_pattern
543    ));
544    match docker_analysis.orchestration_pattern {
545        OrchestrationPattern::SingleContainer => {
546            output.push_str("         Simple containerized application\n");
547        }
548        OrchestrationPattern::DockerCompose => {
549            output.push_str("         Multi-service Docker Compose setup\n");
550        }
551        OrchestrationPattern::Microservices => {
552            output.push_str("         Microservices architecture with service discovery\n");
553        }
554        OrchestrationPattern::EventDriven => {
555            output.push_str("         Event-driven architecture with message queues\n");
556        }
557        OrchestrationPattern::ServiceMesh => {
558            output.push_str("         Service mesh for advanced service communication\n");
559        }
560        OrchestrationPattern::Mixed => {
561            output.push_str("         Mixed/complex orchestration pattern\n");
562        }
563    }
564
565    output
566}