----------------------------------------------------------------------------- -- | -- 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 = traceInternal appendFile {-| Like 'traceFile' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file. -} traceFileW :: FilePath -> String -> a -> a traceFileW = traceInternal 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 fp a = traceFile fp a a {-| Like 'traceFileId' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file. -} traceFileIdW :: FilePath -> String -> String traceFileIdW fp a = traceFileW fp a 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 fp = traceFile fp . 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 fp = traceFileW fp . 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 fp a = traceFile fp (show 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 fp a = traceFileW fp (show 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 fp f a = traceFile fp (f 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 fp f a = traceFileW fp (f 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 fp f = traceFileWith fp (show . 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 fp f = traceFileWithW fp (show . 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 fp string = traceFile fp string $ 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 fp string = traceFileW fp string $ 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 fp = traceFileM fp . 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 fp = traceFileMW fp . show traceInternal :: (FilePath -> String -> IO ()) -> FilePath -> String -> a -> a traceInternal writeFunc fp str val = unsafePerformIO $! writeFunc fp (str ++ "\n") $> val