-- | -- Module : Foundation.Format.CSV.Builder -- License : BSD-style -- Maintainer : Foundation -- Stability : experimental -- Portability : portable -- -- Provies the support for Comma Separated Value {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-} module Foundation.Format.CSV.Builder ( -- * String Bulider csvStringBuilder , rowStringBuilder , fieldStringBuilder -- * Block Builder , csvBlockBuilder , rowBlockBuilder , fieldBlockBuilder -- * Conduit , rowC ) where import Basement.Imports import Basement.String (replace) import Foundation.Collection.Sequential (Sequential(intersperse)) import Foundation.Conduit.Internal import qualified Foundation.String.Builder as String import Basement.Block (Block) import qualified Basement.Block.Builder as Block import GHC.ST (runST) import Foundation.Format.CSV.Types -- | serialise the CSV document into a UTF8 string csvStringBuilder :: CSV -> String.Builder csvStringBuilder = String.unsafeStringBuilder . csvBlockBuilder rowStringBuilder :: Row -> String.Builder rowStringBuilder = String.unsafeStringBuilder . rowBlockBuilder fieldStringBuilder :: Field -> String.Builder fieldStringBuilder = String.unsafeStringBuilder . fieldBlockBuilder -- | serialise the CSV document into a UTF8 encoded (Block Word8) csvBlockBuilder :: CSV -> Block.Builder csvBlockBuilder = mconcat . intersperse (Block.emitString "\r\n") . fmap rowBlockBuilder . toList . unCSV rowBlockBuilder :: Row -> Block.Builder rowBlockBuilder = mconcat . intersperse (Block.emitUTF8Char ',') . fmap fieldBlockBuilder . toList . unRow fieldBlockBuilder :: Field -> Block.Builder fieldBlockBuilder (FieldInteger i) = Block.emitString $ show i fieldBlockBuilder (FieldDouble d) = Block.emitString $ show d fieldBlockBuilder (FieldString s e) = case e of NoEscape -> Block.emitString s Escape -> Block.emitUTF8Char '"' <> Block.emitString s <> Block.emitUTF8Char '"' DoubleEscape -> Block.emitUTF8Char '"' <> Block.emitString (replace "\"" "\"\"" s) <> Block.emitUTF8Char '"' rowC :: (Record row, Monad m) => Conduit row (Block Word8) m () rowC = await >>= go where go Nothing = pure () go (Just r) = let bytes = runST (Block.run $ rowBlockBuilder (toRow r) <> Block.emitString "\r\n") in yield bytes >> await >>= go