rust-ise 0.2.1

Fast Rust-native ISEScan-equivalent insertion-sequence (IS) scanner for bacterial (meta)genomes: rustygal ORFs + MMseqs2 profile search + native affine-SW terminal inverted repeats.
Documentation
// rust-ise CLI — a thin wrapper over the `rust_ise` library.
//
// Parses args, runs the detection pipeline (`rust_ise::run`), and writes the
// TSV/GFF/.sum outputs. All detection logic lives in the library so it can be
// consumed in-process (e.g. by bactars).
use rust_ise::{run, write_gff, write_sum, write_tsv, IseConfig};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::Instant;

fn main() {
    let t0 = Instant::now();
    let args: Vec<String> = std::env::args().collect();
    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; // resolve from --db, then $ISSCAN_DB
    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, // drop fpFlag=host calls (host-lineage FPs)
            "--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");

    // DB is external data (not bundled). Resolve --db, else $ISSCAN_DB, else error.
    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()
    );
}