use std::sync::{Arc, Mutex};
use encoding_rs::UTF_8;
use crate::parser::Parser;
use crate::parser_listener::ParserListener;
pub struct ByteParser<'a, T>
where
T: ParserListener + Send + 'a,
{
parser: Parser<'a, T>,
utf8_decoder: &'static encoding_rs::Encoding,
incomplete: Vec<u8>, }
impl<'a, T> ByteParser<'a, T>
where
T: ParserListener + Send + 'a,
{
pub fn new(listener: Arc<Mutex<T>>) -> Self {
Self {
parser: Parser::new(listener),
utf8_decoder: UTF_8,
incomplete: Vec::new(),
}
}
pub fn feed(&mut self, data: &[u8]) {
let use_utf8 = self.parser.parser_state.lock().unwrap().use_utf8;
let data_str = if use_utf8 {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.incomplete);
bytes.extend_from_slice(data);
let (cow, _had_errors) = self.utf8_decoder.decode_with_bom_removal(&bytes);
if let Some(last_valid) = cow.len().checked_sub(1) {
self.incomplete = bytes[last_valid..].to_vec();
} else {
self.incomplete.clear();
}
cow.into_owned()
} else {
data.iter().map(|&b| b as char).collect::<String>()
};
self.parser.feed(data_str);
}
pub fn select_other_charset(&mut self, code: &str) {
match code {
"@" => {
self.parser.set_use_utf8(false);
self.incomplete.clear();
}
"G" | "8" => {
self.parser.set_use_utf8(true);
}
_ => {}
}
}
}
#[cfg(test)]
mod test {
use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use pretty_assertions::assert_eq;
use crate::byte_parser::ByteParser;
use crate::debug_screen::DebugScreen;
use crate::parser_listener::ParserListener;
use crate::screen::Screen;
#[test]
fn input_output() {
let test_cases = vec!["cat-gpl3", "find-etc", "htop", "ls", "mc", "top", "vi"];
let captured_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("assets")
.join("captured");
for name in test_cases {
let input_path = captured_dir.join(format!("{}.input", name));
let input = fs::read(&input_path)
.unwrap_or_else(|_| panic!("Failed to read input file: {:?}", input_path));
let output_path = captured_dir.join(format!("{}.output", name));
let output: Vec<String> = serde_json::from_str(
&fs::read_to_string(&output_path)
.unwrap_or_else(|_| panic!("Failed to read output file: {:?}", output_path)),
)
.unwrap_or_else(|_| panic!("Failed to parse output JSON for {}", name));
let screen = Arc::new(Mutex::new(Screen::new(80, 24)));
let mut parser = ByteParser::new(screen.clone());
parser.feed(&input);
dbg!(screen.lock().unwrap().display());
dbg!(output.clone());
assert_eq!(
screen.lock().unwrap().display(),
output,
"Output mismatch for test case: {}",
name
);
}
}
#[test]
#[ignore = "this is a utility test to debug intermediate commands"]
fn debug_printer() {
let test_cases = vec!["htop"];
let captured_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("assets")
.join("captured");
for name in test_cases {
let input_path = captured_dir.join(format!("{}.input", name));
let input = fs::read(&input_path)
.unwrap_or_else(|_| panic!("Failed to read input file: {:?}", input_path));
let debug_screen = Arc::new(Mutex::new(DebugScreen::new()));
let mut parser = ByteParser::new(debug_screen.clone());
parser.feed(&input);
let debug_output = debug_screen.lock().unwrap().output.join("\n");
fs::write("htop.memterm", debug_output).expect("Failed to write debug output to file");
}
}
}