use std::io::{self, BufRead, Read as _};
use std::process;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Tm(TmArgs),
Hairpin(SingleSeqArgs),
Homodimer(SingleSeqArgs),
Heterodimer(HeterodimerArgs),
Design(DesignArgs),
}
#[derive(Parser)]
struct TmArgs {
sequences: Vec<String>,
}
#[derive(Parser)]
struct SingleSeqArgs {
sequence: String,
}
#[derive(Parser)]
struct HeterodimerArgs {
seq1: String,
seq2: String,
}
#[derive(Parser)]
struct DesignArgs {
#[arg(long)]
json: bool,
}
fn main() {
let cli = Cli::parse();
let result = match &cli.command {
Command::Tm(args) => run_tm(args),
Command::Hairpin(args) => run_hairpin(args),
Command::Homodimer(args) => run_homodimer(args),
Command::Heterodimer(args) => run_heterodimer(args),
Command::Design(args) => run_design(args),
};
if let Err(e) = result {
eprintln!("error: {e}");
process::exit(1);
}
}
fn run_tm(args: &TmArgs) -> Result<(), Box<dyn std::error::Error>> {
if args.sequences.is_empty() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let seq = line?;
let seq = seq.trim();
if seq.is_empty() {
continue;
}
match primer3::calc_tm(seq) {
Ok(tm) => println!("{seq}\t{tm:.2}"),
Err(e) => eprintln!("{seq}\tERROR: {e}"),
}
}
} else {
for seq in &args.sequences {
match primer3::calc_tm(seq) {
Ok(tm) => println!("{seq}\t{tm:.2}"),
Err(e) => eprintln!("{seq}\tERROR: {e}"),
}
}
}
Ok(())
}
fn run_hairpin(args: &SingleSeqArgs) -> Result<(), Box<dyn std::error::Error>> {
let result = primer3::calc_hairpin(&args.sequence)?;
println!("sequence: {}", args.sequence);
println!("tm: {:.2} C", result.tm());
println!("dg: {:.0} cal/mol", result.dg());
println!("dh: {:.0} cal/mol", result.dh());
println!("ds: {:.2} cal/mol/K", result.ds());
Ok(())
}
fn run_homodimer(args: &SingleSeqArgs) -> Result<(), Box<dyn std::error::Error>> {
let result = primer3::calc_homodimer(&args.sequence)?;
println!("sequence: {}", args.sequence);
println!("tm: {:.2} C", result.tm());
println!("dg: {:.0} cal/mol", result.dg());
println!("dh: {:.0} cal/mol", result.dh());
println!("ds: {:.2} cal/mol/K", result.ds());
Ok(())
}
fn run_heterodimer(args: &HeterodimerArgs) -> Result<(), Box<dyn std::error::Error>> {
let result = primer3::calc_heterodimer(&args.seq1, &args.seq2)?;
println!("seq1: {}", args.seq1);
println!("seq2: {}", args.seq2);
println!("tm: {:.2} C", result.tm());
println!("dg: {:.0} cal/mol", result.dg());
println!("dh: {:.0} cal/mol", result.dh());
println!("ds: {:.2} cal/mol/K", result.ds());
Ok(())
}
fn run_design(args: &DesignArgs) -> Result<(), Box<dyn std::error::Error>> {
let mut input = String::new();
io::stdin().read_to_string(&mut input)?;
let records = primer3::boulder::parse_boulder(&input);
if records.is_empty() {
return Err("no boulder records found on stdin".into());
}
for record in &records {
let seq_args = primer3::boulder::sequence_args_from_boulder(record)?;
let settings = primer3::boulder::primer_settings_from_boulder(record)?;
let result = primer3::design_primers(&seq_args, &settings, None, None)?;
if args.json {
let boulder_rec = primer3::boulder::design_result_to_boulder(&result);
let map: std::collections::BTreeMap<&str, &str> = boulder_rec.iter().collect();
println!("{}", serde_json::to_string_pretty(&map)?);
} else {
let boulder_rec = primer3::boulder::design_result_to_boulder(&result);
print!("{}", primer3::boulder::format_boulder(&boulder_rec));
}
}
Ok(())
}