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.entry(&tech.category).or_default().push(tech);
157    }
158
159    // Find and display primary technology
160    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
161        println!("\nπŸ› οΈ  Technology Stack:");
162        println!(
163            "   🎯 PRIMARY: {} (confidence: {:.1}%)",
164            primary.name,
165            primary.confidence * 100.0
166        );
167        println!("      Architecture driver for this project");
168    }
169
170    // Display categories in order
171    let categories = [
172        (TechnologyCategory::MetaFramework, "πŸ—οΈ  Meta-Frameworks"),
173        (
174            TechnologyCategory::BackendFramework,
175            "πŸ–₯️  Backend Frameworks",
176        ),
177        (
178            TechnologyCategory::FrontendFramework,
179            "🎨 Frontend Frameworks",
180        ),
181        (
182            TechnologyCategory::Library(LibraryType::UI),
183            "🎨 UI Libraries",
184        ),
185        (
186            TechnologyCategory::Library(LibraryType::Utility),
187            "πŸ“š Core Libraries",
188        ),
189        (TechnologyCategory::BuildTool, "πŸ”¨ Build Tools"),
190        (TechnologyCategory::PackageManager, "πŸ“¦ Package Managers"),
191        (TechnologyCategory::Database, "πŸ—ƒοΈ  Database & ORM"),
192        (TechnologyCategory::Runtime, "⚑ Runtimes"),
193        (TechnologyCategory::Testing, "πŸ§ͺ Testing"),
194    ];
195
196    for (category, label) in &categories {
197        if let Some(techs) = by_category.get(category)
198            && !techs.is_empty()
199        {
200            println!("\n   {}:", label);
201            for tech in techs {
202                println!(
203                    "      β€’ {} (confidence: {:.1}%)",
204                    tech.name,
205                    tech.confidence * 100.0
206                );
207                if let Some(version) = &tech.version {
208                    println!("        Version: {}", version);
209                }
210            }
211        }
212    }
213
214    // Handle other Library types separately
215    for (cat, techs) in &by_category {
216        if let TechnologyCategory::Library(lib_type) = cat {
217            let label = match lib_type {
218                LibraryType::StateManagement => "πŸ”„ State Management",
219                LibraryType::DataFetching => "πŸ”ƒ Data Fetching",
220                LibraryType::Routing => "πŸ—ΊοΈ  Routing",
221                LibraryType::Styling => "🎨 Styling",
222                LibraryType::HttpClient => "🌐 HTTP Clients",
223                LibraryType::Authentication => "πŸ” Authentication",
224                LibraryType::Other(_) => "πŸ“¦ Other Libraries",
225                _ => continue, // Skip already handled UI and Utility
226            };
227
228            // Only print if not already handled above
229            if !matches!(lib_type, LibraryType::UI | LibraryType::Utility) && !techs.is_empty() {
230                println!("\n   {}:", label);
231                for tech in techs {
232                    println!(
233                        "      β€’ {} (confidence: {:.1}%)",
234                        tech.name,
235                        tech.confidence * 100.0
236                    );
237                    if let Some(version) = &tech.version {
238                        println!("        Version: {}", version);
239                    }
240                }
241            }
242        }
243    }
244}
245
246/// Helper function for legacy detailed technology display - returns string
247pub fn display_technologies_detailed_legacy_to_string(
248    technologies: &[DetectedTechnology],
249) -> String {
250    let mut output = String::new();
251
252    // Group technologies by category
253    let mut by_category: std::collections::HashMap<&TechnologyCategory, Vec<&DetectedTechnology>> =
254        std::collections::HashMap::new();
255
256    for tech in technologies {
257        by_category.entry(&tech.category).or_default().push(tech);
258    }
259
260    // Find and display primary technology
261    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
262        output.push_str("\nπŸ› οΈ  Technology Stack:\n");
263        output.push_str(&format!(
264            "   🎯 PRIMARY: {} (confidence: {:.1}%)\n",
265            primary.name,
266            primary.confidence * 100.0
267        ));
268        output.push_str("      Architecture driver for this project\n");
269    }
270
271    // Display categories in order
272    let categories = [
273        (TechnologyCategory::MetaFramework, "πŸ—οΈ  Meta-Frameworks"),
274        (
275            TechnologyCategory::BackendFramework,
276            "πŸ–₯️  Backend Frameworks",
277        ),
278        (
279            TechnologyCategory::FrontendFramework,
280            "🎨 Frontend Frameworks",
281        ),
282        (
283            TechnologyCategory::Library(LibraryType::UI),
284            "🎨 UI Libraries",
285        ),
286        (
287            TechnologyCategory::Library(LibraryType::Utility),
288            "πŸ“š Core Libraries",
289        ),
290        (TechnologyCategory::BuildTool, "πŸ”¨ Build Tools"),
291        (TechnologyCategory::PackageManager, "πŸ“¦ Package Managers"),
292        (TechnologyCategory::Database, "πŸ—ƒοΈ  Database & ORM"),
293        (TechnologyCategory::Runtime, "⚑ Runtimes"),
294        (TechnologyCategory::Testing, "πŸ§ͺ Testing"),
295    ];
296
297    for (category, label) in &categories {
298        if let Some(techs) = by_category.get(category)
299            && !techs.is_empty()
300        {
301            output.push_str(&format!("\n   {}:\n", label));
302            for tech in techs {
303                output.push_str(&format!(
304                    "      β€’ {} (confidence: {:.1}%)\n",
305                    tech.name,
306                    tech.confidence * 100.0
307                ));
308                if let Some(version) = &tech.version {
309                    output.push_str(&format!("        Version: {}\n", version));
310                }
311            }
312        }
313    }
314
315    // Handle other Library types separately
316    for (cat, techs) in &by_category {
317        if let TechnologyCategory::Library(lib_type) = cat {
318            let label = match lib_type {
319                LibraryType::StateManagement => "πŸ”„ State Management",
320                LibraryType::DataFetching => "πŸ”ƒ Data Fetching",
321                LibraryType::Routing => "πŸ—ΊοΈ  Routing",
322                LibraryType::Styling => "🎨 Styling",
323                LibraryType::HttpClient => "🌐 HTTP Clients",
324                LibraryType::Authentication => "πŸ” Authentication",
325                LibraryType::Other(_) => "πŸ“¦ Other Libraries",
326                _ => continue, // Skip already handled UI and Utility
327            };
328
329            // Only print if not already handled above
330            if !matches!(lib_type, LibraryType::UI | LibraryType::Utility) && !techs.is_empty() {
331                output.push_str(&format!("\n   {}:\n", label));
332                for tech in techs {
333                    output.push_str(&format!(
334                        "      β€’ {} (confidence: {:.1}%)\n",
335                        tech.name,
336                        tech.confidence * 100.0
337                    ));
338                    if let Some(version) = &tech.version {
339                        output.push_str(&format!("        Version: {}\n", version));
340                    }
341                }
342            }
343        }
344    }
345
346    output
347}
348
349/// Helper function for legacy Docker analysis display
350pub fn display_docker_analysis_detailed_legacy(docker_analysis: &DockerAnalysis) {
351    println!("\n   🐳 Docker Infrastructure Analysis:");
352
353    // Dockerfiles
354    if !docker_analysis.dockerfiles.is_empty() {
355        println!(
356            "      πŸ“„ Dockerfiles ({}):",
357            docker_analysis.dockerfiles.len()
358        );
359        for dockerfile in &docker_analysis.dockerfiles {
360            println!("         β€’ {}", dockerfile.path.display());
361            if let Some(env) = &dockerfile.environment {
362                println!("           Environment: {}", env);
363            }
364            if let Some(base_image) = &dockerfile.base_image {
365                println!("           Base image: {}", base_image);
366            }
367            if !dockerfile.exposed_ports.is_empty() {
368                println!(
369                    "           Exposed ports: {}",
370                    dockerfile
371                        .exposed_ports
372                        .iter()
373                        .map(|p| p.to_string())
374                        .collect::<Vec<_>>()
375                        .join(", ")
376                );
377            }
378            if dockerfile.is_multistage {
379                println!(
380                    "           Multi-stage build: {} stages",
381                    dockerfile.build_stages.len()
382                );
383            }
384            println!("           Instructions: {}", dockerfile.instruction_count);
385        }
386    }
387
388    // Compose files
389    if !docker_analysis.compose_files.is_empty() {
390        println!(
391            "      πŸ“‹ Compose Files ({}):",
392            docker_analysis.compose_files.len()
393        );
394        for compose_file in &docker_analysis.compose_files {
395            println!("         β€’ {}", compose_file.path.display());
396            if let Some(env) = &compose_file.environment {
397                println!("           Environment: {}", env);
398            }
399            if let Some(version) = &compose_file.version {
400                println!("           Version: {}", version);
401            }
402            if !compose_file.service_names.is_empty() {
403                println!(
404                    "           Services: {}",
405                    compose_file.service_names.join(", ")
406                );
407            }
408            if !compose_file.networks.is_empty() {
409                println!("           Networks: {}", compose_file.networks.join(", "));
410            }
411            if !compose_file.volumes.is_empty() {
412                println!("           Volumes: {}", compose_file.volumes.join(", "));
413            }
414        }
415    }
416
417    // Rest of the detailed Docker display...
418    println!(
419        "      πŸ—οΈ  Orchestration Pattern: {:?}",
420        docker_analysis.orchestration_pattern
421    );
422    match docker_analysis.orchestration_pattern {
423        OrchestrationPattern::SingleContainer => {
424            println!("         Simple containerized application");
425        }
426        OrchestrationPattern::DockerCompose => {
427            println!("         Multi-service Docker Compose setup");
428        }
429        OrchestrationPattern::Microservices => {
430            println!("         Microservices architecture with service discovery");
431        }
432        OrchestrationPattern::EventDriven => {
433            println!("         Event-driven architecture with message queues");
434        }
435        OrchestrationPattern::ServiceMesh => {
436            println!("         Service mesh for advanced service communication");
437        }
438        OrchestrationPattern::Mixed => {
439            println!("         Mixed/complex orchestration pattern");
440        }
441    }
442}
443
444/// Helper function for legacy Docker analysis display - returns string
445pub fn display_docker_analysis_detailed_legacy_to_string(
446    docker_analysis: &DockerAnalysis,
447) -> String {
448    let mut output = String::new();
449
450    output.push_str("\n   🐳 Docker Infrastructure Analysis:\n");
451
452    // Dockerfiles
453    if !docker_analysis.dockerfiles.is_empty() {
454        output.push_str(&format!(
455            "      πŸ“„ Dockerfiles ({}):\n",
456            docker_analysis.dockerfiles.len()
457        ));
458        for dockerfile in &docker_analysis.dockerfiles {
459            output.push_str(&format!("         β€’ {}\n", dockerfile.path.display()));
460            if let Some(env) = &dockerfile.environment {
461                output.push_str(&format!("           Environment: {}\n", env));
462            }
463            if let Some(base_image) = &dockerfile.base_image {
464                output.push_str(&format!("           Base image: {}\n", base_image));
465            }
466            if !dockerfile.exposed_ports.is_empty() {
467                output.push_str(&format!(
468                    "           Exposed ports: {}\n",
469                    dockerfile
470                        .exposed_ports
471                        .iter()
472                        .map(|p| p.to_string())
473                        .collect::<Vec<_>>()
474                        .join(", ")
475                ));
476            }
477            if dockerfile.is_multistage {
478                output.push_str(&format!(
479                    "           Multi-stage build: {} stages\n",
480                    dockerfile.build_stages.len()
481                ));
482            }
483            output.push_str(&format!(
484                "           Instructions: {}\n",
485                dockerfile.instruction_count
486            ));
487        }
488    }
489
490    // Compose files
491    if !docker_analysis.compose_files.is_empty() {
492        output.push_str(&format!(
493            "      πŸ“‹ Compose Files ({}):\n",
494            docker_analysis.compose_files.len()
495        ));
496        for compose_file in &docker_analysis.compose_files {
497            output.push_str(&format!("         β€’ {}\n", compose_file.path.display()));
498            if let Some(env) = &compose_file.environment {
499                output.push_str(&format!("           Environment: {}\n", env));
500            }
501            if let Some(version) = &compose_file.version {
502                output.push_str(&format!("           Version: {}\n", version));
503            }
504            if !compose_file.service_names.is_empty() {
505                output.push_str(&format!(
506                    "           Services: {}\n",
507                    compose_file.service_names.join(", ")
508                ));
509            }
510            if !compose_file.networks.is_empty() {
511                output.push_str(&format!(
512                    "           Networks: {}\n",
513                    compose_file.networks.join(", ")
514                ));
515            }
516            if !compose_file.volumes.is_empty() {
517                output.push_str(&format!(
518                    "           Volumes: {}\n",
519                    compose_file.volumes.join(", ")
520                ));
521            }
522        }
523    }
524
525    // Rest of the detailed Docker display...
526    output.push_str(&format!(
527        "      πŸ—οΈ  Orchestration Pattern: {:?}\n",
528        docker_analysis.orchestration_pattern
529    ));
530    match docker_analysis.orchestration_pattern {
531        OrchestrationPattern::SingleContainer => {
532            output.push_str("         Simple containerized application\n");
533        }
534        OrchestrationPattern::DockerCompose => {
535            output.push_str("         Multi-service Docker Compose setup\n");
536        }
537        OrchestrationPattern::Microservices => {
538            output.push_str("         Microservices architecture with service discovery\n");
539        }
540        OrchestrationPattern::EventDriven => {
541            output.push_str("         Event-driven architecture with message queues\n");
542        }
543        OrchestrationPattern::ServiceMesh => {
544            output.push_str("         Service mesh for advanced service communication\n");
545        }
546        OrchestrationPattern::Mixed => {
547            output.push_str("         Mixed/complex orchestration pattern\n");
548        }
549    }
550
551    output
552}