use std::{env, fs};
use txtview::TxtView;
const MAX_BYTES: u64 = 256 * 1024 * 1024;
fn main() -> std::io::Result<()> {
let path = env::args()
.nth(1)
.ok_or_else(|| std::io::Error::other("usage: view_file <path>"))?;
let len = fs::metadata(&path)
.map_err(|e| std::io::Error::other(format!("{}: {}", path, e)))?
.len();
if len > MAX_BYTES {
return Err(std::io::Error::other(format!(
"{}: {} bytes is over the {} byte viewing limit",
path, len, MAX_BYTES
)));
}
let bytes = fs::read(&path).map_err(|e| std::io::Error::other(format!("{}: {}", path, e)))?;
let text = decode(&bytes).map_err(|e| std::io::Error::other(format!("{}: {}", path, e)))?;
let mut viewer = TxtView::new(text);
viewer.run()
}
fn decode(bytes: &[u8]) -> Result<String, String> {
match bytes {
[0xEF, 0xBB, 0xBF, rest @ ..] => utf8(rest),
[0xFF, 0xFE, 0x00, 0x00, rest @ ..] => utf32(rest, true),
[0x00, 0x00, 0xFE, 0xFF, rest @ ..] => utf32(rest, false),
[0xFF, 0xFE, rest @ ..] => utf16(rest, true),
[0xFE, 0xFF, rest @ ..] => utf16(rest, false),
_ if bytes.contains(&0) => match bytes {
[0, _, ..] => utf16(bytes, false),
[_, 0, ..] => utf16(bytes, true),
_ => utf8(bytes),
},
_ => utf8(bytes),
}
}
fn utf8(bytes: &[u8]) -> Result<String, String> {
String::from_utf8(bytes.to_vec()).map_err(|_| "not valid UTF-8".to_string())
}
fn utf16(bytes: &[u8], little: bool) -> Result<String, String> {
let mut chunks = bytes.chunks_exact(2);
let units = chunks
.by_ref()
.map(|pair| {
let pair = [pair[0], pair[1]];
if little {
u16::from_le_bytes(pair)
} else {
u16::from_be_bytes(pair)
}
})
.collect::<Vec<_>>();
if !chunks.remainder().is_empty() {
return Err("the file ends in the middle of a UTF-16 unit".to_string());
}
String::from_utf16(&units).map_err(|_| "unpaired UTF-16 surrogate".to_string())
}
fn utf32(bytes: &[u8], little: bool) -> Result<String, String> {
let mut chunks = bytes.chunks_exact(4);
if !chunks.remainder().is_empty() {
return Err("the file ends in the middle of a UTF-32 unit".to_string());
}
let mut out = String::new();
for quad in chunks.by_ref() {
let quad = [quad[0], quad[1], quad[2], quad[3]];
let scalar = if little {
u32::from_le_bytes(quad)
} else {
u32::from_be_bytes(quad)
};
out.push(char::from_u32(scalar).ok_or("a code point past U+10FFFF")?);
}
Ok(out)
}