use crate::PathResult;
use regex::Regex;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct PathParser {
windows_absolute: Regex,
unix_absolute: Regex,
unc_path: Regex,
}
impl Default for PathParser {
fn default() -> Self {
Self {
windows_absolute: Regex::new(r"^[a-zA-Z]:[/\\].*$").unwrap(),
unix_absolute: Regex::new(r"^/.*$").unwrap(),
unc_path: Regex::new(r"^\\\\[^\\]+\\[^\\]+").unwrap(),
}
}
}
impl PathParser {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn parse(path: &str) -> PathResult<ParsedPath> {
let parser = Self::new();
Ok(parser.parse_internal(path))
}
fn parse_internal(&self, path: &str) -> ParsedPath {
let mut parsed = ParsedPath {
original: path.to_string(),
components: Vec::new(),
is_absolute: false,
has_drive: false,
drive_letter: None,
is_unc: false,
server: None,
share: None,
};
if self.unc_path.is_match(path) {
parsed.is_unc = true;
if let Some((server, share)) = Self::parse_unc_path(path) {
parsed.server = Some(server);
parsed.share = Some(share);
}
parsed.is_absolute = true;
return parsed;
}
if self.windows_absolute.is_match(path) {
parsed.is_absolute = true;
parsed.has_drive = true;
parsed.drive_letter = Some(path.chars().next().unwrap().to_ascii_uppercase());
let normalized = path.replace('\\', "/");
let components: Vec<&str> = normalized.split('/').filter(|s| !s.is_empty()).collect();
parsed.components = components.into_iter().map(String::from).collect();
return parsed;
}
if self.unix_absolute.is_match(path) {
parsed.is_absolute = true;
let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
parsed.components = components.into_iter().map(String::from).collect();
return parsed;
}
let components: Vec<&str> = path.split(['/', '\\']).filter(|s| !s.is_empty()).collect();
parsed.components = components.into_iter().map(String::from).collect();
parsed
}
fn parse_unc_path(path: &str) -> Option<(String, String)> {
let parts: Vec<&str> = path.split('\\').filter(|s| !s.is_empty()).collect();
if parts.len() >= 2 {
return Some((parts[0].to_string(), parts[1].to_string()));
}
None
}
#[must_use]
pub fn detect_style(path: &str) -> super::PathStyle {
let parser = Self::new();
if parser.unc_path.is_match(path) || parser.windows_absolute.is_match(path) {
super::PathStyle::Windows
} else if parser.unix_absolute.is_match(path) {
super::PathStyle::Unix
} else if path.contains('\\') && !path.contains('/') {
super::PathStyle::Windows
} else if path.contains('/') && !path.contains('\\') {
super::PathStyle::Unix
} else {
super::platform::current_style()
}
}
pub fn normalize_path(path: &Path) -> PathResult<PathBuf> {
let mut components = Vec::new();
for component in path.components() {
match component {
std::path::Component::Prefix(_) | std::path::Component::RootDir => {
components.clear();
components.push(component);
}
std::path::Component::CurDir => {
}
std::path::Component::ParentDir => {
if components.is_empty() {
components.push(component);
} else {
let last = components.last().unwrap();
match last {
std::path::Component::ParentDir
| std::path::Component::RootDir
| std::path::Component::Prefix(_) => {
components.push(component);
}
_ => {
components.pop();
}
}
}
}
std::path::Component::Normal(name) => {
components.push(std::path::Component::Normal(name));
}
}
}
let mut normalized = PathBuf::new();
for component in components {
normalized.push(component.as_os_str());
}
Ok(normalized)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedPath {
pub original: String,
pub components: Vec<String>,
pub is_absolute: bool,
pub has_drive: bool,
pub drive_letter: Option<char>,
pub is_unc: bool,
pub server: Option<String>,
pub share: Option<String>,
}