#[cfg(feature = "std")]
use crate::Error;
use crate::Result;
use std::borrow::Cow;
use url::Url;
pub trait SelfHref {
fn self_href(&self) -> Option<&str>;
fn self_href_mut(&mut self) -> &mut Option<String>;
fn set_self_href(&mut self, href: impl ToString) {
*self.self_href_mut() = Some(href.to_string())
}
fn clear_self_href(&mut self) {
*self.self_href_mut() = None
}
}
pub fn is_windows_absolute_path(s: &str) -> bool {
let bytes = s.as_bytes();
bytes.len() >= 3
&& bytes[0].is_ascii_alphabetic()
&& bytes[1] == b':'
&& (bytes[2] == b'\\' || bytes[2] == b'/')
}
pub fn is_absolute(href: &str) -> bool {
is_windows_absolute_path(href) || Url::parse(href).is_ok() || href.starts_with('/')
}
pub fn make_absolute<'a>(href: &'a str, base: &str) -> Result<Cow<'a, str>> {
if is_absolute(href) {
Ok(href.into())
} else if let Ok(url) = Url::parse(base) {
let url = url.join(href)?;
Ok(url.to_string().into())
} else {
let (base, _) = base.split_at(base.rfind('/').unwrap_or(0));
if base.is_empty() {
Ok(normalize_path(href).into())
} else {
Ok(normalize_path(&format!("{base}/{href}")).into())
}
}
}
pub fn make_relative(href: &str, base: &str) -> String {
let mut relative = String::new();
fn extract_path_filename(s: &str) -> (&str, &str) {
let last_slash_idx = s.rfind('/').unwrap_or(0);
let (path, filename) = s.split_at(last_slash_idx);
if filename.is_empty() {
(path, "")
} else {
(path, &filename[1..])
}
}
let (base_path, base_filename) = extract_path_filename(base);
let (href_path, href_filename) = extract_path_filename(href);
let mut base_path = base_path.split('/').peekable();
let mut href_path = href_path.split('/').peekable();
while base_path.peek().is_some() && base_path.peek() == href_path.peek() {
let _ = base_path.next();
let _ = href_path.next();
}
for base_path_segment in base_path {
if base_path_segment.is_empty() {
break;
}
if !relative.is_empty() {
relative.push('/');
}
relative.push_str("..");
}
for href_path_segment in href_path {
if relative.is_empty() {
relative.push_str("./");
} else {
relative.push('/');
}
relative.push_str(href_path_segment);
}
if !relative.is_empty() || base_filename != href_filename {
if href_filename.is_empty() {
relative.push('/');
} else {
if relative.is_empty() {
relative.push_str("./");
} else {
relative.push('/');
}
relative.push_str(href_filename);
}
}
relative
}
#[cfg(feature = "std")]
pub fn make_url(href: &str) -> Result<Url> {
if is_windows_absolute_path(href) || href.starts_with('/') {
Url::from_file_path(href).map_err(|_| Error::InvalidFilePath(href.to_string()))
} else if let Ok(url) = Url::parse(href) {
Ok(url)
} else {
let current_dir = std::env::current_dir()?;
let url = Url::from_directory_path(¤t_dir)
.map_err(|_| Error::InvalidFilePath(current_dir.to_string_lossy().into_owned()))?;
Ok(url.join(href)?)
}
}
fn normalize_path(path: &str) -> String {
let mut parts = if path.starts_with('/') {
Vec::new()
} else {
vec![""]
};
for part in path.split('/') {
match part {
"." => {}
".." => {
let _ = parts.pop();
}
s => parts.push(s),
}
}
parts.join("/")
}