noodles_cram/crai/io/
reader.rs

1use std::io::{self, BufRead, BufReader, Read};
2
3use flate2::read::GzDecoder;
4
5use crate::crai::Index;
6
7/// A CRAM index reader.
8pub struct Reader<R> {
9    inner: BufReader<GzDecoder<R>>,
10}
11
12impl<R> Reader<R>
13where
14    R: Read,
15{
16    /// Creates a CRAM index reader.
17    ///
18    /// # Examples
19    ///
20    /// ```
21    /// use noodles_cram::crai;
22    /// let data = [];
23    /// let reader = crai::io::Reader::new(&data[..]);
24    /// ```
25    pub fn new(inner: R) -> Self {
26        Self {
27            inner: BufReader::new(GzDecoder::new(inner)),
28        }
29    }
30
31    /// Reads a CRAM index.
32    ///
33    /// The position of the stream is expected to be at the start.
34    ///
35    /// # Examples
36    ///
37    /// ```no_run
38    /// # use std::{fs::File, io};
39    /// use noodles_cram::crai;
40    /// let mut reader = File::open("sample.cram.crai").map(crai::io::Reader::new)?;
41    /// let index = reader.read_index()?;
42    /// # Ok::<(), io::Error>(())
43    /// ```
44    pub fn read_index(&mut self) -> io::Result<Index> {
45        let mut buf = String::new();
46        let mut index = Vec::new();
47
48        loop {
49            buf.clear();
50
51            match read_line(&mut self.inner, &mut buf) {
52                Ok(0) => break,
53                Ok(_) => {
54                    let record = buf
55                        .parse()
56                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
57
58                    index.push(record);
59                }
60                Err(e) => return Err(e),
61            }
62        }
63
64        Ok(index)
65    }
66}
67
68fn read_line<R>(reader: &mut R, buf: &mut String) -> io::Result<usize>
69where
70    R: BufRead,
71{
72    match reader.read_line(buf) {
73        Ok(0) => Ok(0),
74        Ok(n) => {
75            buf.pop();
76            Ok(n)
77        }
78        Err(e) => Err(e),
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use std::io::Write;
85
86    use flate2::write::GzEncoder;
87    use noodles_core::Position;
88
89    use crate::crai::Record;
90
91    use super::*;
92
93    #[test]
94    fn test_read_index() -> Result<(), Box<dyn std::error::Error>> {
95        let data = b"\
960\t10946\t6765\t17711\t233\t317811
970\t17711\t121393\t317811\t233\t317811
98";
99
100        let mut writer = GzEncoder::new(Vec::new(), Default::default());
101        writer.write_all(data)?;
102        let compressed_data = writer.finish()?;
103
104        let mut reader = Reader::new(&compressed_data[..]);
105
106        let actual = reader.read_index()?;
107
108        let expected = vec![
109            Record::new(Some(0), Position::new(10946), 6765, 17711, 233, 317811),
110            Record::new(Some(0), Position::new(17711), 121393, 317811, 233, 317811),
111        ];
112
113        assert_eq!(actual, expected);
114
115        Ok(())
116    }
117}