rsomics-bed-validate 0.1.0

Validate BED file format: check field counts, coordinate ordering, and integer parsing
Documentation
use clap::Parser;
use rsomics_bed_validate::validate;
use rsomics_common::{CommonFlags, Result, RsomicsError, Tool, ToolMeta};
use rsomics_help::{Example, FlagSpec, HelpSpec, Section};
use std::fs::File;
use std::io::{self, BufReader};
use std::path::PathBuf;

pub const META: ToolMeta = ToolMeta {
    name: env!("CARGO_PKG_NAME"),
    version: env!("CARGO_PKG_VERSION"),
};

pub const HELP: HelpSpec = HelpSpec {
    name: META.name,
    version: META.version,
    tagline: "Validate BED file format: check field counts, coordinate ordering, and integer parsing.",
    origin: None,
    usage_lines: &["[OPTIONS] [INPUT]"],
    sections: &[Section {
        title: "OPTIONS",
        flags: &[
            FlagSpec {
                short: None,
                long: "strict",
                aliases: &[],
                value: None,
                type_hint: Some("bool"),
                required: false,
                default: Some("false"),
                description: "Exit with non-zero status if any errors are found",
                why_default: None,
            },
            FlagSpec {
                short: Some('h'),
                long: "help",
                aliases: &[],
                value: None,
                type_hint: Some("bool"),
                required: false,
                default: None,
                description: "Show this help",
                why_default: None,
            },
        ],
    }],
    examples: &[
        Example {
            description: "Validate a BED file",
            command: "rsomics-bed-validate intervals.bed",
        },
        Example {
            description: "Fail with non-zero exit if invalid",
            command: "rsomics-bed-validate --strict intervals.bed",
        },
    ],
    json_result_schema_doc: None,
};

#[derive(Parser, Debug)]
#[command(name = "rsomics-bed-validate", disable_help_flag = true)]
pub struct Cli {
    /// Input BED file (default: stdin)
    pub input: Option<PathBuf>,

    /// Exit with non-zero status if any errors are found
    #[arg(long)]
    pub strict: bool,

    #[command(flatten)]
    pub common: CommonFlags,
}

impl Tool for Cli {
    fn meta() -> ToolMeta {
        META
    }
    fn common(&self) -> &CommonFlags {
        &self.common
    }

    fn execute(self) -> Result<()> {
        let result = match &self.input {
            Some(p) => {
                let reader = BufReader::new(File::open(p).map_err(RsomicsError::Io)?);
                validate(reader)?
            }
            None => {
                let stdin = io::stdin();
                validate(stdin.lock())?
            }
        };

        if result.is_valid {
            println!("OK: {} records, no errors", result.records);
        } else {
            for e in &result.errors {
                eprintln!("ERROR: {e}");
            }
            println!(
                "INVALID: {} records, {} errors",
                result.records,
                result.errors.len()
            );
            if self.strict {
                return Err(rsomics_common::RsomicsError::InvalidInput(
                    "BED validation failed".into(),
                ));
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use clap::CommandFactory;

    #[test]
    fn cli_definition_is_valid() {
        super::Cli::command().debug_assert();
    }
}