git_x/adapters/
formatters.rs

1use crate::core::output::Format;
2use crate::domain::{
3    BranchCreationResult, BranchSwitchResult, CleanBranchesResult, HealthLevel, HealthStatus,
4    RepositoryInfo,
5};
6
7/// Formatter for branch operations
8pub struct BranchFormatter;
9
10impl BranchFormatter {
11    pub fn new() -> Self {
12        Self
13    }
14
15    /// Format branch creation result
16    pub fn format_creation_result(&self, result: &BranchCreationResult) -> String {
17        let mut output = Format::success(&format!(
18            "Created and switched to branch '{}'",
19            result.branch_name
20        ));
21
22        if let Some(ref backup) = result.backup_branch {
23            output.push_str(&format!("\n๐Ÿ’พ Backup created: {backup}"));
24        }
25
26        if let Some(ref base) = result.base_commit {
27            output.push_str(&format!("\n๐Ÿ“ Based on: {base}"));
28        }
29
30        output
31    }
32
33    /// Format clean branches result
34    pub fn format_clean_result(&self, result: &CleanBranchesResult) -> String {
35        if result.candidates.is_empty() {
36            return "No merged branches to delete.".to_string();
37        }
38
39        if result.dry_run {
40            let mut output = format!(
41                "๐Ÿงช (dry run) {} branches would be deleted:\n",
42                result.candidates.len()
43            );
44            for branch in &result.candidates {
45                output.push_str(&format!("(dry run) Would delete: {branch}\n"));
46            }
47            output
48        } else {
49            let mut output = format!("๐Ÿงน Deleted {} merged branches:\n", result.deleted.len());
50            for branch in &result.deleted {
51                output.push_str(&format!("โœ… Deleted: {branch}\n"));
52            }
53
54            if !result.failed.is_empty() {
55                output.push_str(&format!(
56                    "\nโŒ Failed to delete {} branches:\n",
57                    result.failed.len()
58                ));
59                for branch in &result.failed {
60                    output.push_str(&format!("โŒ Failed: {branch}\n"));
61                }
62            }
63
64            output
65        }
66    }
67
68    /// Format branch switch result
69    pub fn format_switch_result(&self, result: &BranchSwitchResult) -> String {
70        let mut output = Format::success(&format!("Switched to branch '{}'", result.new_branch));
71
72        if let Some(ref checkpoint) = result.checkpoint {
73            output.push_str(&format!("\n๐Ÿ’พ Checkpoint created: {checkpoint}"));
74        }
75
76        output
77    }
78}
79
80/// Formatter for repository operations
81pub struct RepositoryFormatter;
82
83impl RepositoryFormatter {
84    pub fn new() -> Self {
85        Self
86    }
87
88    /// Format repository information
89    pub fn format_repository_info(&self, info: &RepositoryInfo, detailed: bool) -> String {
90        let mut output = String::new();
91
92        output.push_str(&format!("๐Ÿ—‚๏ธ  Repository: {}\n", Format::bold(&info.name)));
93        output.push_str(&format!(
94            "๐Ÿ“ Current branch: {}\n",
95            Format::bold(&info.current_branch)
96        ));
97
98        // Upstream information
99        if let Some(ref upstream) = info.upstream_branch {
100            if info.is_in_sync() {
101                output.push_str(&format!("๐Ÿ”— Upstream: {upstream} (up to date)\n"));
102            } else {
103                let mut status_parts = Vec::new();
104                if info.ahead_count > 0 {
105                    status_parts.push(format!("{} ahead", info.ahead_count));
106                }
107                if info.behind_count > 0 {
108                    status_parts.push(format!("{} behind", info.behind_count));
109                }
110                output.push_str(&format!(
111                    "๐Ÿ”— Upstream: {} ({})\n",
112                    upstream,
113                    status_parts.join(", ")
114                ));
115            }
116        } else {
117            output.push_str("โŒ No upstream configured\n");
118        }
119
120        // Working directory status
121        if info.is_clean {
122            output.push_str("โœ… Working directory: Clean\n");
123        } else {
124            output.push_str("โš ๏ธ  Working directory: Has changes\n");
125        }
126
127        // Staged files
128        if info.staged_files_count == 0 {
129            output.push_str("๐Ÿ“‹ Staged files: None\n");
130        } else {
131            output.push_str(&format!(
132                "๐Ÿ“‹ Staged files: {} file(s)\n",
133                info.staged_files_count
134            ));
135        }
136
137        // Additional details if requested
138        if detailed {
139            output.push_str(&format!("\n๐Ÿ“‚ Root path: {}\n", info.root_path));
140            output.push_str(&format!("๐Ÿ“Š Status: {}\n", info.status_description()));
141        }
142
143        output
144    }
145
146    /// Format health status
147    pub fn format_health_status(&self, health: &HealthStatus) -> String {
148        let mut output = String::new();
149
150        output.push_str("๐Ÿฅ Repository Health Check\n");
151        output.push_str(&"=".repeat(30));
152        output.push('\n');
153
154        // Overall status
155        match health.level {
156            HealthLevel::Healthy => {
157                output.push_str(&Format::success("Repository is healthy!"));
158            }
159            HealthLevel::Warning => {
160                output.push_str(&Format::warning(&format!(
161                    "Repository has {} warning(s)",
162                    health.warnings.len()
163                )));
164            }
165            HealthLevel::Unhealthy => {
166                output.push_str(&Format::error(&format!(
167                    "Repository has {} issue(s)",
168                    health.issues.len()
169                )));
170            }
171        }
172
173        output.push('\n');
174
175        // List issues
176        if !health.issues.is_empty() {
177            output.push_str("\n๐Ÿšจ Issues:\n");
178            for issue in &health.issues {
179                output.push_str(&format!("   โŒ {issue}\n"));
180            }
181        }
182
183        // List warnings
184        if !health.warnings.is_empty() {
185            output.push_str("\nโš ๏ธ  Warnings:\n");
186            for warning in &health.warnings {
187                output.push_str(&format!("   โš ๏ธ  {warning}\n"));
188            }
189        }
190
191        // Summary
192        output.push_str(&format!("\n๐Ÿ“‹ Summary: {}\n", health.summary()));
193
194        output
195    }
196}
197
198/// Formatter for analysis operations
199pub struct AnalysisFormatter;
200
201impl AnalysisFormatter {
202    pub fn new() -> Self {
203        Self
204    }
205
206    /// Format commit statistics
207    pub fn format_commit_stats(&self, total_commits: u32, period: &str) -> String {
208        Format::info(&format!("๐Ÿ“ˆ {total_commits} commits in {period}"))
209    }
210
211    /// Format contributor information
212    pub fn format_contributors(&self, contributors: &[(String, u32)]) -> String {
213        let mut output = String::new();
214        output.push_str("๐Ÿ‘ฅ Top Contributors:\n");
215
216        for (i, (name, count)) in contributors.iter().enumerate() {
217            let prefix = match i {
218                0 => "๐Ÿฅ‡",
219                1 => "๐Ÿฅˆ",
220                2 => "๐Ÿฅ‰",
221                _ => "๐Ÿ‘ค",
222            };
223            output.push_str(&format!("   {prefix} {name} ({count} commits)\n"));
224        }
225
226        output
227    }
228}
229
230/// Generic formatter utilities
231pub struct FormatterUtils;
232
233impl FormatterUtils {
234    /// Create a section header
235    pub fn section_header(title: &str) -> String {
236        format!("{}\n{}\n", title, "=".repeat(title.len()))
237    }
238
239    /// Create a subsection header
240    pub fn subsection_header(title: &str) -> String {
241        format!("\n{}\n{}\n", title, "-".repeat(title.len()))
242    }
243
244    /// Format a list with bullets
245    pub fn bullet_list(items: &[String], bullet: &str) -> String {
246        items
247            .iter()
248            .map(|item| format!("{bullet} {item}"))
249            .collect::<Vec<_>>()
250            .join("\n")
251    }
252
253    /// Format a numbered list
254    pub fn numbered_list(items: &[String]) -> String {
255        items
256            .iter()
257            .enumerate()
258            .map(|(i, item)| format!("{}. {}", i + 1, item))
259            .collect::<Vec<_>>()
260            .join("\n")
261    }
262}
263
264// Implement Default for formatters
265impl Default for BranchFormatter {
266    fn default() -> Self {
267        Self::new()
268    }
269}
270
271impl Default for RepositoryFormatter {
272    fn default() -> Self {
273        Self::new()
274    }
275}
276
277impl Default for AnalysisFormatter {
278    fn default() -> Self {
279        Self::new()
280    }
281}