use std::str::FromStr;
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(version, about = "Visulise ambigous bases in a bam file.")]
pub struct Cli {
#[clap(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Depth {
#[arg(required = true, value_parser(check_input_exists))]
input: String,
#[arg(required = false)]
region: Option<String>,
#[arg(short = 'o', long = "output", default_value = "ambig")]
output: String,
},
Ambig {
#[arg(required = true, value_parser(check_input_exists))]
input: String,
#[arg(required = false)]
region: Option<String>,
#[arg(short = 'o', long = "output", default_value = "ambig")]
output: String,
#[arg(short = 't', long = "threshold", default_value = "0.1", value_parser(check_threshold_valid))]
threshold: f64,
#[arg(short = 'q', long = "--min-BQ", default_value = "20")]
base_quality_threshold: u8,
#[arg(short = 'Q', long = "--min-MQ", default_value = "60")]
map_quality_threshold: u8,
#[arg(short = 'D', long = "--depth", default_value = "100")]
depth_threshold: u32,
#[arg(short = 'd', long = "--minor-depth", default_value = "20")]
minor_depth_threshold: u32,
#[arg(short = 's', long = "--strand-bias", default_value = "0.1", value_parser(check_threshold_valid))]
strand_bias_threshold: f64,
#[arg(long = "no-indel")]
no_indel: bool,
#[arg(long = "no-label")]
no_label: bool,
#[arg(long = "bed")]
bed: bool,
},
}
fn check_input_exists(s: &str) -> Result<String, String> {
if std::path::Path::new(s).exists() {
Ok(s.to_string())
} else {
Err(format!("File does not exist: {}", s))
}
}
fn check_threshold_valid(s: &str) -> Result<f64, String> {
let threshold = f64::from_str(s).unwrap();
if threshold >= 0.0 && threshold <= 0.5 {
Ok(threshold)
} else {
Err(format!("Threshold must be between 0 and 0.5"))
}
}
pub fn parse_region(region: &str) -> (&str, u32, Option<u32>) {
if region.contains(':') {
let region_parts: Vec<&str> = region.split(':').collect();
let chrom = region_parts[0];
let positions = region_parts[1];
let start: u32;
let end: Option<u32>;
if positions.contains('-') {
let position_parts: Vec<u32> = positions
.split('-')
.map(|x| u32::from_str(x).unwrap())
.collect();
start = position_parts[0];
end = Option::from(position_parts[1]);
} else {
start = u32::from_str(positions).unwrap();
end = None;
}
(chrom, start, end)
} else {
(region, 1, None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_region_chrom_only() {
let region = "chr1";
let (chrom, start, end) = parse_region(region);
assert_eq!(chrom, "chr1");
assert_eq!(start, 1);
assert_eq!(end, None);
}
#[test]
fn test_parse_region_chrom_and_single_position() {
let region = "chr2:100";
let (chrom, start, end) = parse_region(region);
assert_eq!(chrom, "chr2");
assert_eq!(start, 100);
assert_eq!(end, None);
}
#[test]
fn test_parse_region_chrom_and_range() {
let region = "chr3:200-300";
let (chrom, start, end) = parse_region(region);
assert_eq!(chrom, "chr3");
assert_eq!(start, 200);
assert_eq!(end, Some(300));
}
}