-----------------------------------------------------------------------------
-- |
-- Module      :  Debug.Trace.File
-- Maintainer  :  i@ak3n.com
--
-- Like Debug.Trace but writing to files (when eventlog is too much).
--
-- The functions use 'appendFile' and append to files by default.
-- The functions with suffix W (like 'traceFileW', 'traceFileIdW', etc) use 'writeFile'.
-----------------------------------------------------------------------------

module Debug.Trace.File
  (
    -- * Tracing to files
    traceFile
  , traceFileW

  , traceFileId
  , traceFileIdW

  , traceFileShow
  , traceFileShowW

  , traceFileShowId
  , traceFileShowIdW

  , traceFileWith
  , traceFileWithW

  , traceFileShowWith
  , traceFileShowWithW

  , traceFileM
  , traceFileMW

  , traceFileShowM
  , traceFileShowMW
  ) where

import Data.Functor (($>))
import System.IO.Unsafe (unsafePerformIO)

-- $setup
-- >>> import Prelude

{-|
The 'traceFile' function appends to the provided file path given as its first argument,
the trace message given as its second argument, before returning the third argument as its result.

For example, this returns the value of @f x@ and outputs the message to "\/tmp\/message".

>>> let x = 123; f = show
>>> traceFile "/tmp/message" ("calling f with x = " ++ show x) (f x)
"123"
>>> readFile "/tmp/message"
"calling f with x = 123\n"

The 'traceFile' function should /only/ be used for debugging, or for monitoring
execution. The function is not referentially transparent: its type indicates
that it is a pure function but it has the side effect of outputting the
trace message.
-}
traceFile :: FilePath -> String -> a -> a
traceFile :: forall a. FilePath -> FilePath -> a -> a
traceFile = forall a.
(FilePath -> FilePath -> IO ()) -> FilePath -> FilePath -> a -> a
traceInternal FilePath -> FilePath -> IO ()
appendFile

{-|
Like 'traceFile' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileW :: FilePath -> String -> a -> a
traceFileW :: forall a. FilePath -> FilePath -> a -> a
traceFileW = forall a.
(FilePath -> FilePath -> IO ()) -> FilePath -> FilePath -> a -> a
traceInternal FilePath -> FilePath -> IO ()
writeFile

{-|
Like 'traceFile' but returns the message instead of a third value.

>>> traceFileId "/tmp/message" "hello"
"hello"
>>> readFile "/tmp/message"
"hello\n"
-}
traceFileId :: FilePath -> String -> String
traceFileId :: FilePath -> FilePath -> FilePath
traceFileId FilePath
fp FilePath
a = forall a. FilePath -> FilePath -> a -> a
traceFile FilePath
fp FilePath
a FilePath
a

{-|
Like 'traceFileId' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileIdW :: FilePath -> String -> String
traceFileIdW :: FilePath -> FilePath -> FilePath
traceFileIdW FilePath
fp FilePath
a = forall a. FilePath -> FilePath -> a -> a
traceFileW FilePath
fp FilePath
a FilePath
a

{-|
Like 'traceFile', but uses 'show' on the argument to convert it to a 'String'.

This makes it convenient for printing the values of interesting variables or
expressions inside a function. For example here we print the value of the
variables @x@ and @y@:

>>> let f x y = traceFileShow "/tmp/message" (x,y) (x + y) in f (1+2) 5
8
>>> readFile "/tmp/message"
"(3,5)\n"
-}
traceFileShow :: Show a => FilePath -> a -> b -> b
traceFileShow :: forall a b. Show a => FilePath -> a -> b -> b
traceFileShow FilePath
fp = forall a. FilePath -> FilePath -> a -> a
traceFile FilePath
fp forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> FilePath
show

{-|
Like 'traceFileShow' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileShowW :: Show a => FilePath -> a -> b -> b
traceFileShowW :: forall a b. Show a => FilePath -> a -> b -> b
traceFileShowW FilePath
fp = forall a. FilePath -> FilePath -> a -> a
traceFileW FilePath
fp forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> FilePath
show

{-|
Like 'traceFileShow' but returns the shown value instead of a third value.

>>> traceFileShowId "/tmp/message" (1+2+3, "hello" ++ "world")
(6,"helloworld")
>>> readFile "/tmp/message"
"(6,\"helloworld\")\n"
-}
traceFileShowId :: Show a => FilePath -> a -> a
traceFileShowId :: forall a. Show a => FilePath -> a -> a
traceFileShowId FilePath
fp a
a = forall a. FilePath -> FilePath -> a -> a
traceFile FilePath
fp (forall a. Show a => a -> FilePath
show a
a) a
a

{-|
Like 'traceFileShowId' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileShowIdW :: Show a => FilePath -> a -> a
traceFileShowIdW :: forall a. Show a => FilePath -> a -> a
traceFileShowIdW FilePath
fp a
a = forall a. FilePath -> FilePath -> a -> a
traceFileW FilePath
fp (forall a. Show a => a -> FilePath
show a
a) a
a

{-|
Like 'traceFile', but outputs the result of calling a function on the argument.

>>> traceFileWith "/tmp/message" fst ("hello","world")
("hello","world")
>>> readFile "/tmp/message"
"hello\n"
-}
traceFileWith :: FilePath -> (a -> String) -> a -> a
traceFileWith :: forall a. FilePath -> (a -> FilePath) -> a -> a
traceFileWith FilePath
fp a -> FilePath
f a
a = forall a. FilePath -> FilePath -> a -> a
traceFile FilePath
fp (a -> FilePath
f a
a) a
a

{-|
Like 'traceFileWith' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileWithW :: FilePath -> (a -> String) -> a -> a
traceFileWithW :: forall a. FilePath -> (a -> FilePath) -> a -> a
traceFileWithW FilePath
fp a -> FilePath
f a
a = forall a. FilePath -> FilePath -> a -> a
traceFileW FilePath
fp (a -> FilePath
f a
a) a
a

{-|
Like 'traceFileWith', but uses 'show' on the result of the function to convert it to
a 'String'.

>>> traceFileShowWith "/tmp/message" length [1,2,3]
[1,2,3]
>>> readFile "/tmp/message"
"3\n"
-}
traceFileShowWith :: Show b => FilePath -> (a -> b) -> a -> a
traceFileShowWith :: forall b a. Show b => FilePath -> (a -> b) -> a -> a
traceFileShowWith FilePath
fp a -> b
f = forall a. FilePath -> (a -> FilePath) -> a -> a
traceFileWith FilePath
fp (forall a. Show a => a -> FilePath
show forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f)

{-|
Like 'traceFileWith' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileShowWithW :: Show b => FilePath -> (a -> b) -> a -> a
traceFileShowWithW :: forall b a. Show b => FilePath -> (a -> b) -> a -> a
traceFileShowWithW FilePath
fp a -> b
f = forall a. FilePath -> (a -> FilePath) -> a -> a
traceFileWithW FilePath
fp (forall a. Show a => a -> FilePath
show forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f)

{-|
Like 'traceFile' but returning unit in an arbitrary 'Applicative' context. Allows
for convenient use in do-notation.

>>> :{
do
    x <- Just 3
    traceFileM "/tmp/message" ("x: " ++ show x)
    y <- pure 12
    traceFileM "/tmp/message" ("y: " ++ show y)
    pure (x*2 + y)
:}
Just 18
>>> readFile "/tmp/message"
"x: 3\ny: 12\n"
-}
traceFileM :: Applicative f => FilePath -> String -> f ()
traceFileM :: forall (f :: * -> *). Applicative f => FilePath -> FilePath -> f ()
traceFileM FilePath
fp FilePath
string = forall a. FilePath -> FilePath -> a -> a
traceFile FilePath
fp FilePath
string forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

{-|
Like 'traceFileM' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileMW :: Applicative f => FilePath -> String -> f ()
traceFileMW :: forall (f :: * -> *). Applicative f => FilePath -> FilePath -> f ()
traceFileMW FilePath
fp FilePath
string = forall a. FilePath -> FilePath -> a -> a
traceFileW FilePath
fp FilePath
string forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

{-|
Like 'traceFileM', but uses 'show' on the argument to convert it to a 'String'.

>>> :{
do
    x <- Just 3
    traceFileShowM "/tmp/message" x
    y <- pure 12
    traceFileShowM "/tmp/message" y
    pure (x*2 + y)
:}
Just 18
>>> readFile "/tmp/message"
"3\n12\n"
-}
traceFileShowM :: (Show a, Applicative f) => FilePath -> a -> f ()
traceFileShowM :: forall a (f :: * -> *).
(Show a, Applicative f) =>
FilePath -> a -> f ()
traceFileShowM FilePath
fp = forall (f :: * -> *). Applicative f => FilePath -> FilePath -> f ()
traceFileM FilePath
fp forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> FilePath
show

{-|
Like 'traceFileShowM' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
-}
traceFileShowMW :: (Show a, Applicative f) => FilePath -> a -> f ()
traceFileShowMW :: forall a (f :: * -> *).
(Show a, Applicative f) =>
FilePath -> a -> f ()
traceFileShowMW FilePath
fp = forall (f :: * -> *). Applicative f => FilePath -> FilePath -> f ()
traceFileMW FilePath
fp forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> FilePath
show

traceInternal :: (FilePath -> String -> IO ()) -> FilePath -> String -> a -> a
traceInternal :: forall a.
(FilePath -> FilePath -> IO ()) -> FilePath -> FilePath -> a -> a
traceInternal FilePath -> FilePath -> IO ()
writeFunc FilePath
fp FilePath
str a
val = forall a. IO a -> a
unsafePerformIO forall a b. (a -> b) -> a -> b
$! FilePath -> FilePath -> IO ()
writeFunc FilePath
fp (FilePath
str forall a. [a] -> [a] -> [a]
++ FilePath
"\n") forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> a
val