packages feed

BiobaseFasta 0.3.0.1 → 0.4.0.1

raw patch · 7 files changed

Files

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 @@-[![Build Status](https://travis-ci.org/choener/BiobaseFasta.svg?branch=master)](https://travis-ci.org/choener/BiobaseFasta)+![github action: master](https://github.com/choener/BiobaseFasta/actions/workflows/ci.yml/badge.svg?branch=master)+![github action: hackage](https://github.com/choener/BiobaseFasta/actions/workflows/hackage.yml/badge.svg)  # 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]+     [      ]