use anyhow::Context; use log::info; use std::{fs, path::Path}; use uuid::Uuid; use crate::runners::{run_wait, CommandRun, RunReport}; #[derive(Debug)] pub struct BcftoolsConfig { pub bin: String, pub threads: u8, } impl Default for BcftoolsConfig { fn default() -> Self { Self { bin: "/data/tools/bcftools-1.21/bcftools".to_string(), threads: 20, } } } pub fn bcftools_keep_pass( input: &str, output: &str, config: BcftoolsConfig, ) -> anyhow::Result { if !Path::new(input).exists() { anyhow::bail!("File doesnt exist {input}") } // First sort let tmp_file = format!("/tmp/{}", Uuid::new_v4()); let mut cmd_run = CommandRun::new(&config.bin, &["sort", input, "-o", &tmp_file]); let _ = run_wait(&mut cmd_run)?; // 2. norm let tmp2_file = format!("/tmp/{}", Uuid::new_v4()); let mut cmd_run = CommandRun::new( &config.bin, &[ "norm", "--threads", &config.threads.to_string(), "-a", "--atom-overlaps", ".", &tmp_file, "-o", &tmp2_file, ], ); let _ = run_wait(&mut cmd_run)?; fs::remove_file(tmp_file)?; // Then filter let mut cmd_run = CommandRun::new( &config.bin, &[ "view", "--write-index", "--threads", &config.threads.to_string(), "-i", "FILTER='PASS'", &tmp2_file, "-o", output, ], ); let res = run_wait(&mut cmd_run)?; fs::remove_file(tmp2_file)?; Ok(res) } pub fn bcftools_keep_pass_precise( input: &str, output: &str, config: BcftoolsConfig, ) -> anyhow::Result { if !Path::new(input).exists() { anyhow::bail!("File doesnt exist {input}") } // First sort let tmp_file = format!("/tmp/{}", Uuid::new_v4()); let mut cmd_run = CommandRun::new(&config.bin, &["sort", input, "-o", &tmp_file]); let _ = run_wait(&mut cmd_run)?; // 2. norm let tmp2_file = format!("/tmp/{}", Uuid::new_v4()); let mut cmd_run = CommandRun::new( &config.bin, &[ "norm", "--threads", &config.threads.to_string(), "-a", "--atom-overlaps", ".", &tmp_file, "-o", &tmp2_file, ], ); let _ = run_wait(&mut cmd_run)?; fs::remove_file(tmp_file)?; // Then filter let mut cmd_run = CommandRun::new( &config.bin, &[ "view", "--write-index", "--threads", &config.threads.to_string(), "-e", "INFO/IMPRECISE==1 || FILTER!=\"PASS\"", &tmp2_file, "-o", output, ], ); let res = run_wait(&mut cmd_run)?; fs::remove_file(tmp2_file)?; Ok(res) } pub fn bcftools_concat( inputs: Vec, output: &str, config: BcftoolsConfig, ) -> anyhow::Result { info!("Concatening vcf with bcftools: {}", inputs.join(", ")); let tmp_file = format!("/tmp/{}", Uuid::new_v4()); fs::write(&tmp_file, inputs.join("\n"))?; let args = [ "concat", "--write-index", "--threads", &config.threads.to_string(), "-a", "-D", "-f", &tmp_file, "-o", output, ]; // Then filter let mut cmd_run = CommandRun::new(&config.bin, &args); let res = run_wait(&mut cmd_run) .context(format!("Error while running `bcftools {}`", args.join(" ")))?; fs::remove_file(tmp_file)?; Ok(res) } pub fn bcftools_keep_only_in_a( a: &str, b: &str, out: &str, config: &BcftoolsConfig, ) -> anyhow::Result<()> { let args = ["isec", "-C", "-w", "1", a, b, "-o", out]; let mut cmd_run = CommandRun::new(&config.bin, &args); let _ = run_wait(&mut cmd_run) .context(format!("Error while running `bcftools {}`", args.join(" ")))?; Ok(()) } pub fn bcftools_index(vcf: &str, config: &BcftoolsConfig) -> anyhow::Result<()> { let args = ["index", "--threads", &config.threads.to_string(), vcf]; let mut cmd_run = CommandRun::new(&config.bin, &args); let _ = run_wait(&mut cmd_run) .context(format!("Error while running `bcftools {}`", args.join(" ")))?; Ok(()) } pub fn bcftools_compress( in_vcf: &str, out_vcf: &str, config: &BcftoolsConfig, ) -> anyhow::Result<()> { let args = [ "view", "--threads", &config.threads.to_string(), in_vcf, "-Oz", "-o", out_vcf, ]; let mut cmd_run = CommandRun::new(&config.bin, &args); let _ = run_wait(&mut cmd_run) .context(format!("Error while running `bcftools {}`", args.join(" ")))?; Ok(()) }