#![allow(dead_code)]
use core::str;
use crate::util;
pub struct ParsedUtf8Path<'a> {
path: &'a str,
kind: WinPathKind,
prefix_len: usize,
}
impl<'a> ParsedUtf8Path<'a> {
pub fn from_utf8(path: &'a str) -> ParsedUtf8Path<'a> {
let (kind, len) = WinPathKind::from_str_with_len(path);
Self {
path,
kind,
prefix_len: match kind {
WinPathKind::Unc => len + str_unc_prefix_len(&path[len..]),
_ => len,
},
}
}
pub fn as_utf8(&self) -> &str {
self.path
}
pub const fn kind(&self) -> WinPathKind {
self.kind
}
pub fn normalized_str_kind(&self) -> NormalizedStrKind {
match self.kind() {
WinPathKind::DriveRelative(_) => {
let mut buffer = [0; 4];
buffer[..self.prefix_len].copy_from_slice(&self.path.as_bytes()[..self.prefix_len]);
NormalizedStrKind { buffer, len: self.prefix_len }
}
WinPathKind::Drive(_) => {
let mut buffer = [0; 4];
buffer[..self.prefix_len].copy_from_slice(&self.path.as_bytes()[..self.prefix_len]);
buffer[self.prefix_len - 1] = b'\\';
NormalizedStrKind { buffer, len: self.prefix_len }
}
WinPathKind::Verbatim => NormalizedStrKind { buffer: *br"\\?\", len: 4 },
WinPathKind::Device => {
let mut buffer = [b'\\'; 4];
buffer[2] = self.path.as_bytes()[2];
NormalizedStrKind { buffer, len: 4 }
}
WinPathKind::CurrentDirectoryRelative => NormalizedStrKind { buffer: [0; 4], len: 0 },
WinPathKind::RootRelative => NormalizedStrKind { buffer: [b'\\', 0, 0, 0], len: 1 },
WinPathKind::Unc => NormalizedStrKind { buffer: [b'\\', b'\\', 0, 0], len: 2 },
}
}
pub fn parts<'b>(&'b self) -> (&'a str, &'a str)
where
'a: 'b,
{
self.path.split_at(self.prefix_len)
}
}
pub struct NormalizedStrKind {
buffer: [u8; 4],
len: usize,
}
impl NormalizedStrKind {
pub fn as_str(&self) -> &str {
str::from_utf8(&self.buffer[..self.len]).unwrap()
}
}
fn str_unc_prefix_len(path: &str) -> usize {
let mut iter = path.as_bytes().iter();
match iter.position(|&c| c == b'\\' || c == b'/') {
Some(pos) => iter.position(|&c| c == b'\\' || c == b'/').map(|n| pos + n + 1),
None => None,
}
.unwrap_or(path.len())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WinPathKind {
Drive(u16),
Unc,
Device,
CurrentDirectoryRelative,
Verbatim,
DriveRelative(u16),
RootRelative,
}
impl WinPathKind {
pub const fn split_str(path: &str) -> (Self, &str) {
let (kind, len) = Self::from_str_with_len(path);
let rest = unsafe { util::trim_start_str(path, len) };
(kind, rest)
}
pub(crate) const fn from_str_with_len(path: &str) -> (Self, usize) {
let kind = Self::from_str(path);
let len = match kind {
Self::Drive(_) | Self::DriveRelative(_) => {
kind.utf16_len() - 1 + (util::utf8_len(path.as_bytes()[0]) as usize)
}
_ => kind.utf16_len(),
};
(kind, len)
}
pub const fn from_str(path: &str) -> Self {
let bytes = path.as_bytes();
if bytes.is_empty() {
return WinPathKind::CurrentDirectoryRelative;
}
if let [b'\\', b'\\', b'?', b'\\', ..] = bytes {
return Self::Verbatim;
}
if is_verbatim_str(path) {
return Self::Verbatim;
}
match util::utf8_len(bytes[0]) {
4.. => Self::CurrentDirectoryRelative,
n @ 2.. => {
match_pattern! {
util::trim_start(bytes, n);
[':', /, ..] => Self::Drive(util::bmp_utf8_to_utf16(bytes)),
[':', ..] => Self::DriveRelative(util::bmp_utf8_to_utf16(bytes)),
_ => Self::CurrentDirectoryRelative
}
}
_ => match_pattern! {
bytes;
[/, /, '.', /, ..] => Self::Device,
[/, /, ..] => Self::Unc,
[/, ..] => Self::RootRelative,
[_, ':', /, ..] => Self::Drive(bytes[0] as u16),
[_, ':', ..] => Self::DriveRelative(bytes[0] as u16),
_ => Self::CurrentDirectoryRelative
},
}
}
pub const fn is_absolute(self) -> bool {
matches!(self, Self::Drive(_) | Self::Unc | Self::Device | Self::Verbatim)
}
pub const fn as_relative(self) -> Option<Win32Relative> {
Win32Relative::from_kind(self)
}
pub const fn is_legacy_relative(self) -> bool {
matches!(self, Self::DriveRelative(_) | Self::RootRelative)
}
pub const fn utf16_len(self) -> usize {
match self {
Self::Drive(_) => r"C:\".len(),
Self::Unc => r"\\".len(),
Self::Device => r"\\.\".len(),
Self::CurrentDirectoryRelative => "".len(),
Self::Verbatim => r"\\?\".len(),
Self::DriveRelative(_) => "C:".len(),
Self::RootRelative => r"\".len(),
}
}
pub const fn utf8_len(self) -> usize {
const fn drive_utf8_len(drive: u16) -> usize {
if drive > 0x7F {
2
} else {
1
}
}
match self {
Self::Drive(drive) => drive_utf8_len(drive) + r":\".len(),
Self::Unc => r"\\".len(),
Self::Device => r"\\.\".len(),
Self::CurrentDirectoryRelative => "".len(),
Self::Verbatim => r"\\?\".len(),
Self::DriveRelative(drive) => drive_utf8_len(drive) + ":".len(),
Self::RootRelative => r"\".len(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Win32Relative {
CurrentDirectory,
DriveRelative(u16),
Root,
}
impl Win32Relative {
pub const fn from_kind(kind: WinPathKind) -> Option<Self> {
match kind {
WinPathKind::CurrentDirectoryRelative => Some(Self::CurrentDirectory),
WinPathKind::DriveRelative(drive) => Some(Self::DriveRelative(drive)),
WinPathKind::RootRelative => Some(Self::Root),
_ => None,
}
}
pub const fn is_legacy_relative(self) -> bool {
matches!(self, Self::DriveRelative(_) | Self::Root)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Win32Absolute {
Drive(u16),
Unc,
Device,
}
impl Win32Absolute {
pub const fn from_kind(kind: WinPathKind) -> Option<Self> {
match kind {
WinPathKind::Drive(drive) => Some(Self::Drive(drive)),
WinPathKind::Unc => Some(Self::Unc),
WinPathKind::Device => Some(Self::Device),
_ => None,
}
}
pub(crate) const fn from_verbatim_str(path: &str) -> Result<(Self, &str), ()> {
let verbatim = match VerbatimStr::new(path) {
Ok(verbatim) => verbatim,
Err(e) => return Err(e),
};
let kind = verbatim.win32_kind();
let rest = match kind {
Win32Absolute::Unc => unsafe { util::trim_start_str(verbatim.path, "UNC".len()) },
_ => verbatim.path,
};
Ok((kind, rest))
}
}
pub struct VerbatimStr<'a> {
path: &'a str,
}
impl<'a> VerbatimStr<'a> {
const fn new(path: &'a str) -> Result<Self, ()> {
match WinPathKind::split_str(path) {
(WinPathKind::Verbatim, rest) => Ok(Self { path: rest }),
_ => Err(()),
}
}
const fn win32_kind(&self) -> Win32Absolute {
match self.path.as_bytes() {
[b'U', b'N', b'C', b'\\', ..] | [b'U', b'N', b'C'] => Win32Absolute::Unc,
[d, b':', b'\\', ..] | [d, b':'] => Win32Absolute::Drive(*d as u16),
[d1, d2, b':', b'\\', ..] | [d1, d2, b':'] => {
let drive = util::bmp_utf8_to_utf16(&[*d1, *d2]);
Win32Absolute::Drive(drive)
}
_ => Win32Absolute::Device,
}
}
}
#[inline]
pub const fn is_verbatim_str(path: &str) -> bool {
matches!(path.as_bytes(), [b'\\', b'\\', b'?', b'\\', ..])
}