1use crate::parser::ParsedPath;
2use crate::{PathConfig, PathResult, PathStyle};
3use std::fmt;
4use std::fmt::Write;
5
6#[derive(Debug, Clone)]
8pub struct PathFormatter {
9 config: PathConfig,
10}
11
12impl PathFormatter {
13 #[must_use]
15 pub fn new(config: &PathConfig) -> Self {
16 Self {
17 config: config.clone(),
18 }
19 }
20
21 pub fn format(&self, parsed: &ParsedPath, target_style: PathStyle) -> PathResult<String> {
27 match target_style {
28 PathStyle::Windows => Ok(self.format_windows(parsed)),
29 PathStyle::Unix => Ok(self.format_unix(parsed)),
30 PathStyle::Auto => {
31 let current_style = super::platform::current_style();
32 self.format(parsed, current_style)
33 }
34 }
35 }
36
37 fn format_windows(&self, parsed: &ParsedPath) -> String {
39 if parsed.is_unc {
40 return Self::format_unc_windows(parsed);
41 }
42
43 let mut result = String::new();
44
45 if let Some(drive) = parsed.drive_letter {
47 let _ = write!(result, "{drive}:");
48 } else if parsed.is_absolute {
49 result.push_str("C:");
51 }
52
53 if parsed.is_absolute && !parsed.is_unc {
55 result.push('\\');
56 }
57
58 for (i, component) in parsed.components.iter().enumerate() {
60 if i > 0 {
61 result.push('\\');
62 }
63 result.push_str(component);
64 }
65
66 if self.config.normalize {
68 result = Self::normalize_windows_path(&result);
69 }
70
71 result
72 }
73
74 fn format_unix(&self, parsed: &ParsedPath) -> String {
76 if parsed.is_unc {
77 return Self::format_unc_unix(parsed);
78 }
79
80 let mut result = String::new();
81
82 if parsed.is_unc {
84 if let (Some(server), Some(share)) = (&parsed.server, &parsed.share) {
85 let _ = write!(result, "//{server}/{share}");
86 }
87 } else if parsed.is_absolute {
88 if parsed.has_drive {
89 if let Some(drive) = parsed.drive_letter {
91 let drive_lower = drive.to_ascii_lowercase();
92 result.push_str(&self.map_drive_to_unix(&format!("{drive_lower}:")));
93 }
94 } else {
95 result.push('/');
96 }
97 }
98
99 for component in &parsed.components {
101 if !result.ends_with('/') && !result.is_empty() {
102 result.push('/');
103 }
104 result.push_str(component);
105 }
106
107 if self.config.normalize {
109 result = Self::normalize_unix_path(&result);
110 }
111
112 result
113 }
114
115 fn format_unc_windows(parsed: &ParsedPath) -> String {
117 let mut result = String::from(r"\\");
118
119 if let Some(server) = &parsed.server {
120 result.push_str(server);
121 }
122
123 result.push('\\');
124
125 if let Some(share) = &parsed.share {
126 result.push_str(share);
127 }
128
129 for component in &parsed.components {
130 result.push('\\');
131 result.push_str(component);
132 }
133
134 result
135 }
136
137 fn format_unc_unix(parsed: &ParsedPath) -> String {
139 let mut result = String::from("//");
140
141 if let Some(server) = &parsed.server {
142 result.push_str(server);
143 }
144
145 result.push('/');
146
147 if let Some(share) = &parsed.share {
148 result.push_str(share);
149 }
150
151 for component in &parsed.components {
152 result.push('/');
153 result.push_str(component);
154 }
155
156 result
157 }
158
159 fn map_drive_to_unix(&self, drive: &str) -> String {
161 for (windows_drive, unix_mount) in &self.config.drive_mappings {
162 if windows_drive == drive {
163 return unix_mount.clone();
164 }
165 }
166
167 let drive_letter = drive.chars().next().unwrap().to_ascii_lowercase();
169 format!("/mnt/{drive_letter}")
170 }
171
172 fn normalize_windows_path(path: &str) -> String {
174 let mut result = path.to_string();
175
176 result = result.replace('/', "\\");
178
179 while result.contains("\\\\") && !result.starts_with(r"\\") {
181 result = result.replace("\\\\", "\\");
182 }
183
184 if result.ends_with('\\') && result.len() > 3 && !result.starts_with(r"\\") {
186 result.pop();
187 }
188
189 result
190 }
191
192 fn normalize_unix_path(path: &str) -> String {
194 let mut result = path.to_string();
195
196 result = result.replace('\\', "/");
198
199 while result.contains("//") && !result.starts_with("//") {
201 result = result.replace("//", "/");
202 }
203
204 if result.ends_with('/') && result != "/" && !result.starts_with("//") {
206 result.pop();
207 }
208
209 result
210 }
211}
212
213impl fmt::Display for PathFormatter {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 write!(f, "PathFormatter(config: {:?})", self.config)
216 }
217}