BiobaseFasta 0.2.0.0 → 0.3.0.0
raw patch · 6 files changed
+248/−239 lines, 6 filesdep +string-conversionsdep −deepseqdep ~BiobaseTypesdep ~basedep ~lensPVP ok
version bump matches the API change (PVP)
Dependencies added: string-conversions
Dependencies removed: deepseq
Dependency ranges changed: BiobaseTypes, base, lens
API changes (from Hackage documentation)
- Biobase.Fasta.Export: breakByteString :: Int -> ByteString -> [ByteString]
- Biobase.Fasta.Export: fromIntToInt64 :: Int -> Int64
- Biobase.Fasta.Export: instance GHC.Show.Show Biobase.Fasta.Types.Fasta
- Biobase.Fasta.Export: prettyByteStringFasta :: Int -> Fasta -> ByteString
- Biobase.Fasta.Export: prettyPrintFasta :: Int -> Fasta -> String
- Biobase.Fasta.Export: writeFastaFile :: FilePath -> [Fasta] -> IO ()
- Biobase.Fasta.Streaming: Current :: ByteString -> Index 0 -> Current
- Biobase.Fasta.Streaming: Header :: ByteString -> Header
- Biobase.Fasta.Streaming: Overlap :: ByteString -> Overlap
- Biobase.Fasta.Streaming: [currentFasta] :: Current -> ByteString
- Biobase.Fasta.Streaming: [currentStart] :: Current -> Index 0
- Biobase.Fasta.Streaming: [getHeader] :: Header -> ByteString
- Biobase.Fasta.Streaming: [getOverlap] :: Overlap -> ByteString
- Biobase.Fasta.Streaming: [header] :: FindHeader -> !ByteString
- Biobase.Fasta.Streaming: data Current (which :: k)
- Biobase.Fasta.Streaming: eachFasta :: Monad m => Header which1 -> Overlap which2 -> Current which3 -> Stream (Of (ByteString, ByteString, ByteString)) m ()
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Classes.Eq (Biobase.Fasta.Streaming.Current which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Classes.Eq (Biobase.Fasta.Streaming.Header which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Classes.Eq (Biobase.Fasta.Streaming.Overlap which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Classes.Ord (Biobase.Fasta.Streaming.Current which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Classes.Ord (Biobase.Fasta.Streaming.Header which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Classes.Ord (Biobase.Fasta.Streaming.Overlap which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Show.Show (Biobase.Fasta.Streaming.Current which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Show.Show (Biobase.Fasta.Streaming.Header which)
- Biobase.Fasta.Streaming: instance forall k (which :: k). GHC.Show.Show (Biobase.Fasta.Streaming.Overlap which)
- Biobase.Fasta.Streaming: newtype Header (which :: k)
- Biobase.Fasta.Streaming: newtype Overlap (which :: k)
- Biobase.Fasta.Streaming: parseFasta :: ByteString -> [Fasta]
- Biobase.Fasta.Streaming: parseFastaFile :: FilePath -> IO [Fasta]
- Biobase.Fasta.Types: Fasta :: ByteString -> ByteString -> Fasta
- Biobase.Fasta.Types: LineInfo :: !Int -> !Int -> !Int -> !Int -> !Int -> LineInfo
- Biobase.Fasta.Types: RawFastaEntry :: ByteString -> RawFastaEntry
- Biobase.Fasta.Types: StreamFasta :: !ByteString -> !ByteString -> !LineInfo -> !ByteString -> StreamEvent
- Biobase.Fasta.Types: StreamHeader :: !ByteString -> !LineInfo -> StreamEvent
- Biobase.Fasta.Types: [_rawFastaEntry] :: RawFastaEntry -> ByteString
- Biobase.Fasta.Types: [fastaHeader] :: Fasta -> ByteString
- Biobase.Fasta.Types: [fastaSequence] :: Fasta -> ByteString
- Biobase.Fasta.Types: [firstCol] :: LineInfo -> !Int
- Biobase.Fasta.Types: [firstIndex] :: LineInfo -> !Int
- Biobase.Fasta.Types: [firstLine] :: LineInfo -> !Int
- Biobase.Fasta.Types: [lastCol] :: LineInfo -> !Int
- Biobase.Fasta.Types: [lastLine] :: LineInfo -> !Int
- Biobase.Fasta.Types: [prevStreamFasta] :: StreamEvent -> !ByteString
- Biobase.Fasta.Types: [streamFasta] :: StreamEvent -> !ByteString
- Biobase.Fasta.Types: [streamHeader] :: StreamEvent -> !ByteString
- Biobase.Fasta.Types: [streamLines] :: StreamEvent -> !LineInfo
- Biobase.Fasta.Types: data Fasta
- Biobase.Fasta.Types: data LineInfo
- Biobase.Fasta.Types: data StreamEvent
- Biobase.Fasta.Types: instance Control.DeepSeq.NFData Biobase.Fasta.Types.LineInfo
- Biobase.Fasta.Types: instance Control.DeepSeq.NFData Biobase.Fasta.Types.StreamEvent
- Biobase.Fasta.Types: instance GHC.Classes.Eq Biobase.Fasta.Types.Fasta
- Biobase.Fasta.Types: instance GHC.Classes.Eq Biobase.Fasta.Types.LineInfo
- Biobase.Fasta.Types: instance GHC.Classes.Eq Biobase.Fasta.Types.RawFastaEntry
- Biobase.Fasta.Types: instance GHC.Classes.Eq Biobase.Fasta.Types.StreamEvent
- Biobase.Fasta.Types: instance GHC.Classes.Ord Biobase.Fasta.Types.LineInfo
- Biobase.Fasta.Types: instance GHC.Classes.Ord Biobase.Fasta.Types.RawFastaEntry
- Biobase.Fasta.Types: instance GHC.Classes.Ord Biobase.Fasta.Types.StreamEvent
- Biobase.Fasta.Types: instance GHC.Generics.Generic Biobase.Fasta.Types.LineInfo
- Biobase.Fasta.Types: instance GHC.Generics.Generic Biobase.Fasta.Types.StreamEvent
- Biobase.Fasta.Types: instance GHC.Show.Show Biobase.Fasta.Types.LineInfo
- Biobase.Fasta.Types: instance GHC.Show.Show Biobase.Fasta.Types.RawFastaEntry
- Biobase.Fasta.Types: instance GHC.Show.Show Biobase.Fasta.Types.StreamEvent
- Biobase.Fasta.Types: newtype RawFastaEntry
+ Biobase.Fasta.Streaming: [fhHeader] :: FindHeader -> !ByteString
+ Biobase.Fasta.Streaming: fastaUid :: Lens' (SequenceIdentifier w) ByteString
+ Biobase.Fasta.Strict: Fasta :: !SequenceIdentifier which -> !BioSequence ty -> Fasta which ty
+ Biobase.Fasta.Strict: [_fasta] :: Fasta which ty -> !BioSequence ty
+ Biobase.Fasta.Strict: [_header] :: Fasta which ty -> !SequenceIdentifier which
+ Biobase.Fasta.Strict: byteStringToFasta :: ByteString -> Either String (Fasta which ty)
+ Biobase.Fasta.Strict: byteStringToMultiFasta :: ByteString -> [Fasta which ty]
+ Biobase.Fasta.Strict: convertString :: ConvertibleStrings a b => a -> b
+ Biobase.Fasta.Strict: data Fasta which ty
+ Biobase.Fasta.Strict: fasta :: forall which_anQa ty_anQb ty_apOX. Lens (Fasta which_anQa ty_anQb) (Fasta which_anQa ty_apOX) (BioSequence ty_anQb) (BioSequence ty_apOX)
+ Biobase.Fasta.Strict: fastaToBuilder :: Int -> Fasta which ty -> Builder
+ Biobase.Fasta.Strict: fastaToByteString :: Int -> Fasta which ty -> ByteString
+ Biobase.Fasta.Strict: header :: forall which_anQa ty_anQb which_apOY. Lens (Fasta which_anQa ty_anQb) (Fasta which_apOY ty_anQb) (SequenceIdentifier which_anQa) (SequenceIdentifier which_apOY)
+ Biobase.Fasta.Strict: instance forall k1 (which :: k1) k2 (ty :: k2). GHC.Classes.Eq (Biobase.Fasta.Strict.Fasta which ty)
+ Biobase.Fasta.Strict: instance forall k1 (which :: k1) k2 (ty :: k2). GHC.Classes.Ord (Biobase.Fasta.Strict.Fasta which ty)
+ Biobase.Fasta.Strict: instance forall k1 (which :: k1) k2 (ty :: k2). GHC.Generics.Generic (Biobase.Fasta.Strict.Fasta which ty)
+ Biobase.Fasta.Strict: instance forall k1 (which :: k1) k2 (ty :: k2). GHC.Read.Read (Biobase.Fasta.Strict.Fasta which ty)
+ Biobase.Fasta.Strict: instance forall k1 (which :: k1) k2 (ty :: k2). GHC.Show.Show (Biobase.Fasta.Strict.Fasta which ty)
+ Biobase.Fasta.Strict: rawFasta :: Int -> Prism' ByteString (Fasta which ty)
+ Biobase.Fasta.Strict: type FastaUntyped = Fasta Void Void
+ Biobase.Fasta.Strict: windowedFasta :: Lens' (BioSequenceWindow w ty k) (Fasta w ty)
- Biobase.Fasta.Streaming: streamingFasta :: forall m w r a. Monad m => HeaderSize -> OverlapSize -> CurrentSize -> (Header w -> Overlap w -> Current w -> Stream (Of a) m ()) -> ByteString m r -> Stream (Of a) m r
+ Biobase.Fasta.Streaming: streamingFasta :: forall m w ty k r a. Monad m => HeaderSize -> OverlapSize -> CurrentSize -> ByteString m r -> Stream (Of (BioSequenceWindow w ty k)) m r
Files
- Biobase/Fasta/Export.hs +0/−38
- Biobase/Fasta/Streaming.hs +52/−61
- Biobase/Fasta/Strict.hs +103/−0
- Biobase/Fasta/Types.hs +0/−56
- BiobaseFasta.cabal +26/−29
- tests/properties.hs +67/−55
− Biobase/Fasta/Export.hs
@@ -1,38 +0,0 @@--- | Fasta export--module Biobase.Fasta.Export where-import Biobase.Fasta.Types-import qualified Data.ByteString.Lazy.Char8 as B-import Data.List-import GHC.Int--instance Show Fasta where- show (Fasta _header _sequence) =- (B.unpack _header) ++ "\n" ++ (B.unpack _sequence) ++ "\n"--prettyPrintFasta :: Int -> Fasta -> String-prettyPrintFasta number (Fasta _header _sequence) = (B.unpack _header) ++ "\n" ++ (B.unpack sequenceLines) ++ "\n"- where sequenceSlices = breakByteString number _sequence- sequenceLines = B.intercalate (B.pack "\n") sequenceSlices--prettyByteStringFasta :: Int -> Fasta -> B.ByteString-prettyByteStringFasta number (Fasta _header _sequence) = _header `B.append` bslinebreak `B.append` sequenceLines `B.append` bslinebreak- where sequenceSlices = breakByteString number _sequence- sequenceLines = B.intercalate (B.pack "\n") sequenceSlices- bslinebreak = B.pack "\n"---breakByteString :: Int -> B.ByteString -> [B.ByteString]-breakByteString number bs- | B.empty == currentLine = []- | otherwise = currentLine:(breakByteString number rest)- where (currentLine,rest) = B.splitAt (fromIntToInt64 number) bs--fromIntToInt64 :: Int -> Int64-fromIntToInt64 = fromIntegral--writeFastaFile :: FilePath -> [Fasta] -> IO ()-writeFastaFile filePath fastas = do- let fastabs = map (prettyByteStringFasta 80) fastas- let outputbs= B.concat fastabs- B.writeFile filePath outputbs
Biobase/Fasta/Streaming.hs view
@@ -1,20 +1,15 @@+ -- | Streaming Fasta handling via the @streaming@ library. -- -- The functions in here should be streaming in constant memory. -- -- TODO Check if this is actually true with some unit tests. -{-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE DataKinds #-}-- module Biobase.Fasta.Streaming ( module Biobase.Fasta.Streaming ) where +import Control.Lens hiding (Index,Empty, mapped) import Control.Monad import Control.Monad.Trans.Resource (runResourceT, ResourceT(..), MonadResource) import Data.ByteString.Streaming as BSS@@ -22,18 +17,20 @@ import Data.ByteString.Streaming.Internal (ByteString(..)) import Data.Semigroup as SG import Debug.Trace+import GHC.Generics import GHC.TypeLits import Prelude as P import qualified Data.ByteString.Char8 as BS-import qualified Data.ByteString.Lazy.Char8 as B import qualified Streaming.Internal as SI import Streaming as S import Streaming.Prelude as SP-import qualified Data.List as L++import Biobase.Types.BioSequence import Biobase.Types.Index.Type-import Biobase.Fasta.Types+import Biobase.Types.Strand + newtype HeaderSize = HeaderSize Int deriving (Eq,Ord,Show) @@ -43,16 +40,15 @@ newtype CurrentSize = CurrentSize Int deriving (Eq,Ord,Show) -newtype Header (which ∷ k) = Header { getHeader ∷ BS.ByteString }- deriving (Eq,Ord,Show)+-- | lens into the unique id / first word of the header. -newtype Overlap (which ∷ k) = Overlap { getOverlap ∷ BS.ByteString }- deriving (Eq,Ord,Show)+fastaUid ∷ Lens' (SequenceIdentifier w) BS.ByteString+fastaUid = lens getWord updateWord+ where getWord ((BS.words . _sequenceIdentifier) → ws) = case ws of (x:_) → BS.drop 1 x; [] → BS.empty+ updateWord (SequenceIdentifier hdr) w = SequenceIdentifier . BS.unwords $ BS.cons '>' w : tail (BS.words hdr)+{-# Inlinable fastaUid #-} --- | Current Fasta window, together with the start index (0-based). -data Current (which ∷ k) = Current { currentFasta ∷ BS.ByteString, currentStart ∷ Index 0 }- deriving (Eq,Ord,Show) -- | Fully stream a fasta file, making sure to never exceed a constant amount -- of memory. The @go@ function yields values of type @a@ down the line for@@ -64,7 +60,7 @@ -- @ streamingFasta- ∷ forall m w r a+ ∷ forall m w ty k r a . ( Monad m ) ⇒ HeaderSize -- ^ Maximal length of the header. Ok to set to @20 000@, only guards against@@ -75,15 +71,12 @@ -- todo at 'overlappedFasta') → CurrentSize -- ^ The size of each window to be processed.- → (Header w → Overlap w → Current w → Stream (Of a) m ())- -- ^ The processing function. Takes in the header, any overlap from the- -- previous window, the current window and produces a stream of @a@s. → ByteString m r -- ^ A streaming bytestring of Fasta files.- → Stream (Of a) m r- -- ^ The outgoing stream of @a@s being processed.+ → Stream (Of (BioSequenceWindow w ty k)) m r+ -- ^ The outgoing stream of @Current@ windows being processed. {-# Inlinable streamingFasta #-}-streamingFasta (HeaderSize hSz) (OverlapSize oSz) (CurrentSize cSz) f = go (FindHeader [] 0) where+streamingFasta (HeaderSize hSz) (OverlapSize oSz) (CurrentSize cSz) = go (FindHeader [] 0) where -- Find the next FASTA header go (FindHeader hdr cnt) = \case -- No more data to be had. If There is some part of a header, we will run@@ -92,8 +85,8 @@ Empty retVal → do -- handle case of last empty fasta unless (P.null hdr) $ do- let thisHeader = BS.take hSz $ BS.concat $ P.reverse hdr- f (Header thisHeader) (Overlap BS.empty) (Current BS.empty 0)+ let thisHeader = BS.take hSz . BS.drop 1 . BS.concat $ P.reverse hdr+ yield $ seqWindow thisHeader BS.empty BS.empty 0 SI.Return retVal -- Effects are wrapped up into a 'Stream' effect. Go m → SI.Effect $ liftM (go (FindHeader hdr cnt)) m@@ -112,7 +105,7 @@ -- We have found a newline at @k@. Prepare the full header (up to @hSz@ -- size) and hand over to @HasHeader@ which processes actual fasta -- payload.- | Just k ← mk → let thisHeader = BS.take hSz $ BS.concat $ P.reverse $ BS.take k b:hdr+ | Just k ← mk → let thisHeader = BS.take hSz . BS.drop 1 . BS.concat . P.reverse $ BS.take k b:hdr in go (HasHeader thisHeader BS.empty [] 0 0) (Chunk (BS.drop (k+1) b) bytestream) where b = if P.null hdr then BS.dropWhile (\c → c/='>' && c/=';') rawBS else rawBS@@ -121,7 +114,7 @@ go hasHeader@(HasHeader hdr overlap cs cnt entries) = \case -- No more data, process final input and return. Empty retVal → do- when (cnt>0 || entries==0) $ f (Header hdr) (Overlap BS.empty) (Current (BS.concat $ reverse cs) 0)+ when (cnt>0 || entries==0) . yield $ seqWindow hdr BS.empty (BS.concat $ reverse cs) 0 SI.Return retVal -- Effects to be dealt with. Go m → SI.Effect $ liftM (go hasHeader) m@@ -135,7 +128,7 @@ Nothing → let (this,next) = BS.splitAt (cSz-cnt) $ BS.filter (/= '\n') b in if BS.length this + cnt >= cSz then do let thisFasta = BS.concat $ reverse $ this:cs- f (Header hdr) (Overlap overlap) (Current thisFasta 0)+ yield $ seqWindow hdr overlap thisFasta entries go (HasHeader hdr (BS.drop (BS.length thisFasta - oSz) thisFasta) [] 0 (entries+1)) (if BS.null next then bytestream else Chunk next bytestream) else go (HasHeader hdr overlap (this:cs) (BS.length this + cnt) entries)@@ -150,10 +143,19 @@ | otherwise → do let thisFasta = BS.concat $ reverse cs -- we only emit on empty @thisFasta@, if there is -- data, or it is the only (then empty) entry.- when (cnt>0 || entries==0) $ f (Header hdr) (Overlap overlap) (Current thisFasta 0)+ when (cnt>0 || entries==0) . yield $ seqWindow hdr overlap thisFasta entries go (FindHeader [] 0) $ Chunk b bytestream -- Returns the first index (if any) of a new fasta entry symbol. newFastaIndex b = getMin <$> (Min <$> BS.elemIndex '>' b) SG.<> (Min <$> BS.elemIndex ';' b)+ -- build up a seq-window+ seqWindow hdr pfx seq entries = BioSequenceWindow+ { _bswIdentifier = SequenceIdentifier hdr+ , _bswPrefix = BioSequence pfx+ , _bswSequence = BioSequence seq+ , _bswSuffix = BioSequence BS.empty+ , _bswStrand = PlusStrand+ , _bswIndex = Index $ entries * cSz+ } -- | Control structure for 'streamingFasta'. @@ -165,7 +167,7 @@ -- ^ accumulated header length } | HasHeader- { header ∷ !BS.ByteString+ { fhHeader ∷ !BS.ByteString -- ^ the (size-truncated) header for this fasta file , dataOverlap ∷ !BS.ByteString -- ^ overlap (if any) from earlier parts of the fasta file@@ -177,7 +179,6 @@ -- ^ count how many entries we have seen } - {- t0 = P.unlines [ ">Aaaa"@@ -189,40 +190,30 @@ ] -r2 = splitFastaLines $ S8.lines $ S8.fromStrict $ BS.pack t0--r3 = streamFastaLines $ S8.lines $ S8.fromStrict $ BS.pack t0---- r3' ∷ Stream (Stream (Of BS.ByteString) Identity) Identity ()-r3' = toList . mapped toList $ maps (mapped toStrict) r3--r4 = toList . streamingFasta (HeaderSize 2) (OverlapSize 1) (CurrentSize 2) go . S8.fromStrict $ BS.pack t0- where go (Header h) (Overlap o) (Current c) = yield (h,o,c)+r4 = toList . streamingFasta (HeaderSize 2) (OverlapSize 1) (CurrentSize 2) . S8.fromStrict $ BS.pack t0 -} ---eachFasta :: forall (m0 :: * -> *). Header Int -> Overlap Int -> Current Int -> Stream (Of (BS.ByteString, BS.ByteString, BS.ByteString)) (ResourceT IO) ()-eachFasta (Header h) (Overlap o) (Current c p) = SP.yield (h,o,c)----readFastaFile ∷ FilePath → IO () -- [(BS.ByteString,BS.ByteString,BS.ByteString)]---readFastaFile f = do--- let s = 1000000000000--- r ← runResourceT--- $ SP.mapM_ (liftIO . P.print)--- $ streamingFasta (HeaderSize s) (OverlapSize 0) (CurrentSize s) eachFasta--- $ S8.readFile f--- return r+{-+--eachFasta (Header h) (Overlap o) (Current c p) = SP.yield (h,o,c)+eachFasta (Header h) (Overlap o) (Current c p) = SP.yield (BS.length h, BS.length o, BS.length c) -parseFastaFile ∷ FilePath → IO [Fasta]-parseFastaFile f = do+--readFastaFile ∷ FilePath → IO [(BS.ByteString,BS.ByteString,BS.ByteString)]+readFastaFile f = do let s = 1000000000000 r ← runResourceT- $ toList_+ $ SP.mapM_ (liftIO . P.print) $ streamingFasta (HeaderSize s) (OverlapSize 0) (CurrentSize s) eachFasta $ S8.readFile f- let fastas = L.map (\(a,_,c) -> Fasta (B.fromStrict a) (B.fromStrict c)) r- return fastas+ return r+-} -parseFasta ∷ B.ByteString → [Fasta]-parseFasta input = L.map (\(a,_,c) -> Fasta (B.fromStrict a) (B.fromStrict c)) (L.head r)- where s = 1000000000000- r = toList_ $ streamingFasta (HeaderSize s) (OverlapSize 0) (CurrentSize s) eachFasta $ BSS.fromLazy input+{-+readFastaFile f = do+ let s = 1000000000000+ r ← runResourceT+ $ SP.mapM_ (liftIO . P.print)+ $ SP.mapped S8.toStrict+ $ S8.split '>'+ $ S8.readFile f+ return r+-}
+ Biobase/Fasta/Strict.hs view
@@ -0,0 +1,103 @@++-- | A convenience module for *small* @Fasta@ entries, that are completely in+-- memory and *not* to be streamed.+--+-- The @Data.ByteString.Strict.Lens@ module is very helpful for further+-- handling of 'Fasta' entries.+--+-- For convenience, the 'convertString' function from @string-conversions@ is+-- supplied.++module Biobase.Fasta.Strict+ ( module Biobase.Fasta.Strict+ , convertString+ ) where++import Control.Lens+import Data.Bifunctor (first)+import Data.ByteString (ByteString)+import Data.String.Conversions+import Data.Void+import GHC.Generics (Generic)+import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Streaming as BSS+import qualified Streaming.Prelude as SP++import Biobase.Fasta.Streaming as FS+import Biobase.Types.BioSequence++++-- | A *strict* @Fasta@ entry.++data Fasta which ty = Fasta+ { _header ∷ !(SequenceIdentifier which)+ , _fasta ∷ !(BioSequence ty)+ }+ deriving (Eq,Ord,Read,Show,Generic)+makeLenses ''Fasta++-- | If you don't want to deal with the phantom types.++type FastaUntyped = Fasta Void Void++-- | Render a 'Fasta' entry to a 'ByteString'. Will end with a final @\n@ in+-- any case.++fastaToByteString ∷ Int → Fasta which ty → ByteString+{-# Inlinable fastaToByteString #-}+fastaToByteString k' Fasta{..} = BS.cons '>' (_header^._Wrapped) <> "\n" <> go (_fasta^._Wrapped)+ where go (BS.splitAt k → (hd,tl))+ | BS.null hd = mempty+ | otherwise = hd <> "\n" <> go tl+ k = max 1 k'++-- | Render a 'Fasta' entry to a 'Builder'. Will end with a final @\n@ in+-- any case.++fastaToBuilder ∷ Int → Fasta which ty → BB.Builder+{-# Inlinable fastaToBuilder #-}+fastaToBuilder k' Fasta{..} = BB.char8 '>' <> (BB.byteString $ _header^._Wrapped) <> BB.char8 '\n' <> go (_fasta^._Wrapped)+ where go (BS.splitAt k → (hd,tl))+ | BS.null hd = mempty+ | otherwise = BB.byteString hd <> BB.char8 '\n' <> go tl+ k = max 1 k'++-- | Try to parse a 'ByteString' as a 'Fasta', failing with 'Left', succees+-- with 'Right'.++byteStringToFasta ∷ ByteString → Either String (Fasta which ty)+{-# Inlinable byteStringToFasta #-}+byteStringToFasta (BS.lines → ls)+ | null ls = Left "empty bytestring"+ | Just (z, hdr) ← BS.uncons h, z `BS.elem` ">;" = Right $ Fasta { _header = SequenceIdentifier hdr, _fasta = BioSequence $ BS.concat ts }+ | otherwise = Left "no '>'/';' first character"+ where h:ts = ls++-- | Try to parse a 'ByteString' as multiple 'Fasta' entries. Even though this+-- is using the underlying streaming interface, this is not streaming.++byteStringToMultiFasta+ ∷ BSL.ByteString → [Fasta which ty]+{-# Inlinable byteStringToMultiFasta #-}+byteStringToMultiFasta bsl = map (view windowedFasta) $ runIdentity bss+ where bss = SP.toList_ . streamingFasta (HeaderSize maxBound) (OverlapSize 0) (CurrentSize maxBound) $ BSS.fromLazy bsl++-- | A lens that goes from a 'BioSequenceWindow' to a 'Fasta'.++windowedFasta ∷ Lens' (BioSequenceWindow w ty k) (Fasta w ty)+{-# Inline windowedFasta #-}+windowedFasta = lens lr rl+ where lr bsw = Fasta { _header = bsw^.bswIdentifier, _fasta = bsw^.bswSequence }+ rl bsw f = set bswSequence (f^.fasta) $ set bswIdentifier (f^.header) bsw++-- | A prism from a 'ByteString' to a 'Fasta'. Note that this will only be an+-- identity if the underlying fasta file is rendered with @k@ characters per+-- line.++rawFasta ∷ Int → Prism' ByteString (Fasta which ty)+{-# Inline rawFasta #-}+rawFasta k = prism (fastaToByteString k) $ \bs → first (const bs) $ byteStringToFasta bs+
− Biobase/Fasta/Types.hs
@@ -1,56 +0,0 @@-{-# Language DeriveGeneric #-}--module Biobase.Fasta.Types where--import Control.DeepSeq-import Control.Lens-import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Lazy.Char8 as B-import Data.Data-import GHC.Generics-import Biobase.Types.NucleotideSequence-import Biobase.Types.AminoAcidSequence---- |--data Fasta = Fasta { fastaHeader :: B.ByteString, fastaSequence :: B.ByteString }- deriving (Eq)--newtype RawFastaEntry = RawFastaEntry { _rawFastaEntry :: ByteString }- deriving (Show,Eq,Ord,Typeable)----makeLenses ''RawFastaEntry---- | 'StreamEvent's are chunked pieces of data, where the raw data is--- a strict @ByteString@. Each element also retains information on the--- first and last line and column (via 'streamLines') that are part of this--- chunk.--data StreamEvent- -- | A Header event, multiple header events signal that the header name- -- was longer than the chunk size.- = StreamHeader { streamHeader :: !ByteString, streamLines :: !LineInfo }- -- | A data event. We keep a pointer to the previous chunk (which is- -- useful for some algorithms). The chunk is free of newlines!- | StreamFasta { streamFasta :: !ByteString, prevStreamFasta :: !ByteString, streamLines :: !LineInfo, streamHeader :: !ByteString }- deriving (Show,Eq,Ord,Typeable,Generic)--instance NFData StreamEvent------ | Complete information on line and column start and end for a chunk.------ TODO This is a 1-based format? Lets use the BiobaseTypes facilities!--data LineInfo = LineInfo- { firstLine :: !Int -- ^ first line for this chunk @(lines in complete file!)@- , firstCol :: !Int -- ^ first column in first line for this chunk- , lastLine :: !Int -- ^ last line for this chunk @(lines in complete file!)@- , lastCol :: !Int -- ^ last column in last line for this chunk- , firstIndex :: !Int -- ^ first index in this fasta block. Counts just the number of symbols in the @Fasta@ payload.- }- deriving (Show,Eq,Ord,Typeable,Generic)--instance NFData LineInfo-
BiobaseFasta.cabal view
@@ -1,17 +1,17 @@+cabal-version: 2.2 name: BiobaseFasta-version: 0.2.0.0-author: Christian Hoener zu Siederdissen, Florian Eggenhofer+version: 0.3.0.0+author: Christian Hoener zu Siederdissen maintainer: choener@bioinf.uni-leipzig.de homepage: https://github.com/choener/BiobaseFasta bug-reports: https://github.com/choener/BiobaseFasta/issues-copyright: Christian Hoener zu Siederdissen, 2011-2018+copyright: Christian Hoener zu Siederdissen, 2011-2019 category: Bioinformatics-license: GPL-3+license: BSD-3-Clause license-file: LICENSE build-type: Simple stability: experimental-cabal-version: >= 1.10.0-tested-with: GHC == 8.4.3+tested-with: GHC == 8.4.4 synopsis: streaming FASTA parser description: Stream-based handling of FASTA files. The user selects a window@@ -19,9 +19,6 @@ previous (past) window is available, in case some data sits on the boundary between windows. .- FastaTool is a simple tool providing information on FASTA- files, and allowing to extract sequences and subsequences.- . Greg Schwartz' <http://hackage.haskell.org/package/fasta> package is a lot more complete. This one is mostly tailored to my usage requirements (and may at some point use his library).@@ -37,20 +34,16 @@ -library+common deps build-depends: base >= 4.7 && < 5.0 , bytestring+ , lens >= 4.0 , resourcet >= 1.0 , streaming >= 0.1 , streaming-bytestring >= 0.1- , lens- , deepseq+ , string-conversions >= 0.4 --- , BiobaseTypes == 0.1.4.*- exposed-modules:- Biobase.Fasta.Streaming,- Biobase.Fasta.Types,- Biobase.Fasta.Export+ , BiobaseTypes == 0.2.0.* default-language: Haskell2010 default-extensions: BangPatterns@@ -58,24 +51,39 @@ , DeriveDataTypeable , DeriveGeneric , FlexibleContexts+ , FlexibleInstances , GADTs+ , GeneralizedNewtypeDeriving , KindSignatures , LambdaCase+ , MultiParamTypeClasses , MultiWayIf , NoMonomorphismRestriction+ , OverloadedStrings , PolyKinds , RankNTypes , RecordWildCards , ScopedTypeVariables , TemplateHaskell+ , TypeApplications+ , TypeFamilies , UnicodeSyntax , ViewPatterns ghc-options: -O2 +library+ import:+ deps+ exposed-modules:+ Biobase.Fasta.Streaming+ Biobase.Fasta.Strict + test-suite properties+ import:+ deps type: exitcode-stdio-1.0 main-is:@@ -84,19 +92,8 @@ -threaded -rtsopts -with-rtsopts=-N hs-source-dirs: tests- default-language:- Haskell2010- default-extensions: TemplateHaskell- , UnicodeSyntax- , OverloadedStrings- - build-depends: base- , QuickCheck- , bytestring+ build-depends: QuickCheck , filepath- , resourcet >= 1.0- , streaming >= 0.1- , streaming-bytestring >= 0.1 , tasty >= 0.11 , tasty-hunit >= 0.9 , tasty-golden >= 2.3
tests/properties.hs view
@@ -1,63 +1,69 @@ module Main where -import Prelude as P-import Data.Functor.Of-import qualified Data.ByteString.Char8 as BS+import Control.Lens (view)+import Control.Monad.Trans.Resource (runResourceT, ResourceT(..), MonadResource) import Data.ByteString.Streaming as BSS import Data.ByteString.Streaming.Char8 as S8 import Data.ByteString.Streaming.Internal (ByteString(..))+import Data.Functor.Of+import Data.Void+import Prelude as P+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as BL8+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Test.Tasty.Golden as Golden import Streaming as S import Streaming.Prelude as SP+import System.FilePath (takeBaseName, replaceExtension) import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck as QC---import Test.Tasty.Silver as S---import Test.Tasty.Silver.Interactive as SI import Test.Tasty.TH-import Control.Monad.Trans.Resource (runResourceT, ResourceT(..), MonadResource)-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Test.Tasty.Golden as Golden-import System.FilePath (takeBaseName, replaceExtension)-import qualified Data.ByteString.Lazy.Char8 as BL8 +import Biobase.Types.BioSequence+import Biobase.Types.Strand+ import Biobase.Fasta.Streaming+import Biobase.Fasta.Strict --- * golden tests--readFastaFile ∷ FilePath → IO [(BS.ByteString,BS.ByteString,BS.ByteString)]-readFastaFile f = do- let s = 1000000- xs :> r ← runResourceT- $ SP.toList- $ streamingFasta (HeaderSize s) (OverlapSize 0) (CurrentSize s) eachFasta- $ S8.readFile f- return xs--lazyFastaOut = BL8.concat . P.map go- where go (h,o,c) = BL8.concat- [ BL8.fromStrict h- , BL8.pack "\n"- , BL8.fromStrict o- , BL8.pack "\n"- , BL8.fromStrict c- , BL8.pack "\n"- ]--goldenTests ∷ IO TestTree-goldenTests = do- fastaFiles ← Golden.findByExtension [".fa"] "./tests/"- return $ testGroup "readFastaFile golden tests"- [ Golden.goldenVsString- (takeBaseName fastaFile) -- test name- goldenFile -- golden file path- (lazyFastaOut <$> readFastaFile fastaFile) -- action whose result is tested- | fastaFile <- fastaFiles- , let goldenFile = replaceExtension fastaFile ".fa-golden"- ]+-- -- * golden tests+-- +-- eachFasta (HeaderSize h) (OverlapSize o) (CurrentSize c p) = SP.yield (h,o,c)+-- +-- readFastaFile ∷ FilePath → IO [(BS.ByteString,BS.ByteString,BS.ByteString)]+-- readFastaFile f = do+-- let s = 1000000+-- xs :> r ← runResourceT+-- $ SP.toList+-- $ streamingFasta (HeaderSize s) (OverlapSize 0) (CurrentSize s) eachFasta+-- $ S8.readFile f+-- return xs+-- +-- lazyFastaOut = BL8.concat . P.map go+-- where go (h,o,c) = BL8.concat+-- [ BL8.fromStrict h+-- , BL8.pack "\n"+-- , BL8.fromStrict o+-- , BL8.pack "\n"+-- , BL8.fromStrict c+-- , BL8.pack "\n"+-- ]+-- +-- goldenTests ∷ IO TestTree+-- goldenTests = do+-- fastaFiles ← Golden.findByExtension [".fa"] "./tests/"+-- return $ testGroup "readFastaFile golden tests"+-- [ Golden.goldenVsString+-- (takeBaseName fastaFile) -- test name+-- goldenFile -- golden file path+-- (lazyFastaOut <$> readFastaFile fastaFile) -- action whose result is tested+-- | fastaFile <- fastaFiles+-- , let goldenFile = replaceExtension fastaFile ".fa-golden"+-- ] -- * unit tests @@ -70,28 +76,34 @@ , "890" ] -smallTest ∷ Int → Int → Int → Of [(BS.ByteString,BS.ByteString,BS.ByteString)] ()+smallTest ∷ Int → Int → Int → Of [BioSequenceWindow Void Void 1] () smallTest h o c = runIdentity . toList- . streamingFasta (HeaderSize h) (OverlapSize o) (CurrentSize c) go+-- . SP.map (view windowedFasta)+ . streamingFasta (HeaderSize h) (OverlapSize o) (CurrentSize c) . S8.fromStrict $ BS.pack smallInlineFasta- where go (Header h) (Overlap o) (Current c _) = yield (h,o,c)+ where go (HeaderSize h) (OverlapSize o) (CurrentSize c) = yield (h,o,c) smallTest333 = testCase "3/3/3" $ do let res :> r = smallTest 3 3 3 assertEqual "return is null" () r assertEqual "length is 4" 4 (P.length res)- assertEqual "!!0" (">Aa","","123") (res!!0)- assertEqual "!!1" (">Bb","","456") (res!!1)- assertEqual "!!2" (">Bb","456","7") (res!!2)- assertEqual "!!3" (">Cc","","890") (res!!3)+ assertEqual "!!0" (BioSequenceWindow "Aaa" "" "123" "" PlusStrand 1) (res!!0)+ assertEqual "!!1" (BioSequenceWindow "Bbb" "" "456" "" PlusStrand 1) (res!!1)+ assertEqual "!!2" (BioSequenceWindow "Bbb" "456" "7" "" PlusStrand 4) (res!!2)+ assertEqual "!!3" (BioSequenceWindow "Ccc" "" "890" "" PlusStrand 1) (res!!3)+ --+ assertEqual "!!0/Fasta" (Fasta "Aaa" "123") (view windowedFasta $ res!!0)+ assertEqual "!!1/Fasta" (Fasta "Bbb" "456") (view windowedFasta $ res!!1)+ assertEqual "!!2/Fasta" (Fasta "Bbb" "7" ) (view windowedFasta $ res!!2)+ assertEqual "!!3/Fasta" (Fasta "Ccc" "890") (view windowedFasta $ res!!3) main :: IO () main = do- gs ← goldenTests- defaultMain $ testGroup "all tests"- [ testGroup "Golden" [gs]- , testGroup "unit tests" [smallTest333]- ]+-- gs ← goldenTests+ defaultMain $ testGroup "all tests"+-- [ testGroup "Golden" [gs]+ [ testGroup "unit tests" [smallTest333]+ ]