use std::{
fmt,
io::{BufReader, Read},
};
use bytelines::ByteLines;
#[derive(Debug, Default, Clone)]
pub struct Header {
pub version: String,
pub software_name: String,
pub software_version: String,
}
#[derive(Debug, Clone)]
pub struct HeaderParseError {
pub read: Vec<u8>,
}
impl fmt::Display for HeaderParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "HeaderParseError: {:?}", self.read)
}
}
impl std::error::Error for HeaderParseError {}
#[derive(Debug, Clone)]
pub struct HeaderParsing {
pub header: Header,
pub uses_ascii28: bool,
}
type Result = std::result::Result<HeaderParsing, HeaderParseError>;
type Lines<R> = bytelines::ByteLinesIter<BufReader<R>>;
pub fn parse_header(src: &mut impl Read) -> Result {
let mut lines = ByteLines::new(BufReader::with_capacity(1, src)).into_iter();
let mut read_lines = Vec::new();
let first_line = next_line(&mut read_lines, &mut lines)?;
if byte_slice_contains(first_line.as_slice(), b"/*") {
log::debug!("legacy header");
return parse_legacy_header(&mut lines, &mut read_lines);
} else {
log::debug!("non-legacy header");
return parse_nonlegacy_header(&first_line);
}
}
fn parse_legacy_header(lines: &mut Lines<impl Read>, read_lines: &mut Vec<Vec<u8>>) -> Result {
let mut header = Header::default();
header.version = String::from_utf8_lossy(&next_line(read_lines, lines)?).to_string();
Ok(HeaderParsing {
header,
uses_ascii28: false,
})
}
fn parse_nonlegacy_header(line: &Vec<u8>) -> Result {
let mut header = Header::default();
let uses_ascii28 = line.contains(&b'\x1c');
log::debug!("uses_ascii28: {}", uses_ascii28);
let sep = if uses_ascii28 { b'\x1c' } else { b',' };
let mut parts = line.split(|c| *c == sep);
header.version = String::from_utf8_lossy(parts.next().unwrap()).to_string();
header.software_name = String::from_utf8_lossy(parts.next().unwrap()).to_string();
header.software_version = String::from_utf8_lossy(parts.next().unwrap()).to_string();
Ok(HeaderParsing {
header,
uses_ascii28,
})
}
fn next_line(
read_lines: &mut Vec<Vec<u8>>,
lines: &mut Lines<impl Read>,
) -> std::result::Result<Vec<u8>, HeaderParseError> {
let line = match lines.next() {
Some(Ok(line)) => line,
Some(Err(_e)) => {
return Err(HeaderParseError {
read: read_lines.concat(),
})
}
None => {
return Err(HeaderParseError {
read: read_lines.concat(),
})
}
};
read_lines.push(line.clone());
Ok(line)
}
fn byte_slice_contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack
.windows(needle.len())
.any(|window| window == needle)
}