tidyvcf 0.7.3

command-line tool to convert VCF files to tab/comma separated tables
Documentation
use noodles::vcf::{self};

use clap::Parser;
use schema::{format_fields_strings, info_fields_strings, main_fields_strings};
use utils::{main_fields_from_record, push_fmts_cartesian, push_fmts_stacked, push_info_fields};

use std::error::Error;
use std::io::{BufReader, Read, Write};

mod cli;
mod consts;
mod schema;
mod specification;
mod utils;

use cli::Opt;

use specification::read_header_lenient;

#[allow(clippy::missing_panics_doc)]
#[allow(clippy::missing_errors_doc)]
pub fn tidyvcf() -> Result<(), Box<dyn Error>> {
    let opt = Opt::parse();

    let input: Box<dyn Read> = opt.setup_input()?;

    let output: Box<dyn Write> = opt.setup_output()?;

    convert_records(input, output, opt)?;

    Ok(())
}

fn convert_records(
    input: Box<dyn Read>,
    writer: Box<dyn Write>,
    opt: Opt,
) -> Result<(), Box<dyn Error>> {
    let mut vcfreader = vcf::io::Reader::new(BufReader::new(input));

    let mut writer = csv::WriterBuilder::new()
        .delimiter(if opt.csv { b',' } else { b'\t' })
        .from_writer(writer);

    let headerdata: vcf::Header = if opt.lenient {
        read_header_lenient(vcfreader.get_mut())?
    } else {
        vcfreader.read_header()?
    };

    let samples = headerdata.sample_names();

    let info_keys = headerdata
        .infos()
        .keys()
        .filter(|k| opt.include_info_column(k.as_ref()))
        .collect();
    let fmt_keys = headerdata
        .formats()
        .keys()
        .filter(|k| opt.include_column(k.as_ref()))
        .collect();

    let mut hdr_strings = main_fields_strings(&opt);
    let number_csq = info_fields_strings(&mut hdr_strings, &headerdata, &opt)?;
    format_fields_strings(
        &mut hdr_strings,
        &headerdata,
        &opt,
        samples.iter().map(ToString::to_string).collect(),
    );
    writer.write_record(&hdr_strings)?;

    for result in vcfreader.record_bufs(&headerdata) {
        let record = result?;

        let mut fields = main_fields_from_record(&record, &opt);
        push_info_fields(&info_keys, record.info(), &mut fields, number_csq, &opt);

        if opt.stack {
            let fmt_rows = push_fmts_stacked(&record, &fmt_keys, &opt, samples)?;
            for fmt_row in fmt_rows {
                writer.write_record(fields.iter().chain(fmt_row.iter()))?;
            }
        } else {
            push_fmts_cartesian(&mut fields, &record, &fmt_keys, &opt)?;
            writer.write_record(&fields)?;
        }
    }
    Ok(())
}