use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{LazyLock, OnceLock};
use std::time::Instant;
use clap::{Args, Parser};
use clap_verbosity_flag::{Verbosity, WarnLevel};
mod common;
mod reads;
mod vcf;
mod worker;
use indicatif::ProgressStyle;
use reads::Command as ReadsCommand;
use tokio::fs::File;
use tokio::io::{AsyncWriteExt, BufWriter};
use tracing::info;
use tracing_indicatif::filter::{IndicatifFilter, hide_indicatif_span_fields};
use tracing_indicatif::{IndicatifLayer, IndicatifWriter, writer};
use tracing_subscriber::fmt::format::DefaultFields;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;
use tracing_subscriber::{EnvFilter, Layer};
use vcf::cli::Command as VcfCommand;
use crate::common::stats;
static THIS_EXE: LazyLock<PathBuf> =
LazyLock::new(|| std::env::current_exe().expect("Cannot find the executable on the FS"));
type VerbositySelector = Verbosity<WarnLevel>;
static THIS_LOG_LEVEL: OnceLock<VerbositySelector> = OnceLock::new();
static STDERR_LOG_WRITER: OnceLock<IndicatifWriter<writer::Stderr>> = OnceLock::new();
#[derive(Parser, Debug)]
#[allow(clippy::large_enum_variant)]
#[command(version)]
#[command(name = "twitcher")]
#[command(about = "Template sWITCH alignER -- Reannotate variant calls and try to explain mutation clusters with short-range template switches.", long_about = None)]
enum Cli {
Vcf(CliCommand<VcfCommand>),
Reads(CliCommand<ReadsCommand>),
#[clap(hide = true)]
Worker,
}
trait RunnableCommand {
async fn run(self) -> anyhow::Result<()>;
}
#[derive(Parser, Clone, Debug)]
struct CliCommand<T: Args + RunnableCommand> {
#[command(flatten)]
pub command: T,
#[arg(long = "stat-output")]
pub stats: Option<String>,
#[command(flatten)]
pub log_level: VerbositySelector,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli {
Cli::Worker => worker::run(),
Cli::Vcf(cc) => run_command(cc)?,
Cli::Reads(cc) => run_command(cc)?,
}
Ok(())
}
fn run_command<T: Args + RunnableCommand>(cc: CliCommand<T>) -> anyhow::Result<()> {
setup_tracing(cc.log_level);
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
rt.block_on(async {
let start = Instant::now();
cc.command.run().await?;
let elapsed = start.elapsed();
info!("Runtime: {elapsed:?}");
if let Some(file) = cc.stats {
let mut file = BufWriter::new(File::create(file).await?);
for (k, v) in stats::get_stats() {
file.write_all(format!("{k}{v}").as_bytes()).await?;
}
} else {
info!("Counters:");
for (k, v) in stats::get_stats() {
info!("\t{k}: {v}");
}
}
Ok(())
})
}
fn setup_tracing(log_level: VerbositySelector) {
THIS_LOG_LEVEL.get_or_init(|| log_level);
let pb_layer = IndicatifLayer::new()
.with_progress_style(
ProgressStyle::with_template("{elapsed} [{wide_bar}] {msg} {pos:>7}/{len:7}")
.unwrap()
.progress_chars("=> "),
)
.with_filter(IndicatifFilter::new(false));
let writer = pb_layer.inner().get_stderr_writer();
STDERR_LOG_WRITER.get_or_init(|| writer.clone());
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.compact()
.without_time()
.with_target(false)
.fmt_fields(hide_indicatif_span_fields(DefaultFields::new()))
.with_writer(writer)
.with_filter(
EnvFilter::from_str(&format!(
"{},lib_tsalign=warn,template_switch_error_free_inners=warn",
log_level.tracing_level_filter()
))
.unwrap(),
),
)
.with(pb_layer)
.init();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert();
}
}