hts 0.1.17

Rust binding for htslib
Documentation
use crate::hts_ffi::{
    bam_destroy1, bam_init1,
    htslib_obj::{Bam, KString},
    sam_format1, BAM_FDUP, BAM_FMREVERSE, BAM_FMUNMAP, BAM_FPROPER_PAIR, BAM_FQCFAIL, BAM_FREAD1,
    BAM_FREAD2, BAM_FREVERSE, BAM_FUNMAP,
};
use crate::{
    alloc::{MemoryPoolObject, PooledObject},
    HtsResult,
};

use std::{
    borrow::Cow,
    fmt::{Display, Formatter, Result},
    ptr::NonNull,
};

use super::AlignmentFile;

impl MemoryPoolObject for Bam {
    fn alloc() -> Option<NonNull<Self>> {
        unsafe { NonNull::new(bam_init1()) }
    }
    fn dealloc(obj: NonNull<Self>) {
        unsafe { bam_destroy1(obj.as_ptr()) }
    }
}

/// A safe wrapper for a SAM/BAM/CRAM alignment
pub struct Alignment<'a> {
    inner: PooledObject<'a, Bam>,
    file: &'a AlignmentFile,
}

impl<'a> Display for Alignment<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        let mut buf = KString::new();
        if unsafe { sam_format1(self.file.header(), self.inner() as _, &mut buf as _) } < 0 {
            return Err(std::fmt::Error);
        }
        extern "C" {
            fn free(s: *mut std::ffi::c_void);
        }

        let s = unsafe { std::ffi::CStr::from_ptr(buf.s) }
            .to_str()
            .unwrap_or("<Enocding Error>");

        write!(f, "{}", s)?;

        unsafe {
            free(buf.s as _);
        }

        Ok(())
    }
}

impl<'a> Alignment<'a> {
    pub(super) fn from_hts_object<'b>(
        inner: PooledObject<'b, Bam>,
        file: &'b AlignmentFile,
    ) -> Alignment<'b> {
        Alignment { inner, file }
    }
    pub(crate) fn inner(&self) -> &Bam {
        &self.inner
    }

    #[allow(dead_code)]
    pub(crate) fn inner_mut(&mut self) -> &mut Bam {
        &mut self.inner
    }

    pub fn begin(&self) -> i64 {
        self.inner().core.pos
    }

    pub fn mate_chrom_id(&self) -> usize {
        self.inner().core.mtid as usize
    }

    pub fn insert_size(&self) -> i64 {
        self.inner().core.isize_
    }

    pub fn mate_begin(&self) -> i64 {
        self.inner().core.mpos
    }

    pub fn chrom_id(&self) -> usize {
        self.inner().core.tid as usize
    }

    pub fn chrom_name(&self) -> Option<&str> {
        self.file.get_chrom_name_by_id(self.chrom_id()).ok()
    }

    pub fn flags(&self) -> u16 {
        self.inner().core.flag
    }

    pub fn query_name(&self) -> Cow<str> {
        let name_cstr = unsafe { std::ffi::CStr::from_ptr(self.inner().data as _) };
        name_cstr.to_string_lossy()
    }

    pub fn mapping_qual(&self) -> u8 {
        self.inner().core.qual
    }

    pub fn query_len(&self) -> i32 {
        self.inner().core.l_qseq
    }

    pub fn is_mapped(&self) -> bool {
        (self.flags() & BAM_FUNMAP as u16) == 0
    }

    pub fn is_mate_mapped(&self) -> bool {
        (self.flags() & BAM_FMUNMAP as u16) == 0
    }

    pub fn is_first_mate(&self) -> bool {
        (self.flags() & BAM_FREAD1 as u16) != 0
    }

    pub fn is_second_mate(&self) -> bool {
        (self.flags() & BAM_FREAD2 as u16) != 0
    }

    pub fn is_reverse_strand(&self) -> bool {
        (self.flags() & BAM_FREVERSE as u16) != 0
    }

    pub fn is_mate_reverse_strand(&self) -> bool {
        (self.flags() & BAM_FMREVERSE as u16) != 0
    }

    pub fn is_proper_pair(&self) -> bool {
        (self.flags() & BAM_FPROPER_PAIR as u16) != 0
    }

    pub fn is_dup(&self) -> bool {
        (self.flags() & BAM_FDUP as u16) != 0
    }

    pub fn is_failed_qc(&self) -> bool {
        (self.flags() & BAM_FQCFAIL as u16) != 0
    }

    pub(crate) fn raw_seq(&self) -> (&[u8], bool) {
        (
            unsafe {
                std::slice::from_raw_parts(
                    self.inner().data.offset(
                        self.inner().core.l_qname as isize + self.inner().core.n_cigar as isize * 4,
                    ),
                    (self.inner().core.l_qseq + 1) as usize / 2,
                )
            },
            self.inner().core.l_qseq % 2 == 0,
        )
    }

    pub(crate) fn raw_per_base_qual(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(
                self.inner().data.offset(
                    self.inner().core.l_qname as isize
                        + self.inner().core.n_cigar as isize * 4
                        + (self.inner().core.l_qseq + 1) as isize / 2,
                ),
                self.inner().core.l_qseq as usize,
            )
        }
    }

    pub(crate) fn raw_cigar_seq(&self) -> &[u32] {
        unsafe {
            std::slice::from_raw_parts(
                self.inner().data.offset(self.inner().core.l_qname as isize) as *const u32,
                self.inner().core.n_cigar as usize,
            )
        }
    }

    pub(crate) fn raw_aux(&self) -> &[u8] {
        unsafe {
            let begin = self.inner().data.offset(
                self.inner().core.l_qname as isize
                    + self.inner().core.n_cigar as isize * 4
                    + (self.inner().core.l_qseq + 1) as isize / 2
                    + self.inner().core.l_qseq as isize,
            );
            let end = self.inner().data.offset(self.inner().l_data as isize);
            std::slice::from_raw_parts(begin, end.offset_from(begin) as usize)
        }
    }
}

pub struct AlignmentIter<'a, T: AlignmentReader + ?Sized>(&'a T);

impl<'a, T: AlignmentReader> Iterator for AlignmentIter<'a, T> {
    type Item = Alignment<'a>;
    fn next(&mut self) -> Option<Self::Item> {
        (self.0).read_next().ok()?
    }
}

pub trait AlignmentReader {
    fn read_next(&self) -> HtsResult<Option<Alignment>>;
    fn alignment_iter(&self) -> AlignmentIter<'_, Self> {
        AlignmentIter(self)
    }
}