{-# LANGUAGE Trustworthy #-} module Main (main) where import Data.Functor ((<&>)) import Data.List (intercalate, isPrefixOf, isSuffixOf, sort, stripPrefix) import Data.Maybe (fromMaybe, listToMaybe, mapMaybe) import System.Directory (doesDirectoryExist, listDirectory) import System.Environment (getArgs) import System.FilePath (dropExtension, makeRelative, splitDirectories, takeDirectory, ()) main :: IO () main = getArgs >>= \case src : _cur : dst : opts -> do let srcDir = takeDirectory src modules <- discoverSpecs srcDir srcDir writeFile dst . generateModule (moduleNameFrom opts) $ sort modules _ -> error "Usage: hspec-effectful-discover SRC CUR DST [--module-name=NAME]" moduleNameFrom :: [String] -> String moduleNameFrom = fromMaybe "Main" . listToMaybe . mapMaybe (stripPrefix "--module-name=") discoverSpecs :: FilePath -> FilePath -> IO [String] discoverSpecs baseDir dir = fmap concat . traverse (processEntry baseDir dir) =<< listDirectory dir processEntry :: FilePath -> FilePath -> FilePath -> IO [String] processEntry baseDir dir entry | "Spec.hs" `isSuffixOf` entry, entry /= "Spec.hs" = pure [pathToModule baseDir full] | "." `isPrefixOf` entry = pure [] | otherwise = do isDir <- doesDirectoryExist full if isDir then discoverSpecs baseDir full else pure [] where full = dir entry pathToModule :: FilePath -> FilePath -> String pathToModule baseDir = intercalate "." . splitDirectories . dropExtension . makeRelative baseDir generateModule :: String -> [String] -> String generateModule moduleName modules = unlines . mconcat $ [ [ "{-# OPTIONS_GHC -Wno-missing-local-signatures #-}" , "{-# OPTIONS_GHC -Wno-missing-export-lists #-}" , "{-# OPTIONS_GHC -Wno-missing-import-lists #-}" , "" , "module " <> moduleName <> " where" , "" , "import Effectful" , "import Effectful.Hspec" , "import Prelude" , "" ] , modules <&> \m -> "import " <> m <> " qualified" , [ "" , "main :: IO ()" , "main = runEff . runHspec $ do" ] , modules <&> \m -> " describe " <> show (describeLabel m) <> " " <> m <> ".spec" ] describeLabel :: String -> String describeLabel = stripSuffix "Spec" . reverse . takeWhile (/= '.') . reverse stripSuffix :: String -> String -> String stripSuffix suffix str | suffix `isSuffixOf` str = take (length str - length suffix) str | otherwise = str