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 #[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}