{-# LANGUAGE GeneralisedNewtypeDeriving #-}

-- | This module provides functionality for helping writing data values as CSV entries.
--
-- A couple of 'db-analyzer` analysis produce CSV files, which contain
-- the analysis' results. A way to populate these files is to write
-- the headers first, and then, line by line, write the rows that
-- contain the data. Each column in a row containing data must
-- correspond to a given header. To make it easier to maintain this
-- correspondence between headers and data, we usually specify a CSV
-- builder as:
--
-- > [(Builder, a -> IO Builder)]
--
-- where each first component of each tuple in the list represents a
-- header, and each second component determines how the value that
-- corresponds to that header is computed, given a certain value that
-- is needed to compute a row in the resulting CSV.
--
-- We use 'Text.Builder' to efficiently intercalate values with the CSV 'Separator'.
--
module Cardano.Tools.DBAnalyser.CSV (
    Separator (Separator, unSeparator)
  , computeAndWriteLine
  , computeAndWriteLinePure
  , computeColumns
  , computeColumnsPure
  , writeHeaderLine
  , writeLine
  ) where

import           Data.String (IsString)
import qualified Data.Text.IO as Text.IO
import qualified System.IO as IO
import qualified Text.Builder as Builder
import           Text.Builder (Builder)

newtype Separator = Separator { Separator -> Builder
unSeparator :: Builder }
  deriving (Int -> Separator -> ShowS
[Separator] -> ShowS
Separator -> String
(Int -> Separator -> ShowS)
-> (Separator -> String)
-> ([Separator] -> ShowS)
-> Show Separator
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Separator -> ShowS
showsPrec :: Int -> Separator -> ShowS
$cshow :: Separator -> String
show :: Separator -> String
$cshowList :: [Separator] -> ShowS
showList :: [Separator] -> ShowS
Show, String -> Separator
(String -> Separator) -> IsString Separator
forall a. (String -> a) -> IsString a
$cfromString :: String -> Separator
fromString :: String -> Separator
IsString, Semigroup Separator
Separator
Semigroup Separator =>
Separator
-> (Separator -> Separator -> Separator)
-> ([Separator] -> Separator)
-> Monoid Separator
[Separator] -> Separator
Separator -> Separator -> Separator
forall a.
Semigroup a =>
a -> (a -> a -> a) -> ([a] -> a) -> Monoid a
$cmempty :: Separator
mempty :: Separator
$cmappend :: Separator -> Separator -> Separator
mappend :: Separator -> Separator -> Separator
$cmconcat :: [Separator] -> Separator
mconcat :: [Separator] -> Separator
Monoid, NonEmpty Separator -> Separator
Separator -> Separator -> Separator
(Separator -> Separator -> Separator)
-> (NonEmpty Separator -> Separator)
-> (forall b. Integral b => b -> Separator -> Separator)
-> Semigroup Separator
forall b. Integral b => b -> Separator -> Separator
forall a.
(a -> a -> a)
-> (NonEmpty a -> a)
-> (forall b. Integral b => b -> a -> a)
-> Semigroup a
$c<> :: Separator -> Separator -> Separator
<> :: Separator -> Separator -> Separator
$csconcat :: NonEmpty Separator -> Separator
sconcat :: NonEmpty Separator -> Separator
$cstimes :: forall b. Integral b => b -> Separator -> Separator
stimes :: forall b. Integral b => b -> Separator -> Separator
Semigroup)

writeHeaderLine :: IO.Handle -> Separator -> [(Builder, a)] -> IO ()
writeHeaderLine :: forall a. Handle -> Separator -> [(Builder, a)] -> IO ()
writeHeaderLine Handle
handle (Separator Builder
separator) =
      Handle -> Text -> IO ()
Text.IO.hPutStrLn Handle
handle
    (Text -> IO ())
-> ([(Builder, a)] -> Text) -> [(Builder, a)] -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
Builder.run
    (Builder -> Text)
-> ([(Builder, a)] -> Builder) -> [(Builder, a)] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> [Builder] -> Builder
forall (foldable :: * -> *).
Foldable foldable =>
Builder -> foldable Builder -> Builder
Builder.intercalate Builder
separator
    ([Builder] -> Builder)
-> ([(Builder, a)] -> [Builder]) -> [(Builder, a)] -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Builder, a) -> Builder) -> [(Builder, a)] -> [Builder]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Builder, a) -> Builder
forall a b. (a, b) -> a
fst

writeLine :: IO.Handle -> Separator -> [Builder] -> IO ()
writeLine :: Handle -> Separator -> [Builder] -> IO ()
writeLine Handle
handle (Separator Builder
separator) =
      Handle -> Text -> IO ()
Text.IO.hPutStrLn Handle
handle
    (Text -> IO ()) -> ([Builder] -> Text) -> [Builder] -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
Builder.run
    (Builder -> Text) -> ([Builder] -> Builder) -> [Builder] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> [Builder] -> Builder
forall (foldable :: * -> *).
Foldable foldable =>
Builder -> foldable Builder -> Builder
Builder.intercalate Builder
separator

computeAndWriteLine :: IO.Handle -> Separator -> [(a, b -> IO Builder)] -> b -> IO ()
computeAndWriteLine :: forall a b.
Handle -> Separator -> [(a, b -> IO Builder)] -> b -> IO ()
computeAndWriteLine Handle
handle Separator
separator [(a, b -> IO Builder)]
csvBuilder b
b = do
  [b -> IO Builder] -> b -> IO [Builder]
forall a. [a -> IO Builder] -> a -> IO [Builder]
computeColumns (((a, b -> IO Builder) -> b -> IO Builder)
-> [(a, b -> IO Builder)] -> [b -> IO Builder]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (a, b -> IO Builder) -> b -> IO Builder
forall a b. (a, b) -> b
snd [(a, b -> IO Builder)]
csvBuilder) b
b IO [Builder] -> ([Builder] -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Handle -> Separator -> [Builder] -> IO ()
writeLine Handle
handle Separator
separator

computeAndWriteLinePure :: IO.Handle -> Separator -> [(a, b -> Builder)] -> b -> IO ()
computeAndWriteLinePure :: forall a b.
Handle -> Separator -> [(a, b -> Builder)] -> b -> IO ()
computeAndWriteLinePure Handle
handle Separator
separator [(a, b -> Builder)]
csvBuilder b
b =
    Handle -> Separator -> [Builder] -> IO ()
writeLine Handle
handle Separator
separator ([Builder] -> IO ()) -> [Builder] -> IO ()
forall a b. (a -> b) -> a -> b
$ [b -> Builder] -> b -> [Builder]
forall a. [a -> Builder] -> a -> [Builder]
computeColumnsPure (((a, b -> Builder) -> b -> Builder)
-> [(a, b -> Builder)] -> [b -> Builder]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (a, b -> Builder) -> b -> Builder
forall a b. (a, b) -> b
snd [(a, b -> Builder)]
csvBuilder) b
b

computeColumns :: [a -> IO Builder] -> a -> IO [Builder]
computeColumns :: forall a. [a -> IO Builder] -> a -> IO [Builder]
computeColumns [a -> IO Builder]
fBuilders a
a =
  [IO Builder] -> IO [Builder]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
forall (m :: * -> *) a. Monad m => [m a] -> m [a]
sequence ([IO Builder] -> IO [Builder]) -> [IO Builder] -> IO [Builder]
forall a b. (a -> b) -> a -> b
$ ((a -> IO Builder) -> IO Builder)
-> [a -> IO Builder] -> [IO Builder]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((a -> IO Builder) -> a -> IO Builder
forall a b. (a -> b) -> a -> b
$ a
a) [a -> IO Builder]
fBuilders

computeColumnsPure :: [a -> Builder] -> a -> [Builder]
computeColumnsPure :: forall a. [a -> Builder] -> a -> [Builder]
computeColumnsPure [a -> Builder]
fBuilders a
a =
  ((a -> Builder) -> Builder) -> [a -> Builder] -> [Builder]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((a -> Builder) -> a -> Builder
forall a b. (a -> b) -> a -> b
$ a
a) [a -> Builder]
fBuilders