{-# LANGUAGE LambdaCase #-} module Di.Backend ( mkDiStringStderr , mkDiStringHandle ) where import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Monoid (mconcat, mappend) import Data.String (IsString(fromString)) import qualified Data.Time as Time import Prelude hiding (log, filter) import qualified System.IO as IO import Data.Semigroup (Semigroup(..)) import Di.Core (Di, mkDi, contrapath) -------------------------------------------------------------------------------- -- | Strings separated by a forward slash. -- -- The string doesn't contain any of @[\'/'\, \' \', \'\\n\', \'\\r\']@. -- -- Use 'fromString' (GHC's @OverloadedStrings@ extension) to construct a -- 'StringPath'. newtype StringPath = UnsafeStringPath { unStringPath :: String } deriving (Eq, Ord, Show) instance IsString StringPath where fromString = stringPathSingleton {-# INLINE fromString #-} -- | Ocurrences of one of @[\'/'\, \' \', \'\\n\', \'\\r\']@ in the given -- 'String' will be replaced by @\'_\'@. stringPathSingleton :: String -> StringPath stringPathSingleton = \s -> UnsafeStringPath (map f s) where f :: Char -> Char f = \case '/' -> '_' ' ' -> '_' '\n' -> '_' '\r' -> '_' c -> c instance Semigroup StringPath where UnsafeStringPath "" <> b = b a <> UnsafeStringPath "" = a UnsafeStringPath a <> UnsafeStringPath b = UnsafeStringPath (a <> "/" <> b) instance Monoid StringPath where mempty = UnsafeStringPath "" {-# INLINE mempty #-} mappend = (<>) {-# INLINE mappend #-} -- | 'String's are written to 'IO.Handle' using the 'IO.Handle''s locale -- encoding. -- -- The @level@ and @message@ are plain 'String's. -- -- The @path@ is a list of 'String's which will be separated by @\'/\'@ when -- rendered. Ocurrences of one of @[\'/'\, \' \', \'\\n\', \'\\r\']@ in those -- 'String's will be replaced by @\'_\'@. -- -- @ -- > 'log' ('push' [\"cuatro\"] ('push' [\"uno dos\", \"tres\"] di)) \"WARNING\" \"Hello!\" -- WARNING 2018-05-03T09:15:54.819379740000Z uno_dos\/tres\/cuatro: Hello! -- @ mkDiStringHandle :: (MonadIO m) => IO.Handle -> m (Di String [String] String) mkDiStringHandle h = liftIO $ do IO.hSetBuffering h IO.LineBuffering fmap (contrapath (mconcat . map stringPathSingleton)) $ do mkDi $ \ts l p m -> do IO.hPutStrLn h $ mconcat [ l, " ", renderIso8601 ts , if p == mempty then "" else (" " <> unStringPath p) , ": ", noBreaks m ] IO.hFlush h where noBreaks :: String -> String noBreaks = concatMap $ \case '\n' -> "\\n" '\r' -> "\\r" c -> [c] -- | -- @ -- 'mkDiStringStderr' == 'mkDiStringHandle' 'IO.stderr' -- @ mkDiStringStderr :: MonadIO m => m (Di String [String] String) mkDiStringStderr = mkDiStringHandle IO.stderr -------------------------------------------------------------------------------- renderIso8601 :: Time.UTCTime -> String renderIso8601 = Time.formatTime Time.defaultTimeLocale "%Y-%m-%dT%H:%M:%S.%qZ"