noodles_bam/io/indexed_reader/
builder.rs

1use std::{
2    ffi::{OsStr, OsString},
3    fs::File,
4    io::{self, Read},
5    path::{Path, PathBuf},
6};
7
8use noodles_bgzf as bgzf;
9use noodles_csi::{self as csi, BinningIndex};
10
11use super::IndexedReader;
12use crate::bai;
13
14/// An indexed BAM reader builder.
15#[derive(Default)]
16pub struct Builder {
17    index: Option<Box<dyn BinningIndex>>,
18}
19
20impl Builder {
21    /// Sets an index.
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// use noodles_bam::{bai, io::indexed_reader::Builder};
27    /// let index = bai::Index::default();
28    /// let builder = Builder::default().set_index(index);
29    /// ```
30    pub fn set_index<I>(mut self, index: I) -> Self
31    where
32        I: BinningIndex + 'static,
33    {
34        self.index = Some(Box::new(index));
35        self
36    }
37
38    /// Builds an indexed BAM reader from a path.
39    ///
40    /// If no index is set, this will attempt to read an associated index at `<src>.bai` or
41    /// `<src>.csi`, in that order.
42    ///
43    /// # Examples
44    ///
45    /// ```no_run
46    /// use noodles_bam::io::indexed_reader::Builder;
47    /// let reader = Builder::default().build_from_path("sample.bam")?;
48    /// # Ok::<_, std::io::Error>(())
49    /// ```
50    pub fn build_from_path<P>(self, src: P) -> io::Result<IndexedReader<bgzf::io::Reader<File>>>
51    where
52        P: AsRef<Path>,
53    {
54        let src = src.as_ref();
55
56        let index = match self.index {
57            Some(index) => index,
58            None => read_associated_index(src)?,
59        };
60
61        let file = File::open(src)?;
62
63        Ok(IndexedReader::new(file, index))
64    }
65
66    /// Builds an indexed BAM reader from a reader.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use noodles_bam::{bai, io::indexed_reader::Builder};
72    /// let index = bai::Index::default();
73    /// let data = [];
74    /// let reader = Builder::default().set_index(index).build_from_reader(&data[..])?;
75    /// # Ok::<_, std::io::Error>(())
76    /// ```
77    pub fn build_from_reader<R>(self, reader: R) -> io::Result<IndexedReader<bgzf::io::Reader<R>>>
78    where
79        R: Read,
80    {
81        let index = self
82            .index
83            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "missing index"))?;
84
85        Ok(IndexedReader::new(reader, index))
86    }
87}
88
89fn read_associated_index<P>(src: P) -> io::Result<Box<dyn BinningIndex>>
90where
91    P: AsRef<Path>,
92{
93    let src = src.as_ref();
94
95    match bai::fs::read(build_index_src(src, "bai")) {
96        Ok(index) => Ok(Box::new(index)),
97        Err(e) if e.kind() == io::ErrorKind::NotFound => {
98            let index = csi::fs::read(build_index_src(src, "csi"))?;
99            Ok(Box::new(index))
100        }
101        Err(e) => Err(e),
102    }
103}
104
105fn build_index_src<P, S>(src: P, ext: S) -> PathBuf
106where
107    P: AsRef<Path>,
108    S: AsRef<OsStr>,
109{
110    push_ext(src.as_ref().into(), ext)
111}
112
113fn push_ext<S>(path: PathBuf, ext: S) -> PathBuf
114where
115    S: AsRef<OsStr>,
116{
117    let mut s = OsString::from(path);
118    s.push(".");
119    s.push(ext);
120    PathBuf::from(s)
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_push_ext() {
129        assert_eq!(
130            push_ext(PathBuf::from("sample.bam"), "bai"),
131            PathBuf::from("sample.bam.bai")
132        );
133    }
134}