module HsDev.Symbols.Util ( projectOf, packageDbOf, packageOf, inProject, inDepsOfTarget, inDepsOfFile, inDepsOfProject, inPackageDb, inPackageDbStack, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, installed, standalone, imports, qualifier, moduleImported, visible, inScope, newestPackage, sourceModule, visibleModule, preferredModule, uniqueModules, allOf, anyOf ) where import Control.Arrow ((***), (&&&), first) import Control.Lens (view) import Control.Monad (liftM) import Data.Function (on) import Data.Maybe import Data.List (maximumBy, groupBy, sortBy, partition) import Data.Ord (comparing) import Data.String (fromString) import System.FilePath (normalise) import HsDev.Symbols import HsDev.Util (ordNub) -- | Get module project projectOf :: ModuleId -> Maybe Project projectOf m = case view moduleIdLocation m of FileModule _ proj -> proj _ -> Nothing -- | Get module cabal packageDbOf :: ModuleId -> Maybe PackageDb packageDbOf m = case view moduleIdLocation m of InstalledModule c _ _ -> Just c _ -> Nothing -- | Get module package packageOf :: ModuleId -> Maybe ModulePackage packageOf m = case view moduleIdLocation m of InstalledModule _ package' _ -> package' _ -> Nothing -- | Check if module in project inProject :: Project -> ModuleId -> Bool inProject p m = projectOf m == Just p -- | Check if module in deps of project target inDepsOfTarget :: Info -> ModuleId -> Bool inDepsOfTarget i m = any (`inPackage` m) $ view infoDepends i -- | Check if module in deps of source inDepsOfFile :: Project -> FilePath -> ModuleId -> Bool inDepsOfFile p f m = any (`inDepsOfTarget` m) (fileTargets p f) -- | Check if module in deps of project inDepsOfProject :: Project -> ModuleId -> Bool inDepsOfProject = maybe (const False) (anyPackage . ordNub . concatMap (view infoDepends) . infos) . view projectDescription where anyPackage :: [String] -> ModuleId -> Bool anyPackage = liftM or . mapM inPackage -- | Check if module in package-db inPackageDb :: PackageDb -> ModuleId -> Bool inPackageDb c m = case view moduleIdLocation m of InstalledModule d _ _ -> d == c _ -> False -- | Check if module in one of sandboxes inPackageDbStack :: PackageDbStack -> ModuleId -> Bool inPackageDbStack dbs m = case view moduleIdLocation m of InstalledModule d _ _ -> d `elem` packageDbs dbs _ -> False -- | Check if module in package inPackage :: String -> ModuleId -> Bool inPackage p m = case view moduleIdLocation m of InstalledModule _ package' _ -> Just p == fmap (view packageName) package' _ -> False inVersion :: String -> ModuleId -> Bool inVersion v m = case view moduleIdLocation m of InstalledModule _ package' _ -> Just v == fmap (view packageVersion) package' _ -> False -- | Check if module in file inFile :: FilePath -> ModuleId -> Bool inFile fpath m = case view moduleIdLocation m of FileModule f _ -> f == normalise fpath _ -> False -- | Check if module in source inModuleSource :: Maybe String -> ModuleId -> Bool inModuleSource src m = case view moduleIdLocation m of ModuleSource src' -> src' == src _ -> False -- | Check if declaration is in module inModule :: String -> ModuleId -> Bool inModule mname m = fromString mname == view moduleIdName m -- | Check if module defined in file byFile :: ModuleId -> Bool byFile m = case view moduleIdLocation m of FileModule _ _ -> True _ -> False -- | Check if module got from cabal database installed :: ModuleId -> Bool installed m = case view moduleIdLocation m of InstalledModule _ _ _ -> True _ -> False -- | Check if module is standalone standalone :: ModuleId -> Bool standalone m = case view moduleIdLocation m of FileModule _ Nothing -> True _ -> False -- | Get list of imports imports :: Module -> [Import] imports = view moduleImports -- | Get list of imports, which can be accessed with specified qualifier or unqualified qualifier :: Module -> Maybe String -> [Import] qualifier m q = filter (importQualifier (fmap fromString q)) $ import_ (fromString "Prelude") : import_ (view moduleName m) : imports m -- | Check if module imported via imports specified moduleImported :: ModuleId -> [Import] -> Bool moduleImported m = any (\i -> view moduleIdName m == view importModuleName i) -- | Check if module visible from this module within this project visible :: Project -> ModuleId -> ModuleId -> Bool visible p (ModuleId _ (FileModule src _)) m = inProject p m || any (`inPackage` m) deps || maybe False ((`elem` deps) . view projectName) (projectOf m) where deps = concatMap (view infoDepends) $ fileTargets p src visible _ _ _ = False -- | Check if module is in scope with qualifier inScope :: Module -> Maybe String -> ModuleId -> Bool inScope this q m = m `moduleImported` qualifier this q -- | Select symbols with last package version newestPackage :: Symbol a => [a] -> [a] newestPackage = uncurry (++) . ((selectNewest . groupPackages) *** map snd) . partition (isJust . fst) . map ((mpackage . symbolModuleLocation) &&& id) where mpackage (InstalledModule _ (Just p) _) = Just p mpackage _ = Nothing pname = fmap (view packageName) . fst pver = fmap (view packageVersion) . fst groupPackages :: [(Maybe ModulePackage, a)] -> [(Maybe ModulePackage, [a])] groupPackages = map (first head . unzip) . groupBy ((==) `on` fst) . sortBy (comparing fst) selectNewest :: [(Maybe ModulePackage, [a])] -> [a] selectNewest = concatMap (snd . maximumBy (comparing pver)) . groupBy ((==) `on` pname) . sortBy (comparing pname) -- | Select module, defined by sources sourceModule :: Maybe Project -> [Module] -> Maybe Module sourceModule proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (byFile . view moduleId) ms -- | Select module, visible in project or cabal -- TODO: PackageDbStack? visibleModule :: PackageDb -> Maybe Project -> [Module] -> Maybe Module visibleModule d proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (inPackageDb d . view moduleId) ms -- | Select preferred visible module -- TODO: PackageDbStack? preferredModule :: PackageDb -> Maybe Project -> [ModuleId] -> Maybe ModuleId preferredModule d proj ms = listToMaybe $ concatMap (`filter` ms) order where order = [ maybe (const False) inProject proj, byFile, inPackageDb d, const True] -- | Remove duplicate modules, leave only `preferredModule` -- TODO: PackageDbStack? uniqueModules :: PackageDb -> Maybe Project -> [ModuleId] -> [ModuleId] uniqueModules d proj = mapMaybe (preferredModule d proj) . groupBy ((==) `on` view moduleIdName) . sortBy (comparing (view moduleIdName)) -- | Select value, satisfying to all predicates allOf :: [a -> Bool] -> a -> Bool allOf ps x = all ($ x) ps -- | Select value, satisfying one of predicates anyOf :: [a -> Bool] -> a -> Bool anyOf ps x = any ($ x) ps -- | Is file info actual? --isActual :: Symbol a -> IO Bool --isActual = maybe (return False) checkStamp . symbolLocation where -- checkStamp l = do -- actualStamp <- getModificationTime (locationFile l) -- return $ Just actualStamp == locationTimeStamp l