polaranges 0.3.2

Rust-first genomic range operations on top of Polars DataFrames
Documentation
use clap::{Parser, Subcommand};

use crate::commands::execute::{run_command, ExecOptions};
use crate::commands::spec::IntervalCommandSpec;
use crate::commands::Result;

pub mod compat;
pub mod regular;

/// The top-level CLI currently exposes the regular mode directly at the root.
/// A future `compat` subtree can translate bedtools-like syntax into the same
/// shared command specs without changing the execution engine.
#[derive(Parser, Clone, Debug)]
#[command(
    name = "polaranges",
    version,
    about = "Modern interval operations for polaranges. The regular mode is the current default command surface."
)]
pub struct RootCli {
    #[command(subcommand)]
    pub command: RootCommand,
}

#[derive(Subcommand, Clone, Debug)]
pub enum RootCommand {
    /// Modern overlap command from the default regular frontend.
    Overlap(regular::OverlapArgs),
    /// Modern nearest command from the default regular frontend.
    Nearest(regular::NearestArgs),
    /// Planned bedtools-style compatibility layer.
    Compat(compat::CompatCli),
}

impl RootCli {
    pub fn exec_options(&self) -> ExecOptions {
        let execution = match &self.command {
            RootCommand::Overlap(args) => args.execution,
            RootCommand::Nearest(args) => args.execution,
            RootCommand::Compat(_) => regular::ExecutionArgs::default(),
        };

        ExecOptions {
            benchmark: execution.benchmark,
            reps: execution.reps.max(1),
            no_output: execution.no_output,
            ..ExecOptions::default()
        }
    }

    pub fn into_command_spec(self) -> Result<IntervalCommandSpec> {
        match self.command {
            RootCommand::Overlap(args) => {
                regular::translate_regular_command(regular::RegularCommand::Overlap(args))
            }
            RootCommand::Nearest(args) => {
                regular::translate_regular_command(regular::RegularCommand::Nearest(args))
            }
            RootCommand::Compat(args) => compat::translate_compat_cli(args),
        }
    }
}

pub fn run() -> Result<()> {
    let cli = RootCli::parse();
    let options = cli.exec_options();
    let spec = cli.into_command_spec()?;
    run_command(&spec, &options)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::commands::spec;
    use crate::commands::{execute::run_command, ExecOptions};

    #[test]
    fn root_cli_routes_direct_commands_into_shared_specs() {
        let cli = RootCli::try_parse_from([
            "polaranges",
            "overlap",
            "--a",
            "left.bed",
            "--b",
            "right.bed",
        ])
        .unwrap();

        let spec = cli.into_command_spec().unwrap();
        match spec {
            spec::IntervalCommandSpec::Overlap(spec) => {
                assert_eq!(spec.inputs.left_path, std::path::PathBuf::from("left.bed"));
                assert_eq!(
                    spec.inputs.right_path,
                    std::path::PathBuf::from("right.bed")
                );
            }
            other => panic!("expected overlap spec, got {other:?}"),
        }

        let dispatcher: fn(
            &spec::IntervalCommandSpec,
            &ExecOptions,
        ) -> crate::commands::Result<()> = run_command;
        let _ = dispatcher;
    }

    #[test]
    fn root_cli_builds_exec_options_from_regular_flags() {
        let cli = RootCli::try_parse_from([
            "polaranges",
            "nearest",
            "--a",
            "left.bed",
            "--b",
            "right.bed",
            "--benchmark",
            "--reps",
            "4",
            "--no-output",
        ])
        .unwrap();

        let options = cli.exec_options();
        assert!(options.benchmark);
        assert_eq!(options.reps, 4);
        assert!(options.no_output);
    }
}