diff --git a/Biobase/Fasta/Streaming.hs b/Biobase/Fasta/Streaming.hs
--- a/Biobase/Fasta/Streaming.hs
+++ b/Biobase/Fasta/Streaming.hs
@@ -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
diff --git a/Biobase/Fasta/Strict.hs b/Biobase/Fasta/Strict.hs
--- a/Biobase/Fasta/Strict.hs
+++ b/Biobase/Fasta/Strict.hs
@@ -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
diff --git a/BiobaseFasta.cabal b/BiobaseFasta.cabal
--- a/BiobaseFasta.cabal
+++ b/BiobaseFasta.cabal
@@ -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
 
 
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -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
 
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,9 @@
+0.4.0.1
+-------
+
+- streaming-bytestring >= 0.2
+- cleanup of older code
+
 0.2.0.0
 -------
 
diff --git a/src/fastaextract.hs b/src/fastaextract.hs
new file mode 100644
--- /dev/null
+++ b/src/fastaextract.hs
@@ -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
+
diff --git a/tests/properties.hs b/tests/properties.hs
--- a/tests/properties.hs
+++ b/tests/properties.hs
@@ -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]
+     [
      ]
 
