module Shaker.ModuleData where import Control.Arrow import Control.Monad.Reader import Data.List import Data.Maybe import Data.Monoid import Language.Haskell.Syntax import Shaker.HsHelper import Shaker.Io import Shaker.Type import Shaker.Regex import System.Directory import System.FilePath -- * Read and write module data -- | Get the corresponding mdata file from the given source file getCorrespondingModuleDataFile :: FilePath -> Shaker IO FilePath getCorrespondingModuleDataFile srcFile = fmap (`addExtension` moduleDataExtension) (getCorrespondingBuildFile srcFile) -- | Write given moduleData in dist directory writeModuleData :: ModuleData -> Shaker IO () writeModuleData moduleData = do let srcFile = moduleDataFileName moduleData buildFile <- getCorrespondingModuleDataFile srcFile lift $ createDirectoryIfMissing True (dropFileName buildFile) lift $ writeFile buildFile (show moduleData) -- | Parse module data from all haskell sources. parseAllModuleData :: Shaker IO [ ModuleData ] parseAllModuleData = do lstHsFiles <- fmap listenerInputFiles (asks shakerListenerInput) >>= lift . recurseMultipleListFiles fmap catMaybes $ mapM parseModuleData lstHsFiles -- | Read Module data from the given haskell source. It tries to read serialized information beforehand. parseModuleData :: FilePath -> Shaker IO (Maybe ModuleData) parseModuleData srcFile = do may_moduleData <- parseModuleDataIfExist srcFile case may_moduleData of Just _ -> return may_moduleData Nothing -> do may_hsModule <- lift $ parseFileToHsModule srcFile return $ fmap constructModuleData may_hsModule -- | Read Module data from the serialized data. It returns Nothing if the serialized data is absent or out-of-date. parseModuleDataIfExist :: FilePath -> Shaker IO (Maybe ModuleData) parseModuleDataIfExist srcFile = do buildFile <- fmap (`addExtension` moduleDataExtension) (getCorrespondingBuildFile srcFile) isPresent <- lift $ doesFileExist buildFile if isPresent then lift $ do srcTime <- getModificationTime srcFile srcMdata <- getModificationTime buildFile let isUptoDate = srcTime < srcMdata if isUptoDate then fmap (Just . read) (readFile buildFile) else return Nothing else return Nothing -- * Module data util methods convertModuleDataToFullCompileInput :: Shaker IO [CompileInput] convertModuleDataToFullCompileInput = do baseCpIn <- fmap mconcat (asks shakerCompileInputs) lstModuleDatas <- asks shakerModuleData let groupOfCompileModules = groupByValidTargets lstModuleDatas return $ map ( \ lstModules -> baseCpIn { compileInputTargetFiles = map moduleDataFileName lstModules } ) groupOfCompileModules groupByValidTargets :: [ModuleData] -> [ [ ModuleData] ] groupByValidTargets = partition moduleDataHasMain >>> first (groupBy mainGroupPredicate) >>> second nub >>> ( \ (a, b) -> b : a ) where mainGroupPredicate _ _ = False getNonMainCompileInput :: Shaker IO CompileInput getNonMainCompileInput = do baseCpIn <- fmap mconcat (asks shakerCompileInputs) lstModuleDatas <- asks shakerModuleData let filteredModuleDatas = filter (not . moduleDataHasMain) >>> nub $ lstModuleDatas return $ baseCpIn { compileInputTargetFiles = map moduleDataFileName filteredModuleDatas } fillModuleData :: ShakerInput -> IO ShakerInput fillModuleData shIn = do lstHsModules <- shakerListenerInput >>> listenerInputFiles >>> parseHsFiles $ shIn return shIn { shakerModuleData = map constructModuleData lstHsModules } constructModuleData :: HsModule -> ModuleData constructModuleData hsModule = mempty { moduleDataName = hsModuleName hsModule ,moduleDataFileName = hsModuleFileName hsModule ,moduleDataHasMain = getTupleFunctionNameType >>> map fst >>> any (=="main") $ hsModule ,moduleDataProperties = hsModuleCollectProperties hsModule } hsModuleDataHasTest :: ModuleData -> Bool hsModuleDataHasTest hsModuleData = any (not . null) [moduleDataProperties hsModuleData, moduleDataAssertions hsModuleData] -- | Include only module matching the given pattern filterModulesWithPattern :: [ModuleData]-> String -> [ModuleData] filterModulesWithPattern mod_map pattern = filter (\a -> moduleDataName a `elem` filtered_mod_list) mod_map where mod_list = map moduleDataName mod_map filtered_mod_list = processListWithRegexp mod_list [] [pattern] filterFunctionsWithPatterns :: [ModuleData] -> [String] -> [ModuleData] filterFunctionsWithPatterns mod_map patterns = map (`filterFunctionsWithPatterns'` patterns) mod_map filterFunctionsWithPatterns' :: ModuleData -> [String] -> ModuleData filterFunctionsWithPatterns' moduleData@(GhcModuleData _ listAssertions listTestCase) patterns = moduleData { ghcModuleDataAssertions = processListWithRegexp listAssertions [] patterns ,ghcModuleDataTestCase = processListWithRegexp listTestCase [] patterns } filterFunctionsWithPatterns' moduleData@(ModuleData _ _ _ properties hunitAssertions hunitTestCases) patterns = moduleData { moduleDataAssertions = processListWithRegexp hunitAssertions [] patterns ,moduleDataTestCase = processListWithRegexp hunitTestCases [] patterns ,moduleDataProperties = processListWithRegexp properties [] patterns } removeNonTestModules :: [ModuleData] -> [ModuleData] removeNonTestModules = filter ( \ moduleData -> any notEmpty [moduleDataProperties moduleData, moduleDataAssertions moduleData, moduleDataTestCase moduleData] ) where notEmpty = not . null