use crate::parser::ParsedPath;
use crate::{PathConfig, PathResult, PathStyle};
use std::fmt;
use std::fmt::Write;
#[derive(Debug, Clone)]
pub struct PathFormatter {
config: PathConfig,
}
impl PathFormatter {
#[must_use]
pub fn new(config: &PathConfig) -> Self {
Self {
config: config.clone(),
}
}
pub fn format(&self, parsed: &ParsedPath, target_style: PathStyle) -> PathResult<String> {
match target_style {
PathStyle::Windows => Ok(self.format_windows(parsed)),
PathStyle::Unix => Ok(self.format_unix(parsed)),
PathStyle::Auto => {
let current_style = super::platform::current_style();
self.format(parsed, current_style)
}
}
}
fn format_windows(&self, parsed: &ParsedPath) -> String {
if parsed.is_unc {
return Self::format_unc_windows(parsed);
}
let mut result = String::new();
if let Some(drive) = parsed.drive_letter {
let _ = write!(result, "{drive}:");
} else if parsed.is_absolute {
result.push_str("C:");
}
if parsed.is_absolute && !parsed.is_unc {
result.push('\\');
}
for (i, component) in parsed.components.iter().enumerate() {
if i > 0 {
result.push('\\');
}
result.push_str(component);
}
if self.config.normalize {
result = Self::normalize_windows_path(&result);
}
result
}
fn format_unix(&self, parsed: &ParsedPath) -> String {
if parsed.is_unc {
return Self::format_unc_unix(parsed);
}
let mut result = String::new();
if parsed.is_unc {
if let (Some(server), Some(share)) = (&parsed.server, &parsed.share) {
let _ = write!(result, "//{server}/{share}");
}
} else if parsed.is_absolute {
if parsed.has_drive {
if let Some(drive) = parsed.drive_letter {
let drive_lower = drive.to_ascii_lowercase();
result.push_str(&self.map_drive_to_unix(&format!("{drive_lower}:")));
}
} else {
result.push('/');
}
}
for component in &parsed.components {
if !result.ends_with('/') && !result.is_empty() {
result.push('/');
}
result.push_str(component);
}
if self.config.normalize {
result = Self::normalize_unix_path(&result);
}
result
}
fn format_unc_windows(parsed: &ParsedPath) -> String {
let mut result = String::from(r"\\");
if let Some(server) = &parsed.server {
result.push_str(server);
}
result.push('\\');
if let Some(share) = &parsed.share {
result.push_str(share);
}
for component in &parsed.components {
result.push('\\');
result.push_str(component);
}
result
}
fn format_unc_unix(parsed: &ParsedPath) -> String {
let mut result = String::from("//");
if let Some(server) = &parsed.server {
result.push_str(server);
}
result.push('/');
if let Some(share) = &parsed.share {
result.push_str(share);
}
for component in &parsed.components {
result.push('/');
result.push_str(component);
}
result
}
fn map_drive_to_unix(&self, drive: &str) -> String {
for (windows_drive, unix_mount) in &self.config.drive_mappings {
if windows_drive == drive {
return unix_mount.clone();
}
}
let drive_letter = drive.chars().next().unwrap().to_ascii_lowercase();
format!("/mnt/{drive_letter}")
}
fn normalize_windows_path(path: &str) -> String {
let mut result = path.to_string();
result = result.replace('/', "\\");
while result.contains("\\\\") && !result.starts_with(r"\\") {
result = result.replace("\\\\", "\\");
}
if result.ends_with('\\') && result.len() > 3 && !result.starts_with(r"\\") {
result.pop();
}
result
}
fn normalize_unix_path(path: &str) -> String {
let mut result = path.to_string();
result = result.replace('\\', "/");
while result.contains("//") && !result.starts_with("//") {
result = result.replace("//", "/");
}
if result.ends_with('/') && result != "/" && !result.starts_with("//") {
result.pop();
}
result
}
}
impl fmt::Display for PathFormatter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PathFormatter(config: {:?})", self.config)
}
}