use rust_ise::{run, write_gff, write_sum, write_tsv, IseConfig};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::Instant;
mod build_db;
fn run_build_db(rest: &[String]) {
use build_db::BuildArgs;
let mut iso: Option<PathBuf> = None;
let mut fam: Option<PathBuf> = None;
let mut faa: Option<PathBuf> = None;
let mut csv: Option<PathBuf> = None;
let mut out: Option<PathBuf> = None;
let mut work: Option<PathBuf> = None;
let mut isfinder_fna: Option<PathBuf> = None;
let mut fpc_neg: Option<PathBuf> = None;
let mut fpc_pos_extra: Option<PathBuf> = None;
let mut strict_version = false;
let mut fetch = false;
let mut fetch_host = false;
let mut cache_dir: Option<PathBuf> = None;
let mut i = 0;
while i < rest.len() {
match rest[i].as_str() {
"--isosdb-fna" => { i += 1; iso = Some(PathBuf::from(&rest[i])); }
"--fam-annot" => { i += 1; fam = Some(PathBuf::from(&rest[i])); }
"--isfinder-faa" => { i += 1; faa = Some(PathBuf::from(&rest[i])); }
"--isfinder-csv" => { i += 1; csv = Some(PathBuf::from(&rest[i])); }
"--isfinder-fna" => { i += 1; isfinder_fna = Some(PathBuf::from(&rest[i])); }
"--out" | "--db" => { i += 1; out = Some(PathBuf::from(&rest[i])); }
"--work" => { i += 1; work = Some(PathBuf::from(&rest[i])); }
"--host" | "--fpc-neg" => { i += 1; fpc_neg = Some(PathBuf::from(&rest[i])); }
"--fpc-pos-extra" => { i += 1; fpc_pos_extra = Some(PathBuf::from(&rest[i])); }
"--strict-mmseqs-version" => strict_version = true,
"--fetch-sources" => fetch = true,
"--fetch-host" => { fetch = true; fetch_host = true; }
"--cache-dir" => { i += 1; cache_dir = Some(PathBuf::from(&rest[i])); }
"-h" | "--help" => {
eprintln!("usage: rust-ise build-db --out DIR [sources…]");
eprintln!();
eprintln!("Sources (supply locally, or use --fetch-sources to auto-download):");
eprintln!(" --isosdb-fna F --fam-annot F --isfinder-faa F --isfinder-csv F [--isfinder-fna IS.fna]");
eprintln!(" --fetch-sources download+verify any un-supplied source (pinned SHA + sha256)");
eprintln!(" --fetch-host also fetch the host negative pool (Release asset) for fpc");
eprintln!(" --cache-dir DIR source download cache (default ~/.cache/rust-ise/sources)");
eprintln!("fpc reference (optional):");
eprintln!(" --host HOST.fna | --fpc-neg HOST.fna host-lineage N| pool (or use --fetch-host)");
eprintln!(" --fpc-pos-extra EXTRA.fna optional extra P| pool (escape hatch)");
eprintln!("misc: [--work DIR] [--strict-mmseqs-version]");
eprintln!();
eprintln!("Builds <out>/mmdb_union/ + <out>/manifest_union.tsv. With a host pool, also builds");
eprintln!("<out>/fpc/refset* (nt neg-pos FP-control reference: P|=IS-lineage, N|=host-lineage).");
eprintln!("The P| pool is auto-derived in pure Rust: ISOSDB per-ORF CDS + (with --isfinder-fna)");
eprintln!("ISfinder transposase CDS located in IS.fna by 6-frame translation.");
return;
}
x => { eprintln!("unknown build-db arg {x}"); std::process::exit(2); }
}
i += 1;
}
if fetch {
let cache = cache_dir.unwrap_or_else(build_db::fetch::default_cache);
let src = match build_db::fetch::fetch_sources(&cache, fetch_host) {
Ok(s) => s,
Err(e) => { eprintln!("build-db fetch error: {e}"); std::process::exit(1); }
};
iso = iso.or(Some(src.isosdb_fna));
fam = fam.or(Some(src.fam_annot));
faa = faa.or(Some(src.isfinder_faa));
csv = csv.or(Some(src.isfinder_csv));
isfinder_fna = isfinder_fna.or(Some(src.isfinder_fna));
if fpc_neg.is_none() {
fpc_neg = src.host;
}
}
let need = |o: Option<PathBuf>, flag: &str| -> PathBuf {
o.unwrap_or_else(|| {
eprintln!("build-db needs {flag} (or --fetch-sources)");
std::process::exit(2);
})
};
let out_dir = out.unwrap_or_else(|| { eprintln!("build-db needs --out DIR"); std::process::exit(2); });
let work = work.unwrap_or_else(|| out_dir.join("work"));
let cfg = BuildArgs {
isosdb_fna: need(iso, "--isosdb-fna"),
fam_annot: need(fam, "--fam-annot"),
isfinder_faa: need(faa, "--isfinder-faa"),
isfinder_csv: need(csv, "--isfinder-csv"),
isfinder_fna,
out_dir,
work,
strict_version,
fpc_neg,
fpc_pos_extra,
};
if let Err(e) = build_db::run(&cfg) {
eprintln!("build-db error: {e}");
std::process::exit(1);
}
}
fn main() {
let t0 = Instant::now();
let args: Vec<String> = std::env::args().collect();
if args.get(1).map(String::as_str) == Some("build-db") {
run_build_db(&args[2..]);
return;
}
let mut seqfile = String::new();
let mut output = String::new();
let mut threads = 16usize;
let mut gpu = false;
let mut keep = false;
let mut strict = false;
let mut db_dir: Option<PathBuf> = None; let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"-i" | "--seqfile" => {
i += 1;
seqfile = args[i].clone();
}
"-o" | "--output" => {
i += 1;
output = args[i].clone();
}
"-t" | "--threads" => {
i += 1;
threads = args[i].parse().unwrap();
}
"--gpu" => gpu = true,
"--keep" => keep = true,
"--strict" => strict = true, "--db" => {
i += 1;
db_dir = Some(PathBuf::from(args[i].clone()));
}
_ => {
eprintln!("unknown arg {}", args[i]);
std::process::exit(2);
}
}
i += 1;
}
assert!(!seqfile.is_empty() && !output.is_empty(), "need -i and -o");
let db_dir = db_dir
.or_else(|| std::env::var_os("ISSCAN_DB").map(PathBuf::from))
.unwrap_or_else(|| {
eprintln!("error: IS database not set. Pass --db <dir> or set ISSCAN_DB (see README).");
std::process::exit(2);
});
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build_global()
.ok();
fs::create_dir_all(&output).unwrap();
let config = IseConfig { threads, gpu, strict, db_dir };
let calls = match run(Path::new(&seqfile), Path::new(&output), &config) {
Ok(c) => c,
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
};
let stem = Path::new(&output);
write_tsv(&calls, &stem.join("isscan.tsv"));
write_gff(&calls, &stem.join("isscan.gff"));
write_sum(&calls, &stem.join("isscan.sum"));
if !keep {
let _ = fs::remove_dir_all(stem.join("isscan_tmp"));
}
eprintln!(
"[{:.1}s] DONE -> {}/isscan.tsv ({} IS)",
t0.elapsed().as_secs_f64(),
output,
calls.len()
);
}