{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE StandaloneDeriving         #-}

module Test.Hspec.Dirstream
    ( testFiles
    , F.extension
    , Test.Hspec.Dirstream.hasExtension
    ) where

import           Data.DirStream
import           Data.Text                 (Text)
import qualified Filesystem.Path.CurrentOS as F
import           Pipes
import qualified Pipes.Prelude             as P
import           Pipes.Safe
import           System.FilePath
import           Test.Hspec
import           Test.Hspec.Core.Spec

deriving instance MonadCatch (SpecM a)
deriving instance MonadThrow (SpecM a)
deriving instance MonadMask (SpecM a)
deriving instance MonadIO (SpecM a)

hasExtension :: Text -> F.FilePath -> Bool
hasExtension = flip F.hasExtension

mapS :: (a -> SpecM () ()) -> Proxy () a y' y (SafeT (SpecM ())) r
mapS = P.mapM_ . (lift .)

paths :: MonadSafe m => String -> (F.FilePath -> Bool) -> Producer String m ()
paths dir p = every (childOf path) >-> P.filter p >-> P.map F.encodeString
    where path = F.decodeString dir

-- | Helper function to generate a spec. The spec runs on the given directory,
-- filtering by the given function. It then compares their output to the text of
-- the file with @.out@ as the new extension.
--
-- As an example, consider the directory structure
--
-- > test/data
-- > ├── file.hs
-- > └── file.out
--
-- If we have a function called @formatFile@ and we run
--
-- > testFiles "test/data" (hasExtension "hs") formatFile
--
-- This would read @test\/data\/file.hs@, format the file if it can, and compare
-- the output to the contents of @test\/data\/file.out@.
testFiles :: (Eq a, Show a)
          => FilePath -- ^ Base directory
          -> (F.FilePath -> Bool) -- ^ Filter on file extensions
          -> (String -> Either a String) -- ^ Function to process a file
          -> SpecWith ()
testFiles dir p f = runSafeT $ runEffect $ paths dir p >-> mapS (testFile f)

testFile :: (Eq a, Show a) => (String -> Either a String) -> String -> SpecWith ()
testFile fun f = it f $ do
    sample <- readFile f
    expected <- readFile (replaceExtension f ".out")
    fun sample `shouldBe` Right expected