1use std::{env, fs};
2
3use txtview::TxtView;
4
5const MAX_BYTES: u64 = 256 * 1024 * 1024;
6
7fn main() -> std::io::Result<()> {
8 let path = env::args()
9 .nth(1)
10 .ok_or_else(|| std::io::Error::other("usage: view_file <path>"))?;
11
12 let len = fs::metadata(&path)
13 .map_err(|e| std::io::Error::other(format!("{}: {}", path, e)))?
14 .len();
15 if len > MAX_BYTES {
16 return Err(std::io::Error::other(format!(
17 "{}: {} bytes is over the {} byte viewing limit",
18 path, len, MAX_BYTES
19 )));
20 }
21 let bytes = fs::read(&path).map_err(|e| std::io::Error::other(format!("{}: {}", path, e)))?;
22 let text = decode(&bytes).map_err(|e| std::io::Error::other(format!("{}: {}", path, e)))?;
23
24 let mut viewer = TxtView::new(text);
25 viewer.run()
26}
27
28fn decode(bytes: &[u8]) -> Result<String, String> {
33 match bytes {
34 [0xEF, 0xBB, 0xBF, rest @ ..] => utf8(rest),
35 [0xFF, 0xFE, 0x00, 0x00, rest @ ..] => utf32(rest, true),
36 [0x00, 0x00, 0xFE, 0xFF, rest @ ..] => utf32(rest, false),
37 [0xFF, 0xFE, rest @ ..] => utf16(rest, true),
38 [0xFE, 0xFF, rest @ ..] => utf16(rest, false),
39 _ if bytes.contains(&0) => match bytes {
40 [0, _, ..] => utf16(bytes, false),
41 [_, 0, ..] => utf16(bytes, true),
42 _ => utf8(bytes),
43 },
44 _ => utf8(bytes),
45 }
46}
47
48fn utf8(bytes: &[u8]) -> Result<String, String> {
49 String::from_utf8(bytes.to_vec()).map_err(|_| "not valid UTF-8".to_string())
50}
51
52fn utf16(bytes: &[u8], little: bool) -> Result<String, String> {
53 let mut chunks = bytes.chunks_exact(2);
54 let units = chunks
55 .by_ref()
56 .map(|pair| {
57 let pair = [pair[0], pair[1]];
58 if little {
59 u16::from_le_bytes(pair)
60 } else {
61 u16::from_be_bytes(pair)
62 }
63 })
64 .collect::<Vec<_>>();
65 if !chunks.remainder().is_empty() {
66 return Err("the file ends in the middle of a UTF-16 unit".to_string());
67 }
68 String::from_utf16(&units).map_err(|_| "unpaired UTF-16 surrogate".to_string())
69}
70
71fn utf32(bytes: &[u8], little: bool) -> Result<String, String> {
72 let mut chunks = bytes.chunks_exact(4);
73 if !chunks.remainder().is_empty() {
74 return Err("the file ends in the middle of a UTF-32 unit".to_string());
75 }
76 let mut out = String::new();
77 for quad in chunks.by_ref() {
78 let quad = [quad[0], quad[1], quad[2], quad[3]];
79 let scalar = if little {
80 u32::from_le_bytes(quad)
81 } else {
82 u32::from_be_bytes(quad)
83 };
84 out.push(char::from_u32(scalar).ok_or("a code point past U+10FFFF")?);
85 }
86 Ok(out)
87}