{-# LANGUAGE RankNTypes #-}
{-# language DeriveGeneric #-}
{-# language OverloadedStrings #-}
-- | JSON Lines https://jsonlines.org/
module JSONL (
  -- * Encode
  jsonlWriteFile, jsonlToLBS
  , jsonlBuilder
  , jsonLine
  -- * Decode
 , jsonlFromLBS
  ) where

import System.IO (Handle, IOMode(..), withBinaryFile)

-- aeson
import Data.Aeson (ToJSON(..), FromJSON(..), encode, eitherDecode' )
-- bytestring
import qualified Data.ByteString.Builder as BBS (toLazyByteString, Builder, lazyByteString, string7)
import qualified Data.ByteString.Builder.Internal as BBS (hPut, putBuilder)
import qualified Data.ByteString.Internal as BS (c2w)
import qualified Data.ByteString.Lazy as LBS (ByteString, span)

import Prelude hiding (writeFile)

-- | Parse a JSONL-encoded collection of objects from a `LBS.ByteString`
--
-- If parsing fails, returns the first parsing error in a Left
jsonlFromLBS :: FromJSON a => LBS.ByteString -> Either String [a]
jsonlFromLBS :: ByteString -> Either String [a]
jsonlFromLBS = [Either String a] -> Either String [a]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence ([Either String a] -> Either String [a])
-> (ByteString -> [Either String a])
-> ByteString
-> Either String [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [Either String a]
forall a. FromJSON a => ByteString -> [Either String a]
jsonlFromLBS_

jsonlFromLBS_ :: FromJSON a => LBS.ByteString -> [Either String a]
jsonlFromLBS_ :: ByteString -> [Either String a]
jsonlFromLBS_ = [Either String a] -> ByteString -> [Either String a]
forall a.
FromJSON a =>
[Either String a] -> ByteString -> [Either String a]
go [Either String a]
forall a. Monoid a => a
mempty
  where
    go :: FromJSON a => [Either String a] -> LBS.ByteString -> [Either String a]
    go :: [Either String a] -> ByteString -> [Either String a]
go [Either String a]
acc ByteString
lbs = let
      (ByteString
s, ByteString
srest) = (Word8 -> Bool) -> ByteString -> (ByteString, ByteString)
LBS.span (Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Char -> Word8
BS.c2w Char
'\n') ByteString
lbs
      in [Either String a] -> ByteString -> [Either String a]
forall a.
FromJSON a =>
[Either String a] -> ByteString -> [Either String a]
go (ByteString -> Either String a
forall a. FromJSON a => ByteString -> Either String a
eitherDecode' ByteString
s Either String a -> [Either String a] -> [Either String a]
forall a. a -> [a] -> [a]
: [Either String a]
acc) ByteString
srest


-- | Write a collection of objects to a JSONL-encoded file
jsonlWriteFile :: (Foldable t, ToJSON a) => FilePath -> t a -> IO ()
jsonlWriteFile :: String -> t a -> IO ()
jsonlWriteFile String
fpath t a
xs = String -> Builder -> IO ()
writeFile String
fpath (t a -> Builder
forall (t :: * -> *) a. (Foldable t, ToJSON a) => t a -> Builder
jsonlBuilder t a
xs)

-- | Render a collection of objects to a JSONL-encoded `LBS.ByteString`
jsonlToLBS :: (Foldable t, ToJSON a) => t a -> LBS.ByteString
jsonlToLBS :: t a -> ByteString
jsonlToLBS t a
xs = Builder -> ByteString
BBS.toLazyByteString (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$ t a -> Builder
forall (t :: * -> *) a. (Foldable t, ToJSON a) => t a -> Builder
jsonlBuilder t a
xs 

jsonlBuilder :: (Foldable t, ToJSON a) => t a -> BBS.Builder
jsonlBuilder :: t a -> Builder
jsonlBuilder = (a -> Builder) -> t a -> Builder
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap a -> Builder
forall a. ToJSON a => a -> Builder
jsonLine

-- | Render a single JSONL line (together with its newline)
--
-- @since 0.2
jsonLine :: ToJSON a => a -> BBS.Builder
jsonLine :: a -> Builder
jsonLine a
x = a -> Builder
forall a. ToJSON a => a -> Builder
jsonToBuilder a
x Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> String -> Builder
BBS.string7 String
"\n"

jsonToBuilder :: ToJSON a => a -> BBS.Builder
jsonToBuilder :: a -> Builder
jsonToBuilder = ByteString -> Builder
BBS.lazyByteString (ByteString -> Builder) -> (a -> ByteString) -> a -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> ByteString
forall a. ToJSON a => a -> ByteString
encode


-- vendored from bytestring < 0.11.2

hPutBuilder :: Handle -> BBS.Builder -> IO ()
hPutBuilder :: Handle -> Builder -> IO ()
hPutBuilder Handle
h = Handle -> Put () -> IO ()
forall a. Handle -> Put a -> IO a
BBS.hPut Handle
h (Put () -> IO ()) -> (Builder -> Put ()) -> Builder -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Put ()
BBS.putBuilder

modifyFile :: IOMode -> FilePath -> BBS.Builder -> IO ()
modifyFile :: IOMode -> String -> Builder -> IO ()
modifyFile IOMode
mode String
f Builder
bld = String -> IOMode -> (Handle -> IO ()) -> IO ()
forall r. String -> IOMode -> (Handle -> IO r) -> IO r
withBinaryFile String
f IOMode
mode (Handle -> Builder -> IO ()
`hPutBuilder` Builder
bld)

writeFile :: FilePath -> BBS.Builder -> IO ()
writeFile :: String -> Builder -> IO ()
writeFile = IOMode -> String -> Builder -> IO ()
modifyFile IOMode
WriteMode