packages feed

HsHTSLib-1.9.2: src/Bio/HTS/Types.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Bio.HTS.Types
    ( BAM(..)
    , BAMHeader(..)
    , showBamHeader
    , SortOrder(..)
    , SAM(..)
    , CIGAR(..)
    , cigar2String
    , string2Cigar
    , AuxiliaryData(..)
    , showSam
    ) where

import qualified Data.ByteString              as BS
import qualified Data.ByteString.Char8        as B
import           Data.ByteString.Lex.Integral
import           Data.Maybe                   (fromJust, fromMaybe)
import           System.IO.Unsafe         (unsafePerformIO)
import           Data.Word
import           Foreign.ForeignPtr

import Bio.HTS.Internal

-- | The BAM format.
newtype BAM = BAM { unbam :: ForeignPtr Bam1 }

newtype BAMHeader = BAMHeader {unbamHeader :: ForeignPtr BamHdr}

showBamHeader :: BAMHeader -> B.ByteString
showBamHeader header = unsafePerformIO $
    withForeignPtr (unbamHeader header) $ \ptr -> do
        s <- getHeaderText ptr
        l <- getHeaderSize ptr
        B.packCStringLen (s, l)

-- | BAM sort order.
data SortOrder = Unknown
               | Unsorted
               | Queryname
               | Coordinate
               deriving (Show, Eq)

newtype CIGAR = CIGAR [(Int, Char)]

cigar2String :: CIGAR -> B.ByteString
cigar2String (CIGAR c) = B.concat $
    concatMap (\(i, x) -> [fromJust $ packDecimal i, B.singleton x]) c

string2Cigar :: B.ByteString -> CIGAR
string2Cigar c = CIGAR $ go c
  where
    go x = case readDecimal x of
        Nothing -> error $ "parse cigar fail: " ++ show c
        Just (n, remain) -> if B.length remain == 1
            then [(n, B.head remain)]
            else (n, B.head remain) : go (B.tail remain)
        
-- | The SAM format. The SAM format uses 1-based coordinate system.
data SAM = SAM
    { _sam_qname :: !B.ByteString
    , _sam_flag  :: !Word16
    , _sam_rname :: !(Maybe B.ByteString)
    , _sam_pos   :: !Int
    , _sam_mapq  :: !Word8
    , _sam_cigar :: !(Maybe CIGAR)
    , _sam_rnext :: !(Maybe B.ByteString)
    , _sam_pnext :: !Int
    , _sam_tlen  :: !Int
    , _sam_seq   :: !(Maybe B.ByteString)
    , _sam_qual  :: !(Maybe B.ByteString)
    , _sam_aux   :: [((Char,Char), AuxiliaryData)]
    }

showSam :: SAM -> B.ByteString
showSam SAM{..} = B.intercalate "\t" $
    [ _sam_qname, pack' _sam_flag, fromMaybe "*" _sam_rname, pack' _sam_pos
    , pack' _sam_mapq, fromMaybe "*" $ cigar2String <$> _sam_cigar
    , fromMaybe "*" _sam_rnext, pack' _sam_pnext, pack' _sam_tlen
    , fromMaybe "*" _sam_seq, fromMaybe "*" $ BS.map (+33) <$> _sam_qual
    ] ++ map showAuxiliaryData _sam_aux
  where
    pack' :: Integral a => a -> B.ByteString
    pack' = fromJust . packDecimal

-- | Auxiliary data
data AuxiliaryData = AuxChar Char
                   | AuxInt Int
                   | AuxFloat Float
                   | AuxString B.ByteString
                   | AuxByteArray BS.ByteString
                   | AuxIntArray [Int]
                   | AuxFloatArray [Float]
                   deriving (Show)

showAuxiliaryData :: ((Char, Char), AuxiliaryData) -> B.ByteString
showAuxiliaryData ((x1,x2), aux) = B.pack [x1,x2] <> aux'
  where
    aux' = case aux of
        AuxChar x -> B.pack [':', 'A', ':', x]
        AuxInt x -> B.pack $ ":i:" <> show x
        AuxFloat x -> B.pack $ ":f:" <> show x
        AuxString x -> ":Z:" <> x
        _ -> error "Not implemented"

{-
-- | SAM record flag
newtype Flag = Flag Word16
-}