Refactor: Extract the quirks_path package as a standalone module and replace eyre with color-eyre.

This commit is contained in:
2025-01-05 23:51:31 +08:00
parent 40cbf86f0f
commit 2ed2b864b2
28 changed files with 231 additions and 2117 deletions

View File

@@ -1,15 +0,0 @@
[package]
name = "quirks_path"
version = "0.1.0"
edition = "2021"
[dependencies]
nom = "7.1.3"
percent-encoding = "2.3.1"
serde = { version = "1.0.197", features = ["derive"] }
thiserror = "1.0.57"
tracing = "0.1.41"
url = "2.5.0"
[dev-dependencies]
tracing = "0.1.41"

File diff suppressed because it is too large Load Diff

View File

@@ -1,141 +0,0 @@
use std::{borrow::Cow, fmt::Write};
use percent_encoding::{percent_encode, AsciiSet, CONTROLS};
use crate::{windows::parse_drive, Component, Path, Prefix};
const URL_FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
const URL_PATH: &AsciiSet = &URL_FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
const URL_PATH_SEGMENT: &AsciiSet = &URL_PATH.add(b'/').add(b'%');
#[derive(thiserror::Error, Debug)]
pub enum PathToUrlError {
#[error(transparent)]
UrlParseError(#[from] url::ParseError),
#[error("PathNotAbsoluteError {{ path = {path} }}")]
PathNotAbsoluteError { path: Cow<'static, str> },
#[error("NotSupportedPrefixError {{ path = {path}, prefix = {prefix} }}")]
NotSupportedPrefixError {
path: Cow<'static, str>,
prefix: Cow<'static, str>,
},
#[error("NotSupportedFirstComponentError {{ path = {path}, comp = {comp} }}")]
NotSupportedFirstComponentError {
path: Cow<'static, str>,
comp: Cow<'static, str>,
},
}
#[inline]
pub fn to_u32(i: usize) -> Result<u32, url::ParseError> {
if i <= u32::MAX as usize {
Ok(i as u32)
} else {
Err(url::ParseError::Overflow)
}
}
pub(crate) fn path_to_file_url_segments(
path: &Path,
serialization: &mut String,
) -> Result<(u32, Option<::url::Host<String>>), PathToUrlError> {
if !path.is_absolute() {
return Err(PathToUrlError::PathNotAbsoluteError {
path: Cow::Owned(path.as_str().to_string()),
});
}
let mut components = path.components();
let host_start = serialization.len() + 1;
let host_end;
let host_internal: Option<url::Host<String>>;
match components.next() {
Some(Component::Prefix(ref p)) => match p.kind() {
Prefix::Disk { drive } | Prefix::VerbatimDisk { drive } => {
host_end = to_u32(serialization.len()).unwrap();
host_internal = None;
serialization.push('/');
serialization.push(drive);
serialization.push(':');
}
Prefix::UNC { server, share } | Prefix::VerbatimUNC { server, share } => {
let host = url::Host::parse(server)?;
write!(serialization, "{}", host).unwrap();
host_end = to_u32(serialization.len()).unwrap();
host_internal = Some(host);
serialization.push('/');
serialization.extend(percent_encode(share.as_bytes(), URL_PATH_SEGMENT));
}
_ => {
return Err(PathToUrlError::NotSupportedPrefixError {
path: Cow::Owned(path.as_str().to_string()),
prefix: Cow::Owned(p.as_str().to_string()),
});
}
},
Some(Component::RootDir(_)) => {
let host_end = to_u32(serialization.len()).unwrap();
let mut empty = true;
for component in components {
empty = false;
serialization.push('/');
serialization.extend(percent_encode(
component.as_str().as_bytes(),
URL_PATH_SEGMENT,
));
}
if empty {
serialization.push('/');
}
return Ok((host_end, None));
}
Some(comp) => {
return Err(PathToUrlError::NotSupportedFirstComponentError {
path: Cow::Owned(path.as_str().to_string()),
comp: Cow::Owned(comp.as_str().to_string()),
});
}
None => {
return Err(PathToUrlError::NotSupportedFirstComponentError {
path: Cow::Owned(path.as_str().to_string()),
comp: Cow::Borrowed("null"),
});
}
}
let mut path_only_has_prefix = true;
for component in components {
if matches!(component, Component::RootDir(..)) {
continue;
}
path_only_has_prefix = false;
let component = component.as_str();
serialization.push('/');
serialization.extend(percent_encode(component.as_bytes(), URL_PATH_SEGMENT));
}
// A windows drive letter must end with a slash.
if serialization.len() > host_start
&& matches!(parse_drive(&serialization[host_start..]), Ok(..))
&& path_only_has_prefix
{
serialization.push('/');
}
Ok((host_end, host_internal))
}
pub fn path_equals_as_file_url<A: AsRef<Path>, B: AsRef<Path>>(
a: A,
b: B,
) -> Result<bool, PathToUrlError> {
let u1 = a.as_ref().to_file_url()?;
let u2 = b.as_ref().to_file_url()?;
Ok(u1.as_str() == u2.as_str())
}

View File

@@ -1,138 +0,0 @@
use nom::{
bytes::complete::tag,
character::complete::{self, satisfy},
combinator::peek,
error::{context, ContextError, Error, ErrorKind, ParseError},
sequence::pair,
AsChar, IResult, InputIter, InputTakeAtPosition,
};
use crate::Prefix;
fn non_slash(input: &str) -> IResult<&str, &str> {
input.split_at_position_complete(|item| item != '/')
}
pub fn parse_drive(path: &str) -> IResult<&str, char> {
context("drive", satisfy(char::is_alpha))(path).map(|a: (&str, char)| a)
}
pub fn parse_drive_exact(path: &str) -> IResult<&str, char> {
context("drive_exact", pair(parse_drive, complete::char(':')))(path)
.map(|(path, (drive, _))| (path, drive))
}
pub fn is_windows_verbatim_sep(c: char) -> bool {
c == '\\'
}
pub fn is_windows_sep(c: char) -> bool {
c == '\\' || c == '/'
}
pub fn parse_windows_next_component(path: &str, verbatim: bool) -> (&str, &str, &str) {
let separator = if verbatim {
is_windows_verbatim_sep
} else {
is_windows_sep
};
let p = path.as_bytes();
match p.position(|x| separator(x as char)) {
Some(separator_start) => {
let separator_end = separator_start + 1;
let component = &path[0..separator_start];
let path_with_sep = &path[separator_start..];
let path_without_sep = &path[separator_end..];
(component, path_with_sep, path_without_sep)
}
None => (path, "", ""),
}
}
fn context_verify_error<'a>(input: &'a str, context: &'static str) -> nom::Err<Error<&'a str>> {
nom::Err::Error(Error::add_context(
input,
context,
Error::from_error_kind(input, ErrorKind::Verify),
))
}
pub fn parse_windows_path_prefix(raw_path: &str) -> IResult<&str, Prefix<'_>> {
if let Ok((path, _)) = tag(r"\\")(raw_path) as IResult<&str, &str> {
if let Ok((path, _)) = tag(r"?\")(path) as IResult<&str, &str> {
if let Ok((path, _)) = peek(non_slash)(path) as IResult<&str, &str> {
if let Ok((path, _)) = tag(r"UNC\")(path) as IResult<&str, &str> {
let (server, _, other) = parse_windows_next_component(path, true);
let (share, next_input, _) = parse_windows_next_component(other, true);
return Ok((next_input, Prefix::VerbatimUNC { server, share }));
} else if let Ok((path, drive)) = parse_drive_exact(path) {
return Ok((path, Prefix::VerbatimDisk { drive }));
} else {
let (prefix, next_input, _) = parse_windows_next_component(path, true);
return Ok((next_input, Prefix::Verbatim { prefix }));
}
}
}
if let Ok((path, _)) = tag(r".\")(path) as IResult<&str, &str> {
let (prefix, next_input, _) = parse_windows_next_component(path, false);
return Ok((next_input, Prefix::DeviceNS { device: prefix }));
}
let (server, _, other) = parse_windows_next_component(path, false);
let (share, next_input, _) = parse_windows_next_component(other, false);
if !server.is_empty() && !share.is_empty() {
return Ok((next_input, Prefix::UNC { server, share }));
}
Err(context_verify_error(raw_path, "windows path prefix"))
} else if let Ok((path, drive)) = parse_drive_exact(raw_path) {
Ok((path, Prefix::Disk { drive }))
} else {
Err(context_verify_error(raw_path, "windows path prefix"))
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_parse_windows_path_prefix() {
use super::*;
assert_eq!(
parse_windows_path_prefix(r"\\?\UNC\server\share\path"),
Ok((
r"\path",
Prefix::VerbatimUNC {
server: "server",
share: "share"
}
))
);
assert_eq!(
parse_windows_path_prefix(r"\\?\C:\path"),
Ok((r"\path", Prefix::VerbatimDisk { drive: 'C' }))
);
assert_eq!(
parse_windows_path_prefix(r"\\server\share\path"),
Ok((
r"\path",
Prefix::UNC {
server: "server",
share: "share"
}
))
);
assert_eq!(
parse_windows_path_prefix(r"C:\path"),
Ok((r"\path", Prefix::Disk { drive: 'C' }))
);
assert_eq!(
parse_windows_path_prefix(r"\\.\device\path"),
Ok((r"\path", Prefix::DeviceNS { device: "device" }))
);
assert_eq!(
parse_windows_path_prefix(r"\\?\abc\path"),
Ok((r"\path", Prefix::Verbatim { prefix: "abc" }))
)
}
}