Skip to main content

cyto_cli/map/
input.rs

1use std::io::Read;
2
3use anyhow::Result;
4use binseq::BinseqReader;
5use clap::Parser;
6
7pub use anyhow::bail;
8pub use binseq::bq::MmapReader;
9use log::{debug, error};
10use paraseq::fastx;
11
12type FxReader = fastx::Reader<Box<dyn Read + Send>>;
13type FxReaderPair = (FxReader, FxReader);
14
15#[derive(Parser, Debug)]
16#[clap(next_help_heading = "Paired Input Options")]
17pub struct PairedInput {
18    #[clap(
19        short = 'i',
20        long,
21        conflicts_with = "input",
22        required_unless_present = "input"
23    )]
24    pub r1: Option<String>,
25    #[clap(
26        short = 'I',
27        long,
28        conflicts_with = "input",
29        required_unless_present = "input"
30    )]
31    pub r2: Option<String>,
32}
33impl PairedInput {
34    pub fn to_readers(&self) -> Result<FxReaderPair> {
35        match (self.r1.as_ref(), self.r2.as_ref()) {
36            (Some(r1), Some(r2)) => {
37                debug!("Opening readers for {r1} and {r2}");
38                Ok((fastx::Reader::from_path(r1)?, fastx::Reader::from_path(r2)?))
39            }
40            _ => {
41                bail!("Both R1 and R2 must be provided for paired input")
42            }
43        }
44    }
45}
46
47#[derive(Parser, Debug)]
48#[clap(next_help_heading = "Paired input options")]
49pub struct MultiPairedInput {
50    /// Paths to input files to process.
51    ///
52    /// If using BINSEQ input (*.bq/*.vbq), the ordering of files or number of files does not matter.
53    ///
54    /// If using FASTX input, the input files are expected to be paired and sequentially ordered (`S1_R1`, `S1_R2`, `S2_R1`, `S2_R2`, ...).
55    /// This is expecting an even number of files.
56    #[clap(num_args = 1.., required=true)]
57    pub inputs: Vec<String>,
58}
59impl MultiPairedInput {
60    pub fn is_binseq(&self) -> bool {
61        self.inputs
62            .iter()
63            .all(|path| path.ends_with(".bq") || path.ends_with(".vbq") || path.ends_with("cbq"))
64    }
65
66    pub fn to_fx_readers(&self) -> Result<Vec<FxReaderPair>> {
67        let mut readers = Vec::new();
68        if !self.inputs.len().is_multiple_of(2) {
69            error!(
70                "Found {} inputs, expecting an even number of file pairs",
71                self.inputs.len()
72            );
73            bail!("Number of pairs must be even");
74        }
75        for pair in self.inputs.chunks(2) {
76            let r1 = pair[0].clone();
77            let r2 = pair[1].clone();
78            readers.push((fastx::Reader::from_path(r1)?, fastx::Reader::from_path(r2)?));
79        }
80        Ok(readers)
81    }
82
83    pub fn to_binseq_readers(&self) -> Result<Vec<BinseqReader>> {
84        let mut readers = Vec::new();
85        for path in &self.inputs {
86            let reader = BinseqReader::new(path)?;
87            readers.push(reader);
88        }
89        Ok(readers)
90    }
91}
92
93#[derive(Parser, Debug)]
94#[clap(next_help_heading = "Binseq input options")]
95pub struct BinseqInput {
96    #[clap(
97        short = 'b',
98        long,
99        conflicts_with = "pairs",
100        required_unless_present = "pairs"
101    )]
102    pub input: Option<String>,
103}
104impl BinseqInput {
105    #[allow(clippy::wrong_self_convention)]
106    pub fn into_reader(&self) -> Result<BinseqReader> {
107        let path = self.path()?;
108        debug!("Opening binseq reader for {path}");
109        let rdr = BinseqReader::new(path)?;
110        if !rdr.is_paired() {
111            error!("Found unpaired BINSEQ file: {path}");
112            bail!("Input BINSEQ file must be paired!");
113        }
114        Ok(rdr)
115    }
116
117    pub fn path(&self) -> Result<&str> {
118        if let Some(input) = &self.input {
119            Ok(input)
120        } else {
121            bail!("No input file provided to BINSEQ input");
122        }
123    }
124}