use std::{
fmt::{self, Display},
num::ParseIntError,
ops::RangeInclusive,
};
use crate::{FcsError, VALID_FCS_VERSIONS, Seek};
use std::io::BufReader;
use std::str;
use std::io::Read;
#[derive(Debug, PartialEq)]
pub struct Header {
pub version: String,
pub text_offsets: RangeInclusive<usize>,
pub data_offsets: RangeInclusive<usize>,
pub analysis_offsets: RangeInclusive<usize>,
}
impl TryFrom<&str> for Header {
type Error = ParseIntError;
fn try_from(header: &str) -> Result<Self, Self::Error> {
let version = header[0..=5].to_string();
let text_start = header[10..=17].trim_start().parse::<usize>()?;
let text_end = header[18..=25].trim_start().parse::<usize>()?;
let data_start = header[26..=33].trim_start().parse::<usize>()?;
let data_end = header[34..=41].trim_start().parse::<usize>()?;
let analysis_start = header[42..=49]
.trim_start()
.parse::<usize>()
.or_else(zero_when_empty)?;
let analysis_end = header[50..=57]
.trim_start()
.parse::<usize>()
.or_else(zero_when_empty)?;
Ok(Self {
version,
text_offsets: text_start..=text_end,
data_offsets: data_start..=data_end,
analysis_offsets: analysis_start..=analysis_end,
})
}
}
impl Display for Header {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:<10}{:>8}{:>8}{:>8}{:>8}{:>8}{:>8}",
self.version,
self.text_offsets.start(),
self.text_offsets.end(),
self.data_offsets.start(),
self.data_offsets.end(),
self.analysis_offsets.start(),
self.analysis_offsets.end(),
)
}
}
fn zero_when_empty(err: ParseIntError) -> Result<usize, ParseIntError> {
if err.kind() == &std::num::IntErrorKind::Empty {
Ok(0)
} else {
Err(err)
}
}
pub fn read_header<R: Read + Seek>(reader: &mut BufReader<R>) -> Result<Header, FcsError> {
let mut buffer = [0u8; 58];
reader.read_exact(&mut buffer).map_err(FcsError::IoError)?;
let header_line = str::from_utf8(&buffer).map_err(|_| FcsError::InvalidHeader)?;
let header = Header::try_from(header_line).map_err(|_| FcsError::InvalidHeader)?;
let version = &header.version;
if !VALID_FCS_VERSIONS.contains(&version.as_str()) {
return Err(FcsError::InvalidVersion(version.to_owned()));
}
Ok(header)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header1() {
let header =
"FCS3.0 256 1545 1792 202456 0 0";
let parsed_header = Header {
version: "FCS3.0".into(),
text_offsets: 256..=1545,
data_offsets: 1792..=202456,
analysis_offsets: 0..=0,
};
assert_eq!(parsed_header, Header::try_from(header).unwrap());
}
#[test]
fn header1_spaces() {
let header =
"FCS3.0 256 1545 1792 202456 ";
let parsed_header = Header {
version: "FCS3.0".into(),
text_offsets: 256..=1545,
data_offsets: 1792..=202456,
analysis_offsets: 0..=0,
};
assert_eq!(parsed_header, Header::try_from(header).unwrap());
}
#[test]
fn header2() {
let header =
"FCS3.0 256 1545 0 0 0 0";
let parsed_header = Header {
version: "FCS3.0".into(),
text_offsets: 256..=1545,
data_offsets: 0..=0,
analysis_offsets: 0..=0,
};
assert_eq!(parsed_header, Header::try_from(header).unwrap());
}
#[test]
fn header3() {
let header =
"FCS3.0 202451 203140 1792 202450 0 0";
let parsed_header = Header {
version: "FCS3.0".into(),
text_offsets: 202451..=203140,
data_offsets: 1792..=202450,
analysis_offsets: 0..=0,
};
assert_eq!(parsed_header, Header::try_from(header).unwrap());
}
#[test]
fn write_header1() {
let header = Header {
version: "FCS3.0".into(),
text_offsets: 256..=1545,
data_offsets: 1792..=202456,
analysis_offsets: 0..=0,
};
let formatted = header.to_string();
assert_eq!(
"FCS3.0 256 1545 1792 202456 0 0",
formatted
);
}
#[test]
fn write_header2() {
let header = Header {
version: "FCS3.0".into(),
text_offsets: 256..=1545,
data_offsets: 0..=0,
analysis_offsets: 0..=0,
};
let formatted = header.to_string();
assert_eq!(
"FCS3.0 256 1545 0 0 0 0",
formatted
);
}
#[test]
fn write_header3() {
let header = Header {
version: "FCS3.0".into(),
text_offsets: 202451..=203140,
data_offsets: 1792..=202450,
analysis_offsets: 0..=0,
};
let formatted = header.to_string();
assert_eq!(
"FCS3.0 202451 203140 1792 202450 0 0",
formatted
);
}
}