gapsmith_align/
diamond.rs1use crate::blast::which;
16use crate::error::{io_err, AlignError};
17use crate::hit::Hit;
18use crate::tsv::parse_tsv;
19use crate::{AlignOpts, Aligner};
20use std::fs::File;
21use std::io::BufReader;
22use std::path::Path;
23use std::process::Command;
24
25const COLUMNS: &[&str] = &[
28 "qseqid", "pident", "evalue", "bitscore", "qcovhsp", "stitle", "sstart", "send",
29];
30
31pub struct DiamondAligner {
32 pub more_sensitive: bool,
35}
36
37impl DiamondAligner {
38 pub fn new() -> Self {
39 Self { more_sensitive: true }
40 }
41}
42impl Default for DiamondAligner {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl Aligner for DiamondAligner {
49 fn name(&self) -> &'static str {
50 "diamond"
51 }
52
53 fn align(
54 &self,
55 query_fasta: &Path,
56 target_fasta: &Path,
57 opts: &AlignOpts,
58 ) -> Result<Vec<Hit>, AlignError> {
59 require("diamond")?;
60 let tmp = tempfile::tempdir().map_err(|e| io_err(Path::new("/tmp"), e))?;
61 let db_prefix = tmp.path().join("orgdb");
62 let out_tsv = tmp.path().join("aln.tsv");
63
64 let out = Command::new("diamond")
66 .arg("makedb")
67 .arg("--in")
68 .arg(target_fasta)
69 .arg("-d")
70 .arg(&db_prefix)
71 .arg("--quiet")
72 .output()
73 .map_err(|e| io_err(Path::new("diamond"), e))?;
74 if !out.status.success() {
75 return Err(AlignError::ToolFailed {
76 tool: "diamond",
77 status: out.status,
78 stderr: String::from_utf8_lossy(&out.stderr).to_string(),
79 });
80 }
81
82 let dmnd_db = {
84 let mut p = db_prefix.clone();
85 p.set_extension("dmnd");
86 p
87 };
88 let mut cmd = Command::new("diamond");
89 cmd.arg("blastp")
90 .arg("-d")
91 .arg(&dmnd_db)
92 .arg("-q")
93 .arg(query_fasta)
94 .arg("--threads")
95 .arg(opts.threads.to_string())
96 .arg("--out")
97 .arg(&out_tsv)
98 .arg("--outfmt")
99 .arg("6");
100 for c in COLUMNS {
101 cmd.arg(c);
102 }
103 cmd.arg("--query-cover").arg(opts.coverage_pct.to_string());
104 if self.more_sensitive {
105 cmd.arg("--more-sensitive");
106 }
107 if let Some(e) = opts.evalue {
108 cmd.arg("--evalue").arg(e.to_string());
109 }
110 if opts.quiet {
111 cmd.arg("--quiet");
112 }
113 for a in &opts.extra_args {
114 cmd.arg(a);
115 }
116
117 tracing::debug!(?cmd, "running diamond");
118 let out = cmd
119 .output()
120 .map_err(|e| io_err(Path::new("diamond"), e))?;
121 if !out.status.success() {
122 return Err(AlignError::ToolFailed {
123 tool: "diamond",
124 status: out.status,
125 stderr: String::from_utf8_lossy(&out.stderr).to_string(),
126 });
127 }
128 if !opts.quiet && !out.stderr.is_empty() {
129 eprintln!("{}", String::from_utf8_lossy(&out.stderr));
130 }
131
132 let f = File::open(&out_tsv).map_err(|e| io_err(&out_tsv, e))?;
133 parse_tsv(BufReader::new(f), false)
134 }
135}
136
137fn require(tool: &'static str) -> Result<(), AlignError> {
138 if which(tool).is_some() {
139 Ok(())
140 } else {
141 Err(AlignError::ToolMissing { tool })
142 }
143}