{-# LANGUAGE GeneralizedNewtypeDeriving #-} module RIO.Prelude.Display ( Utf8Builder (..) , Display (..) , displayShow , utf8BuilderToText , utf8BuilderToLazyText , displayBytesUtf8 , writeFileUtf8Builder ) where import Data.String (IsString (..)) import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Builder as BB import Data.ByteString.Builder (Builder) import Data.Semigroup (Semigroup(..)) import Data.Text (Text) import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL import UnliftIO import Data.Text.Encoding (decodeUtf8With, encodeUtf8Builder) import Data.Text.Encoding.Error (lenientDecode) import Data.Int import Data.Word import System.Process.Typed (ProcessConfig, setEnvInherit) -- | A builder of binary data, with the invariant that the underlying -- data is supposed to be UTF-8 encoded. -- -- @since 0.1.0.0 newtype Utf8Builder = Utf8Builder { getUtf8Builder :: Builder } deriving (Semigroup) -- Custom instance is created instead of deriving, otherwise list fusion breaks -- for `mconcat`. instance Monoid Utf8Builder where mempty = Utf8Builder mempty {-# INLINE mempty #-} mappend = (Data.Semigroup.<>) {-# INLINE mappend #-} mconcat = foldr mappend mempty {-# INLINE mconcat #-} -- | @since 0.1.0.0 instance IsString Utf8Builder where fromString = Utf8Builder . BB.stringUtf8 -- | A typeclass for values which can be converted to a -- 'Utf8Builder'. The intention of this typeclass is to provide a -- human-friendly display of the data. -- -- @since 0.1.0.0 class Display a where {-# MINIMAL display | textDisplay #-} display :: a -> Utf8Builder display = display . textDisplay -- | Display data as `Text`, which will also be used for `display` if it is -- not overriden. -- -- @since 0.1.7.0 textDisplay :: a -> Text textDisplay = utf8BuilderToText . display -- | @since 0.1.0.0 instance Display Utf8Builder where display = id -- | @since 0.1.0.0 instance Display Text where display = Utf8Builder . encodeUtf8Builder -- | @since 0.1.0.0 instance Display TL.Text where display = foldMap display . TL.toChunks -- | @since 0.1.0.0 instance Display Char where display = Utf8Builder . BB.charUtf8 -- | @since 0.1.0.0 instance Display Integer where display = Utf8Builder . BB.integerDec -- | @since 0.1.0.0 instance Display Float where display = Utf8Builder . BB.floatDec instance Display Double where display = Utf8Builder . BB.doubleDec -- | @since 0.1.0.0 instance Display Int where display = Utf8Builder . BB.intDec -- | @since 0.1.0.0 instance Display Int8 where display = Utf8Builder . BB.int8Dec -- | @since 0.1.0.0 instance Display Int16 where display = Utf8Builder . BB.int16Dec -- | @since 0.1.0.0 instance Display Int32 where display = Utf8Builder . BB.int32Dec -- | @since 0.1.0.0 instance Display Int64 where display = Utf8Builder . BB.int64Dec -- | @since 0.1.0.0 instance Display Word where display = Utf8Builder . BB.wordDec -- | @since 0.1.0.0 instance Display Word8 where display = Utf8Builder . BB.word8Dec -- | @since 0.1.0.0 instance Display Word16 where display = Utf8Builder . BB.word16Dec -- | @since 0.1.0.0 instance Display Word32 where display = Utf8Builder . BB.word32Dec -- | @since 0.1.0.0 instance Display Word64 where display = Utf8Builder . BB.word64Dec -- | @since 0.1.0.0 instance Display SomeException where display = fromString . displayException -- | @since 0.1.0.0 instance Display IOException where display = fromString . displayException -- | @since 0.1.0.0 instance Display (ProcessConfig a b c) where display = displayShow . setEnvInherit -- | Use the 'Show' instance for a value to convert it to a -- 'Utf8Builder'. -- -- @since 0.1.0.0 displayShow :: Show a => a -> Utf8Builder displayShow = fromString . show -- | Convert a 'ByteString' into a 'Utf8Builder'. -- -- /NOTE/ This function performs no checks to ensure that the data is, -- in fact, UTF8 encoded. If you provide non-UTF8 data, later -- functions may fail. -- -- @since 0.1.0.0 displayBytesUtf8 :: ByteString -> Utf8Builder displayBytesUtf8 = Utf8Builder . BB.byteString -- | Convert a 'Utf8Builder' value into a strict 'Text'. -- -- @since 0.1.0.0 utf8BuilderToText :: Utf8Builder -> Text utf8BuilderToText = decodeUtf8With lenientDecode . BL.toStrict . BB.toLazyByteString . getUtf8Builder -- | Convert a 'Utf8Builder' value into a lazy 'Text'. -- -- @since 0.1.0.0 utf8BuilderToLazyText :: Utf8Builder -> TL.Text utf8BuilderToLazyText = TL.decodeUtf8With lenientDecode . BB.toLazyByteString . getUtf8Builder -- | Write the given 'Utf8Builder' value to a file. -- -- @since 0.1.0.0 writeFileUtf8Builder :: MonadIO m => FilePath -> Utf8Builder -> m () writeFileUtf8Builder fp (Utf8Builder builder) = liftIO $ withBinaryFile fp WriteMode $ \h -> BB.hPutBuilder h builder