hsdev-0.1.7.0: src/HsDev/Scan.hs
{-# LANGUAGE FlexibleInstances #-}
module HsDev.Scan (
-- * Enumerate functions
CompileFlag, ModuleToScan, ProjectToScan, PackageDbToScan, ScanContents(..),
EnumContents(..),
enumProject, enumSandbox, enumDirectory,
-- * Scan
scanProjectFile,
scanModule, scanModify, upToDate, rescanModule, changedModule, changedModules,
-- * Reexportss
module HsDev.Database,
module HsDev.Symbols.Types,
module Control.Monad.Except,
) where
import Control.Applicative ((<|>))
import Control.DeepSeq
import Control.Lens (view, preview, set, over, each, _Right, _1, _2, _3, (^.), (^..), at)
import Control.Monad.Except
import Data.Maybe (catMaybes, fromMaybe, isJust)
import Data.List (intercalate)
import System.Directory
import Text.Format
import Data.Deps
import HsDev.Scan.Browse (browsePackages)
import HsDev.Server.Types (FileContents(..))
import HsDev.Sandbox
import HsDev.Symbols
import HsDev.Symbols.Types
import HsDev.Database
import HsDev.Display
import HsDev.Tools.GhcMod
import HsDev.Inspect
import HsDev.Util
-- | Compile flags
type CompileFlag = String
-- | Module with flags ready to scan
type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe String)
-- | Project ready to scan
type ProjectToScan = (Project, [ModuleToScan])
-- | Package-db sandbox to scan (top of stack)
type PackageDbToScan = PackageDbStack
-- | Scan info
data ScanContents = ScanContents {
modulesToScan :: [ModuleToScan],
projectsToScan :: [ProjectToScan],
sandboxesToScan :: [PackageDbStack] }
instance NFData ScanContents where
rnf (ScanContents ms ps ss) = rnf ms `seq` rnf ps `seq` rnf ss
instance Monoid ScanContents where
mempty = ScanContents [] [] []
mappend (ScanContents lm lp ls) (ScanContents rm rp rs) = ScanContents
(uniqueBy (view _1) $ lm ++ rm)
(uniqueBy (view _1) $ lp ++ rp)
(ordNub $ ls ++ rs)
instance FormatBuild ScanContents where
formatBuild (ScanContents ms ps cs) = formatBuild str where
str :: String
str = format "modules: {}, projects: {}, package-dbs: {}"
~~ (intercalate ", " $ ms ^.. each . _1 . moduleFile)
~~ (intercalate ", " $ ps ^.. each . _1 . projectPath)
~~ (intercalate ", " $ map (display . topPackageDb) $ cs ^.. each)
class EnumContents a where
enumContents :: a -> ExceptT String IO ScanContents
instance EnumContents ModuleLocation where
enumContents mloc = return $ ScanContents [(mloc, [], Nothing)] [] []
instance EnumContents (Extensions ModuleLocation) where
enumContents ex = return $ ScanContents [(view entity ex, extensionsOpts ex, Nothing)] [] []
instance EnumContents Project where
enumContents = enumProject
instance EnumContents PackageDbStack where
enumContents pdbs = return $ ScanContents [] [] (packageDbStacks pdbs)
instance EnumContents Sandbox where
enumContents = enumSandbox
instance {-# OVERLAPPING #-} EnumContents a => EnumContents [a] where
enumContents = liftM mconcat . tries . map enumContents
instance {-# OVERLAPS #-} EnumContents FilePath where
enumContents f
| haskellSource f = do
mproj <- liftEIO $ locateProject f
case mproj of
Nothing -> enumContents $ FileModule f Nothing
Just proj -> do
ScanContents _ [(_, mods)] _ <- enumContents proj
return $ ScanContents (filter ((== Just f) . preview (_1 . moduleFile)) mods) [] []
| otherwise = enumDirectory f
instance EnumContents FileContents where
enumContents (FileContents f cts)
| haskellSource f = do
ScanContents [(m, opts, _)] _ _ <- enumContents f
return $ ScanContents [(m, opts, Just cts)] [] []
| otherwise = return mempty
-- | Enum project sources
enumProject :: Project -> ExceptT String IO ScanContents
enumProject p = do
p' <- loadProject p
pdbs <- liftE $ searchPackageDbStack (view projectPath p')
pkgs <- liftM (map $ view packageName) $ browsePackages [] pdbs
let
projOpts :: FilePath -> [String]
projOpts f = concatMap makeOpts $ fileTargets p' f where
makeOpts :: Info -> [String]
makeOpts i = concat [
["-hide-all-packages"],
["-package " ++ view projectName p'],
["-package " ++ dep | dep <- view infoDepends i, dep `elem` pkgs]]
srcs <- projectSources p'
let
mlocs = over each (\src -> over ghcOptions (++ projOpts (view entity src)) . over entity (\f -> FileModule f (Just p')) $ src) srcs
mods <- liftM modulesToScan $ enumContents mlocs
return $ ScanContents [] [(p', mods)] [] -- (sandboxCabals sboxes)
-- | Enum sandbox
enumSandbox :: Sandbox -> ExceptT String IO ScanContents
enumSandbox = sandboxPackageDbStack >=> enumContents
-- | Enum directory modules
enumDirectory :: FilePath -> ExceptT String IO ScanContents
enumDirectory dir = do
cts <- liftException $ traverseDirectory dir
let
projects = filter cabalFile cts
sources = filter haskellSource cts
dirs <- liftE $ filterM doesDirectoryExist cts
sboxes <- liftM catMaybes $ triesMap (liftE . findSandbox) dirs
pdbs <- mapM enumSandbox sboxes
projs <- liftM mconcat $ triesMap (enumProject . project) projects
let
projPaths = map (view projectPath . fst) $ projectsToScan projs
standalone = map (`FileModule` Nothing) $ filter (\s -> not (any (`isParent` s) projPaths)) sources
return $ mconcat [
ScanContents [(s, [], Nothing) | s <- standalone] [] [],
projs,
mconcat pdbs]
-- | Scan project file
scanProjectFile :: [String] -> FilePath -> ExceptT String IO Project
scanProjectFile _ f = do
proj <- (liftE $ locateProject f) >>= maybe (throwError "Can't locate project") return
loadProject proj
-- | Scan module
scanModule :: [(String, String)] -> [String] -> ModuleLocation -> Maybe String -> ExceptT String IO InspectedModule
scanModule defines opts (FileModule f p) mcts = liftM setProj $ inspectFile defines opts f mcts where
setProj =
set (inspectedId . moduleProject) p .
set (inspectionResult . _Right . moduleLocation . moduleProject) p
-- scanModule opts (FileModule f _) = inspectFile opts f >>= traverse infer' where
-- infer' m = tryInfer <|> return m where
-- tryInfer = mapExceptT (withCurrentDirectory (sourceModuleRoot (moduleName m) f)) $
-- runGhcMod defaultOptions $ inferTypes opts Cabal m
scanModule _ opts (InstalledModule c p n) _ = do
pdbs <- liftIO $ getDbs c
browse opts pdbs n p
where
getDbs :: PackageDb -> IO PackageDbStack
getDbs = maybe (return userDb) searchPackageDbStack . preview packageDb
scanModule _ _ (ModuleSource _) _ = throwError "Can inspect only modules in file or cabal"
-- | Scan additional info and modify scanned module. Dones't fail on error, just left module unchanged
scanModify :: ([String] -> PackageDbStack -> Module -> ExceptT String IO Module) -> InspectedModule -> ExceptT String IO InspectedModule
scanModify f im = traverse f' im <|> return im where
f' m = do
pdbs <- liftIO $ case view moduleLocation m of
-- TODO: Get actual sandbox stack
FileModule fpath _ -> searchPackageDbStack fpath
InstalledModule pdb _ _ -> maybe (return userDb) searchPackageDbStack $ preview packageDb pdb
_ -> return userDb
f (fromMaybe [] $ preview (inspection . inspectionOpts) im) pdbs m
-- | Is inspected module up to date?
upToDate :: [String] -> InspectedModule -> ExceptT String IO Bool
upToDate opts (Inspected insp m _) = case m of
FileModule f _ -> liftM (== insp) $ fileInspection f opts
InstalledModule _ _ _ -> return $ insp == browseInspection opts
_ -> return False
-- | Rescan inspected module
rescanModule :: [(String, String)] -> [String] -> InspectedModule -> ExceptT String IO (Maybe InspectedModule)
rescanModule defines opts im = do
up <- upToDate opts im
if up
then return Nothing
else fmap Just $ scanModule defines opts (view inspectedId im) Nothing
-- | Is module new or recently changed
changedModule :: Database -> [String] -> ModuleLocation -> ExceptT String IO Bool
changedModule db opts m = maybe (return True) (liftM not . upToDate opts) m' where
m' = lookupInspected m db
-- | Returns new (to scan) and changed (to rescan) modules
changedModules :: Database -> [String] -> [ModuleToScan] -> ExceptT String IO [ModuleToScan]
changedModules db opts = filterM $ \m -> if isJust (m ^. _3)
then return True
else changedModule db (opts ++ (m ^. _2)) (m ^. _1)