BiobaseFasta 0.3.0.1 → 0.4.0.1
raw patch · 7 files changed
Files
- Biobase/Fasta/Streaming.hs +80/−227
- Biobase/Fasta/Strict.hs +4/−0
- BiobaseFasta.cabal +23/−6
- README.md +2/−1
- changelog.md +6/−0
- src/fastaextract.hs +60/−0
- tests/properties.hs +24/−23
Biobase/Fasta/Streaming.hs view
@@ -5,7 +5,7 @@ -- -- A typical, slightly complicated is this: -- @--- forEach ∷ forall r . Stream (ByteString m) m r → m (Stream (Of ()) m r)+-- forEach :: forall r . Stream (ByteString m) m r -> m (Stream (Of ()) m r) -- forEach dna = do -- -- extract the header, but at most 123 characters, dropping the rest -- hdr SP.:> dta ← extractHeader (Just 123) dna@@ -25,241 +25,114 @@ ( 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-import Data.ByteString.Streaming.Char8 as S8-import Data.ByteString.Streaming.Internal (ByteString(..))-import Data.Semigroup as SG-import Debug.Trace-import GHC.Generics (Generic)-import GHC.TypeLits-import Prelude as P+import Control.Lens hiding (Index,Empty, mapped)+import Control.Monad+import Control.Monad.Trans.Resource (runResourceT, ResourceT(..), MonadResource)+import Data.Semigroup as SG+import Debug.Trace+import GHC.Generics (Generic)+import GHC.TypeLits+import Prelude as P import qualified Data.ByteString.Char8 as BS import qualified Streaming.Internal as SI-import Streaming as S-import Streaming.Prelude as SP--import Data.ByteString.Streaming.Split--import Biobase.Types.BioSequence-import Biobase.Types.Index.Type-import Biobase.Types.Location-import Biobase.Types.Strand----newtype HeaderSize = HeaderSize Int- deriving (Eq,Ord,Show)--newtype OverlapSize = OverlapSize Int- deriving (Eq,Ord,Show)--newtype CurrentSize = CurrentSize Int- deriving (Eq,Ord,Show)+import Streaming as S+import Streaming.ByteString as BSS+import Streaming.ByteString.Char8 as S8+import Streaming.ByteString.Internal as SBI+import Streaming.Prelude as SP --- | lens into the unique id / first word of the header.+import Data.ByteString.Streaming.Split -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 #-}+import Biobase.Types.BioSequence+import Biobase.Types.Index.Type+import Biobase.Types.Location+import Biobase.Types.Position+import Biobase.Types.Strand --- | 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--- continued streaming.------ @--- 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)--- @--streamingFasta- ∷ forall m w ty k r a- . ( Monad m )- ⇒ HeaderSize- -- ^ Maximal length of the header. Ok to set to @20 000@, only guards against- -- an extremely long header line.- → OverlapSize- -- ^ How much of the current size to carry over to the next step. Even if set- -- larger than current size, it will only be at most current size. (But see- -- todo at 'overlappedFasta')- → CurrentSize- -- ^ The size of each window to be processed.- → ByteString m r- -- ^ A streaming bytestring of Fasta files.- → Stream (Of (BioSequenceWindow w ty PartialLocation)) m r- -- ^ The outgoing stream of @Current@ windows being processed.-{-# Inlinable streamingFasta #-}-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- -- the handling function @f@ with empty input. @f@ can decide on how to- -- handle empty FASTA entries.- Empty retVal → do- -- handle case of last empty fasta- unless (P.null hdr) $ do- 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- -- We have a chunk of bytestring @rawBS@ with more data in the bytestream- -- @bs@. We work on @b@, not the @rawBS@. In case we have no header parts- -- yet, all characters preceeding a fasta header symbol ('>' or ';') are- -- dropped.- Chunk rawBS bytestream- -- No newline in the @b@, hence we add the bytestring to the partial- -- header, and continue scanning. Note that we add only if we are below- -- the maximal header size @hSz@ to prevent malicious fasta files from- -- blowing up memory usage.- | Nothing ← mk → if cnt > hSz- then go (FindHeader hdr cnt) bytestream- else go (FindHeader (b:hdr) (BS.length b + cnt)) bytestream- -- 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.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- mk = BS.elemIndex '\n' b- -- We actually do have a valid header now and process fasta in parts.- 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) . 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- -- We have incoming data ...- Chunk b bytestream → case newFastaIndex b of- -- there is no new fasta starting, meaning that we need to process @b@ as- -- payload. We split at the maximal size we are allowed according to- -- @cSz@. If we have hit the limit, we run @f@ on this part of the data- -- and include the overlap as prefix. Otherwise we continue gathering.- -- Any newlines are removed from the data.- 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- 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)- (if BS.null next then bytestream else Chunk next bytestream)- -- We have a new fasta symbol in @b@. We split at the symbol and re-run- -- the first part (which will end up being the @Nothing@ case) and put- -- into @Chunk next bytestream@ the beginning of the next fasta entry.- -- This part will then be handled by the @otherwise@ case here.- Just new- | new > 0 → let (this,next) = BS.splitAt new b- in go (HasHeader hdr overlap cs cnt entries) $ Chunk this (Chunk next bytestream)- | 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) . 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- , _bswLocation = PartialLocation PlusStrand (Index $ entries * cSz) (BS.length seq)--- , _bswStrand = PlusStrand--- , _bswIndex = Index $ entries * cSz- }- -- | -streamedFasta ∷ (Monad m) ⇒ ByteString m r → Stream (Stream (ByteString m) m) m r+streamedFasta :: (Monad m) => ByteStream m r -> Stream (Stream (ByteStream m) m) m r {-# Inlinable streamedFasta #-}-streamedFasta = S.maps (collapseData) . streamOfStreamedFasta+streamedFasta = S.maps collapseData . streamOfStreamedFasta -- | Here each individual fasta file will be a stream. -- -- TODO Once this works, @streamingFasta@ should be @S.concats . streamOfStreamedFasta@ ... streamOfStreamedFasta- ∷ forall m r+ :: forall m r . ( Monad m )- ⇒ ByteString m r- → Stream (Stream (ByteString m) m) m r+ => ByteStream m r+ -> Stream (Stream (ByteStream m) m) m r -- ^ {-# Inlinable streamOfStreamedFasta #-} streamOfStreamedFasta = go . S8.lines where go = \case- SI.Return r → SI.Return r- SI.Effect m → SI.Effect (fmap go m)- SI.Step fs → SI.Step (SI.Step (fmap (fmap go . splitFasta) fs))+ SI.Return r -> SI.Return r+ SI.Effect m -> SI.Effect (fmap go m)+ SI.Step fs -> SI.Step (SI.Step (fmap (fmap go . splitFasta) fs)) -- | Given a 'Stream (ByteString m) m r' which is a 'Stream' of @lines@, split -- off the first @Fasta@ entry. -splitFasta ∷ (Monad m) ⇒ Stream (ByteString m) m r → Stream (ByteString m) m (Stream (ByteString m) m r)+splitFasta :: (Monad m) => Stream (ByteStream m) m r -> Stream (ByteStream m) m (Stream (ByteStream m) m r) {-# Inlinable splitFasta #-} splitFasta = loop False where loop hdr = \case- SI.Return r → SI.Return (SI.Return r)- SI.Effect m → SI.Effect (fmap (loop hdr) m)- SI.Step bs → case bs of- Empty r → loop hdr r+ SI.Return r -> SI.Return (SI.Return r)+ SI.Effect m -> SI.Effect (fmap (loop hdr) m)+ SI.Step bs -> case bs of+ Empty r -> loop hdr r Chunk cs xs- | BS.null cs → loop hdr $ SI.Step xs- | h=='>' || h==';' → if hdr then SI.Return (SI.Step bs) else SI.Step $ fmap (loop True) bs- | otherwise → SI.Step $ fmap (loop True) bs+ | BS.null cs -> loop hdr $ SI.Step xs+ | h=='>' || h==';' -> if hdr then SI.Return (SI.Step bs) else SI.Step $ fmap (loop True) bs+ | otherwise -> SI.Step $ fmap (loop True) bs where h = BS.head cs- Go m → SI.Effect $ fmap ((loop hdr) . SI.Step) m+ Go m -> SI.Effect $ fmap ((loop hdr) . SI.Step) m -- | Given a stream, roughly like @[BS "Header", BS "Data1", BS "Data2", ...]@ -- create a stream like @[BS "Header", BS "Data"]@. This means that the -- resulting stream holds exactly two @ByteString@'s. -collapseData ∷ (Monad m) ⇒ Stream (ByteString m) m r → Stream (ByteString m) m r+collapseData :: (Monad m) => Stream (ByteStream m) m r -> Stream (ByteStream m) m r {-# Inlinable collapseData #-} collapseData = loop where loop = \case- SI.Return r → SI.Return r- SI.Effect m → SI.Effect (fmap loop m)- SI.Step bs → case bs of- Empty r → loop r+ SI.Return r -> SI.Return r+ SI.Effect m -> SI.Effect (fmap loop m)+ SI.Step bs -> case bs of+ Empty r -> loop r Chunk cs xs- | BS.null cs → loop $ SI.Step xs- | h=='>' || h==';' → SI.Step $ fmap (S.yields . S8.concat) bs- | otherwise → SI.Step $ fmap loop bs+ | BS.null cs -> loop $ SI.Step xs+ | h=='>' || h==';' -> SI.Step $ fmap (S.yields . S8.concat) bs+ | otherwise -> SI.Step $ fmap loop bs where h = BS.head cs- Go m → SI.Effect $ fmap (loop . SI.Step) m+ Go m -> SI.Effect $ fmap (loop . SI.Step) m + -- | "Rechunk" a stream of bytestrings. -reChunkBS ∷ (Monad m) ⇒ Int → Stream (ByteString m) m r → Stream (ByteString m) m r+reChunkBS :: (Monad m) => Int -> Stream (ByteStream m) m r -> Stream (ByteStream m) m r {-# Inlinable reChunkBS #-} reChunkBS n = splitsByteStringAt n . S8.concat -- | Assuming a "rechunked" stream of bytestrings, create sequence windows. -chunksToWindows ∷ (Monad m) ⇒ SequenceIdentifier w → Strand → Stream (ByteString m) m r → Stream (Of (BioSequenceWindow w ty PartialLocation)) m r+chunksToWindows :: Monad m => SequenceIdentifier w -> Strand -> Stream (ByteStream m) m r -> Stream (Of (Location w FwdPosition (BioSequence ty))) m r {-# Inlinable chunksToWindows #-}-chunksToWindows seqId s = SP.map go . SP.drop 1 . SP.scan indexed (BS.empty, 0, 0) (\(bs,i,_) → (bs,i)) . S.mapsM S8.toStrict where+chunksToWindows seqId s = SP.map go . SP.drop 1 . SP.scan indexed (BS.empty, 0, 0) (\(bs,i,_) -> (bs,i)) . S.mapsM S8.toStrict where indexed (_,cur,next) bs = (bs,next,next + BS.length bs) go (bs,i)- = BioSequenceWindow- { _bswIdentifier = seqId- , _bswPrefix = BioSequence ""- , _bswSequence = BioSequence bs- , _bswSuffix = BioSequence ""- , _bswLocation = PartialLocation s (Index i) (BS.length bs)--- , _bswStrand = s--- , _bswIndex = Index i+ = Location+ { _locIdentifier = seqId+ , _locPosition = FwdPosition s (Index i)+ , _locSequence = BioSequence bs } ++ -- | Make it possible to take a fasta stream and produce a stream of -- 'BioSequenceWindow's. This is a convenience function around -- 'withSuffix . withPrefix . chunksToWindows . reChunks'.@@ -272,21 +145,23 @@ -- check should be once per @ByteString@. streamedWindows- ∷ (Monad m)- ⇒ Bool- → Bool- → Maybe Int+ :: (Monad m)+ => Maybe Int+ -> Maybe Int+ -> Maybe Int -- ^ desired size or a single huge @Fasta@ entry.- → SequenceIdentifier w- → Strand- → (Stream (ByteString m) m) r- → Stream (Of (BioSequenceWindow w ty PartialLocation)) m r+ -> SequenceIdentifier w+ -> Strand+ -> (Stream (ByteStream m) m) r+-- -> Stream (Of (BioSequenceWindow w ty FwdLocation)) m r+ -> Stream (Of (PIS w FwdPosition (BioSequence ty))) m r {-# Inlinable streamedWindows #-} streamedWindows withPrefix withSuffix winSz seqId strnd- = (if withSuffix then attachSuffixes else id)- . (if withPrefix then attachPrefixes else id)+ = (maybe id attachSuffixes withSuffix)+ . (maybe id attachPrefixes withPrefix)+ . SP.map pis . chunksToWindows seqId strnd- . (case winSz of { Nothing → collapseData; Just sz → reChunkBS sz })+ . (case winSz of { Nothing -> collapseData; Just sz -> reChunkBS sz }) -- | Get the full length of a stream of 'BioSequenceWindow's, counted in -- characters in each 'bswSequence'.@@ -298,46 +173,24 @@ -- -- This value may then be used to fully update negative strand information. -bswSeqLength ∷ (Monad m) ⇒ Stream (Of (BioSequenceWindow w ty k)) m r → m (Of Int r)-{-# Inlinable bswSeqLength #-}-bswSeqLength = SP.fold (\x w → x + view (bswSequence._BioSequence.to BS.length) w) 0 id+streamLocationLength :: (Monad m, ModifyLocation posTy seqTy) => Stream (Of (Location i posTy seqTy)) m r -> m (Of Int r)+{-# Inlinable streamLocationLength #-}+streamLocationLength = SP.fold (\x w -> x + locLength w) 0 id --- | As a first function, the header should be extracted from a @Fasta@ stream. Since headers may be malformed / malicious, we make it possible to+-- | As a first function, the header should be extracted from a @Fasta@ stream. Since headers may be+-- malformed / malicious, we make it possible to extractHeader- ∷ (Monad m)- ⇒ Maybe Int- → Stream (ByteString m) m r- → m (Of BS.ByteString (Stream (ByteString m) m r))+ :: (Monad m)+ => Maybe Int+ -> Stream (ByteStream m) m r+ -> m (Of BS.ByteString (Stream (ByteStream m) m r)) {-# Inlinable extractHeader #-} extractHeader hdrSz =- let go = case hdrSz of { Nothing → id; Just sz → S8.drained . S8.splitAt (fromIntegral sz) }+ let go = case hdrSz of { Nothing -> id; Just sz -> S8.drained . S8.splitAt (fromIntegral sz) } in S8.toStrict . go . S8.concat . S.splitsAt 1 -foo = S8.fromStrict ">a\na\na\n>b\nb\nb\n" --- | Control structure for 'streamingFasta'.--data FindHeader- = FindHeader- { headerParts ∷ [BS.ByteString]- -- ^ the collected header parts (in reverse order)- , headerLength ∷ !Int- -- ^ accumulated header length- }- | HasHeader- { fhHeader ∷ !BS.ByteString- -- ^ the (size-truncated) header for this fasta file- , dataOverlap ∷ !BS.ByteString- -- ^ overlap (if any) from earlier parts of the fasta file- , dataParts ∷ [BS.ByteString]- -- ^ collection of dataParts, in reverse order!- , dataLength ∷ !Int- -- ^ total length of data parts, simplifies checking if enough data was collected- , entries ∷ !Int- -- ^ count how many entries we have seen- }- {- t0 = P.unlines [ ">Aaaa"@@ -356,7 +209,7 @@ --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) ---readFastaFile ∷ FilePath → IO [(BS.ByteString,BS.ByteString,BS.ByteString)]+--readFastaFile :: FilePath -> IO [(BS.ByteString,BS.ByteString,BS.ByteString)] readFastaFile f = do let s = 1000000000000 r ← runResourceT
Biobase/Fasta/Strict.hs view
@@ -79,19 +79,23 @@ -- | 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
BiobaseFasta.cabal view
@@ -1,17 +1,17 @@ cabal-version: 2.2 name: BiobaseFasta-version: 0.3.0.1+version: 0.4.0.1 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-2019+copyright: Christian Hoener zu Siederdissen, 2011-2021 category: Bioinformatics license: BSD-3-Clause license-file: LICENSE build-type: Simple stability: experimental-tested-with: GHC == 8.4.4+tested-with: GHC == 8.8.4, GHC == 8.10.4, GHC == 9.0 synopsis: streaming FASTA parser description: Stream-based handling of FASTA files. The user selects a window@@ -40,11 +40,11 @@ , lens >= 4.0 , resourcet >= 1.0 , streaming >= 0.1- , streaming-bytestring >= 0.1+ , streaming-bytestring >= 0.2 , string-conversions >= 0.4 --- , BiobaseTypes == 0.2.0.*- , DPutils == 0.1.0.*+ , BiobaseTypes == 0.2.1.*+ , DPutils == 0.1.1.* default-language: Haskell2010 default-extensions: BangPatterns@@ -79,6 +79,23 @@ exposed-modules: Biobase.Fasta.Streaming Biobase.Fasta.Strict++++-- | A simple tool for fasta files, showing some features++executable fastaextract+ import: deps+ build-depends: base+ , optparse-applicative >= 0.14+ --+ , BiobaseFasta+ hs-source-dirs:+ src+ main-is:+ fastaextract.hs+ ghc-options:+ -rtsopts
README.md view
@@ -1,4 +1,5 @@-[](https://travis-ci.org/choener/BiobaseFasta)++ # BiobaseFasta
changelog.md view
@@ -1,3 +1,9 @@+0.4.0.1+-------++- streaming-bytestring >= 0.2+- cleanup of older code+ 0.2.0.0 -------
+ src/fastaextract.hs view
@@ -0,0 +1,60 @@++module Main where++import Control.Monad.IO.Class+import Data.ByteString.Char8 as BS+import Data.ByteString.Streaming.Char8 as BSS+import Options.Applicative+import Streaming as S+import Streaming.Prelude as SP+import System.IO (stdin)++import Biobase.Fasta.Streaming as FS++++data Options+ -- Extract all sequences that have "header" as "infix".+ = Extract+ { header ∷ String+ , from ∷ Int+ , to ∷ Int+ }++options ∷ Parser Options+options+ = Extract+ <$> strOption (long "header" <> short 'h' <> help "header infix to grep")+ <*> option auto (long "from" <> short 'f' <> help "first nucleotide in sequence")+ <*> option auto (long "to" <> short 't' <> help "last nucleotide in sequence")++-- | Extract a fasta piece from a larger fasta++extract+ ∷ ( Monad m )+ ⇒ String → Int → Int+ → Stream (BSS.ByteString m) m r+ → BSS.ByteString m r+{-# Inlinable extract #-}+extract ifx' f' t' s = BSS.mwrap $ do+ let ifx = BS.pack ifx'+ let f = fromIntegral $ min f' t'+ let t = fromIntegral $ max f' t'+ hdr :> dta ← extractHeader Nothing s+ if ifx `BS.isInfixOf` hdr+ -- we actually have a stream to return+ then return $ do+ BSS.fromStrict $ hdr `BS.snoc` '\n'+ BSS.drained . BSS.splitAt (t-f+1) . BSS.drop (f-1) $ BSS.concat dta+ -- just drain this stream+ else mapsM_ BSS.effects dta >>= return . return++main ∷ IO ()+main = do+ p ← execParser (info options fullDesc)+ case p of+ Extract hdr f t+ → BSS.stdout . BSS.unlines . BSS.denull+ . maps (extract hdr f t)+ . FS.streamedFasta $ BSS.stdin+
tests/properties.hs view
@@ -77,34 +77,35 @@ , "890" ] -smallTest ∷ Int → Int → Int → Of [BioSequenceWindow Void Void PartialLocation] ()-smallTest h o c = runIdentity- . toList--- . SP.map (view windowedFasta)- . streamingFasta (HeaderSize h) (OverlapSize o) (CurrentSize c)- . S8.fromStrict- $ BS.pack smallInlineFasta- 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" (BioSequenceWindow "Aaa" "" "123" "" (PartialLocation PlusStrand 0 3)) (res!!0)- assertEqual "!!1" (BioSequenceWindow "Bbb" "" "456" "" (PartialLocation PlusStrand 0 3)) (res!!1)- assertEqual "!!2" (BioSequenceWindow "Bbb" "456" "7" "" (PartialLocation PlusStrand 3 1)) (res!!2)- assertEqual "!!3" (BioSequenceWindow "Ccc" "" "890" "" (PartialLocation PlusStrand 0 3)) (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)+--smallTest ∷ Int → Int → Int → Of [BioSequenceWindow Void Void PartialLocation] ()+--smallTest h o c = runIdentity+-- . toList+---- . SP.map (view windowedFasta)+-- . streamingFasta (HeaderSize h) (OverlapSize o) (CurrentSize c)+-- . S8.fromStrict+-- $ BS.pack smallInlineFasta+-- 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" (BioSequenceWindow "Aaa" "" "123" "" (PartialLocation PlusStrand 0 3)) (res!!0)+-- assertEqual "!!1" (BioSequenceWindow "Bbb" "" "456" "" (PartialLocation PlusStrand 0 3)) (res!!1)+-- assertEqual "!!2" (BioSequenceWindow "Bbb" "456" "7" "" (PartialLocation PlusStrand 3 1)) (res!!2)+-- assertEqual "!!3" (BioSequenceWindow "Ccc" "" "890" "" (PartialLocation PlusStrand 0 3)) (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]+-- [ testGroup "unit tests" [smallTest333]+ [ ]