{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

{-|
Module      : $header$
Copyright   : (c) Laurent P René de Cotret, 2020
License     : GNU GPL, version 2 or above
Maintainer  : laurent.decotret@outlook.com
Stability   : internal
Portability : portable

Logging primitives.
-}

module Text.Pandoc.Filter.Plot.Monad.Logging 
    ( Verbosity(..)
    , LogSink(..)
    , LoggingM
    , runLoggingM
    , log
    ) where


import           Control.Monad.Trans         (liftIO)
import           Control.Monad.Writer.Strict (WriterT, runWriterT, tell)

import           Data.Char                   (toLower)
import           Data.List                   (sortOn)
import           Data.String                 (IsString(..))
import           Data.Text                   (Text, unpack)
import qualified Data.Text                   as T
import           Data.Text.IO                (hPutStr)
import           Data.Time.Clock.System      (getSystemTime, SystemTime(..))
import           Data.Yaml

import           System.IO                   (stderr, withFile, IOMode (AppendMode) )

import           Prelude                     hiding (log, fst, snd)


-- | Verbosity of the logger.

data Verbosity = Debug    -- ^ Log all messages, including debug messages.

               | Error    -- ^ Log information, warnings, and errors.

               | Warning  -- ^ Log information and warning messages.

               | Info     -- ^ Only log information messages.

               | Silent   -- ^ Don't log anything. 

               deriving (Verbosity -> Verbosity -> Bool
(Verbosity -> Verbosity -> Bool)
-> (Verbosity -> Verbosity -> Bool) -> Eq Verbosity
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Verbosity -> Verbosity -> Bool
$c/= :: Verbosity -> Verbosity -> Bool
== :: Verbosity -> Verbosity -> Bool
$c== :: Verbosity -> Verbosity -> Bool
Eq, Eq Verbosity
Eq Verbosity =>
(Verbosity -> Verbosity -> Ordering)
-> (Verbosity -> Verbosity -> Bool)
-> (Verbosity -> Verbosity -> Bool)
-> (Verbosity -> Verbosity -> Bool)
-> (Verbosity -> Verbosity -> Bool)
-> (Verbosity -> Verbosity -> Verbosity)
-> (Verbosity -> Verbosity -> Verbosity)
-> Ord Verbosity
Verbosity -> Verbosity -> Bool
Verbosity -> Verbosity -> Ordering
Verbosity -> Verbosity -> Verbosity
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Verbosity -> Verbosity -> Verbosity
$cmin :: Verbosity -> Verbosity -> Verbosity
max :: Verbosity -> Verbosity -> Verbosity
$cmax :: Verbosity -> Verbosity -> Verbosity
>= :: Verbosity -> Verbosity -> Bool
$c>= :: Verbosity -> Verbosity -> Bool
> :: Verbosity -> Verbosity -> Bool
$c> :: Verbosity -> Verbosity -> Bool
<= :: Verbosity -> Verbosity -> Bool
$c<= :: Verbosity -> Verbosity -> Bool
< :: Verbosity -> Verbosity -> Bool
$c< :: Verbosity -> Verbosity -> Bool
compare :: Verbosity -> Verbosity -> Ordering
$ccompare :: Verbosity -> Verbosity -> Ordering
$cp1Ord :: Eq Verbosity
Ord, Int -> Verbosity -> ShowS
[Verbosity] -> ShowS
Verbosity -> String
(Int -> Verbosity -> ShowS)
-> (Verbosity -> String)
-> ([Verbosity] -> ShowS)
-> Show Verbosity
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Verbosity] -> ShowS
$cshowList :: [Verbosity] -> ShowS
show :: Verbosity -> String
$cshow :: Verbosity -> String
showsPrec :: Int -> Verbosity -> ShowS
$cshowsPrec :: Int -> Verbosity -> ShowS
Show)


-- | Description of the possible ways to sink log messages.

data LogSink = StdErr           -- ^ Standard error stream.

             | LogFile FilePath -- ^ Appended to file.

             deriving (LogSink -> LogSink -> Bool
(LogSink -> LogSink -> Bool)
-> (LogSink -> LogSink -> Bool) -> Eq LogSink
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: LogSink -> LogSink -> Bool
$c/= :: LogSink -> LogSink -> Bool
== :: LogSink -> LogSink -> Bool
$c== :: LogSink -> LogSink -> Bool
Eq, Int -> LogSink -> ShowS
[LogSink] -> ShowS
LogSink -> String
(Int -> LogSink -> ShowS)
-> (LogSink -> String) -> ([LogSink] -> ShowS) -> Show LogSink
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [LogSink] -> ShowS
$cshowList :: [LogSink] -> ShowS
show :: LogSink -> String
$cshow :: LogSink -> String
showsPrec :: Int -> LogSink -> ShowS
$cshowsPrec :: Int -> LogSink -> ShowS
Show)

type LogMessage = (Verbosity, SystemTime, Text)

type LoggingM = WriterT [LogMessage] IO


runLoggingM :: Verbosity -> LogSink -> LoggingM a -> IO a
runLoggingM :: Verbosity -> LogSink -> LoggingM a -> IO a
runLoggingM Silent _       = Verbosity -> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
forall a.
Verbosity -> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
runLoggingM' Verbosity
Silent (([LogMessage] -> IO ()) -> LoggingM a -> IO a)
-> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
forall a b. (a -> b) -> a -> b
$ (LogMessage -> IO Text) -> [LogMessage] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Text -> IO Text
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> IO Text) -> (LogMessage -> Text) -> LogMessage -> IO Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LogMessage -> Text
forall a b c. (a, b, c) -> c
trd)
runLoggingM v :: Verbosity
v StdErr       = Verbosity -> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
forall a.
Verbosity -> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
runLoggingM' Verbosity
v (([LogMessage] -> IO ()) -> LoggingM a -> IO a)
-> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
forall a b. (a -> b) -> a -> b
$ (LogMessage -> IO ()) -> [LogMessage] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Handle -> Text -> IO ()
hPutStr Handle
stderr (Text -> IO ()) -> (LogMessage -> Text) -> LogMessage -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LogMessage -> Text
forall a b c. (a, b, c) -> c
trd)
runLoggingM v :: Verbosity
v (LogFile fp :: String
fp) = Verbosity -> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
forall a.
Verbosity -> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
runLoggingM' Verbosity
v (([LogMessage] -> IO ()) -> LoggingM a -> IO a)
-> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
forall a b. (a -> b) -> a -> b
$ (LogMessage -> IO ()) -> [LogMessage] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\m :: LogMessage
m -> String -> IOMode -> (Handle -> IO ()) -> IO ()
forall r. String -> IOMode -> (Handle -> IO r) -> IO r
withFile String
fp IOMode
AppendMode ((Handle -> IO ()) -> IO ()) -> (Handle -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \h :: Handle
h -> Handle -> Text -> IO ()
hPutStr Handle
h (LogMessage -> Text
forall a b c. (a, b, c) -> c
trd LogMessage
m))


runLoggingM' :: Verbosity                -- ^ Minimum verbosity to keep

             -> ([LogMessage] -> IO ())  -- ^ Log sink

             -> LoggingM a
             -> IO a
runLoggingM' :: Verbosity -> ([LogMessage] -> IO ()) -> LoggingM a -> IO a
runLoggingM' v :: Verbosity
v f :: [LogMessage] -> IO ()
f m :: LoggingM a
m = do
    (r :: a
r, t :: [LogMessage]
t) <- LoggingM a -> IO (a, [LogMessage])
forall w (m :: * -> *) a. WriterT w m a -> m (a, w)
runWriterT LoggingM a
m
    -- Messages with lower level than minimum are discarded

    -- We also re-order messages to be chronological

    let t' :: [LogMessage]
t' = (LogMessage -> SystemTime) -> [LogMessage] -> [LogMessage]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn LogMessage -> SystemTime
forall a b c. (a, b, c) -> b
snd ([LogMessage] -> [LogMessage]) -> [LogMessage] -> [LogMessage]
forall a b. (a -> b) -> a -> b
$ (LogMessage -> Bool) -> [LogMessage] -> [LogMessage]
forall a. (a -> Bool) -> [a] -> [a]
filter (\message :: LogMessage
message -> LogMessage -> Verbosity
forall a b c. (a, b, c) -> a
fst LogMessage
message Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
>= Verbosity
v) [LogMessage]
t
    IO () -> IO ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ [LogMessage] -> IO ()
f [LogMessage]
t'
    a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
r


-- | General logging function.

-- Input text will be decomposed into lines, with each

-- line becoming a log line.    

log :: Text       -- ^ Header  

    -> Verbosity  
    -> Text       -- ^ Message

    -> LoggingM ()
log :: Text -> Verbosity -> Text -> LoggingM ()
log h :: Text
h v :: Verbosity
v t :: Text
t = do
    SystemTime
timestamp <- IO SystemTime -> WriterT [LogMessage] IO SystemTime
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO SystemTime -> WriterT [LogMessage] IO SystemTime)
-> IO SystemTime -> WriterT [LogMessage] IO SystemTime
forall a b. (a -> b) -> a -> b
$ IO SystemTime
getSystemTime
    [LogMessage] -> LoggingM ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [(Verbosity
v, SystemTime
timestamp, Text
h Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
l Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "\n") | Text
l <- Text -> [Text]
T.lines Text
t]


instance IsString Verbosity where
    fromString :: String -> Verbosity
fromString s :: String
s
        | String
ls String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "silent"  = Verbosity
Silent
        | String
ls String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "info"    = Verbosity
Info
        | String
ls String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "warning" = Verbosity
Warning
        | String
ls String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "error"   = Verbosity
Error
        | String
ls String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "debug"   = Verbosity
Debug
        | Bool
otherwise = String -> Verbosity
forall a. HasCallStack => String -> a
error (String -> Verbosity) -> String -> Verbosity
forall a b. (a -> b) -> a -> b
$ "Unrecognized verbosity " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
s
        where
            ls :: String
ls = Char -> Char
toLower (Char -> Char) -> ShowS
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String
s

instance FromJSON Verbosity where
    parseJSON :: Value -> Parser Verbosity
parseJSON (String t :: Text
t) = Verbosity -> Parser Verbosity
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Verbosity -> Parser Verbosity) -> Verbosity -> Parser Verbosity
forall a b. (a -> b) -> a -> b
$ String -> Verbosity
forall a. IsString a => String -> a
fromString (String -> Verbosity) -> (Text -> String) -> Text -> Verbosity
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
unpack (Text -> Verbosity) -> Text -> Verbosity
forall a b. (a -> b) -> a -> b
$ Text
t
    parseJSON _ = String -> Parser Verbosity
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Parser Verbosity) -> String -> Parser Verbosity
forall a b. (a -> b) -> a -> b
$ "Could not parse the logging verbosity."


fst :: (a,b,c) -> a
fst :: (a, b, c) -> a
fst (a :: a
a,_,_) = a
a


snd :: (a,b,c) -> b
snd :: (a, b, c) -> b
snd (_,b :: b
b,_) = b
b


trd :: (a,b,c) -> c
trd :: (a, b, c) -> c
trd (_,_,c :: c
c) = c
c