kira-spliceqc 0.2.0

Deterministic, explainable splicing QC for single-cell expression data.
Documentation
use std::path::PathBuf;

use clap::{Args, Parser, Subcommand, ValueEnum};
use kira_spliceqc::cli::config::{AnalysisMode, RunConfig, RunMode};
use kira_spliceqc::cli::run::{SpliceQcError, run_pipeline};
use tracing_subscriber::EnvFilter;

#[derive(Parser)]
#[command(name = "kira-spliceqc", version, author)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Option<Commands>,
    #[command(flatten)]
    pub run: RunArgs,
}

#[derive(Subcommand)]
pub enum Commands {
    Run(RunArgs),
}

#[derive(ValueEnum, Clone, Copy)]
pub enum ModeArg {
    Cell,
    Sample,
}

#[derive(ValueEnum, Clone, Copy)]
pub enum RunModeArg {
    Standalone,
    Pipeline,
}

#[derive(Args, Clone)]
pub struct RunArgs {
    #[arg(long)]
    pub input: Option<PathBuf>,
    #[arg(long)]
    pub out: Option<PathBuf>,
    #[arg(long)]
    pub cache: Option<PathBuf>,
    #[arg(long, value_enum, default_value = "cell")]
    pub mode: ModeArg,
    #[arg(long)]
    pub json: bool,
    #[arg(long)]
    pub tsv: bool,
    #[arg(long)]
    pub extended: bool,
    #[arg(long)]
    pub threads: Option<usize>,
    #[arg(long, value_enum, default_value = "standalone")]
    pub run_mode: RunModeArg,
}

fn main() {
    let cli = Cli::parse();
    init_tracing();

    let run_args = match cli.command {
        Some(Commands::Run(args)) => args,
        None => cli.run,
    };

    let result = execute_run(run_args);

    if let Err(err) = result {
        handle_error(err);
    }
}

fn init_tracing() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
    tracing_subscriber::fmt().with_env_filter(filter).init();
}

fn handle_error(err: SpliceQcError) -> ! {
    match err {
        SpliceQcError::InvalidInput(msg) => {
            eprintln!("error: {msg}");
            std::process::exit(1);
        }
        SpliceQcError::Io(io) => {
            eprintln!("error: {io}");
            std::process::exit(1);
        }
        SpliceQcError::PipelineFailure(msg) => {
            eprintln!("error: {msg}");
            std::process::exit(2);
        }
    }
}

fn execute_run(args: RunArgs) -> Result<(), SpliceQcError> {
    let input = args
        .input
        .ok_or_else(|| SpliceQcError::InvalidInput("--input is required".to_string()))?;
    let out = args
        .out
        .ok_or_else(|| SpliceQcError::InvalidInput("--out is required".to_string()))?;

    let config = RunConfig {
        input,
        out_dir: out,
        cache_path: args.cache,
        mode: match args.mode {
            ModeArg::Cell => AnalysisMode::Cell,
            ModeArg::Sample => AnalysisMode::Sample,
        },
        run_mode: match args.run_mode {
            RunModeArg::Standalone => RunMode::Standalone,
            RunModeArg::Pipeline => RunMode::Pipeline,
        },
        output_json: args.json,
        output_tsv: args.tsv,
        extended: args.extended,
        threads: args.threads,
    };
    run_pipeline(config)
}