packages feed

brush-strokes-0.1.0.0: src/lib/Debug/Utils.hs

module Debug.Utils
  ( trace
  , logToFile
  ) where

-- base
import Control.Concurrent.MVar
  ( MVar, withMVarMasked )
import Control.Monad
  ( void )
import System.IO
  ( BufferMode(..), hSetBuffering, hFlush, hPutStrLn, stdout )
import System.IO.Unsafe
  ( unsafePerformIO )

-- code-page
import System.IO.CodePage
  ( withCP65001 )

-- deepseq
import Control.DeepSeq
  ( force )

-- directory
import qualified System.Directory as Directory
  ( createDirectoryIfMissing )

-- filepath
import qualified System.FilePath as FilePath
  ( takeDirectory )

-- time
import qualified Data.Time.Clock as Time
  ( getCurrentTime )
import qualified  Data.Time.Format as Time
  ( defaultTimeLocale, formatTime )

--------------------------------------------------------------------------------

-- | Like 'Debug.Trace.trace', but using 'withCP65001` in order to handle
-- Unicode without crashing on Windows.
trace :: String -> a -> a
trace ( force -> !str ) expr =
  unsafePerformIO $ withCP65001 do
    hSetBuffering stdout ( BlockBuffering $ Just 50 )
    hPutStrLn stdout str
    hFlush stdout
    return expr

-- | Log the second argument to the file stored in the 'MVar' in the first
-- argument.
--
-- The 'MVar' is used to avoid jumbled contents when attempting to write to
-- the file concurrently.
logToFile :: MVar FilePath -> String -> ()
logToFile logFileMVar logContents =
  unsafePerformIO $ withCP65001 $ void $ withMVarMasked logFileMVar $ \ logFile -> do
    now <- Time.getCurrentTime
    let timeString = Time.formatTime Time.defaultTimeLocale "%0Y-%m-%d (%Hh %Mm %Ss)" now
        logContentsWithHeader = unlines
          [ replicate 80 '='
          , timeString
          , logContents
          ]
    Directory.createDirectoryIfMissing True $
      FilePath.takeDirectory logFile
    appendFile logFile logContentsWithHeader
    return logFile
{-# NOINLINE logToFile #-}