filter_clipped/
cli.rs

1pub use clap::Parser;
2use std::string::String;
3
4/// Remove alignments with high number of clipped base. Sometimes aligner has very loose scoring methods and write alignments with
5/// high abundant of soft/hard-clipped base into alignment BAM files.
6/// This program is for filtering these reads out by gating the number of clipped bases
7/// in relative to the read sequence length
8#[derive(Parser, Debug)]
9#[clap(author, version, about, long_about = None)]
10pub struct Command {
11    /// maximum fraction of bases on the sequence being clipped
12    /// from the left side (5' end)
13    #[clap(short, long, value_parser=check_fraction, default_value_t = 0.1)]
14    pub left_side: f64,
15
16    /// maximum fraction of bases on the sequence being clipped
17    /// from the right side (3' end)
18    #[clap(short, long, value_parser=check_fraction, default_value_t = 0.1)]
19    pub right_side: f64,
20
21    /// maximum fraction of total bases on the sequence being clipped
22    #[clap(short, long, value_parser=check_fraction, default_value_t = 0.1)]
23    pub both_end: f64,
24
25    /// input bam file path  ("-" for stdin)
26    #[clap(short, long, value_parser)]
27    pub in_bam: String,
28
29    /// output bam file path ("-" for stdout)
30    #[clap(short, long, value_parser, default_value = "-")]
31    pub out_bam: String,
32
33    /// keeping the failed ones (high-clipped-fraction alignments)
34    #[clap(long, action)]
35    pub inverse: bool,
36
37    /// make the record to unmapped instead of removing it, ignore --inverse flag
38    #[clap(short, long, action)]
39    pub unalign: bool,
40}
41
42/// check if a give value is between 0 and 1
43///
44/// # Arguments
45/// - val: a file path to test for it's existence
46///
47/// # Returns
48/// - Err if no
49///
50/// # Example
51/// ```
52/// use filter_clipped::cli::check_fraction;
53/// assert_eq!(check_fraction("0.5").unwrap(), 0.5);
54/// ```
55pub fn check_fraction(val: &str) -> Result<f64, String> {
56    let f_val: f64 = val.parse::<f64>().map_err(|e| e.to_string())?;
57    if (0.0..=1.0).contains(&f_val) {
58        Ok(f_val)
59    } else {
60        Err(format!("{} is not within 0 and 1", val))
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use rstest::rstest;
68
69    #[rstest]
70    #[case("1.0", 1.0)]
71    #[case("0.0", 0.0)]
72    #[case("0.5", 0.5)]
73    fn test_check_fraction(#[case] val: &str, #[case] out: f64) {
74        assert_eq!(check_fraction(val).unwrap(), out);
75    }
76
77    #[rstest]
78    #[case("1.1")]
79    #[case("-0.1")]
80    #[should_panic]
81    fn test_check_fraction_panic(#[case] val: &str) {
82        check_fraction(val).unwrap();
83    }
84}