module Language.Haskell.Modules.Internal
( runCleanT
, modifyParams
, markForDelete
, Params(..)
, CleanT
, MonadClean(getParams, putParams)
, ModuleResult(..)
, doResult
, fixExport
) where
import Control.Exception (SomeException, try)
import "MonadCatchIO-mtl" Control.Monad.CatchIO as IO (catch, MonadCatchIO, throw)
import Control.Monad.State (MonadState(get, put), StateT(runStateT))
import Control.Monad.Trans (liftIO, MonadIO)
import Data.Map as Map (empty, Map)
import Data.Monoid ((<>))
import Data.Sequence as Seq (Seq, (|>))
import Data.Set as Set (empty, insert, Set, toList)
import qualified Language.Haskell.Exts.Annotated as A (ExportSpec)
import Language.Haskell.Exts.Annotated.Simplify (sExportSpec)
import Language.Haskell.Exts.Pretty (prettyPrint)
import qualified Language.Haskell.Exts.Syntax as S (ExportSpec(..), ImportDecl, ModuleName(..))
import Language.Haskell.Modules.ModuVerse (delName, ModuVerse(..), moduVerseInit, ModuVerseState, putModuleAnew, unloadModule)
import Language.Haskell.Modules.SourceDirs (PathKey(..), modulePath)
import Language.Haskell.Modules.Util.DryIO (createDirectoryIfMissing, MonadDryRun(..), removeFileIfPresent, replaceFile, tildeBackup)
import Language.Haskell.Modules.Util.QIO (MonadVerbosity(..), qLnPutStr, quietly)
import Language.Haskell.Modules.Util.Temp (withTempDirectory)
import Prelude hiding (writeFile)
import System.Directory (removeFile)
import System.FilePath (dropExtension, takeDirectory)
import System.IO.Error (isDoesNotExistError)
data Params
= Params
{ scratchDir :: FilePath
, dryRun :: Bool
, verbosity :: Int
, hsFlags :: [String]
, moduVerse :: ModuVerseState
, junk :: Set FilePath
, removeEmptyImports :: Bool
, extraImports :: Map S.ModuleName (Set S.ImportDecl)
, testMode :: Bool
} deriving (Eq, Ord, Show)
type CleanT m = StateT Params m
instance MonadClean m => ModuVerse m where
getModuVerse = getParams >>= return . moduVerse
modifyModuVerse f = modifyParams (\ p -> p {moduVerse = f (moduVerse p)})
class (MonadIO m, MonadCatchIO m, Functor m) => MonadClean m where
getParams :: m Params
putParams :: Params -> m ()
modifyParams :: MonadClean m => (Params -> Params) -> m ()
modifyParams f = getParams >>= putParams . f
instance (MonadCatchIO m, Functor m) => MonadClean (CleanT m) where
getParams = get
putParams = put
instance MonadClean m => MonadVerbosity m where
getVerbosity = getParams >>= return . verbosity
putVerbosity v = modifyParams (\ p -> p {verbosity = v})
instance MonadClean m => MonadDryRun m where
dry = getParams >>= return . dryRun
putDry x = modifyParams (\ p -> p {dryRun = x})
runCleanT :: MonadCatchIO m => CleanT m a -> m a
runCleanT action =
withTempDirectory "." "scratch" $ \ scratch ->
do (result, params) <- runStateT action (Params {scratchDir = scratch,
dryRun = False,
verbosity = 1,
hsFlags = [],
moduVerse = moduVerseInit,
junk = Set.empty,
removeEmptyImports = True,
extraImports = Map.empty,
testMode = False})
mapM_ (\ x -> liftIO (try (removeFile x)) >>= \ (_ :: Either SomeException ()) -> return ()) (toList (junk params))
return result
markForDelete :: MonadClean m => FilePath -> m ()
markForDelete x = modifyParams (\ p -> p {junk = insert x (junk p)})
data ModuleResult
= Unchanged S.ModuleName PathKey
| Removed S.ModuleName PathKey
| Modified S.ModuleName PathKey String
| Created S.ModuleName String
deriving (Show, Eq, Ord)
doResult :: (ModuVerse m, MonadDryRun m, MonadVerbosity m) => ModuleResult -> m ModuleResult
doResult x@(Unchanged _name key) =
do quietly (qLnPutStr ("unchanged: " ++ show key))
return x
doResult x@(Removed name key) =
do quietly (qLnPutStr ("removed: " ++ show key))
let path = unPathKey key
unloadModule key
removeFileIfPresent path `IO.catch` (\ (e :: IOError) -> if isDoesNotExistError e then return () else throw e)
delName name
return x
doResult x@(Modified m@(S.ModuleName name) key text) =
do quietly (qLnPutStr ("modified: " ++ show key))
path <- modulePath "hs" m
replaceFile tildeBackup path text
putModuleAnew name
return x
doResult x@(Created m@(S.ModuleName name) text) =
do quietly (qLnPutStr ("created: " ++ name))
path <- modulePath "hs" m
createDirectoryIfMissing True (takeDirectory . dropExtension $ path)
replaceFile tildeBackup path text
putModuleAnew name
return x
fixExport :: [S.ModuleName] -> S.ModuleName -> S.ModuleName
-> A.ExportSpec l -> String -> String -> String -> Seq String -> Seq String
fixExport inNames outName thisName e pref s suff r =
case sExportSpec e of
S.EModuleContents name
| thisName == outName && elem name inNames -> r
| elem name inNames -> r |> pref <> prettyPrint (S.EModuleContents outName) <> suff
_ -> r |> pref <> s <> suff