use crate::cli::PgLiftOptions;
use anyhow::{Context, Result};
use duct::cmd;
pub fn run_pg_lift(opts: &PgLiftOptions) -> Result<()> {
let input_type = if opts.bam { "BAM" } else { "BED" };
log::info!("Starting pangenome annotation lift pipeline");
log::info!(" Source reference: {}", opts.reference);
log::info!(" Input file: {} ({})", opts.input, input_type);
log::info!(" Pangenome graph: {}", opts.graph);
log::info!(" Target sample: {}", opts.target);
log::info!(" panSN prefix: {}", opts.prefix);
validate_pipeline_requirements(opts)?;
run_piped_pipeline(opts)?;
log::info!("Pangenome annotation lift completed successfully");
log::info!("Output written to: {}", opts.out);
Ok(())
}
fn validate_pipeline_requirements(opts: &PgLiftOptions) -> Result<()> {
log::info!("Validating pipeline requirements...");
which_vg(&opts.vg_binary).context("vg binary validation failed")?;
if !std::path::Path::new(&opts.reference).exists() {
return Err(anyhow::anyhow!(
"Reference FASTA file not found: {}",
opts.reference
));
}
if !std::path::Path::new(&opts.input).exists() {
return Err(anyhow::anyhow!("Input file not found: {}", opts.input));
}
if !std::path::Path::new(&opts.graph).exists() {
return Err(anyhow::anyhow!(
"Pangenome graph file not found: {}",
opts.graph
));
}
log::info!("✓ All pipeline requirements validated");
Ok(())
}
fn run_piped_pipeline(opts: &PgLiftOptions) -> Result<()> {
let split_size_str = opts.split_size.to_string();
let vg_threads_str = opts.vg_threads.to_string();
let delimiter_str = opts.delimiter.to_string();
let mut verbose_args = Vec::new();
if opts.global.quiet {
verbose_args.push("--quiet".to_string());
}
for _ in 0..opts.global.verbose {
verbose_args.push("--verbose".to_string());
}
let temp_header_file =
tempfile::NamedTempFile::new().context("Failed to create temporary header file")?;
let temp_header_path = temp_header_file.path().to_string_lossy().to_string();
log::info!("Using temporary header file: {}", temp_header_path);
let first_cmd = if opts.bam {
log::info!(
"Running BAM pipeline: pg-pansn --prefix | vg inject | vg surject | pg-pansn --strip"
);
let mut args = vec![
"pg-pansn",
&opts.input,
"--prefix",
&opts.prefix,
"--uncompressed",
"--header-out",
&temp_header_path,
];
args.extend(verbose_args.iter().map(|s| s.as_str()));
cmd(std::env::current_exe()?, args)
} else {
log::info!(
"Running BED pipeline: pg-inject | vg inject | vg surject | pg-inject --extract"
);
let mut pg_inject_args = vec![
"pg-inject",
&opts.reference,
"--prefix",
&opts.prefix,
"--split-size",
&split_size_str,
"--bed",
&opts.input,
"--uncompressed", "--header-out",
&temp_header_path,
];
pg_inject_args.extend(verbose_args.iter().map(|s| s.as_str()));
cmd(std::env::current_exe()?, pg_inject_args)
};
let vg_inject_cmd = cmd!(
&opts.vg_binary,
"inject",
"-t",
&vg_threads_str,
"-x",
&opts.graph,
"-" );
let vg_surject_cmd = cmd!(
&opts.vg_binary,
"surject",
"-C",
"0",
"-t",
&vg_threads_str,
"-x",
&opts.graph,
"-r",
"-b",
"-n",
&opts.target,
"-" );
let final_cmd = if opts.bam {
let mut args = vec![
"pg-pansn",
"-", "--strip",
"--delimiter",
&delimiter_str,
"--out",
&opts.out,
"--copy-header",
&temp_header_path,
"--uncompressed",
];
args.extend(verbose_args.iter().map(|s| s.as_str()));
cmd(std::env::current_exe()?, args)
} else {
let mut args = vec![
"pg-inject",
"-", "--extract",
"--strip",
"--delimiter",
&delimiter_str,
"--out",
&opts.out,
"--copy-header",
&temp_header_path,
];
args.extend(verbose_args.iter().map(|s| s.as_str()));
cmd(std::env::current_exe()?, args)
};
let pipeline = first_cmd
.pipe(vg_inject_cmd)
.pipe(vg_surject_cmd)
.pipe(final_cmd);
pipeline.run().context("Failed to execute pipeline")?;
Ok(())
}
fn which_vg(vg_binary: &str) -> Result<()> {
cmd!(vg_binary, "help")
.stdout_null()
.stderr_null()
.run()
.with_context(|| {
format!(
"Failed to find vg binary at '{}'. Please install vg or specify correct path with --vg-binary",
vg_binary
)
})?;
Ok(())
}