Read a source file to a UTF-8 string, transparently handling UTF-16 LE/BE (detected via BOM). Returns an IO error only when the file genuinely cannot be read or decoded.
(path: &Path)
| 10 | /// (detected via BOM). Returns an IO error only when the file genuinely cannot |
| 11 | /// be read or decoded. |
| 12 | pub fn read_source_file(path: &Path) -> std::io::Result<String> { |
| 13 | let bytes = read_file_bytes(path)?; |
| 14 | |
| 15 | // UTF-16 LE BOM: FF FE |
| 16 | if bytes.starts_with(&[0xFF, 0xFE]) { |
| 17 | let u16s: Vec<u16> = bytes[2..] |
| 18 | .chunks_exact(2) |
| 19 | .map(|pair| u16::from_le_bytes([pair[0], pair[1]])) |
| 20 | .collect(); |
| 21 | return String::from_utf16(&u16s) |
| 22 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)); |
| 23 | } |
| 24 | |
| 25 | // UTF-16 BE BOM: FE FF |
| 26 | if bytes.starts_with(&[0xFE, 0xFF]) { |
| 27 | let u16s: Vec<u16> = bytes[2..] |
| 28 | .chunks_exact(2) |
| 29 | .map(|pair| u16::from_be_bytes([pair[0], pair[1]])) |
| 30 | .collect(); |
| 31 | return String::from_utf16(&u16s) |
| 32 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)); |
| 33 | } |
| 34 | |
| 35 | // Strip UTF-8 BOM if present, then validate |
| 36 | let start = if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) { |
| 37 | 3 |
| 38 | } else { |
| 39 | 0 |
| 40 | }; |
| 41 | String::from_utf8(bytes[start..].to_vec()) |
| 42 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) |
| 43 | } |
| 44 | |
| 45 | /// Reads a file's bytes, absorbing transient Windows file locks. |
| 46 | /// |