coven_foundation/files/
path_bytes.rs1use std::path::{Path, PathBuf};
11
12pub fn path_bytes(path: &Path) -> Vec<u8> {
19 #[cfg(unix)]
20 {
21 use std::os::unix::ffi::OsStrExt;
22 path.as_os_str().as_bytes().to_vec()
23 }
24 #[cfg(windows)]
25 {
26 use std::os::windows::ffi::OsStrExt;
27 wtf8(path.as_os_str().encode_wide())
28 }
29}
30
31pub fn path_from_bytes(bytes: Vec<u8>) -> Result<PathBuf, PathBytesError> {
35 #[cfg(unix)]
36 {
37 use std::os::unix::ffi::OsStringExt;
38 Ok(std::ffi::OsString::from_vec(bytes).into())
39 }
40 #[cfg(windows)]
41 {
42 use std::os::windows::ffi::OsStringExt;
43 units(&bytes)
44 .map(|units| std::ffi::OsString::from_wide(&units).into())
45 .ok_or(PathBytesError)
46 }
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
52#[error("the bytes are not a path recorded on this operating system")]
53pub struct PathBytesError;
54
55pub fn path_text(path: &Path) -> String {
57 path_bytes_text(&path_bytes(path))
58}
59
60pub fn path_bytes_text(bytes: &[u8]) -> String {
67 use std::fmt::Write;
68 let mut text = String::with_capacity(bytes.len());
69 for chunk in bytes.utf8_chunks() {
70 text.push_str(chunk.valid());
71 for byte in chunk.invalid() {
72 write!(text, "\\x{byte:02X}").expect("writing to a String succeeds");
73 }
74 }
75 text
76}
77
78#[cfg(any(windows, test))]
80fn wtf8(units: impl Iterator<Item = u16>) -> Vec<u8> {
81 let mut bytes = Vec::new();
82 for unit in char::decode_utf16(units) {
83 match unit {
84 Ok(character) => {
85 let mut buffer = [0; 4];
86 bytes.extend_from_slice(character.encode_utf8(&mut buffer).as_bytes());
87 }
88 Err(unpaired) => {
89 let surrogate = unpaired.unpaired_surrogate();
90 bytes.extend([
91 0xE0 | (surrogate >> 12) as u8,
92 0x80 | ((surrogate >> 6) & 0x3F) as u8,
93 0x80 | (surrogate & 0x3F) as u8,
94 ]);
95 }
96 }
97 }
98 bytes
99}
100
101#[cfg(any(windows, test))]
105fn units(bytes: &[u8]) -> Option<Vec<u16>> {
106 let mut units = Vec::with_capacity(bytes.len());
107 let mut rest = bytes;
108 loop {
109 let (valid, after) = match std::str::from_utf8(rest) {
110 Ok(valid) => (valid, &[][..]),
111 Err(error) => {
112 let (valid, after) = rest.split_at(error.valid_up_to());
113 (std::str::from_utf8(valid).expect("the bytes up to the error are UTF-8"), after)
114 }
115 };
116 units.extend(valid.encode_utf16());
117 let [0xED, second @ 0xA0..=0xBF, third @ 0x80..=0xBF, after @ ..] = after else {
118 return after.is_empty().then_some(units);
119 };
120 let surrogate = 0xD000 | (u16::from(second & 0x3F) << 6) | u16::from(third & 0x3F);
121 let trail = (0xDC00..=0xDFFF).contains(&surrogate);
122 if trail && units.last().is_some_and(|unit| (0xD800..=0xDBFF).contains(unit)) {
123 return None;
124 }
125 units.push(surrogate);
126 rest = after;
127 }
128}
129
130#[cfg(test)]
131#[path = "path_bytes_tests.rs"]
132mod tests;