use std::path::{Path, PathBuf};
use url::Url;
use crate::mojibake::repair_path_mojibake;
pub fn uri_to_fs_path(uri: &str) -> Option<PathBuf> {
let url = if let Ok(parsed) = Url::parse(uri) {
parsed
} else {
let normalized = crate::classify::normalize_legacy_windows_uri(uri)?;
Url::parse(&normalized).ok()?
};
if url.scheme() != "file" {
return None;
}
let path = url
.to_file_path()
.ok()
.or_else(|| local_authority_file_uri_to_path(&url))
.or_else(|| windows_rooted_file_uri_to_path(&url))?;
Some(repair_path_mojibake(path))
}
pub fn source_path_from_uri_or_path(input: &str) -> Option<PathBuf> {
let trimmed = input.trim();
let path = Path::new(trimmed);
if path.is_absolute() {
return Some(path.to_path_buf());
}
uri_to_fs_path(trimmed)
}
pub fn fs_path_to_uri<P: AsRef<Path>>(path: P) -> Result<String, String> {
let path = normalize_filesystem_path(path.as_ref());
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.map_err(|e| format!("Failed to get current directory: {e}"))?
.join(path)
};
Url::from_file_path(&abs_path)
.map(|url| url.to_string())
.map_err(|_| format!("Failed to convert path to URI: {}", abs_path.display()))
}
fn normalize_filesystem_path(path: &Path) -> PathBuf {
#[cfg(windows)]
{
if let Some(path_str) = path.to_str() {
if let Some(stripped) = path_str.strip_prefix(r"\\?\UNC\") {
return PathBuf::from(format!(r"\\{}", stripped));
}
if let Some(stripped) = path_str.strip_prefix(r"\\?\") {
return PathBuf::from(stripped);
}
}
}
path.to_path_buf()
}
fn local_authority_file_uri_to_path(url: &Url) -> Option<PathBuf> {
if !crate::classify::is_local_file_authority(url.host_str()) {
return None;
}
let canonical = Url::parse(&format!("file://{}", url.path())).ok()?;
canonical.to_file_path().ok()
}
#[cfg(windows)]
fn windows_rooted_file_uri_to_path(url: &Url) -> Option<PathBuf> {
use percent_encoding::percent_decode_str;
match url.host_str() {
None => {}
host if crate::classify::is_local_file_authority(host) => {}
Some(_) => return None,
}
let decoded = percent_decode_str(url.path()).decode_utf8().ok()?;
if decoded.is_empty() {
return None;
}
let native = if decoded.len() > 3
&& decoded.starts_with('/')
&& decoded.as_bytes()[2] == b':'
&& decoded.as_bytes()[1].is_ascii_alphabetic()
{
decoded[1..].replace('/', "\\")
} else {
decoded.replace('/', "\\")
};
Some(PathBuf::from(native))
}
#[cfg(not(windows))]
fn windows_rooted_file_uri_to_path(_url: &Url) -> Option<PathBuf> {
None
}