use alloc::string::String;
use crate::Url;
use crate::parser::percent::{
in_path_segment_encode_set, in_special_path_segment_encode_set, utf8_percent_encode,
};
use crate::parser::{SchemeType, is_windows_drive_letter, to_u32};
#[derive(Debug)]
pub struct PathSegmentsMut<'m, 'u> {
url: &'m mut Url<'u>,
after_first_slash: usize,
after_path: String,
old_after_path_position: u32,
}
pub(crate) fn new<'m, 'u>(url: &'m mut Url<'u>) -> PathSegmentsMut<'m, 'u> {
url.serialization.ensure_owned();
let after_path = url.take_after_path();
let old_after_path_position = to_u32(url.serialization.len()).unwrap_or(u32::MAX);
let path_start = url.path_start as usize;
let after_first_slash = if url.as_str().as_bytes().get(path_start) == Some(&b'/') {
path_start + 1
} else {
path_start
};
PathSegmentsMut {
url,
after_first_slash,
after_path,
old_after_path_position,
}
}
impl Drop for PathSegmentsMut<'_, '_> {
fn drop(&mut self) {
self.url
.restore_after_path(self.old_after_path_position, &self.after_path);
}
}
impl PathSegmentsMut<'_, '_> {
pub fn clear(&mut self) -> &mut Self {
let path_start = self.url.path_start as usize;
let scheme_end = self.url.scheme_end as usize;
let ser = self.url.serialization.as_mut_string();
let anarchist = path_start == scheme_end + 3
&& ser.as_bytes().get(scheme_end + 1) == Some(&b'/')
&& ser.as_bytes().get(scheme_end + 2) == Some(&b'.');
if anarchist {
let new_path_start = scheme_end + 1;
ser.truncate(new_path_start);
ser.push('/');
self.url.path_start = to_u32(new_path_start).unwrap_or(self.url.path_start);
self.after_first_slash = new_path_start + 1;
return self;
}
if ser.as_bytes().get(path_start) == Some(&b'/') {
ser.truncate(path_start + 1);
self.after_first_slash = path_start + 1;
} else {
ser.truncate(path_start);
ser.push('/');
self.after_first_slash = path_start + 1;
}
self
}
pub fn pop_if_empty(&mut self) -> &mut Self {
let ser = self.url.serialization.as_mut_string();
if self.after_first_slash >= ser.len() {
return self;
}
if ser[self.after_first_slash..].ends_with('/') {
ser.pop();
}
self
}
pub fn pop(&mut self) -> &mut Self {
let ser = self.url.serialization.as_mut_string();
if self.after_first_slash >= ser.len() {
return self;
}
let last_slash = ser[self.after_first_slash..].rfind('/').unwrap_or(0);
ser.truncate(self.after_first_slash + last_slash);
self
}
pub fn push(&mut self, segment: &str) -> &mut Self {
self.extend(Some(segment))
}
pub fn extend<I>(&mut self, segments: I) -> &mut Self
where
I: IntoIterator,
I::Item: AsRef<str>,
{
let path_start = self.url.path_start as usize;
let scheme_type = SchemeType::from(self.url.scheme());
let special = scheme_type.is_special();
let is_file = scheme_type.is_file();
for segment in segments {
let segment = segment.as_ref();
if matches!(segment, "." | "..") {
continue;
}
let ser = self.url.serialization.as_mut_string();
if ser.len() > path_start + 1 || ser.len() == path_start {
ser.push('/');
if self.after_first_slash == path_start {
self.after_first_slash = path_start + 1;
}
}
let segment_start = ser.len();
let only_slashes_before = ser[path_start..segment_start].bytes().all(|b| b == b'/');
if is_file && only_slashes_before && is_windows_drive_letter(segment) {
let letter = segment.as_bytes()[0] as char;
ser.push(letter);
ser.push(':');
continue;
}
if special {
utf8_percent_encode(segment, in_special_path_segment_encode_set, ser);
} else {
utf8_percent_encode(segment, in_path_segment_encode_set, ser);
}
}
self
}
}