use anyhow::{Context, Result};
use clap::{Parser, ValueEnum};
use std::{
io::{self, Write},
path::PathBuf,
};
use termal_alignment::{
rgb::ColorMapName,
rgb::ResidueColorMap,
seq::{fasta, file},
Alignment,
};
use termal_export::{compute_layout, export_svg, Region, ExportOpts};
#[derive(Copy, Clone, Debug, ValueEnum)]
enum ColorMapArg {
AALesk,
AAClustalx,
DNAJalview,
Monochrome,
}
impl From<ColorMapArg> for ColorMapName {
fn from(v: ColorMapArg) -> Self {
match v {
ColorMapArg::AALesk => ColorMapName::AALesk,
ColorMapArg::AAClustalx => ColorMapName::AAClustalX,
ColorMapArg::DNAJalview => ColorMapName::DNAJalView,
ColorMapArg::Monochrome => ColorMapName::Monochrome,
}
}
}
#[derive(Parser, Debug)]
#[command(name = "termal-export")]
#[command(about = "Export a (region of a) multiple sequence alignment as graphics", long_about = None)]
struct Args {
input: PathBuf,
#[arg(short, long)]
order: Option<String>,
#[arg(short, long, value_enum, default_value_t = ColorMapArg::AAClustalx)]
colormap_name: ColorMapArg,
#[arg(long)]
rows: Option<String>,
#[arg(long)]
cols: Option<String>,
#[arg(long, default_value_t = 11.0)]
cell_width: f32,
#[arg(long, default_value_t = 16.0)]
cell_height: f32,
#[arg(long, default_value_t = 14)]
residue_font_size: u32,
#[arg(long, default_value_t = 12.0)]
ascent_corr: f32,
#[arg(long, default_value_t = 8.0)]
char_width: f32,
#[arg(long, default_value_t = 1.1)]
hdr_pane_width_corr: f32,
#[arg(long, default_value_t = 10.0)]
margin_x: f32,
#[arg(long, default_value_t = 10.0)]
margin_y: f32,
#[arg(long)]
cell_frames: bool,
}
fn parse_range(s: &str) -> Result<std::ops::Range<usize>> {
let (a, b) = s
.split_once(':')
.with_context(|| format!("invalid range '{s}', expected START:END"))?;
let start: usize = a
.parse()
.with_context(|| format!("invalid START in '{s}'"))?;
let end: usize = b.parse().with_context(|| format!("invalid END in '{s}'"))?;
anyhow::ensure!(start <= end, "range START must be <= END in '{s}'");
Ok(start - 1 .. end - 1) }
fn main() -> Result<()> {
let args = Args::parse();
let aln_file: file::SeqFile = fasta::read_fasta_file(&args.input)?;
let aln: Alignment = Alignment::from_file(aln_file);
let colormap_name: ColorMapName = args.colormap_name.into();
let colormap = ResidueColorMap::by_name(colormap_name);
let row_range = match &args.rows {
Some(r) => parse_range(r)?,
None => 0..aln.num_seq(),
};
let col_range = match &args.cols {
Some(r) => parse_range(r)?,
None => 0..aln.aln_len(),
};
let region = Region { rows: row_range, cols: col_range };
let opts = ExportOpts {
region: region,
cell_width: args.cell_width,
cell_height: args.cell_height,
residue_font_size: args.residue_font_size,
colormap,
ascent_corr: args.ascent_corr,
margin_x: args.margin_x,
margin_y: args.margin_y,
cell_frames: args.cell_frames,
hdr_pane_width_corr: args.hdr_pane_width_corr,
..Default::default()
};
let layout = compute_layout(&aln, &opts);
let mut stdout = io::BufWriter::new(io::stdout().lock());
export_svg(&aln, &opts, &layout, &mut stdout)?;
stdout.flush()?;
Ok(())
}