use std::io::BufWriter;
use std::path::PathBuf;
use clap::Parser;
use rsomics_common::{CommonFlags, Result, RsomicsError, Tool, ToolMeta};
use rsomics_help::{Example, FlagSpec, HelpSpec, Origin, Section};
use rsomics_vcf_reheader::{passthrough, reheader_replace, reheader_samples};
pub const META: ToolMeta = ToolMeta {
name: env!("CARGO_PKG_NAME"),
version: env!("CARGO_PKG_VERSION"),
};
#[derive(Parser, Debug)]
#[command(
name = "rsomics-vcf-reheader",
version,
about,
long_about = None,
disable_help_flag = true
)]
pub struct Cli {
#[arg(value_name = "INPUT")]
pub input: Option<PathBuf>,
#[arg(long = "header", value_name = "FILE")]
pub header: Option<PathBuf>,
#[arg(short = 's', long = "samples", value_name = "FILE")]
pub samples: Option<PathBuf>,
#[arg(short = 'o', long = "output", default_value = "-")]
pub output: String,
#[command(flatten)]
pub common: CommonFlags,
}
impl Cli {
pub fn execute(self) -> Result<()> {
let mut input_box: Box<dyn std::io::Read> = match &self.input {
Some(p) => Box::new(
std::fs::File::open(p)
.map_err(|e| RsomicsError::InvalidInput(format!("{}: {e}", p.display())))?,
),
None => Box::new(std::io::stdin()),
};
let mut out: Box<dyn std::io::Write> = if self.output == "-" {
Box::new(BufWriter::new(std::io::stdout().lock()))
} else {
Box::new(BufWriter::new(
std::fs::File::create(&self.output).map_err(RsomicsError::Io)?,
))
};
let n = match (&self.header, &self.samples) {
(Some(hdr), _) => reheader_replace(input_box.as_mut(), hdr, out.as_mut())?,
(None, Some(smp)) => reheader_samples(input_box.as_mut(), smp, out.as_mut())?,
(None, None) => passthrough(input_box.as_mut(), out.as_mut())?,
};
if !self.common.quiet {
eprintln!("{n} data records written");
}
Ok(())
}
}
impl Tool for Cli {
fn meta() -> ToolMeta {
META
}
fn common(&self) -> &CommonFlags {
&self.common
}
fn execute(self) -> Result<()> {
self.execute()
}
}
pub static HELP: HelpSpec = HelpSpec {
name: META.name,
version: META.version,
tagline: "Replace a VCF header or rename samples — Rust port of bcftools reheader.",
origin: Some(Origin {
upstream: "bcftools reheader",
upstream_license: "MIT",
our_license: "MIT OR Apache-2.0",
paper_doi: Some("10.1093/gigascience/giab008"),
}),
usage_lines: &["[OPTIONS] [INPUT]"],
sections: &[Section {
title: "OPTIONS",
flags: &[
FlagSpec {
short: None,
long: "INPUT",
aliases: &[],
value: Some("<path>"),
type_hint: Some("Path"),
required: false,
default: Some("stdin"),
description: "Input VCF file. Reads from stdin when omitted.",
why_default: None,
},
FlagSpec {
short: None,
long: "header",
aliases: &[],
value: Some("<FILE>"),
type_hint: Some("Path"),
required: false,
default: None,
description: "Replace the entire header with the contents of FILE. \
Data records are passed through unchanged.",
why_default: None,
},
FlagSpec {
short: Some('s'),
long: "samples",
aliases: &[],
value: Some("<FILE>"),
type_hint: Some("Path"),
required: false,
default: None,
description: "Rename samples on the #CHROM line. FILE contains one new \
sample name per line (positional) or `old new` pairs \
(map-based). Data records are passed through unchanged.",
why_default: None,
},
FlagSpec {
short: Some('o'),
long: "output",
aliases: &[],
value: Some("<path>"),
type_hint: Some("Path"),
required: false,
default: Some("-"),
description: "Output VCF file (default: stdout).",
why_default: None,
},
],
}],
examples: &[
Example {
description: "Replace header with contents of new_header.txt",
command: "rsomics-vcf-reheader --header new_header.txt in.vcf",
},
Example {
description: "Rename samples (positional, one name per line)",
command: "rsomics-vcf-reheader -s new_samples.txt in.vcf",
},
Example {
description: "Rename samples (old→new pairs) and write to file",
command: "rsomics-vcf-reheader -s renames.txt -o out.vcf in.vcf",
},
],
json_result_schema_doc: None,
};
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn cli_debug_assert() {
Cli::command().debug_assert();
}
}