readcon_core/
trajectory.rs1use std::path::Path;
4
5use crate::compression::{self, Compression, FileContents};
6use crate::error::ParseError;
7use crate::frame_index::{self, FrameIndex};
8use crate::iterators::ConFrameIterator;
9use crate::types::ConFrame;
10
11pub struct ConTrajectory {
18 contents: FileContents,
19 index: FrameIndex,
20}
21
22impl ConTrajectory {
23 pub fn open(path: &Path, write_index: bool) -> Result<Self, Box<dyn std::error::Error>> {
25 let (contents, compression) = compression::read_file_contents_detected(path)?;
26 let text = contents.as_str_sliced()?;
27 let index = if compression == Compression::None {
28 frame_index::load_or_build(path, text, write_index)?
29 } else {
30 FrameIndex::scan(text)?
31 };
32 Ok(Self { contents, index })
33 }
34
35 pub fn len(&self) -> usize {
36 self.index.len()
37 }
38
39 pub fn is_empty(&self) -> bool {
40 self.index.is_empty()
41 }
42
43 pub fn frame(&self, i: usize) -> Result<ConFrame, ParseError> {
46 let out_of_range = ParseError::IndexOutOfBounds {
47 index: i,
48 len: self.len(),
49 };
50 let range = self.index.range(i).ok_or(out_of_range)?;
51 let text = self.text().get(range).ok_or(ParseError::IncompleteFrame)?;
52 ConFrameIterator::new(text)
53 .next()
54 .unwrap_or(Err(ParseError::IncompleteFrame))
55 }
56
57 fn text(&self) -> &str {
58 unsafe { std::str::from_utf8_unchecked(self.contents.as_bytes()) }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn offset_inside_a_character_is_an_error() {
71 let text = "é\n".repeat(4);
72 let traj = ConTrajectory {
73 contents: FileContents::Owned(text.clone()),
74 index: FrameIndex {
75 offsets: vec![0, 1],
76 data_len: text.len() as u64,
77 },
78 };
79 assert!(matches!(traj.frame(0), Err(ParseError::IncompleteFrame)));
80 assert!(matches!(traj.frame(1), Err(ParseError::IncompleteFrame)));
81 }
82}