{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes        #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-|
Module      : Frames.Streamly.CSV
Description : CSV parsing/formatting tools for the Frames library, operating via streamly Streams.
Copyright   : (c) Adam Conner-Sax 2020
License     : BSD-3-Clause
Maintainer  : adam_conner_sax@yahoo.com
Stability   : experimental

This module can be used in-place of Frames.CSV in order to use streamly streams where Frames uses pipes.
This module adds some functionality for formatting in more flexible ways than the pipes version in Frames.
It allows us of Show instances, in addition to the ShowCSV class included in Frames.  And it allows one-off
specification of a format as well.  See the example for more details.
-}
module Frames.Streamly.CSV
    (
      -- * read from File to Stream of Recs 
      readTable
    , readTableOpt
    , readTableMaybe
    , readTableMaybeOpt
    , readTableEither
    , readTableEitherOpt
      -- * convert streaming Text to streaming Records
    , streamTable
    , streamTableOpt
    , streamTableMaybe  
    , streamTableMaybeOpt
    , streamTableEither
    , streamTableEitherOpt
      -- * Produce (streaming) Text from Records
    , streamToCSV
    , streamCSV
    , streamToSV
    , streamSV
    , streamSV'
      -- *  write Records to Text File
    , writeCSV
    , writeSV
    , writeStreamSV
    , writeCSV_Show
    , writeSV_Show
    , writeStreamSV_Show
      -- * Utilities
    , streamToList
    , liftFieldFormatter
    , liftFieldFormatter1
    , formatTextAsIs
    , formatWithShow
    , formatWithShowCSV
    , writeLines
    , writeLines'
    , word8ToTextLines
    )
where

import qualified Streamly.Prelude                       as Streamly
import qualified Streamly                               as Streamly
import           Streamly                                ( IsStream )
import qualified Streamly.Data.Fold                     as Streamly.Fold
import qualified Streamly.Data.Unicode.Stream           as Streamly.Unicode
import qualified Streamly.Internal.FileSystem.File      as Streamly.File
import qualified Streamly.Internal.Data.Unfold          as Streamly.Unfold
import           Control.Monad.Catch                     ( MonadCatch )
import           Control.Monad.IO.Class                  ( MonadIO )

import           Data.Maybe                              (isNothing)
import qualified Data.Text                              as T

import qualified Data.Vinyl                             as Vinyl
import qualified Data.Vinyl.Functor                     as Vinyl
import qualified Data.Vinyl.TypeLevel                   as Vinyl
import qualified Data.Vinyl.Class.Method                as Vinyl
import           Data.Word                               ( Word8 )

import qualified Frames                                 as Frames
import qualified Frames.CSV                             as Frames
import qualified Frames.ShowCSV                         as Frames 

import Data.Proxy (Proxy (..))

  

-- | Given a stream of @Records@, for which all fields satisfy the `ShowCSV` constraint,
-- produce a stream of `Text`, one item (line) per `Record` with the specified separator
-- between fields.
streamToSV
  :: forall rs m t.
     ( Frames.ColumnHeaders rs
     , Monad m
     , Vinyl.RecordToList rs
     , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
     , IsStream t
     )
  => T.Text -- ^ column separator
  -> t m (Frames.Record rs) -- ^ stream of Records
  -> t m T.Text -- ^ stream of 'Text' rows
streamToSV :: Text -> t m (Record rs) -> t m Text
streamToSV = (forall a. ShowCSV a => a -> Text)
-> Text -> t m (Record rs) -> t m Text
forall (c :: * -> Constraint) (rs :: [(Symbol, *)])
       (t :: (* -> *) -> * -> *) (m :: * -> *).
(RecMapMethod c ElField rs, RecordToList rs, ColumnHeaders rs,
 IsStream t, Monad m) =>
(forall a. c a => a -> Text) -> Text -> t m (Record rs) -> t m Text
streamSVClass @Frames.ShowCSV forall a. ShowCSV a => a -> Text
Frames.showCSV
{-# INLINEABLE streamToSV #-}

-- | Given a stream of @Records@, for which all fields satisfy the `ShowCSV` constraint,
-- produce a stream of CSV `Text`, one item (line) per `Record`.
streamToCSV
  :: forall rs m t
     . ( Frames.ColumnHeaders rs
       , Monad m
       , Vinyl.RecordToList rs
       , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
       , IsStream t
       )
  => t m (Frames.Record rs) -- ^ stream of Records
  -> t m T.Text -- ^ stream of 'Text' rows
streamToCSV :: t m (Record rs) -> t m Text
streamToCSV = Text -> t m (Record rs) -> t m Text
forall (rs :: [(Symbol, *)]) (m :: * -> *)
       (t :: (* -> *) -> * -> *).
(ColumnHeaders rs, Monad m, RecordToList rs,
 RecMapMethod ShowCSV ElField rs, IsStream t) =>
Text -> t m (Record rs) -> t m Text
streamToSV Text
","
{-# INLINEABLE streamToCSV #-}

-- | Given a foldable of @Records@, for which all fields satisfy the `ShowCSV` constraint,
-- produce a stream of `Text`, one item (line) per `Record` with the specified separator
-- between fields. 
streamSV
  :: forall f rs m t.
     ( Frames.ColumnHeaders rs
     , Foldable f
     , Monad m
     , Vinyl.RecordToList rs
     , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
     , IsStream t
     )
  => T.Text -- ^ column separator
  -> f (Frames.Record rs) -- ^ foldable of Records
  -> t m T.Text -- ^ stream of 'Text' rows
streamSV :: Text -> f (Record rs) -> t m Text
streamSV Text
sep = Text -> t m (Record rs) -> t m Text
forall (rs :: [(Symbol, *)]) (m :: * -> *)
       (t :: (* -> *) -> * -> *).
(ColumnHeaders rs, Monad m, RecordToList rs,
 RecMapMethod ShowCSV ElField rs, IsStream t) =>
Text -> t m (Record rs) -> t m Text
streamToSV Text
sep (t m (Record rs) -> t m Text)
-> (f (Record rs) -> t m (Record rs)) -> f (Record rs) -> t m Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. f (Record rs) -> t m (Record rs)
forall (t :: (* -> *) -> * -> *) (f :: * -> *) a (m :: * -> *).
(IsStream t, Foldable f) =>
f a -> t m a
Streamly.fromFoldable  
{-# INLINEABLE streamSV #-}

-- | Given a foldable of @Records@, for which all fields satisfy the `ShowCSV` constraint,
-- produce a stream of CSV `Text`, one item (line) per `Record`.
streamCSV
  :: forall f rs m t.
     ( Frames.ColumnHeaders rs
     , Foldable f
     , Monad m
     , Vinyl.RecordToList rs
     , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
     , IsStream t
     )
  => f (Frames.Record rs)  -- ^ 'Foldable' of Records
  -> t m T.Text -- ^ stream of 'Text' rows
streamCSV :: f (Record rs) -> t m Text
streamCSV = Text -> f (Record rs) -> t m Text
forall (f :: * -> *) (rs :: [(Symbol, *)]) (m :: * -> *)
       (t :: (* -> *) -> * -> *).
(ColumnHeaders rs, Foldable f, Monad m, RecordToList rs,
 RecMapMethod ShowCSV ElField rs, IsStream t) =>
Text -> f (Record rs) -> t m Text
streamSV Text
","

-- | Convert @Rec@s to lines of `Text` using a class (which must have an instance
-- for each type in the record) to covert each field to `Text`.
streamSVClass
  :: forall c rs t m .
      ( Vinyl.RecMapMethod c Vinyl.ElField rs
      , Vinyl.RecordToList rs
      , Frames.ColumnHeaders rs
      , IsStream t
      , Monad m
     )
  => (forall a. c a => a -> T.Text) -- ^ @show@-like function for some constraint satisfied by all fields.
  -> T.Text -- ^ column separator
  -> t m (Frames.Record rs)  -- ^ stream of Records
  -> t m T.Text -- ^ stream of 'Text' rows
streamSVClass :: (forall a. c a => a -> Text) -> Text -> t m (Record rs) -> t m Text
streamSVClass forall a. c a => a -> Text
toText Text
sep t m (Record rs)
s =
  (Text -> [Text] -> Text
T.intercalate Text
sep ([Text] -> Text) -> ([String] -> [Text]) -> [String] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Text) -> [String] -> [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> Text
T.pack ([String] -> Text) -> [String] -> Text
forall a b. (a -> b) -> a -> b
$ Proxy (Record rs) -> [String]
forall (cs :: [(Symbol, *)]) (proxy :: * -> *)
       (f :: (Symbol, *) -> *).
ColumnHeaders cs =>
proxy (Rec f cs) -> [String]
Frames.columnHeaders (Proxy (Record rs)
forall k (t :: k). Proxy t
Proxy :: Proxy (Frames.Record rs)))
  Text -> t m Text -> t m Text
forall (t :: (* -> *) -> * -> *) a (m :: * -> *).
IsStream t =>
a -> t m a -> t m a
`Streamly.cons`
  ((Record rs -> Text) -> t m (Record rs) -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> b) -> t m a -> t m b
Streamly.map (Text -> [Text] -> Text
T.intercalate Text
sep ([Text] -> Text) -> (Record rs -> [Text]) -> Record rs -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Rec (Const Text) rs -> [Text]
forall u (rs :: [u]) a. RecordToList rs => Rec (Const a) rs -> [a]
Vinyl.recordToList (Rec (Const Text) rs -> [Text])
-> (Record rs -> Rec (Const Text) rs) -> Record rs -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall (a :: (Symbol, *)).
 c (PayloadType ElField a) =>
 ElField a -> Const Text a)
-> Record rs -> Rec (Const Text) rs
forall u (c :: * -> Constraint) (f :: u -> *) (ts :: [u])
       (g :: u -> *).
RecMapMethod c f ts =>
(forall (a :: u). c (PayloadType f a) => f a -> g a)
-> Rec f ts -> Rec g ts
Vinyl.rmapMethod @c forall (a :: (Symbol, *)).
c (PayloadType ElField a) =>
ElField a -> Const Text a
aux) t m (Record rs)
s)
  where
    aux :: (c (Vinyl.PayloadType Vinyl.ElField a))
        => Vinyl.ElField a
        -> Vinyl.Const T.Text a
    aux :: ElField a -> Const Text a
aux (Vinyl.Field t
x) = Text -> Const Text a
forall k a (b :: k). a -> Const a b
Vinyl.Const (Text -> Const Text a) -> Text -> Const Text a
forall a b. (a -> b) -> a -> b
$ t -> Text
forall a. c a => a -> Text
toText t
x

    
-- | Given a record of functions to map each field to Text,
-- transform a stream of records into a stream of lines of Text,
-- headers first, with headers/fields separated by the given separator.
streamSV'
  :: forall rs t m f.
     (Vinyl.RecordToList rs
     , Vinyl.RApply rs
     , Frames.ColumnHeaders rs
     , IsStream t
     , Monad m
     )
  => Vinyl.Rec (Vinyl.Lift (->) f (Vinyl.Const T.Text)) rs -- ^ Vinyl record of formatting functions for the row-type.
  -> T.Text  -- ^ column separator
  -> t m (Frames.Rec f rs)  -- ^ stream of Records
  -> t m T.Text -- ^ stream of 'Text' rows
streamSV' :: Rec (Lift (->) f (Const Text)) rs
-> Text -> t m (Rec f rs) -> t m Text
streamSV' Rec (Lift (->) f (Const Text)) rs
toTextRec Text
sep t m (Rec f rs)
s = 
  (Text -> [Text] -> Text
T.intercalate Text
sep ([Text] -> Text) -> ([String] -> [Text]) -> [String] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Text) -> [String] -> [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> Text
T.pack ([String] -> Text) -> [String] -> Text
forall a b. (a -> b) -> a -> b
$ Proxy (Rec ElField rs) -> [String]
forall (cs :: [(Symbol, *)]) (proxy :: * -> *)
       (f :: (Symbol, *) -> *).
ColumnHeaders cs =>
proxy (Rec f cs) -> [String]
Frames.columnHeaders (Proxy (Rec ElField rs)
forall k (t :: k). Proxy t
Proxy :: Proxy (Frames.Record rs)))
  Text -> t m Text -> t m Text
forall (t :: (* -> *) -> * -> *) a (m :: * -> *).
IsStream t =>
a -> t m a -> t m a
`Streamly.cons`
  ((Rec f rs -> Text) -> t m (Rec f rs) -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> b) -> t m a -> t m b
Streamly.map (Text -> [Text] -> Text
T.intercalate Text
sep ([Text] -> Text) -> (Rec f rs -> [Text]) -> Rec f rs -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Rec (Const Text) rs -> [Text]
forall u (rs :: [u]) a. RecordToList rs => Rec (Const a) rs -> [a]
Vinyl.recordToList (Rec (Const Text) rs -> [Text])
-> (Rec f rs -> Rec (Const Text) rs) -> Rec f rs -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Rec (Lift (->) f (Const Text)) rs
-> Rec f rs -> Rec (Const Text) rs
forall u (rs :: [u]) (f :: u -> *) (g :: u -> *).
RApply rs =>
Rec (Lift (->) f g) rs -> Rec f rs -> Rec g rs
Vinyl.rapply Rec (Lift (->) f (Const Text)) rs
toTextRec) t m (Rec f rs)
s)
{-# INLINEABLE streamSV' #-}

-- | Convert a streamly stream into a (lazy) list
streamToList :: (IsStream t, Monad m) => t m a -> m [a] 
streamToList :: t m a -> m [a]
streamToList = SerialT m a -> m [a]
forall (m :: * -> *) a. Monad m => SerialT m a -> m [a]
Streamly.toList (SerialT m a -> m [a]) -> (t m a -> SerialT m a) -> t m a -> m [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. t m a -> SerialT m a
forall (t1 :: (* -> *) -> * -> *) (t2 :: (* -> *) -> * -> *)
       (m :: * -> *) a.
(IsStream t1, IsStream t2) =>
t1 m a -> t2 m a
Streamly.adapt

-- | lift a field formatting function into the right form to append to a Rec of formatters
liftFieldFormatter :: Vinyl.KnownField t
                   => (Vinyl.Snd t -> T.Text) -- ^ formatting function for the type in Field @t@
                   -> Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t -- ^ formatting function in the form required to use in row-formatters.
liftFieldFormatter :: (Snd t -> Text) -> Lift (->) ElField (Const Text) t
liftFieldFormatter Snd t -> Text
toText = (ElField t -> Const Text t) -> Lift (->) ElField (Const Text) t
forall l l' k (op :: l -> l' -> *) (f :: k -> l) (g :: k -> l')
       (x :: k).
op (f x) (g x) -> Lift op f g x
Vinyl.Lift ((ElField t -> Const Text t) -> Lift (->) ElField (Const Text) t)
-> (ElField t -> Const Text t) -> Lift (->) ElField (Const Text) t
forall a b. (a -> b) -> a -> b
$ Text -> Const Text '(Fst t, Snd t)
forall k a (b :: k). a -> Const a b
Vinyl.Const (Text -> Const Text '(Fst t, Snd t))
-> (ElField '(Fst t, Snd t) -> Text)
-> ElField '(Fst t, Snd t)
-> Const Text '(Fst t, Snd t)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Snd t -> Text
toText (Snd t -> Text)
-> (ElField '(Fst t, Snd t) -> Snd t)
-> ElField '(Fst t, Snd t)
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ElField '(Fst t, Snd t) -> Snd t
forall (s :: Symbol) t. ElField '(s, t) -> t
Vinyl.getField
{-# INLINEABLE liftFieldFormatter #-}

-- | lift a composed-field formatting function into the right form to append to a Rec of formatters
-- | Perhaps to format a parsed file with @Maybe@ or @Either@ composed with @ElField@
liftFieldFormatter1 :: (Functor f, Vinyl.KnownField t)
                    => (f (Vinyl.Snd t) -> T.Text) -- ^ formatting function for things like @Maybe a@
                    -> Vinyl.Lift (->) (f Vinyl.:. Vinyl.ElField) (Vinyl.Const T.Text) t
liftFieldFormatter1 :: (f (Snd t) -> Text) -> Lift (->) (f :. ElField) (Const Text) t
liftFieldFormatter1 f (Snd t) -> Text
toText = ((:.) f ElField t -> Const Text t)
-> Lift (->) (f :. ElField) (Const Text) t
forall l l' k (op :: l -> l' -> *) (f :: k -> l) (g :: k -> l')
       (x :: k).
op (f x) (g x) -> Lift op f g x
Vinyl.Lift (((:.) f ElField t -> Const Text t)
 -> Lift (->) (f :. ElField) (Const Text) t)
-> ((:.) f ElField t -> Const Text t)
-> Lift (->) (f :. ElField) (Const Text) t
forall a b. (a -> b) -> a -> b
$ Text -> Const Text '(Fst t, Snd t)
forall k a (b :: k). a -> Const a b
Vinyl.Const (Text -> Const Text '(Fst t, Snd t))
-> (Compose f ElField '(Fst t, Snd t) -> Text)
-> Compose f ElField '(Fst t, Snd t)
-> Const Text '(Fst t, Snd t)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. f (Snd t) -> Text
toText (f (Snd t) -> Text)
-> (Compose f ElField '(Fst t, Snd t) -> f (Snd t))
-> Compose f ElField '(Fst t, Snd t)
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ElField '(Fst t, Snd t) -> Snd t)
-> f (ElField '(Fst t, Snd t)) -> f (Snd t)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ElField '(Fst t, Snd t) -> Snd t
forall (s :: Symbol) t. ElField '(s, t) -> t
Vinyl.getField (f (ElField '(Fst t, Snd t)) -> f (Snd t))
-> (Compose f ElField '(Fst t, Snd t)
    -> f (ElField '(Fst t, Snd t)))
-> Compose f ElField '(Fst t, Snd t)
-> f (Snd t)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Compose f ElField '(Fst t, Snd t) -> f (ElField '(Fst t, Snd t))
forall l (f :: l -> *) k (g :: k -> l) (x :: k).
Compose f g x -> f (g x)
Vinyl.getCompose
{-# INLINEABLE liftFieldFormatter1 #-}

-- | Format a @Text@ field as-is.
formatTextAsIs :: (Vinyl.KnownField t, Vinyl.Snd t ~ T.Text) => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
formatTextAsIs :: Lift (->) ElField (Const Text) t
formatTextAsIs = (Snd t -> Text) -> Lift (->) ElField (Const Text) t
forall (t :: (Symbol, *)).
KnownField t =>
(Snd t -> Text) -> Lift (->) ElField (Const Text) t
liftFieldFormatter Snd t -> Text
forall a. a -> a
id
{-# INLINE formatTextAsIs #-}

-- | Format a field using the @Show@ instance of the contained type 
formatWithShow :: (Vinyl.KnownField t, Show (Vinyl.Snd t)) => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
formatWithShow :: Lift (->) ElField (Const Text) t
formatWithShow = (Snd t -> Text) -> Lift (->) ElField (Const Text) t
forall (t :: (Symbol, *)).
KnownField t =>
(Snd t -> Text) -> Lift (->) ElField (Const Text) t
liftFieldFormatter ((Snd t -> Text) -> Lift (->) ElField (Const Text) t)
-> (Snd t -> Text) -> Lift (->) ElField (Const Text) t
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack (String -> Text) -> (Snd t -> String) -> Snd t -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Snd t -> String
forall a. Show a => a -> String
show
{-# INLINE formatWithShow #-}

-- | Format a field using the @Frames.ShowCSV@ instance of the contained type 
formatWithShowCSV :: (Vinyl.KnownField t, Frames.ShowCSV (Vinyl.Snd t)) => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
formatWithShowCSV :: Lift (->) ElField (Const Text) t
formatWithShowCSV = (Snd t -> Text) -> Lift (->) ElField (Const Text) t
forall (t :: (Symbol, *)).
KnownField t =>
(Snd t -> Text) -> Lift (->) ElField (Const Text) t
liftFieldFormatter Snd t -> Text
forall a. ShowCSV a => a -> Text
Frames.showCSV
{-# INLINE formatWithShowCSV #-}

-- NB: Uses some internal modules from Streamly.  Will have to change when they become stable
-- | write a stream of @Text@ to a file, one line per stream item.
writeLines' :: (Streamly.MonadAsync m, MonadCatch m, Streamly.IsStream t) => FilePath -> t m T.Text -> m ()
writeLines' :: String -> t m Text -> m ()
writeLines' String
fp t m Text
s = do
  Fold m Word8 () -> SerialT m Word8 -> m ()
forall (m :: * -> *) a b.
Monad m =>
Fold m a b -> SerialT m a -> m b
Streamly.fold (String -> Fold m Word8 ()
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
String -> Fold m Word8 ()
Streamly.File.write String
fp)
    (SerialT m Word8 -> m ()) -> SerialT m Word8 -> m ()
forall a b. (a -> b) -> a -> b
$ SerialT m Char -> SerialT m Word8
forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(Monad m, IsStream t) =>
t m Char -> t m Word8
Streamly.Unicode.encodeUtf8
    (SerialT m Char -> SerialT m Word8)
-> SerialT m Char -> SerialT m Word8
forall a b. (a -> b) -> a -> b
$ t m Char -> SerialT m Char
forall (t1 :: (* -> *) -> * -> *) (t2 :: (* -> *) -> * -> *)
       (m :: * -> *) a.
(IsStream t1, IsStream t2) =>
t1 m a -> t2 m a
Streamly.adapt
    (t m Char -> SerialT m Char) -> t m Char -> SerialT m Char
forall a b. (a -> b) -> a -> b
$ Unfold m String Char -> t m String -> t m Char
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
Unfold m a b -> t m a -> t m b
Streamly.concatUnfold Unfold m String Char
forall (m :: * -> *) a. Monad m => Unfold m [a] a
Streamly.Unfold.fromList
    (t m String -> t m Char) -> t m String -> t m Char
forall a b. (a -> b) -> a -> b
$ (Text -> String) -> t m Text -> t m String
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> b) -> t m a -> t m b
Streamly.map Text -> String
T.unpack
    (t m Text -> t m String) -> t m Text -> t m String
forall a b. (a -> b) -> a -> b
$ Text -> t m Text -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(IsStream t, MonadAsync m) =>
a -> t m a -> t m a
Streamly.intersperse Text
"\n" t m Text
s
{-# INLINEABLE writeLines' #-}

-- | write a stream of @Text@ to a file, one line per stream item.
-- | Monomorphised to serial streams for ease of use.
writeLines :: (Streamly.MonadAsync m, MonadCatch m) => FilePath -> Streamly.SerialT m T.Text -> m ()
writeLines :: String -> SerialT m Text -> m ()
writeLines = String -> SerialT m Text -> m ()
forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(MonadAsync m, MonadCatch m, IsStream t) =>
String -> t m Text -> m ()
writeLines'
{-# INLINE writeLines #-}

-- NB: Uses some internal modules from Streamly.  Will have to change when they become stable
-- | write a stream of @Records@ to a file, one line per @Record@.
-- Use the 'Frames.ShowCSV' class to format each field to @Text@
writeStreamSV
  ::  forall rs m t.
   ( Frames.ColumnHeaders rs
   , MonadCatch m
   , Vinyl.RecordToList rs
   , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
   , IsStream t
   , Streamly.MonadAsync m
   )
  => T.Text -- ^ column separator
  -> FilePath -- ^ path
  -> t m (Frames.Record rs) -- ^ stream of Records
  -> m ()
writeStreamSV :: Text -> String -> t m (Record rs) -> m ()
writeStreamSV Text
sep String
fp = String -> t m Text -> m ()
forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(MonadAsync m, MonadCatch m, IsStream t) =>
String -> t m Text -> m ()
writeLines' String
fp (t m Text -> m ())
-> (t m (Record rs) -> t m Text) -> t m (Record rs) -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> t m (Record rs) -> t m Text
forall (rs :: [(Symbol, *)]) (m :: * -> *)
       (t :: (* -> *) -> * -> *).
(ColumnHeaders rs, Monad m, RecordToList rs,
 RecMapMethod ShowCSV ElField rs, IsStream t) =>
Text -> t m (Record rs) -> t m Text
streamToSV Text
sep 
{-# INLINEABLE writeStreamSV #-}

-- | write a foldable of @Records@ to a file, one line per @Record@.
-- Use the 'Frames.ShowCSV' class to format each field to @Text@
writeSV
  ::  forall rs m f.
   ( Frames.ColumnHeaders rs
   , MonadCatch m
   , Vinyl.RecordToList rs
   , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
   , Streamly.MonadAsync m
   , Foldable f
   )
  => T.Text -- ^ column separator
  -> FilePath -- ^ file path
  -> f (Frames.Record rs) -- ^ Foldable of Records
  -> m ()
writeSV :: Text -> String -> f (Record rs) -> m ()
writeSV Text
sep String
fp = Text -> String -> AheadT m (Record rs) -> m ()
forall (rs :: [(Symbol, *)]) (m :: * -> *)
       (t :: (* -> *) -> * -> *).
(ColumnHeaders rs, MonadCatch m, RecordToList rs,
 RecMapMethod ShowCSV ElField rs, IsStream t, MonadAsync m) =>
Text -> String -> t m (Record rs) -> m ()
writeStreamSV Text
sep String
fp (AheadT m (Record rs) -> m ())
-> (f (Record rs) -> AheadT m (Record rs)) -> f (Record rs) -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a (m :: * -> *).
(IsStream AheadT, Foldable f) =>
f a -> AheadT m a
forall (t :: (* -> *) -> * -> *) (f :: * -> *) a (m :: * -> *).
(IsStream t, Foldable f) =>
f a -> t m a
Streamly.fromFoldable @Streamly.AheadT
{-# INLINEABLE writeSV #-}

-- | write a foldable of @Records@ to a file, one line per @Record@.
-- Use the 'Frames.ShowCSV' class to format each field to @Text@
writeCSV
  ::  forall rs m f.
   ( Frames.ColumnHeaders rs
   , MonadCatch m
   , Vinyl.RecordToList rs
   , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
   , Streamly.MonadAsync m
   , Foldable f
   )
  => FilePath -- ^ file path
  -> f (Frames.Record rs) -- ^ 'Foldable' of Records
  -> m ()
writeCSV :: String -> f (Record rs) -> m ()
writeCSV String
fp = Text -> String -> f (Record rs) -> m ()
forall (rs :: [(Symbol, *)]) (m :: * -> *) (f :: * -> *).
(ColumnHeaders rs, MonadCatch m, RecordToList rs,
 RecMapMethod ShowCSV ElField rs, MonadAsync m, Foldable f) =>
Text -> String -> f (Record rs) -> m ()
writeSV Text
"," String
fp 
{-# INLINEABLE writeCSV #-}

-- NB: Uses some internal modules from Streamly.  Will have to change when they become stable
-- | write a stream of @Records@ to a file, one line per @Record@.
-- Use the 'Show' class to format each field to @Text@
writeStreamSV_Show
  ::  forall rs m t.
   ( Frames.ColumnHeaders rs
   , MonadCatch m
   , Vinyl.RecordToList rs
   , Vinyl.RecMapMethod Show Vinyl.ElField rs
   , IsStream t
   , Streamly.MonadAsync m
   )
  => T.Text -- ^ column separator
  -> FilePath -- ^ file path
  -> t m (Frames.Record rs) -- ^ stream of Records
  -> m ()
writeStreamSV_Show :: Text -> String -> t m (Record rs) -> m ()
writeStreamSV_Show Text
sep String
fp = String -> t m Text -> m ()
forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(MonadAsync m, MonadCatch m, IsStream t) =>
String -> t m Text -> m ()
writeLines' String
fp (t m Text -> m ())
-> (t m (Record rs) -> t m Text) -> t m (Record rs) -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a. Show a => a -> Text)
-> Text -> t m (Record rs) -> t m Text
forall (c :: * -> Constraint) (rs :: [(Symbol, *)])
       (t :: (* -> *) -> * -> *) (m :: * -> *).
(RecMapMethod c ElField rs, RecordToList rs, ColumnHeaders rs,
 IsStream t, Monad m) =>
(forall a. c a => a -> Text) -> Text -> t m (Record rs) -> t m Text
streamSVClass @Show (String -> Text
T.pack (String -> Text) -> (a -> String) -> a -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show) Text
sep
{-# INLINEABLE writeStreamSV_Show #-}

-- | write a foldable of @Records@ to a file, one line per @Record@.
-- Use the 'Show' class to format each field to @Text@
writeSV_Show
  ::  forall rs m f.
   ( Frames.ColumnHeaders rs
   , MonadCatch m
   , Vinyl.RecordToList rs
   , Vinyl.RecMapMethod Show Vinyl.ElField rs
   , Streamly.MonadAsync m
   , Foldable f
   )
  => T.Text -- ^ column separator
  -> FilePath  -- ^ file path
  -> f (Frames.Record rs) -- ^ 'Foldable' of Records
  -> m ()
writeSV_Show :: Text -> String -> f (Record rs) -> m ()
writeSV_Show Text
sep String
fp = Text -> String -> AheadT m (Record rs) -> m ()
forall (rs :: [(Symbol, *)]) (m :: * -> *)
       (t :: (* -> *) -> * -> *).
(ColumnHeaders rs, MonadCatch m, RecordToList rs,
 RecMapMethod Show ElField rs, IsStream t, MonadAsync m) =>
Text -> String -> t m (Record rs) -> m ()
writeStreamSV_Show Text
sep String
fp (AheadT m (Record rs) -> m ())
-> (f (Record rs) -> AheadT m (Record rs)) -> f (Record rs) -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a (m :: * -> *).
(IsStream AheadT, Foldable f) =>
f a -> AheadT m a
forall (t :: (* -> *) -> * -> *) (f :: * -> *) a (m :: * -> *).
(IsStream t, Foldable f) =>
f a -> t m a
Streamly.fromFoldable @Streamly.AheadT
{-# INLINEABLE writeSV_Show #-}

-- | write a foldable of @Records@ to a file, one line per @Record@.
-- Use the 'Show' class to format each field to @Text@
writeCSV_Show
  ::  forall rs m f.
   ( Frames.ColumnHeaders rs
   , MonadCatch m
   , Vinyl.RecordToList rs
   , Vinyl.RecMapMethod Show Vinyl.ElField rs
   , Streamly.MonadAsync m
   , Foldable f
   )
  => FilePath -- ^ file path
  -> f (Frames.Record rs) -- ^ 'Foldable' of Records
  -> m ()
writeCSV_Show :: String -> f (Record rs) -> m ()
writeCSV_Show String
fp = Text -> String -> f (Record rs) -> m ()
forall (rs :: [(Symbol, *)]) (m :: * -> *) (f :: * -> *).
(ColumnHeaders rs, MonadCatch m, RecordToList rs,
 RecMapMethod Show ElField rs, MonadAsync m, Foldable f) =>
Text -> String -> f (Record rs) -> m ()
writeSV_Show Text
"," String
fp 
{-# INLINEABLE writeCSV_Show #-}

-- Thanks to Tim Pierson for the functions below!

-- | Stream a table from a file path, using the default options.
-- Results composed with the @Maybe@ functor. Unparsed fields are returned as @Nothing@.
-- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
readTableMaybe
    :: forall rs t m.
    (MonadIO m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs
    , MonadCatch m)
    => FilePath -- ^ file path
    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Maybe :. ElField@ records after parsing.  
readTableMaybe :: String -> t m (Rec (Maybe :. ElField) rs)
readTableMaybe = ParserOptions -> String -> t m (Rec (Maybe :. ElField) rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(MonadIO m, IsStream t, RMap rs, ReadRec rs, MonadCatch m) =>
ParserOptions -> String -> t m (Rec (Maybe :. ElField) rs)
readTableMaybeOpt ParserOptions
Frames.defaultParser
{-# INLINEABLE readTableMaybe #-}

-- | Stream a table from a file path.
-- Results composed with the @Maybe@ functor. Unparsed fields are returned as @Nothing@.
-- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
readTableMaybeOpt
    :: forall rs t m.
    (MonadIO m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs
    , MonadCatch m)
    => Frames.ParserOptions -- ^ parsing options
    -> FilePath -- ^ file path
    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Maybe :. ElField@ records after parsing. 
readTableMaybeOpt :: ParserOptions -> String -> t m (Rec (Maybe :. ElField) rs)
readTableMaybeOpt ParserOptions
opts = (Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs)
-> t m (Rec (Either Text :. ElField) rs)
-> t m (Rec (Maybe :. ElField) rs)
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> b) -> t m a -> t m b
Streamly.map Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
forall (rs :: [(Symbol, *)]).
RMap rs =>
Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
recEitherToMaybe (t m (Rec (Either Text :. ElField) rs)
 -> t m (Rec (Maybe :. ElField) rs))
-> (String -> t m (Rec (Either Text :. ElField) rs))
-> String
-> t m (Rec (Maybe :. ElField) rs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ParserOptions -> String -> t m (Rec (Either Text :. ElField) rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(MonadIO m, IsStream t, RMap rs, ReadRec rs, MonadCatch m) =>
ParserOptions -> String -> t m (Rec (Either Text :. ElField) rs)
readTableEitherOpt ParserOptions
opts
{-# INLINEABLE readTableMaybeOpt #-}

-- | Stream a table from a file path.
-- Results composed with the @Either Text@ functor. Unparsed fields are returned as a @Left@
-- containing the string that failed to parse.
-- Uses default options.
-- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
readTableEither
  :: forall rs t m.
     (MonadIO m
     , IsStream t
     , Vinyl.RMap rs
     , Frames.ReadRec rs
     , MonadCatch m)
  => FilePath -- ^ file path
  -> t m (Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Either :. ElField@ records after parsing. 
readTableEither :: String -> t m (Rec (Either Text :. ElField) rs)
readTableEither = ParserOptions -> String -> t m (Rec (Either Text :. ElField) rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(MonadIO m, IsStream t, RMap rs, ReadRec rs, MonadCatch m) =>
ParserOptions -> String -> t m (Rec (Either Text :. ElField) rs)
readTableEitherOpt ParserOptions
Frames.defaultParser

-- | Stream a table from a file path.
-- Results composed with the @Either Text@ functor. Unparsed fields are returned as a @Left@
-- containing the string that failed to parse.
-- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
readTableEitherOpt
  :: forall rs t m.
     (MonadIO m
     , IsStream t
     , Vinyl.RMap rs
     , Frames.ReadRec rs
     , MonadCatch m)
  => Frames.ParserOptions -- ^ parsing options
  -> FilePath -- ^ file path
  -> t m (Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Either :. ElField@ records after parsing. 
readTableEitherOpt :: ParserOptions -> String -> t m (Rec (Either Text :. ElField) rs)
readTableEitherOpt ParserOptions
opts = ParserOptions -> t m Text -> t m (Rec (Either Text :. ElField) rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(Monad m, IsStream t, RMap rs, ReadRec rs) =>
ParserOptions -> t m Text -> t m (Rec (Either Text :. ElField) rs)
streamTableEitherOpt ParserOptions
opts (t m Text -> t m (Rec (Either Text :. ElField) rs))
-> (String -> t m Text)
-> String
-> t m (Rec (Either Text :. ElField) rs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. t m Word8 -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *).
(IsStream t, Monad m) =>
t m Word8 -> t m Text
word8ToTextLines (t m Word8 -> t m Text)
-> (String -> t m Word8) -> String -> t m Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> t m Word8
forall (t :: (* -> *) -> * -> *) (m :: * -> *).
(IsStream t, MonadCatch m, MonadIO m) =>
String -> t m Word8
Streamly.File.toBytes 
{-# INLINEABLE readTableEitherOpt #-}


-- | Stream Table from a file path, dropping rows where any field fails to parse
-- | Use default options
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
readTable
  :: forall rs t m.
     (MonadIO m
     , IsStream t
     , Vinyl.RMap rs
     , Frames.ReadRec rs
     , MonadCatch m)
  => FilePath -- ^ file path
  -> t m (Frames.Record rs) -- ^ stream of Records
readTable :: String -> t m (Record rs)
readTable = ParserOptions -> String -> t m (Record rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(MonadIO m, IsStream t, RMap rs, ReadRec rs, MonadCatch m) =>
ParserOptions -> String -> t m (Record rs)
readTableOpt ParserOptions
Frames.defaultParser
{-# INLINEABLE readTable #-}

-- | Stream Table from a file path, dropping rows where any field fails to parse
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
readTableOpt
  :: forall rs t m.
     (MonadIO m
     , IsStream t
     , Vinyl.RMap rs
     , Frames.ReadRec rs
     , MonadCatch m)
  => Frames.ParserOptions  -- ^ parsing options
  -> FilePath -- ^ file path
  -> t m (Frames.Record rs)  -- ^ stream of Records
readTableOpt :: ParserOptions -> String -> t m (Record rs)
readTableOpt ParserOptions
opts = ParserOptions -> t m Text -> t m (Record rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(Monad m, IsStream t, RMap rs, ReadRec rs) =>
ParserOptions -> t m Text -> t m (Record rs)
streamTableOpt ParserOptions
opts (t m Text -> t m (Record rs))
-> (String -> t m Text) -> String -> t m (Record rs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. t m Word8 -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *).
(IsStream t, Monad m) =>
t m Word8 -> t m Text
word8ToTextLines (t m Word8 -> t m Text)
-> (String -> t m Word8) -> String -> t m Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> t m Word8
forall (t :: (* -> *) -> * -> *) (m :: * -> *).
(IsStream t, MonadCatch m, MonadIO m) =>
String -> t m Word8
Streamly.File.toBytes 
{-# INLINEABLE readTableOpt #-}

-- | Convert a stream of lines of `Text` to a table
-- Each field is returned in an @Either Text@ functor. @Right a@ for successful parses
-- and @Left Text@ when parsing fails, containing the text that failed to Parse.
--
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
streamTableEither
    :: forall rs t m.
    (Monad m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs)
    => t m T.Text -- ^ stream of 'Text' rows
    -> t m (Vinyl.Rec ((Either T.Text) Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Either :. ElField@ rows
streamTableEither :: t m Text -> t m (Rec (Either Text :. ElField) rs)
streamTableEither = ParserOptions -> t m Text -> t m (Rec (Either Text :. ElField) rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(Monad m, IsStream t, RMap rs, ReadRec rs) =>
ParserOptions -> t m Text -> t m (Rec (Either Text :. ElField) rs)
streamTableEitherOpt ParserOptions
Frames.defaultParser
{-# INLINEABLE streamTableEither #-}

-- | Convert a stream of lines of `Text` to records.
-- Each field is returned in an @Either Text@ functor. @Right a@ for successful parses
-- and @Left Text@ when parsing fails, containing the text that failed to Parse.
--
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will..go awry.
streamTableEitherOpt
    :: forall rs t m.
    (Monad m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs)
    => Frames.ParserOptions -- ^ parsing options
    -> t m T.Text -- ^ stream of 'Text' rows
    -> t m (Vinyl.Rec ((Either T.Text) Vinyl.:. Vinyl.ElField) rs)  -- ^ stream of parsed @Either :. ElField@ rows
streamTableEitherOpt :: ParserOptions -> t m Text -> t m (Rec (Either Text :. ElField) rs)
streamTableEitherOpt ParserOptions
opts =
    (Text -> Rec (Either Text :. ElField) rs)
-> t m Text -> t m (Rec (Either Text :. ElField) rs)
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> b) -> t m a -> t m b
Streamly.map ([Text] -> Rec (Either Text :. ElField) rs
doParse ([Text] -> Rec (Either Text :. ElField) rs)
-> (Text -> [Text]) -> Text -> Rec (Either Text :. ElField) rs
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ParserOptions -> Text -> [Text]
Frames.tokenizeRow ParserOptions
opts)
    (t m Text -> t m (Rec (Either Text :. ElField) rs))
-> (t m Text -> t m Text)
-> t m Text
-> t m (Rec (Either Text :. ElField) rs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. t m Text -> t m Text
handleHeader
  where
    handleHeader :: t m Text -> t m Text
handleHeader | Maybe [Text] -> Bool
forall a. Maybe a -> Bool
isNothing (ParserOptions -> Maybe [Text]
Frames.headerOverride ParserOptions
opts) = Int -> t m Text -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(IsStream t, Monad m) =>
Int -> t m a -> t m a
Streamly.drop Int
1
                 | Bool
otherwise                       = t m Text -> t m Text
forall a. a -> a
id
    doParse :: [Text] -> Rec (Either Text :. ElField) rs
doParse = [Text] -> Rec (Either Text :. ElField) rs
forall (rs :: [(Symbol, *)]).
ReadRec rs =>
[Text] -> Rec (Either Text :. ElField) rs
Frames.readRec    
{-# INLINEABLE streamTableEitherOpt #-}

-- | Convert a stream of lines of `Text` to a table.
--
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will..go awry.
streamTableMaybe
    :: forall rs t m.
    (Monad m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs)
    => t m T.Text -- ^ stream of 'Text' rows 
    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Maybe :. ElField@ rows
streamTableMaybe :: t m Text -> t m (Rec (Maybe :. ElField) rs)
streamTableMaybe = ParserOptions -> t m Text -> t m (Rec (Maybe :. ElField) rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(Monad m, IsStream t, RMap rs, ReadRec rs) =>
ParserOptions -> t m Text -> t m (Rec (Maybe :. ElField) rs)
streamTableMaybeOpt ParserOptions
Frames.defaultParser 
{-# INLINEABLE streamTableMaybe #-}

-- | Convert a stream of lines of Text to a table .
--
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will..go awry.
streamTableMaybeOpt
    :: forall rs t m.
    (Monad m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs)
    => Frames.ParserOptions -- ^ parsing options
    -> t m T.Text -- ^ stream of 'Text' rows
    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Maybe :. ElField@ rows
streamTableMaybeOpt :: ParserOptions -> t m Text -> t m (Rec (Maybe :. ElField) rs)
streamTableMaybeOpt ParserOptions
opts = (Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs)
-> t m (Rec (Either Text :. ElField) rs)
-> t m (Rec (Maybe :. ElField) rs)
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> b) -> t m a -> t m b
Streamly.map Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
forall (rs :: [(Symbol, *)]).
RMap rs =>
Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
recEitherToMaybe (t m (Rec (Either Text :. ElField) rs)
 -> t m (Rec (Maybe :. ElField) rs))
-> (t m Text -> t m (Rec (Either Text :. ElField) rs))
-> t m Text
-> t m (Rec (Maybe :. ElField) rs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ParserOptions -> t m Text -> t m (Rec (Either Text :. ElField) rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(Monad m, IsStream t, RMap rs, ReadRec rs) =>
ParserOptions -> t m Text -> t m (Rec (Either Text :. ElField) rs)
streamTableEitherOpt ParserOptions
opts
{-# INLINEABLE streamTableMaybeOpt #-}

-- | Convert a stream of lines of 'Text' to a table,
-- dropping rows where any field fails to parse.
-- Use default options.
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
streamTable
    :: forall rs t m.
    (Monad m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs
    )
    => t m T.Text -- ^ stream of 'Text' rows
    -> t m (Frames.Record rs) -- ^ stream of Records
streamTable :: t m Text -> t m (Record rs)
streamTable = ParserOptions -> t m Text -> t m (Record rs)
forall (rs :: [(Symbol, *)]) (t :: (* -> *) -> * -> *)
       (m :: * -> *).
(Monad m, IsStream t, RMap rs, ReadRec rs) =>
ParserOptions -> t m Text -> t m (Record rs)
streamTableOpt ParserOptions
Frames.defaultParser
{-# INLINEABLE streamTable #-}

-- | Convert a stream of lines of 'Text' `Word8` to a table,
-- dropping rows where any field fails to parse.
-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
streamTableOpt
    :: forall rs t m.
    (Monad m
    , IsStream t
    , Vinyl.RMap rs
    , Frames.ReadRec rs
    )
    => Frames.ParserOptions -- ^ parsing options
    -> t m T.Text  -- ^ stream of 'Text' rows
    -> t m (Frames.Record rs) -- ^ stream of Records
streamTableOpt :: ParserOptions -> t m Text -> t m (Record rs)
streamTableOpt ParserOptions
opts =
    (Text -> Maybe (Record rs)) -> t m Text -> t m (Record rs)
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> Maybe b) -> t m a -> t m b
Streamly.mapMaybe (Rec (Maybe :. ElField) rs -> Maybe (Record rs)
forall (cs :: [(Symbol, *)]).
Rec (Maybe :. ElField) cs -> Maybe (Record cs)
Frames.recMaybe (Rec (Maybe :. ElField) rs -> Maybe (Record rs))
-> (Text -> Rec (Maybe :. ElField) rs) -> Text -> Maybe (Record rs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> Rec (Maybe :. ElField) rs
doParse ([Text] -> Rec (Maybe :. ElField) rs)
-> (Text -> [Text]) -> Text -> Rec (Maybe :. ElField) rs
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ParserOptions -> Text -> [Text]
Frames.tokenizeRow ParserOptions
opts)
    (t m Text -> t m (Record rs))
-> (t m Text -> t m Text) -> t m Text -> t m (Record rs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. t m Text -> t m Text
handleHeader    
  where
    handleHeader :: t m Text -> t m Text
handleHeader | Maybe [Text] -> Bool
forall a. Maybe a -> Bool
isNothing (ParserOptions -> Maybe [Text]
Frames.headerOverride ParserOptions
opts) = Int -> t m Text -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(IsStream t, Monad m) =>
Int -> t m a -> t m a
Streamly.drop Int
1
                 | Bool
otherwise                       = t m Text -> t m Text
forall a. a -> a
id
    doParse :: [Text] -> Rec (Maybe :. ElField) rs
doParse = Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
forall (rs :: [(Symbol, *)]).
RMap rs =>
Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
recEitherToMaybe (Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs)
-> ([Text] -> Rec (Either Text :. ElField) rs)
-> [Text]
-> Rec (Maybe :. ElField) rs
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> Rec (Either Text :. ElField) rs
forall (rs :: [(Symbol, *)]).
ReadRec rs =>
[Text] -> Rec (Either Text :. ElField) rs
Frames.readRec
{-# INLINE streamTableOpt #-}

recEitherToMaybe :: Vinyl.RMap rs => Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs -> Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs
recEitherToMaybe :: Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
recEitherToMaybe = (forall (x :: (Symbol, *)).
 (:.) (Either Text) ElField x -> (:.) Maybe ElField x)
-> Rec (Either Text :. ElField) rs -> Rec (Maybe :. ElField) rs
forall u (rs :: [u]) (f :: u -> *) (g :: u -> *).
RMap rs =>
(forall (x :: u). f x -> g x) -> Rec f rs -> Rec g rs
Vinyl.rmap ((Text -> Compose Maybe ElField x)
-> (ElField x -> Compose Maybe ElField x)
-> Either Text (ElField x)
-> Compose Maybe ElField x
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Compose Maybe ElField x -> Text -> Compose Maybe ElField x
forall a b. a -> b -> a
const (Maybe (ElField x) -> Compose Maybe ElField x
forall l k (f :: l -> *) (g :: k -> l) (x :: k).
f (g x) -> Compose f g x
Vinyl.Compose Maybe (ElField x)
forall a. Maybe a
Nothing)) (Maybe (ElField x) -> Compose Maybe ElField x
forall l k (f :: l -> *) (g :: k -> l) (x :: k).
f (g x) -> Compose f g x
Vinyl.Compose (Maybe (ElField x) -> Compose Maybe ElField x)
-> (ElField x -> Maybe (ElField x))
-> ElField x
-> Compose Maybe ElField x
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ElField x -> Maybe (ElField x)
forall a. a -> Maybe a
Just) (Either Text (ElField x) -> Compose Maybe ElField x)
-> (Compose (Either Text) ElField x -> Either Text (ElField x))
-> Compose (Either Text) ElField x
-> Compose Maybe ElField x
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Compose (Either Text) ElField x -> Either Text (ElField x)
forall l (f :: l -> *) k (g :: k -> l) (x :: k).
Compose f g x -> f (g x)
Vinyl.getCompose)
{-# INLINE recEitherToMaybe #-}

-- | Convert a stream of Word8 to lines of `Text` by decoding as UTF8 and splitting on "\n"
word8ToTextLines :: (IsStream t, Monad m) => t m Word8 -> t m T.Text
word8ToTextLines :: t m Word8 -> t m Text
word8ToTextLines =  (Char -> Bool) -> Fold m Char Text -> t m Char -> t m Text
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
(a -> Bool) -> Fold m a b -> t m a -> t m b
Streamly.splitOnSuffix (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\n') ((String -> Text) -> Fold m Char String -> Fold m Char Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> Text
T.pack (Fold m Char String -> Fold m Char Text)
-> Fold m Char String -> Fold m Char Text
forall a b. (a -> b) -> a -> b
$ Fold m Char String
forall (m :: * -> *) a. Monad m => Fold m a [a]
Streamly.Fold.toList)
                    (t m Char -> t m Text)
-> (t m Word8 -> t m Char) -> t m Word8 -> t m Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. t m Word8 -> t m Char
forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(Monad m, IsStream t) =>
t m Word8 -> t m Char
Streamly.Unicode.decodeUtf8
{-# INLINE word8ToTextLines #-}


-- tracing fold
{-
runningCountF :: MonadIO m => T.Text -> (Int -> T.Text) -> T.Text -> Streamly.Fold.Fold m a ()
runningCountF startMsg countMsg endMsg = Streamly.Fold.Fold step start done where
  start = liftIO (T.putStr startMsg) >> return 0
  step !n _ = liftIO $ do
    t <- System.Clock.getTime System.Clock.ProcessCPUTime
    putStr $ show t ++ ": "
    T.putStrLn $ countMsg n
    return (n+1)
  done _ = liftIO $ T.putStrLn endMsg
-}