oxo-flow-cli 0.29.1

CLI for the oxo-flow bioinformatics pipeline engine
/// CLI command: oxo-flow import <source> [--from nextflow|snakemake] [-o <output>]
use anyhow::{Context, Result};
use colored::Colorize;
use oxo_flow_core::transpile::{SourceFormat, transpile_with_warnings};
use std::fs;
use std::path::Path;

/// Handle `oxo-flow import` command.
pub fn handle_import(
    source_path: &Path,
    from: Option<SourceFormat>,
    output: Option<&Path>,
) -> Result<()> {
    let source_content = fs::read_to_string(source_path)
        .with_context(|| format!("failed to read source file '{}'", source_path.display()))?;

    let format = match from {
        Some(f) => f,
        None => SourceFormat::detect_from_path(source_path)
            .or_else(|| SourceFormat::detect_from_content(&source_content))
            .ok_or_else(|| {
                anyhow::anyhow!(
                    "could not auto-detect workflow format for '{}'; please specify --from nextflow or --from snakemake",
                    source_path.display()
                )
            })?,
    };

    let transpiled = transpile_with_warnings(&source_content, format)
        .with_context(|| format!("failed to transpile '{}'", source_path.display()))?;

    // Translation losses are loud, not silent: a construct the importer could
    // not map is reported on stderr before the workflow is written.
    for warning in &transpiled.warnings {
        eprintln!("{} {}", "warning:".yellow().bold(), warning);
    }

    // Validate generated workflow TOML with oxo-flow parser
    oxo_flow_core::config::WorkflowConfig::parse(&transpiled.toml)
        .with_context(|| "generated workflow failed oxo-flow parse validation")?;

    match output {
        Some(out_path) => {
            // Same "generators never clobber" convention as init
            // (--force) and `report --init-template`: silently overwriting
            // a hand-edited pipeline destroys work with no undo.
            if out_path.exists() {
                anyhow::bail!(
                    "refusing to overwrite existing '{}' — move it away or pass a different -o path",
                    out_path.display()
                );
            }
            if let Some(parent) = out_path.parent()
                && !parent.as_os_str().is_empty()
            {
                fs::create_dir_all(parent)?;
            }
            fs::write(out_path, &transpiled.toml)
                .with_context(|| format!("failed to write output to '{}'", out_path.display()))?;
            eprintln!(
                "{} Transpiled {} ({:?}) -> {}",
                "✓".green().bold(),
                source_path.display(),
                format,
                out_path.display().to_string().cyan()
            );
        }
        None => {
            println!("{}", transpiled.toml);
        }
    }

    Ok(())
}