noodles_vcf/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};
10use noodles_tabix as tabix;
11
12use super::IndexedReader;
13
14/// An indexed VCF 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_tabix as tabix;
27    /// use noodles_vcf::io::indexed_reader::Builder;
28    ///
29    /// let index = tabix::Index::default();
30    /// let builder = Builder::default().set_index(index);
31    /// ```
32    pub fn set_index<I>(mut self, index: I) -> Self
33    where
34        I: BinningIndex + 'static,
35    {
36        self.index = Some(Box::new(index));
37        self
38    }
39
40    /// Builds an indexed VCF reader from a path.
41    ///
42    /// # Examples
43    ///
44    /// ```no_run
45    /// use noodles_vcf::io::indexed_reader::Builder;
46    /// let reader = Builder::default().build_from_path("sample.vcf.gz")?;
47    /// # Ok::<_, std::io::Error>(())
48    /// ```
49    pub fn build_from_path<P>(self, src: P) -> io::Result<IndexedReader<bgzf::Reader<File>>>
50    where
51        P: AsRef<Path>,
52    {
53        let src = src.as_ref();
54
55        let index = match self.index {
56            Some(index) => index,
57            None => read_associated_index(src)?,
58        };
59
60        let file = File::open(src)?;
61
62        Ok(IndexedReader::new(file, index))
63    }
64
65    /// Builds an indexed VCF reader from a reader.
66    ///
67    /// # Examples
68    ///
69    /// ```
70    /// # use std::io;
71    /// use noodles_tabix as tabix;
72    /// use noodles_vcf::io::indexed_reader::Builder;
73    ///
74    /// let index = tabix::Index::default();
75    /// let reader = Builder::default()
76    ///     .set_index(index)
77    ///     .build_from_reader(io::empty())?;
78    /// # Ok::<_, io::Error>(())
79    /// ```
80    pub fn build_from_reader<R>(self, reader: R) -> io::Result<IndexedReader<bgzf::Reader<R>>>
81    where
82        R: Read,
83    {
84        let index = self
85            .index
86            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "missing index"))?;
87
88        Ok(IndexedReader::new(reader, index))
89    }
90}
91
92fn read_associated_index<P>(src: P) -> io::Result<Box<dyn BinningIndex>>
93where
94    P: AsRef<Path>,
95{
96    let src = src.as_ref();
97
98    match tabix::fs::read(build_index_src(src, "tbi")) {
99        Ok(index) => Ok(Box::new(index)),
100        Err(e) if e.kind() == io::ErrorKind::NotFound => {
101            let index = csi::fs::read(build_index_src(src, "csi"))?;
102            Ok(Box::new(index))
103        }
104        Err(e) => Err(e),
105    }
106}
107
108fn build_index_src<P, S>(src: P, ext: S) -> PathBuf
109where
110    P: AsRef<Path>,
111    S: AsRef<OsStr>,
112{
113    push_ext(src.as_ref().into(), ext)
114}
115
116fn push_ext<S>(path: PathBuf, ext: S) -> PathBuf
117where
118    S: AsRef<OsStr>,
119{
120    let mut s = OsString::from(path);
121    s.push(".");
122    s.push(ext);
123    PathBuf::from(s)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_push_ext() {
132        assert_eq!(
133            push_ext(PathBuf::from("sample.vcf.gz"), "tbi"),
134            PathBuf::from("sample.vcf.gz.tbi")
135        );
136    }
137}