hsdev 0.1.7.3 → 0.1.8.0
raw patch · 28 files changed
+1033/−753 lines, 28 files
Files
- hsdev.cabal +9/−1
- src/HsDev.hs +4/−0
- src/HsDev/Client/Commands.hs +68/−84
- src/HsDev/Database/Update.hs +57/−75
- src/HsDev/Database/Update/Types.hs +7/−17
- src/HsDev/Error.hs +61/−0
- src/HsDev/Inspect.hs +33/−28
- src/HsDev/Project.hs +12/−265
- src/HsDev/Project/Types.hs +268/−0
- src/HsDev/Sandbox.hs +12/−11
- src/HsDev/Scan.hs +41/−39
- src/HsDev/Scan/Browse.hs +37/−33
- src/HsDev/Server/Base.hs +17/−13
- src/HsDev/Server/Commands.hs +9/−15
- src/HsDev/Server/Message.hs +7/−15
- src/HsDev/Server/Types.hs +48/−54
- src/HsDev/Stack.hs +17/−13
- src/HsDev/Symbols.hs +21/−1
- src/HsDev/Symbols/Location.hs +1/−1
- src/HsDev/Symbols/Types.hs +62/−15
- src/HsDev/Tools/Base.hs +10/−9
- src/HsDev/Tools/Ghc/Check.hs +13/−10
- src/HsDev/Tools/Ghc/Types.hs +10/−7
- src/HsDev/Tools/Ghc/Worker.hs +75/−32
- src/HsDev/Tools/GhcMod.hs +7/−5
- src/HsDev/Types.hs +107/−0
- tools/Tool.hs +11/−1
- tools/hsinspect.hs +9/−9
hsdev.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: hsdev -version: 0.1.7.3 +version: 0.1.8.0 synopsis: Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references etc. description: Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc. @@ -43,9 +43,11 @@ HsDev.Database.Update HsDev.Database.Update.Types HsDev.Display + HsDev.Error HsDev.Inspect HsDev.PackageDb HsDev.Project + HsDev.Project.Types HsDev.Scan HsDev.Scan.Browse HsDev.Server.Base @@ -75,6 +77,7 @@ HsDev.Tools.HDocs HsDev.Tools.HLint HsDev.Tools.Types + HsDev.Types HsDev.Util HsDev.Version HsDev.Watcher @@ -182,6 +185,7 @@ aeson-pretty >= 0.7.0, bytestring >= 0.10.0, containers >= 0.5.0, + data-default >= 0.5.0, directory >= 1.2.0, filepath >= 1.4.0, mtl >= 2.2.0, @@ -203,6 +207,7 @@ aeson-pretty >= 0.7.0, bytestring >= 0.10.0, containers >= 0.5.0, + data-default >= 0.5.0, directory >= 1.2.0, ghc >= 7.10.0 && < 7.11.0, haskell-src-exts >= 1.17.0 && < 1.18.0, @@ -226,6 +231,7 @@ aeson-pretty >= 0.7.0, bytestring >= 0.10.0, containers >= 0.5.0, + data-default >= 0.5.0, mtl >= 2.2.0, optparse-applicative >= 0.11, text >= 1.2.0, @@ -245,6 +251,7 @@ aeson-pretty >= 0.7.0, bytestring >= 0.10.0, containers >= 0.5.0, + data-default >= 0.5.0, mtl >= 2.2.0, optparse-applicative >= 0.11, text >= 1.2.0, @@ -263,6 +270,7 @@ aeson >= 0.7.0, aeson-pretty >= 0.7.0, bytestring >= 0.10.0, + data-default >= 0.5.0, directory >= 1.2.0, lens >= 4.8, mtl >= 2.2.0,
src/HsDev.hs view
@@ -1,6 +1,8 @@ module HsDev ( module Data.Default, + module HsDev.Types, + module HsDev.Error, module HsDev.Server.Base, module HsDev.Server.Commands, module HsDev.Client.Commands, @@ -16,6 +18,8 @@ import Data.Default +import HsDev.Types +import HsDev.Error import HsDev.Server.Base import HsDev.Server.Commands import HsDev.Client.Commands
src/HsDev/Client/Commands.hs view
@@ -5,7 +5,6 @@ ) where import Control.Applicative -import Control.Arrow import Control.Concurrent.MVar import Control.Exception (displayException) import Control.Lens (view, preview, _Just) @@ -13,7 +12,8 @@ import Control.Monad.Except import Control.Monad.Reader import qualified Control.Monad.State as State -import Control.Monad.Catch (try, SomeException(..)) +import Control.Monad.Catch (try, catch, SomeException(..)) +import Control.Monad.CatchIO (bracket) import Data.Aeson hiding (Result, Error) import Data.List import Data.Foldable (toList) @@ -22,7 +22,6 @@ import Data.String (fromString) import Data.Text (unpack) import qualified Data.Text as T (isInfixOf, isPrefixOf, isSuffixOf) -import Data.Text.Lens (packed) import System.Directory import System.FilePath import qualified System.Log.Simple as Log @@ -34,6 +33,7 @@ import Text.Format import HsDev.Cache import HsDev.Commands +import HsDev.Error import qualified HsDev.Database.Async as DB import HsDev.Server.Message as M import HsDev.Server.Types @@ -59,11 +59,8 @@ runClient :: (ToJSON a, ServerMonadBase m) => CommandOptions -> ClientM m a -> ServerM m Result runClient copts = mapServerM toResult . runClientM where - toResult :: (ToJSON a, ServerMonadBase m) => ExceptT CommandError (ReaderT CommandOptions m) a -> m Result - toResult act = liftM asResult $ runReaderT (runExceptT act) copts - asResult :: ToJSON a => Either CommandError a -> Result - asResult (Left (CommandError e ds)) = Error e $ M.fromList $ map (first unpack) ds - asResult (Right r') = Result $ toJSON r' + toResult :: (ToJSON a, ServerMonadBase m) => ReaderT CommandOptions m a -> m Result + toResult act = liftM (either Error (Result . toJSON)) $ runReaderT (try act) copts mapServerM :: (Monad m, Monad n) => (m a -> n b) -> ServerM m a -> ServerM n b mapServerM f = ServerM . mapReaderT f . runServerM @@ -72,8 +69,13 @@ runCommand :: ServerMonadBase m => Command -> ClientM m Value runCommand Ping = toValue $ return $ object ["message" .= ("pong" :: String)] -runCommand Listen = toValue $ do +runCommand (Listen (Just r)) = bracket (serverSetLogRules ["/: {}" ~~ r]) serverSetLogRules $ \_ -> runCommand (Listen Nothing) +runCommand (Listen Nothing) = toValue $ do serverListen >>= mapM_ (\msg -> commandNotify (Notification $ object ["message" .= msg])) +runCommand (SetLogConfig rs) = toValue $ do + rules' <- serverSetLogRules rs + Log.log Log.Debug $ "log rules switched from '{}' to '{}'" ~~ intercalate ", " rules' ~~ intercalate ", " rs + Log.log Log.Info $ "log rules updated to: {}" ~~ intercalate ", " rs runCommand (AddData cts) = toValue $ mapM_ updateData cts where updateData (AddedDatabase db) = toValue $ serverUpdateDB db updateData (AddedModule m) = toValue $ serverUpdateDB $ fromModule m @@ -116,11 +118,11 @@ forM_ projects $ \proj -> do DB.clear db (return $ projectDB proj dbval) liftIO $ unwatchProject w proj - dbPDbs <- liftIO $ mapM restorePackageDbStack $ databasePackageDbs dbval + dbPDbs <- mapM restorePackageDbStack $ databasePackageDbs dbval flip State.evalStateT dbPDbs $ do when cabal $ removePackageDbStack userDb forM_ sboxes' $ \sbox -> do - pdbs <- lift $ mapCommandIO $ sandboxPackageDbStack sbox + pdbs <- lift $ sandboxPackageDbStack sbox removePackageDbStack pdbs forM_ files $ \file -> do DB.clear db (return $ filterDB (inFile file) (const False) dbval) @@ -174,121 +176,102 @@ getScope | exports = exportsModule | otherwise = scopeModule - case lookupFile fpath dbval of - Nothing -> commandError "File not found" [] - Just m -> return $ getScope $ resolveOne dbval m + m <- refineSourceModule fpath + return $ getScope $ resolveOne dbval m runCommand (InfoProject (Left projName)) = toValue $ findProject projName runCommand (InfoProject (Right projPath)) = toValue $ liftIO $ searchProject projPath runCommand (InfoSandbox sandbox') = toValue $ liftIO $ searchSandbox sandbox' runCommand (Lookup nm fpath) = toValue $ do dbval <- getSDb fpath - mapCommandIO $ lookupSymbol dbval fpath nm + liftIO $ hsdevLift $ lookupSymbol dbval fpath nm runCommand (Whois nm fpath) = toValue $ do dbval <- getSDb fpath - mapCommandIO $ whois dbval fpath nm + liftIO $ hsdevLift $ whois dbval fpath nm runCommand (ResolveScopeModules sq fpath) = toValue $ do dbval <- getSDb fpath - liftM (filterMatch sq . map (view moduleId)) $ mapCommandIO $ scopeModules dbval fpath + liftM (filterMatch sq . map (view moduleId)) $ liftIO $ hsdevLift $ scopeModules dbval fpath runCommand (ResolveScope sq global fpath) = toValue $ do dbval <- getSDb fpath - liftM (filterMatch sq) $ mapCommandIO $ scope dbval fpath global + liftM (filterMatch sq) $ liftIO $ hsdevLift $ scope dbval fpath global runCommand (Complete input wide fpath) = toValue $ do dbval <- getSDb fpath - mapCommandIO $ completions dbval fpath input wide + liftIO $ hsdevLift $ completions dbval fpath input wide runCommand (Hayoo hq p ps) = toValue $ liftM concat $ forM [p .. p + pred ps] $ \i -> liftM (mapMaybe Hayoo.hayooAsDeclaration . Hayoo.resultResult) $ - mapCommandIO $ Hayoo.hayoo hq (Just i) -runCommand (CabalList packages) = toValue $ mapCommandIO $ Cabal.cabalList packages + liftIO $ hsdevLift $ Hayoo.hayoo hq (Just i) +runCommand (CabalList packages) = toValue $ liftIO $ hsdevLift $ Cabal.cabalList packages runCommand (Lint fs fcts) = toValue $ do - mapCommandIO $ liftM2 (++) + liftIO $ hsdevLift $ liftM2 (++) (liftM concat $ mapM HLint.hlintFile fs) (liftM concat $ mapM (\(FileContents f c) -> HLint.hlintSource f c) fcts) runCommand (Check fs fcts ghcs') = toValue $ Log.scope "check" $ do - dbval <- getDb ghc <- askSession sessionGhc liftIO $ restartWorker ghc let checkSome file fn = Log.scope "checkSome" $ do - pdbs <- liftIO $ searchPackageDbStack file - m <- maybe - (commandError_ $ "File '{}' not found" ~~ file) - return - (lookupFile file dbval) - notes <- inWorkerWith (commandError_ . show) ghc - (runExceptT $ fn pdbs m) - either commandError_ return notes + pdbs <- searchPackageDbStack file + m <- refineSourceModule file + inWorkerWith (hsdevError . GhcError . displayException) ghc (fn pdbs m) liftM concat $ mapM (uncurry checkSome) $ [(f, Check.checkFile ghcs') | f <- fs] ++ [(f, \pdbs m -> Check.checkSource ghcs' pdbs m src) | FileContents f src <- fcts] runCommand (CheckLint fs fcts ghcs') = toValue $ do - dbval <- getDb ghc <- askSession sessionGhc liftIO $ restartWorker ghc let checkSome file fn = do - pdbs <- liftIO $ searchPackageDbStack file - m <- maybe - (commandError_ $ "File '" ++ file ++ "' not found") - return - (lookupFile file dbval) - notes <- inWorkerWith (commandError_ . show) ghc - (runExceptT $ fn pdbs m) - either commandError_ return notes + pdbs <- searchPackageDbStack file + m <- refineSourceModule file + inWorkerWith (hsdevError . GhcError . displayException) ghc (fn pdbs m) checkMsgs <- liftM concat $ mapM (uncurry checkSome) $ [(f, Check.checkFile ghcs') | f <- fs] ++ [(f, \pdbs m -> Check.checkSource ghcs' pdbs m src) | FileContents f src <- fcts] - lintMsgs <- mapCommandIO $ liftM2 (++) + lintMsgs <- liftIO $ hsdevLift $ liftM2 (++) (liftM concat $ mapM HLint.hlintFile fs) (liftM concat $ mapM (\(FileContents f src) -> HLint.hlintSource f src) fcts) return $ checkMsgs ++ lintMsgs runCommand (Types fs fcts ghcs') = toValue $ do - dbval <- getDb ghc <- askSession sessionGhc let cts = [(f, Nothing) | f <- fs] ++ [(f, Just src) | FileContents f src <- fcts] liftM concat $ forM cts $ \(file, msrc) -> do - pdbs <- liftIO $ searchPackageDbStack file - m <- maybe - (commandError_ $ "File '" ++ file ++ "' not found") - return - (lookupFile file dbval) - notes <- inWorkerWith (commandError_ . show) ghc - (runExceptT $ Types.fileTypes ghcs' pdbs m msrc) - either commandError_ return notes -runCommand (GhcMod GhcModLang) = toValue $ mapCommandIO GhcMod.langs -runCommand (GhcMod GhcModFlags) = toValue $ mapCommandIO GhcMod.flags + pdbs <- searchPackageDbStack file + m <- refineSourceModule file + inWorkerWith (hsdevError . GhcError . displayException) ghc (Types.fileTypes ghcs' pdbs m msrc) +runCommand (GhcMod GhcModLang) = toValue $ liftIO $ hsdevLift GhcMod.langs +runCommand (GhcMod GhcModFlags) = toValue $ liftIO $ hsdevLift GhcMod.flags runCommand (GhcMod (GhcModType (Position line column) fpath ghcs')) = toValue $ do ghcmod <- askSession sessionGhcMod dbval <- getDb - pdbs <- liftIO $ searchPackageDbStack fpath - pkgs <- mapCommandIO $ Scan.browsePackages ghcs' pdbs - (fpath', m', _) <- mapCommandIO $ fileCtx dbval fpath - mapCommandIO $ GhcMod.waitMultiGhcMod ghcmod fpath' $ + pdbs <- searchPackageDbStack fpath + pkgs <- Scan.browsePackages ghcs' pdbs + (fpath', m', _) <- liftIO $ hsdevLift $ fileCtx dbval fpath + liftIO $ GhcMod.waitMultiGhcMod ghcmod fpath' $ GhcMod.typeOf (ghcs' ++ moduleOpts pkgs m') pdbs fpath' line column runCommand (GhcMod (GhcModLint fs hlints')) = toValue $ do ghcmod <- askSession sessionGhcMod - mapCommandIO $ liftM concat $ forM fs $ \file -> + liftIO $ liftM concat $ forM fs $ \file -> GhcMod.waitMultiGhcMod ghcmod file $ GhcMod.lint hlints' file runCommand (GhcMod (GhcModCheck fs ghcs')) = toValue $ do ghcmod <- askSession sessionGhcMod dbval <- getDb - mapCommandIO $ liftM concat $ forM fs $ \file -> do + liftM concat $ forM fs $ \file -> do mproj <- liftIO $ locateProject file - pdbs <- liftIO $ searchPackageDbStack file + pdbs <- searchPackageDbStack file pkgs <- Scan.browsePackages ghcs' pdbs - (_, m', _) <- fileCtx dbval file - GhcMod.waitMultiGhcMod ghcmod file $ + (_, m', _) <- liftIO $ hsdevLift $ fileCtx dbval file + liftIO $ GhcMod.waitMultiGhcMod ghcmod file $ GhcMod.check (ghcs' ++ moduleOpts pkgs m') pdbs [file] mproj runCommand (GhcMod (GhcModCheckLint fs ghcs' hlints')) = toValue $ do ghcmod <- askSession sessionGhcMod dbval <- getDb - mapCommandIO $ liftM concat $ forM fs $ \file -> do + liftM concat $ forM fs $ \file -> do mproj <- liftIO $ locateProject file - pdbs <- liftIO $ searchPackageDbStack file + pdbs <- searchPackageDbStack file pkgs <- Scan.browsePackages ghcs' pdbs - (_, m', _) <- fileCtx dbval file - GhcMod.waitMultiGhcMod ghcmod file $ do + (_, m', _) <- liftIO $ hsdevLift $ fileCtx dbval file + liftIO $ GhcMod.waitMultiGhcMod ghcmod file $ do checked <- GhcMod.check (ghcs' ++ moduleOpts pkgs m') pdbs [file] mproj linted <- GhcMod.lint hlints' file return $ checked ++ linted @@ -308,10 +291,10 @@ runFix file | isPure = return $ fst $ doFix file "" | otherwise = do - (corrs', cts') <- liftM (doFix file) $ liftE $ readFileUtf8 file - liftE $ writeFileUtf8 file cts' + (corrs', cts') <- liftM (doFix file) $ liftIO $ readFileUtf8 file + liftIO $ writeFileUtf8 file cts' return corrs' - mapCommandIO $ liftM concat $ mapM runFix files + liftM concat $ mapM runFix files runCommand (GhcEval exprs) = toValue $ do ghci <- askSession sessionGhci async' <- liftIO $ pushTask ghci $ mapM (try . evaluate) exprs @@ -319,7 +302,7 @@ return $ map toValue' res where waitAsync :: CommandMonad m => Async a -> m a - waitAsync a = liftIO (waitCatch a) >>= either (commandError_ . displayException) return + waitAsync a = liftIO (waitCatch a) >>= either (hsdevError . GhcError . displayException) return toValue' :: ToJSON a => Either SomeException a -> Value toValue' (Left (SomeException e)) = object ["fail" .= show e] toValue' (Right s) = toJSON s @@ -339,7 +322,7 @@ TargetDepsOf dep -> liftM inDeps $ findDep dep TargetPackageDb pdb -> return $ inPackageDb pdb TargetCabal -> return $ inPackageDbStack userDb - TargetSandbox sbox -> liftM inPackageDbStack $ findSandbox sbox >>= mapCommandIO . sandboxPackageDbStack + TargetSandbox sbox -> liftM inPackageDbStack $ findSandbox sbox >>= sandboxPackageDbStack TargetPackage pack -> return $ inPackage pack TargetSourced -> return byFile TargetStandalone -> return standalone @@ -359,11 +342,14 @@ findSandbox fpath = do fpath' <- findPath fpath sbox <- liftIO $ S.findSandbox fpath' - maybe - (commandError ("Sandbox {} not found" ~~ fpath') ["sandbox" .= fpath']) - return - sbox + maybe (hsdevError $ FileNotFound fpath') return sbox +-- | Get module by source +refineSourceModule :: (CommandMonad m, Functor m) => FilePath -> m Module +refineSourceModule fpath = do + db' <- getDb + maybe (hsdevError (NotInspected $ FileModule fpath Nothing)) return $ lookupFile fpath db' + -- | Get list of enumerated sandboxes getSandboxes :: (CommandMonad m, Functor m) => [FilePath] -> m [Sandbox] getSandboxes = traverse (findPath >=> findSandbox) @@ -377,7 +363,7 @@ resultProj = refineProject db' (project proj') <|> find ((== proj) . view projectName) (databaseProjects db') - maybe (commandError_ $ "Project {} not found" ~~ proj) return resultProj + maybe (hsdevError $ ProjectNotFound proj) return resultProj where addCabal p | takeExtension p == ".cabal" = p @@ -390,15 +376,14 @@ proj <- msum [ do p <- liftIO (locateProject depPath) - p' <- maybe (commandError_ $ "Project {} not found" ~~ depName) return p - r <- liftIO $ runExceptT $ loadProject p' - either commandError_ return r, + p' <- maybe (hsdevError $ ProjectNotFound depName) return p + liftIO $ loadProject p', findProject depName] let src | takeExtension depPath == ".hs" = Just depPath | otherwise = Nothing - pdbs <- liftIO $ searchPackageDbStack $ view projectPath proj + pdbs <- searchPackageDbStack $ view projectPath proj return (proj, src, pdbs) -- FIXME: Doesn't work for file without project @@ -422,15 +407,14 @@ getSDb :: SessionMonad m => FilePath -> m Database getSDb fpath = do dbval <- getDb - pdbs <- liftIO $ searchPackageDbStack fpath + pdbs <- searchPackageDbStack fpath return $ filterDB (restrictPackageDbStack pdbs) (const True) dbval -mapCommandIO :: CommandMonad m => ExceptT String IO a -> m a -mapCommandIO act = liftIO (runExceptT act) >>= either commandError_ return - -- | Run DB update action updateProcess :: ServerMonadBase m => Update.UpdateOptions -> [Update.UpdateM m ()] -> ClientM m () -updateProcess uopts acts = Update.runUpdate uopts $ sequence_ [act `catchError` (Log.log Log.Error . view (commandErrorMsg . packed)) | act <- acts] +updateProcess uopts acts = Update.runUpdate uopts $ mapM_ runAct acts where + runAct act = catch act onError + onError e = Log.log Log.Error $ "{}" ~~ (e :: HsDevError) -- | Filter declarations with prefix and infix filterMatch :: Symbol a => SearchQuery -> [a] -> [a]
src/HsDev/Database/Update.hs view
@@ -18,9 +18,6 @@ scan, updateEvent, processEvent, - -- * Helpers - liftExceptT, liftExceptT_, - module HsDev.Watcher, module Control.Monad.Except @@ -31,6 +28,7 @@ import Control.Concurrent.Lifted (fork) import Control.DeepSeq import Control.Lens (preview, _Just, view, over, set, _1, mapMOf_, each, (^..), _head, _Right) +import Control.Monad.Catch (catch) import Control.Monad.Except import Control.Monad.Reader import Control.Monad.Writer @@ -45,6 +43,7 @@ import qualified System.Log.Simple as Log import Control.Concurrent.Worker (inWorker, restartWorker) +import HsDev.Error import qualified HsDev.Cache.Structured as Cache import HsDev.Database import HsDev.Database.Async hiding (Event) @@ -54,13 +53,13 @@ import HsDev.Sandbox import HsDev.Stack import HsDev.Symbols -import HsDev.Tools.Ghc.Worker (setCmdOpts) +import HsDev.Tools.Ghc.Worker (setCmdOpts, liftGhc) import HsDev.Tools.Ghc.Types (inferTypes) import HsDev.Tools.HDocs import qualified HsDev.Scan as S import HsDev.Scan.Browse -import HsDev.Util (liftE, isParent, ordNub) -import HsDev.Server.Types (commandNotify, serverWriteCache, serverReadCache, CommandError(..), commandError_) +import HsDev.Util (isParent, ordNub) +import HsDev.Server.Types (commandNotify, serverWriteCache, serverReadCache) import HsDev.Server.Message import HsDev.Database.Update.Types import HsDev.Watcher @@ -111,27 +110,18 @@ return r scanDocs_ :: UpdateMonad m => InspectedModule -> m () scanDocs_ im = do - im' <- - liftExceptT - ("Scanning docs for {} failed: {}" ~~ view inspectedId im) - (S.scanModify (\opts _ -> inspectDocs opts) im) - <|> return im + im' <- (S.scanModify (\opts _ -> liftIO . inspectDocs opts) im) <|> return im updater $ return $ fromModule im' inferModTypes_ :: UpdateMonad m => InspectedModule -> m () inferModTypes_ im = do -- TODO: locate sandbox s <- getSession - im' <- - liftExceptT - ("Inferring types for {} failed: {}" ~~ view inspectedId im) - (S.scanModify (infer' s) im) - <|> return im + im' <- (S.scanModify (infer' s) im) <|> return im updater $ return $ fromModule im' - infer' :: Session -> [String] -> PackageDbStack -> Module -> ExceptT String IO Module + infer' :: UpdateMonad m => Session -> [String] -> PackageDbStack -> Module -> m Module infer' s opts pdbs m = case preview (moduleLocation . moduleFile) m of Nothing -> return m - Just _ -> inWorkerT (sessionGhc s) $ inferTypes opts pdbs m Nothing - inWorkerT w = ExceptT . inWorker w . runExceptT + Just _ -> liftIO $ inWorker (sessionGhc s) $ inferTypes opts pdbs m Nothing -- | Post status postStatus :: UpdateMonad m => Task -> m () @@ -184,8 +174,8 @@ x <- childTask task act x `deepseq` postStatus (set taskStatus StatusOk task) return x - `catchError` - (\c@(CommandError e _) -> postStatus (set taskStatus (StatusError e) task) >> throwError c) + `catch` + (\e -> postStatus (set taskStatus (StatusError e) task) >> hsdevError e) where task = Task { _taskName = action, @@ -210,7 +200,7 @@ scanModule :: UpdateMonad m => [String] -> ModuleLocation -> Maybe String -> m () scanModule opts mloc mcts = runTask "scanning" mloc $ Log.scope "module" $ do defs <- askSession sessionDefines - im <- liftExceptT_ $ S.scanModule defs opts mloc mcts + im <- S.scanModule defs opts mloc mcts updater $ return $ fromModule im _ <- return $ view inspectionResult im return () @@ -231,16 +221,16 @@ -- | Scan source file with contents scanFileContents :: UpdateMonad m => [String] -> FilePath -> Maybe String -> m () -scanFileContents opts fpath mcts = Log.scope "file" $ do +scanFileContents opts fpath mcts = Log.scope "file" $ hsdevLiftIO $ do dbval <- readDB - fpath' <- liftCIO $ canonicalizePath fpath - ex <- liftCIO $ doesFileExist fpath' + fpath' <- liftIO $ canonicalizePath fpath + ex <- liftIO $ doesFileExist fpath' mlocs <- if ex then do mloc <- case lookupFile fpath' dbval of Just m -> return $ view moduleLocation m Nothing -> do - mproj <- liftCIO $ locateProject fpath' + mproj <- liftIO $ locateProject fpath' return $ FileModule fpath' mproj return [(mloc, [], mcts)] else return [] @@ -268,8 +258,8 @@ -- | Prepare sandbox for scanning. This is used for stack project to build & configure. prepareSandbox :: UpdateMonad m => Sandbox -> m () prepareSandbox sbox@(Sandbox StackWork fpath) = Log.scope "prepare" $ runTasks [ - runTask "building dependencies" sbox $ void $ liftIO $ runMaybeT $ buildDeps myaml, - runTask "configuring" sbox $ void $ liftIO $ runMaybeT $ configure myaml] + runTask "building dependencies" sbox $ void $ buildDeps myaml, + runTask "configuring" sbox $ void $ configure myaml] where myaml = Just $ takeDirectory fpath </> "stack.yaml" prepareSandbox _ = return () @@ -279,7 +269,7 @@ scanSandbox opts sbox = Log.scope "sandbox" $ do dbval <- readDB prepareSandbox sbox - pdbs <- liftExceptT_ $ sandboxPackageDbStack sbox + pdbs <- sandboxPackageDbStack sbox let scannedDbs = databasePackageDbs dbval unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks pdbs @@ -293,10 +283,10 @@ watch (\w -> watchPackageDb w pdbs opts) mlocs <- liftM (filter (\mloc -> preview modulePackageDb mloc == Just (topPackageDb pdbs))) $ - liftExceptT_ $ listModules opts pdbs + listModules opts pdbs scan (Cache.loadPackageDb (topPackageDb pdbs)) (packageDbDB (topPackageDb pdbs)) ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do - ms <- liftExceptT_ $ browseModules opts pdbs (mlocs' ^.. each . _1) - docs <- liftExceptT_ $ hdocsCabal pdbs opts + ms <- browseModules opts pdbs (mlocs' ^.. each . _1) + docs <- liftIO $ hsdevLiftWith (ToolError "hdocs") $ hdocsCabal pdbs opts updater $ return $ mconcat $ map (fromModule . fmap (setDocs' docs)) ms where setDocs' :: Map String (Map String String) -> Module -> Module @@ -304,7 +294,7 @@ -- | Scan project file scanProjectFile :: UpdateMonad m => [String] -> FilePath -> m Project -scanProjectFile opts cabal = runTask "scanning" cabal $ liftExceptT_ $ S.scanProjectFile opts cabal +scanProjectFile opts cabal = runTask "scanning" cabal $ S.scanProjectFile opts cabal -- | Scan project and related package-db stack scanProjectStack :: UpdateMonad m => [String] -> FilePath -> m () @@ -319,7 +309,7 @@ scanProject opts cabal = runTask "scanning" (project cabal) $ Log.scope "project" $ do proj <- scanProjectFile opts cabal watch (\w -> watchProject w proj opts) - S.ScanContents _ [(_, sources)] _ <- liftExceptT_ $ S.enumProject proj + S.ScanContents _ [(_, sources)] _ <- S.enumProject proj scan (Cache.loadProject $ view projectCabal proj) (projectDB proj) sources opts $ \ms -> do scanModules opts ms updater $ return $ fromProject proj @@ -327,7 +317,7 @@ -- | Scan directory for source files and projects scanDirectory :: UpdateMonad m => [String] -> FilePath -> m () scanDirectory opts dir = runTask "scanning" dir $ Log.scope "directory" $ do - S.ScanContents standSrcs projSrcs pdbss <- liftExceptT_ $ S.enumDirectory dir + S.ScanContents standSrcs projSrcs pdbss <- S.enumDirectory dir runTasks [scanProject opts (view projectCabal p) | (p, _) <- projSrcs] runTasks $ map (scanPackageDb opts) pdbss -- TODO: Don't rescan mapMOf_ (each . _1) (watch . flip watchModule) standSrcs @@ -358,37 +348,41 @@ scanDocs ims = do -- w <- liftIO $ ghcWorker ["-haddock"] (return ()) w <- askSession sessionGhc - liftIO $ do - restartWorker w - inWorker w $ setCmdOpts ["-haddock"] - runTasks $ map (scanDocs' w) ims + liftIO $ restartWorker w + runTasks $ map scanDocs' ims where - scanDocs' w im = runTask "scanning docs" (view inspectedId im) $ Log.scope "docs" $ do - Log.log Log.Trace $ "Scanning docs for {}" ~~ view inspectedId im - im' <- - liftExceptT - ("Scanning docs for {} failed: {}" ~~ view inspectedId im) - (S.scanModify (\opts _ -> inWorkerT w . inspectDocsGhc opts) im) - <|> return im - Log.log Log.Trace $ "Docs for {} updated: documented {} declarations" ~~ - view inspectedId im' ~~ - length (im' ^.. inspectionResult . _Right . moduleDeclarations . each . declarationDocs . _Just) - updater $ return $ fromModule im' - inWorkerT w = ExceptT . inWorker w . runExceptT + scanDocs' im + | not $ hasTag RefinedDocsTag im = runTask "scanning docs" (view inspectedId im) $ Log.scope "docs" $ do + Log.log Log.Trace $ "Scanning docs for {}" ~~ view inspectedId im + im' <- (liftM (setTag RefinedDocsTag) $ S.scanModify doScan im) + <|> return im + Log.log Log.Trace $ "Docs for {} updated: documented {} declarations" ~~ + view inspectedId im' ~~ + length (im' ^.. inspectionResult . _Right . moduleDeclarations . each . declarationDocs . _Just) + updater $ return $ fromModule im' + | otherwise = Log.log Log.Trace $ "Docs for {} already scanned" ~~ view inspectedId im + doScan opts _ m = do + w <- askSession sessionGhc + pdbs <- case view moduleLocation m of + FileModule fpath _ -> searchPackageDbStack fpath + InstalledModule pdb _ _ -> restorePackageDbStack pdb + ModuleSource _ -> return userDb + pkgs <- browsePackages opts pdbs + liftIO $ inWorker w $ do + setCmdOpts ("-haddock" : (opts ++ moduleOpts pkgs m)) + liftGhc $ inspectDocsGhc (opts ++ moduleOpts pkgs m) m inferModTypes :: UpdateMonad m => [InspectedModule] -> m () inferModTypes = runTasks . map inferModTypes' where - inferModTypes' im = runTask "inferring types" (view inspectedId im) $ Log.scope "docs" $ do - w <- askSession sessionGhc - Log.log Log.Trace $ "Inferring types for {}" ~~ view inspectedId im - im' <- - liftExceptT - ("Inferring types for {} failed: {}" ~~ view inspectedId im) - (S.scanModify (\opts cabal m -> inWorkerT w (inferTypes opts cabal m Nothing)) im) - <|> return im - Log.log Log.Trace $ "Types for {} inferred" ~~ view inspectedId im - updater $ return $ fromModule im' - inWorkerT w = ExceptT . inWorker w . runExceptT + inferModTypes' im + | not $ hasTag InferredTypesTag im = runTask "inferring types" (view inspectedId im) $ Log.scope "docs" $ do + w <- askSession sessionGhc + Log.log Log.Trace $ "Inferring types for {}" ~~ view inspectedId im + im' <- (liftM (setTag InferredTypesTag) $ S.scanModify (\opts cabal m -> liftIO (inWorker w (inferTypes opts cabal m Nothing))) im) + <|> return im + Log.log Log.Trace $ "Types for {} inferred" ~~ view inspectedId im + updater $ return $ fromModule im' + | otherwise = Log.log Log.Trace $ "Types for {} already inferred" ~~ view inspectedId im -- | Generic scan function. Reads cache only if data is not already loaded, removes obsolete modules and rescans changed modules. scan :: UpdateMonad m @@ -407,7 +401,7 @@ dbval <- getCache cache' part' let obsolete = filterDB (\m -> view moduleIdLocation m `notElem` (mlocs ^.. each . _1)) (const False) dbval - changed <- liftExceptT "Getting changed modules failed: {}" $ S.changedModules dbval opts mlocs + changed <- liftIO $ S.changedModules dbval opts mlocs cleaner $ return obsolete act changed @@ -445,18 +439,6 @@ preview (inspection . inspectionOpts) $ getInspected dbval m scanFile opts $ view eventPath e | otherwise = return () - -liftExceptT_ :: CommandMonad m => ExceptT String IO a -> m a -liftExceptT_ = liftExceptT "{}" - -liftExceptT :: CommandMonad m => Format -> ExceptT String IO a -> m a -liftExceptT msg act = liftIO (runExceptT act) >>= either onError return where - onError e = do - Log.log Log.Error $ msg ~~ e - commandError_ $ msg ~~ e - -liftCIO ::CommandMonad m => IO a -> m a -liftCIO = liftExceptT "Exception during IO action: {}" . liftE processEvent :: UpdateOptions -> Watched -> Event -> ClientM IO () processEvent uopts w e = runUpdate uopts $ updateEvent w e
src/HsDev/Database/Update/Types.hs view
@@ -10,6 +10,7 @@ import Control.Applicative import Control.Lens (makeLenses) import Control.Monad.Base +import Control.Monad.Catch import Control.Monad.CatchIO import Control.Monad.Except import Control.Monad.Reader @@ -19,22 +20,23 @@ import Data.Default import qualified System.Log.Simple as Log -import HsDev.Server.Types (ServerMonadBase, Session(..), CommandOptions(..), SessionMonad(..), askSession, CommandError, CommandMonad(..), ClientM(..)) +import HsDev.Server.Types (ServerMonadBase, Session(..), CommandOptions(..), SessionMonad(..), askSession, CommandMonad(..), ClientM(..)) import HsDev.Symbols +import HsDev.Types import HsDev.Util ((.::)) -data Status = StatusWorking | StatusOk | StatusError String +data Status = StatusWorking | StatusOk | StatusError HsDevError instance ToJSON Status where toJSON StatusWorking = toJSON ("working" :: String) toJSON StatusOk = toJSON ("ok" :: String) - toJSON (StatusError e) = toJSON $ object ["error" .= e] + toJSON (StatusError e) = toJSON e instance FromJSON Status where parseJSON v = msum $ map ($ v) [ withText "status" $ \t -> guard (t == "working") *> return StatusWorking, withText "status" $ \t -> guard (t == "ok") *> return StatusOk, - withObject "status" $ \obj -> StatusError <$> (obj .:: "error"), + liftM StatusError . parseJSON, fail "invalid status"] data Progress = Progress { @@ -88,7 +90,7 @@ type UpdateMonad m = (CommandMonad m, MonadReader UpdateOptions m, MonadWriter [ModuleLocation] m) newtype UpdateM m a = UpdateM { runUpdateM :: ReaderT UpdateOptions (WriterT [ModuleLocation] (ClientM m)) a } - deriving (Applicative, Monad, MonadIO, MonadCatchIO, Functor, MonadReader UpdateOptions, MonadWriter [ModuleLocation]) + deriving (Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadThrow, MonadCatch, MonadCatchIO, Functor, MonadReader UpdateOptions, MonadWriter [ModuleLocation]) instance MonadTrans UpdateM where lift = UpdateM . lift . lift . lift @@ -101,18 +103,6 @@ instance ServerMonadBase m => CommandMonad (UpdateM m) where getOptions = UpdateM $ lift $ lift getOptions - -instance Monad m => MonadError CommandError (UpdateM m) where - throwError = UpdateM . lift . lift . throwError - catchError act handler = UpdateM $ catchError (runUpdateM act) (runUpdateM . handler) - -instance Monad m => Alternative (UpdateM m) where - empty = UpdateM empty - x <|> y = UpdateM $ runUpdateM x <|> runUpdateM y - -instance Monad m => MonadPlus (UpdateM m) where - mzero = UpdateM mzero - mplus l r = UpdateM $ runUpdateM l `mplus` runUpdateM r instance MonadBase b m => MonadBase b (UpdateM m) where liftBase = UpdateM . liftBase
+ src/HsDev/Error.hs view
@@ -0,0 +1,61 @@+module HsDev.Error ( + hsdevError, hsdevOtherError, hsdevLift, hsdevLiftWith, hsdevCatch, hsdevExcept, hsdevLiftIO, hsdevLiftIOWith, hsdevIgnore, + hsdevHandle, hsdevLog, + + module HsDev.Types + ) where + +import Prelude hiding (log) + +import Control.Exception (IOException) +import Control.Monad.Catch +import Control.Monad.Except +import Data.String (fromString) +import System.Log.Simple (MonadLog(..), log, Level) + +import HsDev.Types + +-- | Throw `HsDevError` +hsdevError :: MonadThrow m => HsDevError -> m a +hsdevError = throwM + +-- | Throw as `OtherError` +hsdevOtherError :: (Exception e, MonadThrow m) => e -> m a +hsdevOtherError = hsdevError . OtherError . displayException + +-- | Throw as `OtherError` +hsdevLift :: MonadThrow m => ExceptT String m a -> m a +hsdevLift = hsdevLiftWith OtherError + +-- | Throw as some `HsDevError` +hsdevLiftWith :: MonadThrow m => (String -> HsDevError) -> ExceptT String m a -> m a +hsdevLiftWith ctor act = runExceptT act >>= either (hsdevError . ctor) return + +hsdevCatch :: MonadCatch m => m a -> m (Either HsDevError a) +hsdevCatch = try + +hsdevExcept :: MonadCatch m => m a -> ExceptT HsDevError m a +hsdevExcept = ExceptT . hsdevCatch + +-- | Rethrow IO exceptions as `HsDevError` +hsdevLiftIO :: MonadCatch m => m a -> m a +hsdevLiftIO = hsdevLiftIOWith IOFailed + +-- | Rethrow IO exceptions +hsdevLiftIOWith :: MonadCatch m => (String -> HsDevError) -> m a -> m a +hsdevLiftIOWith ctor act = catch act onError where + onError :: MonadThrow m => IOException -> m a + onError = hsdevError . ctor . displayException + +-- | Ignore hsdev exception +hsdevIgnore :: MonadCatch m => a -> m a -> m a +hsdevIgnore v act = hsdevCatch act >>= either (const $ return v) return + +-- | Handle hsdev exception +hsdevHandle :: MonadCatch m => (HsDevError -> m a) -> m a -> m a +hsdevHandle h act = hsdevCatch act >>= either h return + +-- | Log hsdev exception and rethrow +hsdevLog :: (MonadLog m, MonadCatch m) => Level -> m a -> m a +hsdevLog lev act = hsdevCatch act >>= either logError return where + logError e = log lev (fromString $ show e) >> hsdevError e
src/HsDev/Inspect.hs view
@@ -38,8 +38,10 @@ import Data.Generics.Uniplate.Data import HDocs.Haddock +import HsDev.Error import HsDev.Symbols import HsDev.Tools.Base+import HsDev.Tools.Ghc.Worker () import HsDev.Tools.HDocs (hdocsy, hdocs, hdocsProcess) import HsDev.Util @@ -318,28 +320,28 @@ addDocs docsMap = over moduleDeclarations (map $ addDoc docsMap) -- | Extract files docs and set them to declarations-inspectDocsChunk :: [String] -> [Module] -> ExceptT String IO [Module]-inspectDocsChunk opts ms = do- docsMaps <- liftE $ hdocsy (map (view moduleLocation) ms) opts+inspectDocsChunk :: [String] -> [Module] -> IO [Module]+inspectDocsChunk opts ms = hsdevLiftIOWith (ToolError "hdocs") $ do+ docsMaps <- hdocsy (map (view moduleLocation) ms) opts return $ zipWith addDocs docsMaps ms -- | Extract file docs and set them to module declarations-inspectDocs :: [String] -> Module -> ExceptT String IO Module+inspectDocs :: [String] -> Module -> IO Module inspectDocs opts m = do let hdocsWorkaround = False- docsMap <- liftE $ if hdocsWorkaround+ docsMap <- hsdevLiftIOWith (ToolError "hdocs") $ if hdocsWorkaround then hdocsProcess (fromMaybe (T.unpack $ view moduleName m) (preview (moduleLocation . moduleFile) m)) opts else liftM Just $ hdocs (view moduleLocation m) opts return $ maybe id addDocs docsMap m -- | Like @inspectDocs@, but in @Ghc@ monad-inspectDocsGhc :: [String] -> Module -> ExceptT String Ghc Module+inspectDocsGhc :: [String] -> Module -> Ghc Module inspectDocsGhc opts m = case view moduleLocation m of FileModule fpath _ -> do- docsMap <- liftM (fmap (formatDocs . snd) . listToMaybe) $ readSourcesGhc opts [fpath]+ docsMap <- liftM (fmap (formatDocs . snd) . listToMaybe) $ hsdevLift $ readSourcesGhc opts [fpath] return $ maybe id addDocs docsMap m- _ -> throwError "Can inspect only source file docs"+ _ -> hsdevError $ ModuleNotSource (view moduleLocation m) -- | Inspect contents inspectContents :: String -> [(String, String)] -> [String] -> String -> ExceptT String IO InspectedModule@@ -354,17 +356,17 @@ contentsInspection _ _ = return InspectionNone -- crc or smth -- | Inspect file-inspectFile :: [(String, String)] -> [String] -> FilePath -> Maybe String -> ExceptT String IO InspectedModule-inspectFile defines opts file mcts = do- proj <- liftE $ locateProject file- absFilename <- liftE $ Dir.canonicalizePath file- ex <- liftE $ Dir.doesFileExist absFilename- unless ex $ throwError $ "File '" ++ absFilename ++ "' doesn't exist"+inspectFile :: [(String, String)] -> [String] -> FilePath -> Maybe String -> IO InspectedModule+inspectFile defines opts file mcts = hsdevLiftIO $ do+ proj <- locateProject file+ absFilename <- Dir.canonicalizePath file+ ex <- Dir.doesFileExist absFilename+ unless ex $ hsdevError $ FileNotFound absFilename inspect (FileModule absFilename proj) ((if isJust mcts then fileContentsInspection else fileInspection) absFilename opts) $ do -- docsMap <- liftE $ if hdocsWorkaround -- then hdocsProcess absFilename opts -- else liftM Just $ hdocs (FileModule absFilename Nothing) opts- forced <- ExceptT $ E.handle onError $ do+ forced <- hsdevLiftWith InspectError $ ExceptT $ E.handle onError $ do analyzed <- liftM (\s -> analyzeModule exts (Just absFilename) s <|> analyzeModule_ exts (Just absFilename) s) $ maybe (readFileUtf8 absFilename >>= preprocess_ defines exts file) return mcts force analyzed `deepseq` return analyzed@@ -377,40 +379,40 @@ exts = mapMaybe flagExtension opts -- | File inspection data-fileInspection :: FilePath -> [String] -> ExceptT String IO Inspection+fileInspection :: FilePath -> [String] -> IO Inspection fileInspection f opts = do- tm <- liftE $ Dir.getModificationTime f+ tm <- Dir.getModificationTime f return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ sort $ ordNub opts -- | File contents inspection data-fileContentsInspection :: FilePath -> [String] -> ExceptT String IO Inspection+fileContentsInspection :: FilePath -> [String] -> IO Inspection fileContentsInspection _ opts = do- tm <- liftE getPOSIXTime+ tm <- getPOSIXTime return $ InspectionAt tm $ sort $ ordNub opts -- | Enumerate project dirs-projectDirs :: Project -> ExceptT String IO [Extensions FilePath]+projectDirs :: Project -> IO [Extensions FilePath] projectDirs p = do p' <- loadProject p return $ ordNub $ map (fmap (normalise . (view projectPath p' </>))) $ maybe [] sourceDirs $ view projectDescription p' -- | Enumerate project source files-projectSources :: Project -> ExceptT String IO [Extensions FilePath]+projectSources :: Project -> IO [Extensions FilePath] projectSources p = do dirs <- projectDirs p let enumCabals = liftM (map takeDirectory . filter cabalFile) . traverseDirectory dirs' = map (view entity) dirs -- enum inner projects and dont consider them as part of this project- subProjs <- liftM (delete (view projectPath p) . ordNub . concat) $ triesMap (liftE . enumCabals) dirs'+ subProjs <- liftM (delete (view projectPath p) . ordNub . concat) $ triesMap (enumCabals) dirs' let enumHs = liftM (filter thisProjectSource) . traverseDirectory thisProjectSource h = haskellSource h && not (any (`isParent` h) subProjs)- liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (liftE . enumHs)) dirs+ liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (enumHs)) dirs -- | Inspect project-inspectProject :: [(String, String)] -> [String] -> Project -> ExceptT String IO (Project, [InspectedModule])-inspectProject defines opts p = do+inspectProject :: [(String, String)] -> [String] -> Project -> IO (Project, [InspectedModule])+inspectProject defines opts p = hsdevLiftIO $ do p' <- loadProject p srcs <- projectSources p' modules <- mapM inspectFile' srcs@@ -433,14 +435,17 @@ onIO :: E.IOException -> IO [(String, String)] onIO _ = return [] -preprocess :: [(String, String)] -> FilePath -> String -> ExceptT String IO String+preprocess :: [(String, String)] -> FilePath -> String -> IO String preprocess defines fpath cts = do- cts' <- liftE $ Cpphs.cppIfdef fpath defines [] Cpphs.defaultBoolOptions cts+ cts' <- E.catch (Cpphs.cppIfdef fpath defines [] Cpphs.defaultBoolOptions cts) onIOError return $ unlines $ map snd cts'+ where+ onIOError :: E.IOException -> IO [(Cpphs.Posn, String)]+ onIOError _ = return [] preprocess_ :: [(String, String)] -> [String] -> FilePath -> String -> IO String preprocess_ defines exts fpath cts- | hasCPP = runExceptT (preprocess defines fpath cts) >>= either (const $ return cts) return+ | hasCPP = preprocess defines fpath cts | otherwise = return cts where exts' = map H.parseExtension exts ++ maybe [] snd (H.readExtensions cts)
src/HsDev/Project.hs view
@@ -1,19 +1,10 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} - module HsDev.Project ( - Project(..), - ProjectDescription(..), Target(..), Library(..), Executable(..), Test(..), Info(..), infoSourceDirsDef, + module HsDev.Project.Types, + + infoSourceDirsDef, readProject, loadProject, - project, - Extensions(..), withExtensions, + withExtensions, infos, inTarget, fileTargets, findSourceDir, sourceDirs, - - projectName, projectPath, projectCabal, projectDescription, projectVersion, projectLibrary, projectExecutables, projectTests, - libraryModules, libraryBuildInfo, - executableName, executablePath, executableBuildInfo, - testName, testEnabled, testBuildInfo, - infoDepends, infoLanguage, infoExtensions, infoGHCOptions, infoSourceDirs, - extensions, ghcOptions, entity, targetOpts, -- * Helpers @@ -22,193 +13,24 @@ ) where import Control.Arrow -import Control.DeepSeq (NFData(..)) -import Control.Lens (makeLenses, Simple, Lens, view, lens) -import Control.Exception +import Control.Lens (Simple, Lens, view, lens) import Control.Monad.Except -import Data.Aeson -import Data.Aeson.Types (Parser) import Data.List import Data.Maybe -import Data.Monoid -import Data.Ord import Data.Version (showVersion) import Distribution.Compiler (CompilerFlavor(GHC)) import qualified Distribution.Package as P import qualified Distribution.PackageDescription as PD import Distribution.PackageDescription.Parse import Distribution.ModuleName (components) -import Distribution.Text (display, simpleParse) -import qualified Distribution.Text (Text) +import Distribution.Text (display) import Language.Haskell.Extension import System.FilePath +import HsDev.Project.Types +import HsDev.Error import HsDev.Util --- | Cabal project -data Project = Project { - _projectName :: String, - _projectPath :: FilePath, - _projectCabal :: FilePath, - _projectDescription :: Maybe ProjectDescription } - deriving (Read) - -instance NFData Project where - rnf (Project n p c _) = rnf n `seq` rnf p `seq` rnf c - -instance Eq Project where - l == r = _projectCabal l == _projectCabal r - -instance Ord Project where - compare l r = compare (_projectName l, _projectCabal l) (_projectName r, _projectCabal r) - -instance Show Project where - show p = unlines $ [ - "project " ++ _projectName p, - "\tcabal: " ++ _projectCabal p, - "\tdescription:"] ++ concatMap (map (tab 2) . lines . show) (maybeToList $ _projectDescription p) - -instance ToJSON Project where - toJSON p = object [ - "name" .= _projectName p, - "path" .= _projectPath p, - "cabal" .= _projectCabal p, - "description" .= _projectDescription p] - -instance FromJSON Project where - parseJSON = withObject "project" $ \v -> Project <$> - v .:: "name" <*> - v .:: "path" <*> - v .:: "cabal" <*> - v .:: "description" - -data ProjectDescription = ProjectDescription { - _projectVersion :: String, - _projectLibrary :: Maybe Library, - _projectExecutables :: [Executable], - _projectTests :: [Test] } - deriving (Eq, Read) - -instance Show ProjectDescription where - show pd = unlines $ - concatMap (lines . show) (maybeToList (_projectLibrary pd)) ++ - concatMap (lines . show) (_projectExecutables pd) ++ - concatMap (lines . show) (_projectTests pd) - -instance ToJSON ProjectDescription where - toJSON d = object [ - "version" .= _projectVersion d, - "library" .= _projectLibrary d, - "executables" .= _projectExecutables d, - "tests" .= _projectTests d] - -instance FromJSON ProjectDescription where - parseJSON = withObject "project description" $ \v -> ProjectDescription <$> - v .:: "version" <*> - v .:: "library" <*> - v .:: "executables" <*> - v .:: "tests" - -class Target a where - buildInfo :: a -> Info - --- | Library in project -data Library = Library { - _libraryModules :: [[String]], - _libraryBuildInfo :: Info } - deriving (Eq, Read) - -instance Target Library where - buildInfo = _libraryBuildInfo - -instance Show Library where - show l = unlines $ - ["library", "\tmodules:"] ++ - (map (tab 2 . intercalate ".") $ _libraryModules l) ++ - (map (tab 1) . lines . show $ _libraryBuildInfo l) - -instance ToJSON Library where - toJSON l = object [ - "modules" .= fmap (intercalate ".") (_libraryModules l), - "info" .= _libraryBuildInfo l] - -instance FromJSON Library where - parseJSON = withObject "library" $ \v -> Library <$> (fmap splitModule <$> v .:: "modules") <*> v .:: "info" where - splitModule :: String -> [String] - splitModule = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break (== '.')) - --- | Executable -data Executable = Executable { - _executableName :: String, - _executablePath :: FilePath, - _executableBuildInfo :: Info } - deriving (Eq, Read) - -instance Target Executable where - buildInfo = _executableBuildInfo - -instance Show Executable where - show e = unlines $ - ["executable " ++ _executableName e, "\tpath: " ++ _executablePath e] ++ - (map (tab 1) . lines . show $ _executableBuildInfo e) - -instance ToJSON Executable where - toJSON e = object [ - "name" .= _executableName e, - "path" .= _executablePath e, - "info" .= _executableBuildInfo e] - -instance FromJSON Executable where - parseJSON = withObject "executable" $ \v -> Executable <$> - v .:: "name" <*> - v .:: "path" <*> - v .:: "info" - --- | Test -data Test = Test { - _testName :: String, - _testEnabled :: Bool, - _testBuildInfo :: Info } - deriving (Eq, Read) - -instance Target Test where - buildInfo = _testBuildInfo - -instance Show Test where - show t = unlines $ - ["test " ++ _testName t, "\tenabled: " ++ show (_testEnabled t)] ++ - (map (tab 1) . lines . show $ _testBuildInfo t) - -instance ToJSON Test where - toJSON t = object [ - "name" .= _testName t, - "enabled" .= _testEnabled t, - "info" .= _testBuildInfo t] - -instance FromJSON Test where - parseJSON = withObject "test" $ \v -> Test <$> - v .:: "name" <*> - v .:: "enabled" <*> - v .:: "info" - --- | Build info -data Info = Info { - _infoDepends :: [String], - _infoLanguage :: Maybe Language, - _infoExtensions :: [Extension], - _infoGHCOptions :: [String], - _infoSourceDirs :: [FilePath] } - deriving (Eq, Read) - -instance Monoid Info where - mempty = Info [] Nothing [] [] [] - mappend l r = Info - (ordNub $ _infoDepends l ++ _infoDepends r) - (getFirst $ First (_infoLanguage l) `mappend` First (_infoLanguage r)) - (_infoExtensions l ++ _infoExtensions r) - (_infoGHCOptions l ++ _infoGHCOptions r) - (ordNub $ _infoSourceDirs l ++ _infoSourceDirs r) - -- | infoSourceDirs lens with default infoSourceDirsDef :: Simple Lens Info [FilePath] infoSourceDirsDef = lens get' set' where @@ -218,33 +40,6 @@ set' i ["."] = i { _infoSourceDirs = [] } set' i dirs = i { _infoSourceDirs = dirs } -instance Show Info where - show i = unlines $ lang ++ exts ++ opts ++ sources where - lang = maybe [] (\l -> ["default-language: " ++ display l]) $ _infoLanguage i - exts - | null (_infoExtensions i) = [] - | otherwise = "extensions:" : map (tab 1 . display) (_infoExtensions i) - opts - | null (_infoGHCOptions i) = [] - | otherwise = "ghc-options:" : map (tab 1) (_infoGHCOptions i) - sources = "source-dirs:" : (map (tab 1) $ _infoSourceDirs i) - -instance ToJSON Info where - toJSON i = object [ - "build-depends" .= _infoDepends i, - "language" .= fmap display (_infoLanguage i), - "extensions" .= map display (_infoExtensions i), - "ghc-options" .= _infoGHCOptions i, - "source-dirs" .= _infoSourceDirs i] - -instance FromJSON Info where - parseJSON = withObject "info" $ \v -> Info <$> - v .: "build-depends" <*> - ((v .:: "language") >>= traverse (parseDT "Language")) <*> - ((v .:: "extensions") >>= traverse (parseDT "Extension")) <*> - v .:: "ghc-options" <*> - v .:: "source-dirs" - -- | Analyze cabal file analyzeCabal :: String -> Either String ProjectDescription analyzeCabal source = case liftM flattenDescr $ parsePackageDescription source of @@ -287,56 +82,20 @@ flattenBranch (_, t, mb) = flattenTree f t : map (flattenTree f) (maybeToList mb) -- | Read project info from .cabal -readProject :: FilePath -> ExceptT String IO Project +readProject :: FilePath -> IO Project readProject file = do - source <- ExceptT $ handle (\e -> return (Left ("IO error: " ++ show (e :: IOException)))) (fmap Right $ readFile file) - length source `seq` either throwError (return . mkProject) $ analyzeCabal source + source <- readFile file + length source `seq` either (hsdevError . InspectCabalError file) (return . mkProject) $ analyzeCabal source where mkProject desc = (project file) { _projectDescription = Just desc } -- | Load project description -loadProject :: Project -> ExceptT String IO Project +loadProject :: Project -> IO Project loadProject p | isJust (_projectDescription p) = return p | otherwise = readProject (_projectCabal p) --- | Make project by .cabal file -project :: FilePath -> Project -project file = Project { - _projectName = takeBaseName (takeDirectory cabal), - _projectPath = takeDirectory cabal, - _projectCabal = cabal, - _projectDescription = Nothing } - where - file' = dropTrailingPathSeparator $ normalise file - cabal - | takeExtension file' == ".cabal" = file' - | otherwise = file' </> (takeBaseName file' <.> "cabal") - --- | Entity with project extensions -data Extensions a = Extensions { - _extensions :: [Extension], - _ghcOptions :: [String], - _entity :: a } - deriving (Eq, Read, Show) - -instance Ord a => Ord (Extensions a) where - compare = comparing _entity - -instance Functor Extensions where - fmap f (Extensions e o x) = Extensions e o (f x) - -instance Applicative Extensions where - pure = Extensions [] [] - (Extensions l lo f) <*> (Extensions r ro x) = Extensions (ordNub $ l ++ r) (ordNub $ lo ++ ro) (f x) - -instance Foldable Extensions where - foldMap f (Extensions _ _ x) = f x - -instance Traversable Extensions where - traverse f (Extensions e o x) = Extensions e o <$> f x - -- | Extensions for target withExtensions :: a -> Info -> Extensions a withExtensions x i = Extensions { @@ -376,10 +135,6 @@ sourceDirs = ordNub . concatMap dirs . infos where dirs i = map (`withExtensions` i) $ view infoSourceDirsDef i -parseDT :: Distribution.Text.Text a => String -> String -> Parser a -parseDT typeName v = maybe err return (simpleParse v) where - err = fail $ "Can't parse " ++ typeName ++ ": " ++ v - -- | Get options for specific target targetOpts :: Info -> [String] targetOpts info' = concat [ @@ -402,11 +157,3 @@ -- | Extensions as opts to GHC extensionsOpts :: Extensions a -> [String] extensionsOpts e = map (extensionFlag . showExtension) (_extensions e) ++ _ghcOptions e - -makeLenses ''Project -makeLenses ''ProjectDescription -makeLenses ''Library -makeLenses ''Executable -makeLenses ''Test -makeLenses ''Info -makeLenses ''Extensions
+ src/HsDev/Project/Types.hs view
@@ -0,0 +1,268 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} + +module HsDev.Project.Types ( + Project(..), projectName, projectPath, projectCabal, projectDescription, project, + ProjectDescription(..), projectVersion, projectLibrary, projectExecutables, projectTests, + Target(..), + Library(..), libraryModules, libraryBuildInfo, + Executable(..), executableName, executablePath, executableBuildInfo, + Test(..), testName, testEnabled, testBuildInfo, + Info(..), infoDepends, infoLanguage, infoExtensions, infoGHCOptions, infoSourceDirs, + Extensions(..), extensions, ghcOptions, entity, + ) where + +import Control.Arrow +import Control.DeepSeq (NFData(..)) +import Control.Lens (makeLenses) +import Data.Aeson +import Data.Aeson.Types (Parser) +import Data.List +import Data.Maybe +import Data.Monoid +import Data.Ord +import Distribution.Text (display, simpleParse) +import qualified Distribution.Text (Text) +import Language.Haskell.Extension +import Text.Format +import System.FilePath + +import HsDev.Util + +-- | Cabal project +data Project = Project { + _projectName :: String, + _projectPath :: FilePath, + _projectCabal :: FilePath, + _projectDescription :: Maybe ProjectDescription } + deriving (Read) + +instance NFData Project where + rnf (Project n p c _) = rnf n `seq` rnf p `seq` rnf c + +instance Eq Project where + l == r = _projectCabal l == _projectCabal r + +instance Ord Project where + compare l r = compare (_projectName l, _projectCabal l) (_projectName r, _projectCabal r) + +instance Show Project where + show p = unlines $ [ + "project " ++ _projectName p, + "\tcabal: " ++ _projectCabal p, + "\tdescription:"] ++ concatMap (map (tab 2) . lines . show) (maybeToList $ _projectDescription p) + +instance ToJSON Project where + toJSON p = object [ + "name" .= _projectName p, + "path" .= _projectPath p, + "cabal" .= _projectCabal p, + "description" .= _projectDescription p] + +instance FromJSON Project where + parseJSON = withObject "project" $ \v -> Project <$> + v .:: "name" <*> + v .:: "path" <*> + v .:: "cabal" <*> + v .:: "description" + +-- | Make project by .cabal file +project :: FilePath -> Project +project file = Project { + _projectName = takeBaseName (takeDirectory cabal), + _projectPath = takeDirectory cabal, + _projectCabal = cabal, + _projectDescription = Nothing } + where + file' = dropTrailingPathSeparator $ normalise file + cabal + | takeExtension file' == ".cabal" = file' + | otherwise = file' </> (takeBaseName file' <.> "cabal") + +data ProjectDescription = ProjectDescription { + _projectVersion :: String, + _projectLibrary :: Maybe Library, + _projectExecutables :: [Executable], + _projectTests :: [Test] } + deriving (Eq, Read) + +instance Show ProjectDescription where + show pd = unlines $ + concatMap (lines . show) (maybeToList (_projectLibrary pd)) ++ + concatMap (lines . show) (_projectExecutables pd) ++ + concatMap (lines . show) (_projectTests pd) + +instance ToJSON ProjectDescription where + toJSON d = object [ + "version" .= _projectVersion d, + "library" .= _projectLibrary d, + "executables" .= _projectExecutables d, + "tests" .= _projectTests d] + +instance FromJSON ProjectDescription where + parseJSON = withObject "project description" $ \v -> ProjectDescription <$> + v .:: "version" <*> + v .:: "library" <*> + v .:: "executables" <*> + v .:: "tests" + +class Target a where + buildInfo :: a -> Info + +-- | Library in project +data Library = Library { + _libraryModules :: [[String]], + _libraryBuildInfo :: Info } + deriving (Eq, Read) + +instance Target Library where + buildInfo = _libraryBuildInfo + +instance Show Library where + show l = unlines $ + ["library", "\tmodules:"] ++ + (map (tab 2 . intercalate ".") $ _libraryModules l) ++ + (map (tab 1) . lines . show $ _libraryBuildInfo l) + +instance ToJSON Library where + toJSON l = object [ + "modules" .= fmap (intercalate ".") (_libraryModules l), + "info" .= _libraryBuildInfo l] + +instance FromJSON Library where + parseJSON = withObject "library" $ \v -> Library <$> (fmap splitModule <$> v .:: "modules") <*> v .:: "info" where + splitModule :: String -> [String] + splitModule = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break (== '.')) + +-- | Executable +data Executable = Executable { + _executableName :: String, + _executablePath :: FilePath, + _executableBuildInfo :: Info } + deriving (Eq, Read) + +instance Target Executable where + buildInfo = _executableBuildInfo + +instance Show Executable where + show e = unlines $ + ["executable " ++ _executableName e, "\tpath: " ++ _executablePath e] ++ + (map (tab 1) . lines . show $ _executableBuildInfo e) + +instance ToJSON Executable where + toJSON e = object [ + "name" .= _executableName e, + "path" .= _executablePath e, + "info" .= _executableBuildInfo e] + +instance FromJSON Executable where + parseJSON = withObject "executable" $ \v -> Executable <$> + v .:: "name" <*> + v .:: "path" <*> + v .:: "info" + +-- | Test +data Test = Test { + _testName :: String, + _testEnabled :: Bool, + _testBuildInfo :: Info } + deriving (Eq, Read) + +instance Target Test where + buildInfo = _testBuildInfo + +instance Show Test where + show t = unlines $ + ["test " ++ _testName t, "\tenabled: " ++ show (_testEnabled t)] ++ + (map (tab 1) . lines . show $ _testBuildInfo t) + +instance ToJSON Test where + toJSON t = object [ + "name" .= _testName t, + "enabled" .= _testEnabled t, + "info" .= _testBuildInfo t] + +instance FromJSON Test where + parseJSON = withObject "test" $ \v -> Test <$> + v .:: "name" <*> + v .:: "enabled" <*> + v .:: "info" + +-- | Build info +data Info = Info { + _infoDepends :: [String], + _infoLanguage :: Maybe Language, + _infoExtensions :: [Extension], + _infoGHCOptions :: [String], + _infoSourceDirs :: [FilePath] } + deriving (Eq, Read) + +instance Monoid Info where + mempty = Info [] Nothing [] [] [] + mappend l r = Info + (ordNub $ _infoDepends l ++ _infoDepends r) + (getFirst $ First (_infoLanguage l) `mappend` First (_infoLanguage r)) + (_infoExtensions l ++ _infoExtensions r) + (_infoGHCOptions l ++ _infoGHCOptions r) + (ordNub $ _infoSourceDirs l ++ _infoSourceDirs r) + +instance Show Info where + show i = unlines $ lang ++ exts ++ opts ++ sources where + lang = maybe [] (\l -> ["default-language: " ++ display l]) $ _infoLanguage i + exts + | null (_infoExtensions i) = [] + | otherwise = "extensions:" : map (tab 1 . display) (_infoExtensions i) + opts + | null (_infoGHCOptions i) = [] + | otherwise = "ghc-options:" : map (tab 1) (_infoGHCOptions i) + sources = "source-dirs:" : (map (tab 1) $ _infoSourceDirs i) + +instance ToJSON Info where + toJSON i = object [ + "build-depends" .= _infoDepends i, + "language" .= fmap display (_infoLanguage i), + "extensions" .= map display (_infoExtensions i), + "ghc-options" .= _infoGHCOptions i, + "source-dirs" .= _infoSourceDirs i] + +instance FromJSON Info where + parseJSON = withObject "info" $ \v -> Info <$> + v .: "build-depends" <*> + ((v .:: "language") >>= traverse (parseDT "Language")) <*> + ((v .:: "extensions") >>= traverse (parseDT "Extension")) <*> + v .:: "ghc-options" <*> + v .:: "source-dirs" + where + parseDT :: Distribution.Text.Text a => String -> String -> Parser a + parseDT typeName v = maybe err return (simpleParse v) where + err = fail $ "Can't parse {}: {}" ~~ typeName ~~ v + +-- | Entity with project extensions +data Extensions a = Extensions { + _extensions :: [Extension], + _ghcOptions :: [String], + _entity :: a } + deriving (Eq, Read, Show) + +instance Ord a => Ord (Extensions a) where + compare = comparing _entity + +instance Functor Extensions where + fmap f (Extensions e o x) = Extensions e o (f x) + +instance Applicative Extensions where + pure = Extensions [] [] + (Extensions l lo f) <*> (Extensions r ro x) = Extensions (ordNub $ l ++ r) (ordNub $ lo ++ ro) (f x) + +instance Foldable Extensions where + foldMap f (Extensions _ _ x) = f x + +instance Traversable Extensions where + traverse f (Extensions e o x) = Extensions e o <$> f x + +makeLenses ''Project +makeLenses ''ProjectDescription +makeLenses ''Library +makeLenses ''Executable +makeLenses ''Test +makeLenses ''Info +makeLenses ''Extensions
src/HsDev/Sandbox.hs view
@@ -12,6 +12,7 @@ import Control.Arrow import Control.DeepSeq (NFData(..)) +import Control.Monad.Catch (MonadCatch(..)) import Control.Monad.Trans.Maybe import Control.Monad.Except import Control.Lens (view, makeLenses) @@ -24,6 +25,7 @@ import qualified Distribution.Text as T (display) import System.FilePath import System.Directory +import System.Log.Simple (MonadLog(..)) import System.Directory.Paths import HsDev.PackageDb @@ -93,31 +95,30 @@ searchSandbox p = runMaybeT $ searchPath p (MaybeT . findSandbox) -- | Get package-db stack for sandbox -sandboxPackageDbStack :: Sandbox -> ExceptT String IO PackageDbStack +sandboxPackageDbStack :: (MonadLog m, MonadCatch m) => Sandbox -> m PackageDbStack sandboxPackageDbStack (Sandbox CabalSandbox fpath) = do dir <- cabalSandboxPackageDb return $ PackageDbStack [PackageDb $ fpath </> dir] -sandboxPackageDbStack (Sandbox StackWork fpath) = maybeToExceptT "Can't locate stack environment" $ - liftM (view stackPackageDbStack) $ projectEnv $ takeDirectory fpath +sandboxPackageDbStack (Sandbox StackWork fpath) = liftM (view stackPackageDbStack) $ projectEnv $ takeDirectory fpath -- | Search package-db stack with user-db as default -searchPackageDbStack :: FilePath -> IO PackageDbStack +searchPackageDbStack :: (MonadLog m, MonadCatch m) => FilePath -> m PackageDbStack searchPackageDbStack p = do - mbox <- searchSandbox p + mbox <- liftIO $ searchSandbox p case mbox of Nothing -> return userDb - Just sbox -> liftM (either (const userDb) id) $ runExceptT $ sandboxPackageDbStack sbox + Just sbox -> sandboxPackageDbStack sbox -- | Restore package-db stack by package-db -restorePackageDbStack :: PackageDb -> IO PackageDbStack +restorePackageDbStack :: (MonadLog m, MonadCatch m) => PackageDb -> m PackageDbStack restorePackageDbStack GlobalDb = return globalDb restorePackageDbStack UserDb = return userDb restorePackageDbStack (PackageDb p) = liftM (fromMaybe $ fromPackageDb p) $ runMaybeT $ do - sbox <- MaybeT $ searchSandbox p - exceptToMaybeT $ sandboxPackageDbStack sbox + sbox <- MaybeT $ liftIO $ searchSandbox p + lift $ sandboxPackageDbStack sbox -- | Get actual sandbox build path: <arch>-<platform>-<compiler>-<version> -cabalSandboxLib :: ExceptT String IO FilePath +cabalSandboxLib :: MonadLog m => m FilePath cabalSandboxLib = do res <- withPackages ["-no-user-package-db"] $ return . @@ -131,5 +132,5 @@ return $ T.display buildPlatform ++ "-" ++ compiler ++ "-" ++ ver -- | Get sandbox package-db: <arch>-<platform>-<compiler>-<version>-packages.conf.d -cabalSandboxPackageDb :: ExceptT String IO FilePath +cabalSandboxPackageDb :: MonadLog m => m FilePath cabalSandboxPackageDb = liftM (++ "-packages.conf.d") cabalSandboxLib
src/HsDev/Scan.hs view
@@ -19,13 +19,14 @@ import Control.DeepSeq import Control.Lens (view, preview, set, over, each, _Right, _1, _2, _3, (^.), (^..)) import Control.Monad.Except -import Data.Maybe (catMaybes, fromMaybe, isJust) +import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe) import Data.List (intercalate) import System.Directory import Text.Format -import HsDev.Scan.Browse (browsePackages) -import HsDev.Server.Types (FileContents(..)) +import HsDev.Error +import HsDev.Scan.Browse (browsePackages, browseModules) +import HsDev.Server.Types (FileContents(..), CommandMonad(..)) import HsDev.Sandbox import HsDev.Symbols import HsDev.Symbols.Types @@ -69,7 +70,7 @@ ~~ (intercalate ", " $ map (display . topPackageDb) $ cs ^.. each) class EnumContents a where - enumContents :: a -> ExceptT String IO ScanContents + enumContents :: CommandMonad m => a -> m ScanContents instance EnumContents ModuleLocation where enumContents mloc = return $ ScanContents [(mloc, [], Nothing)] [] [] @@ -91,8 +92,8 @@ instance {-# OVERLAPS #-} EnumContents FilePath where enumContents f - | haskellSource f = do - mproj <- liftEIO $ locateProject f + | haskellSource f = hsdevLiftIO $ do + mproj <- liftIO $ locateProject f case mproj of Nothing -> enumContents $ FileModule f Nothing Just proj -> do @@ -108,10 +109,10 @@ | otherwise = return mempty -- | Enum project sources -enumProject :: Project -> ExceptT String IO ScanContents -enumProject p = do - p' <- loadProject p - pdbs <- liftE $ searchPackageDbStack (view projectPath p') +enumProject :: CommandMonad m => Project -> m ScanContents +enumProject p = hsdevLiftIO $ do + p' <- liftIO $ loadProject p + pdbs <- searchPackageDbStack (view projectPath p') pkgs <- liftM (map $ view (package . packageName)) $ browsePackages [] pdbs let projOpts :: FilePath -> [String] @@ -121,25 +122,25 @@ ["-hide-all-packages"], ["-package " ++ view projectName p'], ["-package " ++ dep | dep <- view infoDepends i, dep `elem` pkgs]] - srcs <- projectSources p' + srcs <- liftIO $ 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 :: CommandMonad m => Sandbox -> m ScanContents enumSandbox = sandboxPackageDbStack >=> enumContents -- | Enum directory modules -enumDirectory :: FilePath -> ExceptT String IO ScanContents -enumDirectory dir = do - cts <- liftException $ traverseDirectory dir +enumDirectory :: CommandMonad m => FilePath -> m ScanContents +enumDirectory dir = hsdevLiftIO $ do + cts <- liftIO $ traverseDirectory dir let projects = filter cabalFile cts sources = filter haskellSource cts - dirs <- liftE $ filterM doesDirectoryExist cts - sboxes <- liftM catMaybes $ triesMap (liftE . findSandbox) dirs + dirs <- liftIO $ filterM doesDirectoryExist cts + sboxes <- liftM catMaybes $ triesMap (liftIO . findSandbox) dirs pdbs <- mapM enumSandbox sboxes projs <- liftM mconcat $ triesMap (enumProject . project) projects let @@ -151,14 +152,14 @@ 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 +scanProjectFile :: CommandMonad m => [String] -> FilePath -> m Project +scanProjectFile _ f = hsdevLiftIO $ do + proj <- (liftIO $ locateProject f) >>= maybe (hsdevError $ FileNotFound f) return + liftIO $ 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 +scanModule :: CommandMonad m => [(String, String)] -> [String] -> ModuleLocation -> Maybe String -> m InspectedModule +scanModule defines opts (FileModule f p) mcts = hsdevLiftIO $ liftM setProj $ liftIO $ inspectFile defines opts f mcts where setProj = set (inspectedId . moduleProject) p . set (inspectionResult . _Right . moduleLocation . moduleProject) p @@ -166,19 +167,20 @@ -- 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 +scanModule _ opts mloc@(InstalledModule c _ n) _ = hsdevLiftIO $ do + pdbs <- getDbs c + ims <- browseModules opts pdbs [mloc] + maybe (hsdevError $ BrowseNoModuleInfo n) return $ listToMaybe ims where - getDbs :: PackageDb -> IO PackageDbStack + getDbs :: CommandMonad m => PackageDb -> m PackageDbStack getDbs = maybe (return userDb) searchPackageDbStack . preview packageDb -scanModule _ _ (ModuleSource _) _ = throwError "Can inspect only modules in file or cabal" +scanModule _ _ (ModuleSource _) _ = hsdevError $ InspectError "Can inspect only modules in file or cabal" -- | Scan additional info and modify scanned module -scanModify :: ([String] -> PackageDbStack -> Module -> ExceptT String IO Module) -> InspectedModule -> ExceptT String IO InspectedModule +scanModify :: CommandMonad m => ([String] -> PackageDbStack -> Module -> m Module) -> InspectedModule -> m InspectedModule scanModify f im = traverse f' im where f' m = do - pdbs <- liftIO $ case view moduleLocation m of + pdbs <- case view moduleLocation m of -- TODO: Get actual sandbox stack FileModule fpath _ -> searchPackageDbStack fpath InstalledModule pdb _ _ -> maybe (return userDb) searchPackageDbStack $ preview packageDb pdb @@ -186,27 +188,27 @@ 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 +upToDate :: [String] -> InspectedModule -> IO Bool +upToDate opts im = case view inspectedId im of + FileModule f _ -> liftM (== view inspection im) $ fileInspection f opts + InstalledModule _ _ _ -> return $ view inspection im == browseInspection opts _ -> return False -- | Rescan inspected module -rescanModule :: [(String, String)] -> [String] -> InspectedModule -> ExceptT String IO (Maybe InspectedModule) +rescanModule :: CommandMonad m => [(String, String)] -> [String] -> InspectedModule -> m (Maybe InspectedModule) rescanModule defines opts im = do - up <- upToDate opts im + up <- liftIO $ 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 +changedModule :: Database -> [String] -> ModuleLocation -> IO Bool +changedModule db opts m = maybe (return True) (liftM not . liftIO . 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 :: Database -> [String] -> [ModuleToScan] -> IO [ModuleToScan] changedModules db opts = filterM $ \m -> if isJust (m ^. _3) then return True else changedModule db (opts ++ (m ^. _2)) (m ^. _1)
src/HsDev/Scan/Browse.hs view
@@ -14,6 +14,7 @@ import Control.Arrow import Control.Lens (view, preview, _Just) +import Control.Monad.Catch (MonadCatch, catch, SomeException) import Control.Monad.Except import Data.List (isPrefixOf) import Data.Maybe @@ -21,12 +22,15 @@ import Data.Version import System.Directory import System.FilePath +import System.Log.Simple.Monad (MonadLog) import Data.Deps import HsDev.PackageDb import HsDev.Symbols +import HsDev.Error import HsDev.Tools.Base (inspect) -import HsDev.Util (liftIOErrors, ordNub) +import HsDev.Tools.Ghc.Worker (GhcM(..), runGhcM) +import HsDev.Util (ordNub) import qualified ConLike as GHC import qualified DataCon as GHC @@ -34,6 +38,7 @@ import qualified GHC import qualified GHC.PackageDb as GHC import qualified GhcMonad as GHC (liftIO) +import GhcMonad (GhcMonad) import qualified GHC.Paths as GHC import qualified Name as GHC import qualified Outputable as GHC @@ -45,49 +50,47 @@ import Pretty -- | Browse packages -browsePackages :: [String] -> PackageDbStack -> ExceptT String IO [PackageConfig] -browsePackages opts dbs = liftIOErrors $ withPackages_ (packageDbStackOpts dbs ++ opts) $ - liftM (map readPackageConfig) $ lift packageConfigs +browsePackages :: MonadLog m => [String] -> PackageDbStack -> m [PackageConfig] +browsePackages opts dbs = withPackages_ (packageDbStackOpts dbs ++ opts) $ + liftM (map readPackageConfig) packageConfigs -- | Get packages with deps -browsePackagesDeps :: [String] -> PackageDbStack -> ExceptT String IO (Deps PackageConfig) -browsePackagesDeps opts dbs = liftIOErrors $ withPackages (packageDbStackOpts dbs ++ opts) $ \df -> do - cfgs <- lift packageConfigs +browsePackagesDeps :: MonadLog m => [String] -> PackageDbStack -> m (Deps PackageConfig) +browsePackagesDeps opts dbs = withPackages (packageDbStackOpts dbs ++ opts) $ \df -> do + cfgs <- packageConfigs return $ mapDeps (toPkg df) $ mconcat $ map (uncurry deps) $ map (GHC.installedPackageId &&& GHC.depends) cfgs where toPkg df' = readPackageConfig . GHC.getPackageDetails df' . GHC.resolveInstalledPackageId df' -listModules :: [String] -> PackageDbStack -> ExceptT String IO [ModuleLocation] -listModules opts dbs = liftIOErrors $ withPackages_ (packageDbStackOpts dbs ++ opts) $ do - ms <- lift packageDbModules +listModules :: MonadLog m => [String] -> PackageDbStack -> m [ModuleLocation] +listModules opts dbs = withPackages_ (packageDbStackOpts dbs ++ opts) $ do + ms <- packageDbModules pdbs <- mapM (liftIO . ghcPackageDb . fst) ms return [ghcModuleLocation pdb p m | (pdb, (p, m)) <- zip pdbs ms] -browseModules :: [String] -> PackageDbStack -> [ModuleLocation] -> ExceptT String IO [InspectedModule] -browseModules opts dbs mlocs = liftIOErrors $ withPackages_ (packageDbStackOpts dbs ++ opts) $ do - ms <- lift packageDbModules +browseModules :: MonadLog m => [String] -> PackageDbStack -> [ModuleLocation] -> m [InspectedModule] +browseModules opts dbs mlocs = withPackages_ (packageDbStackOpts dbs ++ opts) $ do + ms <- packageDbModules pdbs <- mapM (liftIO . ghcPackageDb . fst) ms liftM catMaybes $ sequence [browseModule' pdb p m | (pdb, (p, m)) <- zip pdbs ms, ghcModuleLocation pdb p m `elem` mlocs] where - browseModule' :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ExceptT String GHC.Ghc (Maybe InspectedModule) + browseModule' :: PackageDb -> GHC.PackageConfig -> GHC.Module -> GhcM (Maybe InspectedModule) browseModule' pdb p m = tryT $ inspect (ghcModuleLocation pdb p m) (return $ InspectionAt 0 opts) (browseModule pdb p m) -- | Browse modules, if third argument is True - browse only modules in top of package-db stack -browse :: [String] -> PackageDbStack -> ExceptT String IO [InspectedModule] +browse :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule] browse opts dbs = listModules opts dbs >>= browseModules opts dbs -- | Browse modules in top of package-db stack -browseDb :: [String] -> PackageDbStack -> ExceptT String IO [InspectedModule] +browseDb :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule] browseDb opts dbs = listModules opts dbs >>= browseModules opts dbs . filter inTop where inTop = (== Just (topPackageDb dbs)) . preview modulePackageDb -browseModule :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ExceptT String GHC.Ghc Module +browseModule :: PackageDb -> GHC.PackageConfig -> GHC.Module -> GhcM Module browseModule pdb package' m = do - mi <- lift (GHC.getModuleInfo m) >>= maybe (throwError "Can't find module info") return + mi <- GHC.getModuleInfo m >>= maybe (hsdevError $ BrowseNoModuleInfo thisModule) return ds <- mapM (toDecl mi) (GHC.modInfoExports mi) - let - thisModule = GHC.moduleNameString (GHC.moduleName m) return Module { _moduleName = fromString thisModule, _moduleDocs = Nothing, @@ -96,15 +99,16 @@ _moduleImports = [import_ iname | iname <- ordNub (mapMaybe (preview definedModule) ds), iname /= fromString thisModule], _moduleDeclarations = sortDeclarations ds } where + thisModule = GHC.moduleNameString (GHC.moduleName m) thisLoc = view moduleIdLocation $ mloc m mloc m' = ModuleId (fromString mname') $ ghcModuleLocation pdb package' m' where mname' = GHC.moduleNameString $ GHC.moduleName m' toDecl minfo n = do - tyInfo <- lift $ GHC.modInfoLookupName minfo n - tyResult <- lift $ maybe (inModuleSource n) (return . Just) tyInfo - dflag <- lift GHC.getSessionDynFlags + tyInfo <- GHC.modInfoLookupName minfo n + tyResult <- maybe (inModuleSource n) (return . Just) tyInfo + dflag <- GHC.getSessionDynFlags let decl' = decl (fromString $ GHC.getOccString n) $ fromMaybe (Function Nothing [] Nothing) @@ -125,8 +129,8 @@ | otherwise = Type showResult _ _ = Nothing -withInitializedPackages :: [String] -> (GHC.DynFlags -> GHC.Ghc a) -> IO a -withInitializedPackages ghcOpts cont = GHC.runGhc (Just GHC.libdir) $ do +withInitializedPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a +withInitializedPackages ghcOpts cont = runGhcM (Just GHC.libdir) $ do fs <- GHC.getSessionDynFlags GHC.defaultCleanupHandler fs $ do (fs', _, _) <- GHC.parseDynamicFlags fs (map GHC.noLoc ghcOpts) @@ -134,13 +138,13 @@ (result, _) <- GHC.liftIO $ GHC.initPackages fs' cont result -withPackages :: [String] -> (GHC.DynFlags -> ExceptT String GHC.Ghc a) -> ExceptT String IO a -withPackages ghcOpts cont = ExceptT $ withInitializedPackages ghcOpts (runExceptT . cont) +withPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a +withPackages ghcOpts cont = withInitializedPackages ghcOpts cont -withPackages_ :: [String] -> ExceptT String GHC.Ghc a -> ExceptT String IO a +withPackages_ :: MonadLog m => [String] -> GhcM a -> m a withPackages_ ghcOpts act = withPackages ghcOpts (const act) -inModuleSource :: GHC.Name -> GHC.Ghc (Maybe GHC.TyThing) +inModuleSource :: GhcMonad m => GHC.Name -> m (Maybe GHC.TyThing) inModuleSource nm = GHC.getModuleInfo (GHC.nameModule nm) >> GHC.lookupGlobalName nm formatType :: GHC.NamedThing a => GHC.DynFlags -> (a -> GHC.Type) -> a -> String @@ -166,8 +170,8 @@ styleUnqualified :: GHC.PprStyle styleUnqualified = GHC.mkUserStyle GHC.neverQualify GHC.AllTheWay -tryT :: Monad m => ExceptT e m a -> ExceptT e m (Maybe a) -tryT act = catchError (liftM Just act) (const $ return Nothing) +tryT :: MonadCatch m => m a -> m (Maybe a) +tryT act = catch (liftM Just act) (const (return Nothing) . (id :: SomeException -> SomeException)) readPackage :: GHC.PackageConfig -> ModulePackage readPackage pc = ModulePackage (GHC.packageNameString pc) (showVersion (GHC.packageVersion pc)) @@ -224,10 +228,10 @@ packageDbCandidate_ :: FilePath -> IO PackageDb packageDbCandidate_ = packageDbCandidate >=> maybe (return GlobalDb) return -packageConfigs :: GHC.GhcMonad m => m [GHC.PackageConfig] +packageConfigs :: GhcM [GHC.PackageConfig] packageConfigs = liftM (fromMaybe [] . GHC.pkgDatabase) GHC.getSessionDynFlags -packageDbModules :: GHC.GhcMonad m => m [(GHC.PackageConfig, GHC.Module)] +packageDbModules :: GhcM [(GHC.PackageConfig, GHC.Module)] packageDbModules = do pkgs <- packageConfigs dflags <- GHC.getSessionDynFlags
src/HsDev/Server/Base.hs view
@@ -50,29 +50,36 @@ #endif -- | Inits log chan and returns functions (print message, wait channel) -initLog :: ServerOpts -> IO (Log, Log.Level -> String -> IO (), IO [String], IO ()) +initLog :: ServerOpts -> IO SessionLog initLog sopts = do msgs <- F.newChan - l <- newLog (constant [rule']) $ concat [ + rulesVar <- newMVar [ruleStr] + let + getRules = do + rs <- readMVar rulesVar + return $ map (parseRule_ . fromString) rs + l <- newLog (return getRules) $ concat [ [logger text console | not $ serverSilent sopts], [logger text (chaner msgs)], maybeToList $ (logger text . file) <$> serverLog sopts] Log.writeLog l Log.Info ("Log politics: low = {}, high = {}" ~~ logLow ~~ logHigh) let listenLog = F.dupChan msgs >>= F.readChan - return (l, \lev -> writeLog l lev . T.pack, listenLog, stopLog l) + return $ SessionLog l rulesVar listenLog (stopLog l) where - rule' :: Log.Rule - rule' = parseRule_ $ T.pack ("/: " ++ serverLogConfig sopts) - (Log.Politics logLow logHigh) = Log.rulePolitics rule' Log.defaultPolitics + ruleStr :: String + ruleStr = "/: {}" ~~ serverLogConfig sopts + (Log.Politics logLow logHigh) = Log.rulePolitics (parseRule_ (fromString ruleStr)) Log.defaultPolitics instance FormatBuild Log.Level where -- | Run server runServer :: ServerOpts -> ServerM IO () -> IO () -runServer sopts act = bracket (initLog sopts) (\(_, _, _, x) -> x) $ \(logger', outputStr, listenLog, waitOutput) -> Log.scopeLog logger' (T.pack "hsdev") $ Watcher.withWatcher $ \watcher -> do +runServer sopts act = bracket (initLog sopts) sessionLogWait $ \slog -> Log.scopeLog (sessionLogger slog) (T.pack "hsdev") $ Watcher.withWatcher $ \watcher -> do waitSem <- newQSem 0 db <- DB.newAsync + let + outputStr = Log.writeLog (sessionLogger slog) withCache sopts () $ \cdir -> do outputStr Log.Trace $ "Checking cache version in {}" ~~ cdir ver <- Cache.readVersion $ cdir </> Cache.versionCache @@ -96,8 +103,8 @@ #if mingw32_HOST_OS mmapPool <- Just <$> createPool "hsdev" #endif - ghcw <- ghcWorker [] (return ()) - ghciw <- ghciWorker + ghcw <- withLog (sessionLogger slog) $ ghcWorker [] (return ()) + ghciw <- withLog (sessionLogger slog) ghciWorker ghcmodw <- ghcModMultiWorker defs <- getDefines let @@ -105,10 +112,7 @@ db (writeCache sopts) (readCache sopts) - outputStr - logger' - listenLog - waitOutput + slog watcher #if mingw32_HOST_OS mmapPool
src/HsDev/Server/Commands.hs view
@@ -14,7 +14,6 @@ import Control.Applicative import Control.Concurrent import Control.Concurrent.Async -import Control.Exception (SomeException) import Control.Lens (set, traverseOf, view, over, Lens', Lens, _1, _2, _Left) import Control.Monad import Control.Monad.CatchIO @@ -24,7 +23,6 @@ import qualified Data.ByteString.Char8 as BS import Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.ByteString.Lazy.Char8 as L -import qualified Data.Map as M import Data.Maybe import Data.String (fromString) import qualified Data.Text as T (pack) @@ -48,6 +46,7 @@ import qualified HsDev.Database.Async as DB import HsDev.Server.Base import HsDev.Server.Types +import HsDev.Error import HsDev.Util import HsDev.Version @@ -72,7 +71,7 @@ asyncAct <- async sendReceive res <- waitCatch asyncAct case res of - Left e -> return $ Error (show e) $ M.fromList [] + Left e -> return $ Error $ OtherError (show e) Right r -> return r where sendReceive = do @@ -100,7 +99,7 @@ parseResponse h resp parseResponse h str = case eitherDecode str of - Left e -> return $ Error e $ M.fromList [("response", toJSON $ fromUtf8 str)] + Left e -> return $ Error $ ResponseError ("can't parse: {}" ~~ e) (fromUtf8 str) Right (Message _ r) -> do Response r' <- unMmap r case r' of @@ -209,7 +208,7 @@ bracket (socketToHandle s ReadWriteMode) hClose $ \h -> forM_ [(1 :: Integer)..] $ \i -> ignoreIO $ do input' <- hGetLineBS stdin case decodeMsg input' of - Left em -> L.putStrLn $ encodeMessage $ set msg (Message Nothing $ responseError "invalid command" []) em + Left em -> L.putStrLn $ encodeMessage $ set msg (Message Nothing $ responseError $ OtherError "invalid command") em Right m -> do L.hPutStrLn h $ encodeMessage $ set msg (Message (Just $ show i) $ Request (view msg m) curDir True (clientTimeout copts) False) m waitResp h @@ -316,7 +315,7 @@ case decodeMessage req' of Left em -> do Log.log Log.Warning $ "Invalid request {}" ~~ fromUtf8 req' - answer $ set msg (Message Nothing $ responseError "Invalid request" ["request" .= fromUtf8 req']) em + answer $ set msg (Message Nothing $ responseError $ RequestError "invalid request" $ fromUtf8 req') em Right m -> Log.scope (T.pack $ fromMaybe "_" (view (msg . messageId) m)) $ do resp' <- flip (traverseOf (msg . message)) m $ \(Request c cdir noFile tm silent) -> do let @@ -324,7 +323,7 @@ | silent = return () | otherwise = traverseOf (msg . message) (const $ mmap' noFile (Response $ Left n)) m >>= answer Log.log Log.Trace $ "{} >> {}" ~~ name ~~ fromUtf8 (encode c) - resp <- liftIO $ fmap (Response . Right) $ handleTimeout tm $ handleError $ withSession s $ + resp <- liftIO $ fmap (Response . Right) $ handleTimeout tm $ hsdevLiftIO $ withSession s $ processRequest CommandOptions { commandOptionsRoot = cdir, @@ -337,12 +336,7 @@ where handleTimeout :: Int -> IO Result -> IO Result handleTimeout 0 = id - handleTimeout tm = fmap (fromMaybe $ Error "Timeout" M.empty) . timeout tm - - handleError :: IO Result -> IO Result - handleError = flip catch onErr where - onErr :: SomeException -> IO Result - onErr e = return $ Error "Exception" $ M.fromList [("what", toJSON $ show e)] + handleTimeout tm = fmap (fromMaybe $ Error $ OtherError "timeout") . timeout tm mmap' :: SessionMonad m => Bool -> Response -> m Response #if mingw32_HOST_OS @@ -414,9 +408,9 @@ | Just (MmapFile f) <- parseMaybe parseJSON v = do cts <- runExceptT (fmap L.fromStrict (readMapFile f)) case cts of - Left _ -> return $ responseError "Unable to read map view of file" ["file" .= f] + Left _ -> return $ responseError $ ResponseError "can't read map view of file" f Right r' -> case eitherDecode r' of - Left e' -> return $ responseError "Invalid response" ["response" .= fromUtf8 r', "parser error" .= e'] + Left e' -> return $ responseError $ ResponseError ("can't parse response: {}" ~~ e') (fromUtf8 r') Right r'' -> return r'' #endif unMmap r = return r
src/HsDev/Server/Message.hs view
@@ -8,18 +8,14 @@ groupResponses, responsesById ) where -import Control.Arrow (first) import Control.Applicative import Control.Lens (makeLenses) import Control.Monad (join) import Data.Aeson hiding (Error, Result) -import Data.Aeson.Types (Pair) import Data.Either (lefts, isRight) import Data.List (unfoldr) -import Data.Map (Map) -import qualified Data.Map as M -import Data.Text (unpack) +import HsDev.Types import HsDev.Util ((.::), (.::?), objectUnion) -- | Message with id to link request and response @@ -60,20 +56,16 @@ data Result = Result Value | -- ^ Result - Error String (Map String Value) + Error HsDevError -- ^ Error deriving (Show) instance ToJSON Result where toJSON (Result r) = object ["result" .= r] - toJSON (Error msg rs) = object [ - "error" .= msg, - "details" .= toJSON rs] + toJSON (Error e) = toJSON e instance FromJSON Result where - parseJSON = withObject "result" $ \v -> - (Result <$> v .:: "result") <|> - (Error <$> v .:: "error" <*> v .:: "details") + parseJSON j = (withObject "result" (\v -> (Result <$> v .:: "result")) j) <|> (Error <$> parseJSON j) -- | Part of result list, returns via notification data ResultPart = ResultPart Value @@ -95,8 +87,8 @@ result :: ToJSON a => a -> Response result = Response . Right . Result . toJSON -responseError :: String -> [Pair] -> Response -responseError e ds = Response $ Right $ Error e $ M.fromList $ map (first unpack) ds +responseError :: HsDevError -> Response +responseError = Response . Right . Error resultPart :: ToJSON a => a -> Notification resultPart = Notification . toJSON . ResultPart . toJSON @@ -116,7 +108,7 @@ (ns, cs') = break (isRight . unResponse) cs r = case cs' of (Response (Right r') : _) -> r' - [] -> Error "groupResponses: no result" mempty + [] -> Error $ OtherError "groupResponses: no result" _ -> error "groupResponses: impossible happened" responsesById :: Maybe String -> [Message Response] -> [([Notification], Result)]
src/HsDev/Server/Types.hs view
@@ -3,10 +3,10 @@ module HsDev.Server.Types ( ServerMonadBase, - Session(..), SessionMonad(..), askSession, ServerM(..), - CommandOptions(..), CommandError(..), commandErrorMsg, commandErrorDetails, commandError, commandError_, CommandMonad(..), askOptions, ClientM(..), - withSession, serverListen, serverWait, serverUpdateDB, serverWriteCache, serverReadCache, serverExit, commandRoot, commandNotify, commandLink, commandHold, - ServerCommand(..), ConnectionPort(..), ServerOpts(..), ClientOpts(..), serverOptsArgs, Request(..), + SessionLog(..), Session(..), SessionMonad(..), askSession, ServerM(..), + CommandOptions(..), CommandMonad(..), askOptions, ClientM(..), + withSession, serverListen, serverSetLogRules, serverWait, serverUpdateDB, serverWriteCache, serverReadCache, serverExit, commandRoot, commandNotify, commandLink, commandHold, + ServerCommand(..), ConnectionPort(..), ServerOpts(..), silentOpts, ClientOpts(..), serverOptsArgs, Request(..), Command(..), AddedContents(..), GhcModCommand(..), @@ -16,7 +16,8 @@ ) where import Control.Applicative -import Control.Lens (each, makeLenses) +import Control.Concurrent.MVar (MVar, swapMVar) +import Control.Lens (each) import Control.Monad.Base import Control.Monad.Catch import Control.Monad.CatchIO @@ -27,6 +28,7 @@ import qualified Data.Aeson.Types as A import qualified Data.ByteString.Lazy.Char8 as L import Data.Default +import Data.Monoid import Data.Foldable (asum) import Options.Applicative import System.Log.Simple hiding (Command) @@ -41,7 +43,7 @@ import HsDev.Server.Message import HsDev.Watcher.Types (Watcher) import HsDev.Tools.GhcMod (OutputMessage, WorkerMap) -import HsDev.Tools.Ghc.Worker (Worker, Ghc) +import HsDev.Tools.Ghc.Worker (Worker, GhcM) import HsDev.Tools.Types (Note) import HsDev.Tools.AutoFix (Correction) import HsDev.Util @@ -50,22 +52,25 @@ import System.Win32.FileMapping.NamePool (Pool) #endif -type ServerMonadBase m = (MonadCatchIO m, MonadBaseControl IO m) +type ServerMonadBase m = (MonadThrow m, MonadCatch m, MonadCatchIO m, MonadBaseControl IO m, Alternative m, MonadPlus m) +data SessionLog = SessionLog { + sessionLogger :: Log, + sessionLogRules :: MVar [String], + sessionListenLog :: IO [String], + sessionLogWait :: IO () } + data Session = Session { sessionDatabase :: DB.Async Database, sessionWriteCache :: Database -> ServerM IO (), sessionReadCache :: (FilePath -> ExceptT String IO Structured) -> ServerM IO (Maybe Database), - sessionLog :: Level -> String -> IO (), - sessionLogger :: Log, - sessionListenLog :: IO [String], - sessionLogWait :: IO (), + sessionLog :: SessionLog, sessionWatcher :: Watcher, #if mingw32_HOST_OS sessionMmapPool :: Maybe Pool, #endif - sessionGhc :: Worker Ghc, - sessionGhci :: Worker Ghc, + sessionGhc :: Worker GhcM, + sessionGhci :: Worker GhcM, sessionGhcMod :: Worker (ReaderT WorkerMap IO), sessionExit :: IO (), sessionWait :: IO (), @@ -77,10 +82,11 @@ askSession :: SessionMonad m => (Session -> a) -> m a askSession f = liftM f getSession -newtype ServerM m a = ServerM { runServerM :: ReaderT Session m a } deriving (Functor, Applicative, Monad, MonadReader Session, MonadIO, MonadTrans, MonadCatchIO, MonadThrow, MonadCatch) +newtype ServerM m a = ServerM { runServerM :: ReaderT Session m a } + deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadCatchIO, MonadReader Session, MonadTrans, MonadThrow, MonadCatch) instance MonadCatchIO m => MonadLog (ServerM m) where - askLog = ServerM $ asks sessionLogger + askLog = ServerM $ asks (sessionLogger . sessionLog) instance ServerMonadBase m => SessionMonad (ServerM m) where getSession = ask @@ -102,60 +108,32 @@ instance Default CommandOptions where def = CommandOptions "." (const $ return ()) (return ()) (return ()) -data CommandError = CommandError { - _commandErrorMsg :: String, - _commandErrorDetails :: [A.Pair] } - -makeLenses ''CommandError - -instance Monoid CommandError where - mempty = CommandError "" [] - mappend (CommandError lmsg lp) (CommandError rmsg rp) = CommandError (lmsg ++ ", " ++ rmsg) (lp ++ rp) - -class (SessionMonad m, MonadError CommandError m, MonadPlus m) => CommandMonad m where +class (SessionMonad m, MonadPlus m) => CommandMonad m where getOptions :: m CommandOptions -commandError :: CommandMonad m => String -> [A.Pair] -> m a -commandError m ds = throwError $ CommandError m ds - -commandError_ :: CommandMonad m => String -> m a -commandError_ m = commandError m [] - askOptions :: CommandMonad m => (CommandOptions -> a) -> m a askOptions f = liftM f getOptions -newtype ClientM m a = ClientM { runClientM :: ServerM (ExceptT CommandError (ReaderT CommandOptions m)) a } - deriving (Functor, Applicative, Monad, MonadIO, MonadCatchIO, MonadThrow, MonadCatch) +newtype ClientM m a = ClientM { runClientM :: ServerM (ReaderT CommandOptions m) a } + deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadCatchIO, MonadThrow, MonadCatch) instance MonadTrans ClientM where - lift = ClientM . lift . lift . lift + lift = ClientM . lift . lift instance MonadCatchIO m => MonadLog (ClientM m) where askLog = ClientM askLog -instance Monad m => MonadError CommandError (ClientM m) where - throwError = ClientM . lift . throwError - catchError act handler = ClientM $ ServerM $ catchError (runServerM $ runClientM act) (runServerM . runClientM . handler) - -instance Monad m => Alternative (ClientM m) where - empty = ClientM $ ServerM empty - x <|> y = ClientM $ ServerM $ runServerM (runClientM x) <|> runServerM (runClientM y) - -instance Monad m => MonadPlus (ClientM m) where - mzero = ClientM $ ServerM mzero - mplus l r = ClientM $ ServerM $ runServerM (runClientM l) `mplus` runServerM (runClientM r) - instance ServerMonadBase m => SessionMonad (ClientM m) where getSession = ClientM getSession instance ServerMonadBase m => CommandMonad (ClientM m) where - getOptions = ClientM $ lift $ lift ask + getOptions = ClientM $ lift ask instance MonadBase b m => MonadBase b (ClientM m) where liftBase = ClientM . liftBase instance MonadBaseControl b m => MonadBaseControl b (ClientM m) where - type StM (ClientM m) a = StM (ServerM (ExceptT CommandError (ReaderT CommandOptions m))) a + type StM (ClientM m) a = StM (ServerM (ReaderT CommandOptions m)) a liftBaseWith f = ClientM $ liftBaseWith (\f' -> f (f' . runClientM)) restoreM = ClientM . restoreM @@ -165,8 +143,14 @@ -- | Listen server's log serverListen :: SessionMonad m => m [String] -serverListen = join . liftM liftIO $ askSession sessionListenLog +serverListen = join . liftM liftIO $ askSession (sessionListenLog . sessionLog) +-- | Set server's log config +serverSetLogRules :: SessionMonad m => [String] -> m [String] +serverSetLogRules rs = do + rvar <- askSession (sessionLogRules . sessionLog) + liftIO $ swapMVar rvar rs + -- | Wait for server serverWait :: SessionMonad m => m () serverWait = join . liftM liftIO $ askSession sessionWait @@ -240,6 +224,10 @@ instance Default ServerOpts where def = ServerOpts def 0 Nothing "use default" Nothing False False +-- | Silent server with no connection, useful for ghci +silentOpts :: ServerOpts +silentOpts = def { serverSilent = True } + -- | Client options data ClientOpts = ClientOpts { clientPort :: ConnectionPort, @@ -351,7 +339,8 @@ -- | Command from client data Command = Ping | - Listen | + Listen (Maybe String) | + SetLogConfig [String] | AddData { addedContents :: [AddedContents] } | Scan { scanProjects :: [FilePath], @@ -511,7 +500,8 @@ instance FromCmd Command where cmdP = subparser $ mconcat [ cmd "ping" "ping server" (pure Ping), - cmd "listen" "listen server log" (pure Listen), + cmd "listen" "listen server log" (Listen <$> optional ruleArg), + cmd "set-log" "set log config rules" (SetLogConfig <$> many (strArgument idm)), cmd "add" "add info to database" (AddData <$> option readJSON idm), cmd "scan" "scan sources" $ Scan <$> many projectArg <*> @@ -622,6 +612,7 @@ pathArg :: Mod OptionFields String -> Parser FilePath projectArg :: Parser String pureFlag :: Parser Bool +ruleArg :: Parser String sandboxArg :: Parser FilePath wideFlag :: Parser Bool @@ -648,12 +639,14 @@ pathArg f = strOption (long "path" <> metavar "path" <> short 'p' <> f) projectArg = strOption (long "project" <> long "proj" <> metavar "project") pureFlag = switch (long "pure" <> help "don't modify actual file, just return result") +ruleArg = strOption (long "config" <> metavar "rule" <> help "set new log rules while in listen command") sandboxArg = strOption (long "sandbox" <> metavar "path" <> help "path to cabal sandbox") wideFlag = switch (long "wide" <> short 'w' <> help "wide mode - complete as if there were no import lists") instance ToJSON Command where toJSON Ping = cmdJson "ping" [] - toJSON Listen = cmdJson "listen" [] + toJSON (Listen r) = cmdJson "listen" ["rule" .= r] + toJSON (SetLogConfig rs) = cmdJson "set-log" ["rules" .= rs] toJSON (AddData cts) = cmdJson "add" ["data" .= cts] toJSON (Scan projs cabal sboxes fs ps contents ghcs docs' infer') = cmdJson "scan" [ "projects" .= projs, @@ -698,7 +691,8 @@ instance FromJSON Command where parseJSON = withObject "command" $ \v -> asum [ guardCmd "ping" v *> pure Ping, - guardCmd "listen" v *> pure Listen, + guardCmd "listen" v *> (Listen <$> v .::? "rule"), + guardCmd "set-log" v *> (SetLogConfig <$> v .:: "rules"), guardCmd "add" v *> (AddData <$> v .:: "data"), guardCmd "scan" v *> (Scan <$> v .::?! "projects" <*>
src/HsDev/Stack.hs view
@@ -16,6 +16,7 @@ import Control.Arrow import Control.Lens (makeLenses, Lens', at, ix, lens, (^?), (^.)) import Control.Monad +import Control.Monad.Catch (MonadCatch(..)) import Control.Monad.Trans.Maybe import Control.Monad.IO.Class import Data.Char @@ -29,17 +30,19 @@ import System.Environment import System.FilePath import System.Process +import System.Log.Simple (MonadLog(..)) import qualified GHC import qualified Packages as GHC +import HsDev.Error import HsDev.PackageDb import HsDev.Scan.Browse (withPackages) import HsDev.Util (withCurrentDirectory) -- | Get compiler version -stackCompiler :: MaybeT IO String -stackCompiler = exceptToMaybeT $ do +stackCompiler :: MonadLog m => m String +stackCompiler = do res <- withPackages ["-no-user-package-db"] $ return . map (GHC.packageNameString &&& GHC.packageVersion) . @@ -54,12 +57,13 @@ -- | Get arch for stack stackArch :: String stackArch = T.display buildArch + -- | Invoke stack command, we are trying to get actual stack near current hsdev executable -stack :: [String] -> MaybeT IO String -stack cmd = do +stack :: (MonadLog m, MonadCatch m) => [String] -> m String +stack cmd = hsdevLiftIO $ do curExe <- liftIO getExecutablePath withCurrentDirectory (takeDirectory curExe) $ do - stackExe <- MaybeT $ findExecutable "stack" + stackExe <- liftIO (findExecutable "stack") >>= maybe (hsdevError $ ToolNotFound "stack") return comp <- stackCompiler liftIO $ readProcess stackExe (cmd ++ ["--compiler", comp, "--arch", stackArch]) "" @@ -71,7 +75,7 @@ type Paths = Map String FilePath -- | Stack path -path :: Maybe FilePath -> MaybeT IO Paths +path :: (MonadLog m, MonadCatch m) => Maybe FilePath -> m Paths path mcfg = liftM (M.fromList . map breakPath . lines) $ stack ("path" : yaml mcfg) where breakPath :: String -> (String, FilePath) breakPath = second (dropWhile isSpace . drop 1) . break (== ':') @@ -81,15 +85,15 @@ pathOf = at -- | Build stack project -build :: [String] -> Maybe FilePath -> MaybeT IO () +build :: (MonadLog m, MonadCatch m) => [String] -> Maybe FilePath -> m () build opts mcfg = void $ stack $ "build" : (opts ++ yaml mcfg) -- | Build only dependencies -buildDeps :: Maybe FilePath -> MaybeT IO () +buildDeps :: (MonadLog m, MonadCatch m) => Maybe FilePath -> m () buildDeps = build ["--only-dependencies"] -- | Configure project -configure :: Maybe FilePath -> MaybeT IO () +configure :: (MonadLog m, MonadCatch m) => Maybe FilePath -> m () configure = build ["--only-configure"] data StackEnv = StackEnv { @@ -112,12 +116,12 @@ (p ^. pathOf "local-pkg-db") -- | Projects paths -projectEnv :: FilePath -> MaybeT IO StackEnv -projectEnv p = do +projectEnv :: (MonadLog m, MonadCatch m) => FilePath -> m StackEnv +projectEnv p = hsdevLiftIO $ do hasConfig <- liftIO $ doesFileExist yaml' - guard hasConfig + unless hasConfig $ hsdevError $ FileNotFound yaml' paths' <- path (Just yaml') - MaybeT $ return $ getStackEnv paths' + maybe (hsdevError $ ToolError "stack" "can't get paths") return $ getStackEnv paths' where yaml' = p </> "stack.yaml"
src/HsDev/Symbols.hs view
@@ -30,6 +30,9 @@ -- * Other unalias, + -- * Tags + setTag, hasTag, removeTag, dropTags, + -- * Reexportss module HsDev.Symbols.Types, module HsDev.Symbols.Class, @@ -46,6 +49,7 @@ import Data.Maybe (fromMaybe, listToMaybe, catMaybes) import qualified Data.Map as M import Data.Ord (comparing) +import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T (concat) import System.Directory @@ -227,7 +231,7 @@ locateSourceDir f = runMaybeT $ do file <- liftIO $ canonicalizePath f p <- MaybeT $ locateProject file - proj <- MaybeT $ fmap (either (const Nothing) Just) $ runExceptT $ loadProject p + proj <- lift $ loadProject p MaybeT $ return $ findSourceDir proj file -- | Make `Info` for standalone `Module` @@ -266,3 +270,19 @@ -- | Unalias import name unalias :: Module -> Text -> [Text] unalias m alias = [view importModuleName i | i <- view moduleImports m, view importAs i == Just alias] + +-- | Set tag to `Inspected` +setTag :: Ord t => t -> Inspected i t a -> Inspected i t a +setTag tag = over inspectionTags (S.insert tag) + +-- | Check whether `Inspected` has tag +hasTag :: Ord t => t -> Inspected i t a -> Bool +hasTag tag = has (inspectionTags . ix tag) + +-- | Drop tag from `Inspected` +removeTag :: Ord t => t -> Inspected i t a -> Inspected i t a +removeTag tag = over inspectionTags (S.delete tag) + +-- | Drop all tags +dropTags :: Inspected i t a -> Inspected i t a +dropTags = set inspectionTags S.empty
src/HsDev/Symbols/Location.hs view
@@ -34,7 +34,7 @@ import Text.Read (readMaybe) import HsDev.PackageDb -import HsDev.Project +import HsDev.Project.Types import HsDev.Util ((.::), (.::?), (.::?!), objectUnion) -- | Just package name and version without its location
src/HsDev/Symbols/Types.hs view
@@ -15,8 +15,9 @@ ModuleDeclaration(..), declarationModuleId, moduleDeclaration, ExportedDeclaration(..), exportedBy, exportedDeclaration, Inspection(..), inspectionAt, inspectionOpts, - Inspected(..), inspection, inspectedId, inspectionResult, - InspectedModule, + Inspected(..), inspection, inspectedId, inspectionTags, inspectionResult, noTags, + ModuleTag(..), + InspectedModule, notInspected, module HsDev.PackageDb, module HsDev.Project, @@ -32,14 +33,19 @@ import Data.Aeson import Data.List (intercalate) import Data.Maybe (fromMaybe) +import Data.Function +import Data.Ord import Data.Text (Text, unpack) import qualified Data.Text as T +import Data.Set (Set) +import qualified Data.Set as S import Data.Time.Clock.POSIX (POSIXTime) import HsDev.PackageDb import HsDev.Project import HsDev.Symbols.Class import HsDev.Symbols.Documented +import HsDev.Types import HsDev.Util (tab, tabs, (.::), (.::?), (.::?!), noNulls) -- | What to export/import for data/class etc @@ -456,6 +462,14 @@ instance Read POSIXTime where readsPrec i = map (first (fromIntegral :: Integer -> POSIXTime)) . readsPrec i +instance Monoid Inspection where + mempty = InspectionNone + mappend InspectionNone r = r + mappend l InspectionNone = l + mappend (InspectionAt ltm lopts) (InspectionAt rtm ropts) + | ltm >= rtm = InspectionAt ltm lopts + | otherwise = InspectionAt rtm ropts + instance ToJSON Inspection where toJSON InspectionNone = object ["inspected" .= False] toJSON (InspectionAt tm fs) = object [ @@ -468,32 +482,60 @@ (InspectionAt <$> (fromInteger <$> v .:: "mtime") <*> (v .:: "flags")) -- | Inspected entity -data Inspected i a = Inspected { +data Inspected i t a = Inspected { _inspection :: Inspection, _inspectedId :: i, - _inspectionResult :: Either String a } - deriving (Eq, Ord) + _inspectionTags :: Set t, + _inspectionResult :: Either HsDevError a } -instance Functor (Inspected i) where +inspectedTup :: Inspected i t a -> (Inspection, i, Set t, Maybe a) +inspectedTup (Inspected insp i tags res) = (insp, i, tags, either (const Nothing) Just res) + +instance (Eq i, Eq t, Eq a) => Eq (Inspected i t a) where + (==) = (==) `on` inspectedTup + +instance (Ord i, Ord t, Ord a) => Ord (Inspected i t a) where + compare = comparing inspectedTup + +instance Functor (Inspected i t) where fmap f insp = insp { _inspectionResult = fmap f (_inspectionResult insp) } -instance Foldable (Inspected i) where +instance Foldable (Inspected i t) where foldMap f = either mempty f . _inspectionResult -instance Traversable (Inspected i) where - traverse f (Inspected insp i r) = Inspected insp i <$> either (pure . Left) (liftA Right . f) r +instance Traversable (Inspected i t) where + traverse f (Inspected insp i ts r) = Inspected insp i ts <$> either (pure . Left) (liftA Right . f) r -instance (NFData i, NFData a) => NFData (Inspected i a) where - rnf (Inspected t i r) = rnf t `seq` rnf i `seq` rnf r +instance (NFData i, NFData t, NFData a) => NFData (Inspected i t a) where + rnf (Inspected t i ts r) = rnf t `seq` rnf i `seq` rnf ts `seq` rnf r +-- | Empty tags +noTags :: Ord t => Set t +noTags = S.empty + +data ModuleTag = InferredTypesTag | RefinedDocsTag deriving (Eq, Ord, Read, Show, Enum, Bounded) + +instance NFData ModuleTag where + rnf InferredTypesTag = () + rnf RefinedDocsTag = () + +instance ToJSON ModuleTag where + toJSON InferredTypesTag = toJSON ("types" :: String) + toJSON RefinedDocsTag = toJSON ("docs" :: String) + +instance FromJSON ModuleTag where + parseJSON = withText "module-tag" $ \txt -> msum [ + guard (txt == "types") >> return InferredTypesTag, + guard (txt == "docs") >> return RefinedDocsTag] + -- | Inspected module -type InspectedModule = Inspected ModuleLocation Module +type InspectedModule = Inspected ModuleLocation ModuleTag Module instance Show InspectedModule where - show (Inspected i mi m) = unlines [either showError show m, "\tinspected: " ++ show i] where - showError :: String -> String - showError e = unlines $ ("\terror: " ++ e) : case mi of + show (Inspected i mi ts m) = unlines [either showError show m, "\tinspected: " ++ show i, "\ttags: " ++ intercalate ", " (map show $ S.toList ts)] where + showError :: HsDevError -> String + showError e = unlines $ ("\terror: " ++ show e) : case mi of FileModule f p -> ["file: " ++ f, "project: " ++ maybe "" (view projectPath) p] InstalledModule c p n -> ["cabal: " ++ show c, "package: " ++ maybe "" show p, "name: " ++ n] ModuleSource src -> ["source: " ++ fromMaybe "" src] @@ -502,13 +544,18 @@ toJSON im = object [ "inspection" .= _inspection im, "location" .= _inspectedId im, + "tags" .= S.toList (_inspectionTags im), either ("error" .=) ("module" .=) (_inspectionResult im)] instance FromJSON InspectedModule where parseJSON = withObject "inspected module" $ \v -> Inspected <$> v .:: "inspection" <*> v .:: "location" <*> + (S.fromList <$> (v .::?! "tags")) <*> ((Left <$> v .:: "error") <|> (Right <$> v .:: "module")) + +notInspected :: ModuleLocation -> InspectedModule +notInspected mloc = Inspected mempty mloc noTags (Left $ NotInspected mloc) instance Symbol Module where symbolName = _moduleName
src/HsDev/Tools/Base.hs view
@@ -13,6 +13,7 @@ ) where import Control.Lens (set) +import Control.Monad.Catch (MonadCatch) import Control.Monad.Except import Control.Monad.State import Data.Array (assocs) @@ -22,6 +23,7 @@ import System.Process import Text.Regex.PCRE ((=~), MatchResult(..)) +import HsDev.Error import HsDev.Tools.Types import HsDev.Symbols import HsDev.Util (liftIOErrors) @@ -72,15 +74,14 @@ at_ :: (Int -> Maybe String) -> Int -> String at_ g = fromMaybe "" . g -inspect :: Monad m => ModuleLocation -> ExceptT String m Inspection -> ExceptT String m Module -> ExceptT String m InspectedModule -inspect mloc insp act = lift $ execStateT inspect' (Inspected InspectionNone mloc (Left "not inspected")) where - inspect' = runExceptT $ do - i <- mapExceptT lift insp - modify (set inspection i) - v <- mapExceptT lift act - modify (set inspectionResult (Right v)) - `catchError` - \e -> modify (set inspectionResult (Left e)) +inspect :: MonadCatch m => ModuleLocation -> m Inspection -> m Module -> m InspectedModule +inspect mloc insp act = execStateT inspect' (notInspected mloc) where + inspect' = do + r <- hsdevCatch $ hsdevLiftIO $ do + i <- lift insp + modify (set inspection i) + lift act + modify (set inspectionResult r) type ReadM a = StateT String [] a
src/HsDev/Tools/Ghc/Check.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE PatternGuards #-} +{-# LANGUAGE PatternGuards, OverloadedStrings #-} module HsDev.Tools.Ghc.Check ( checkFiles, check, checkFile, checkSource, @@ -15,13 +15,16 @@ import Control.Lens (preview, view, each, _Just, (^..)) import Control.Monad.Except +import Control.Monad.Catch (MonadThrow(..)) import Data.Maybe (fromMaybe) import System.FilePath (makeRelative) import System.Directory (doesDirectoryExist) +import System.Log.Simple (MonadLog(..), scope) import GHC hiding (Warning, Module, moduleName) import Control.Concurrent.FiniteChan +import HsDev.Error import HsDev.PackageDb import HsDev.Scan.Browse (browsePackages) import HsDev.Symbols (moduleOpts) @@ -33,8 +36,8 @@ import HsDev.Util (readFileUtf8, ordNub) -- | Check files and collect warnings and errors -checkFiles :: [String] -> PackageDbStack -> [FilePath] -> Maybe Project -> Ghc [Note OutputMessage] -checkFiles opts pdbs files _ = do +checkFiles :: (MonadLog m, GhcMonad m) => [String] -> PackageDbStack -> [FilePath] -> Maybe Project -> m [Note OutputMessage] +checkFiles opts pdbs files _ = scope "check-files" $ do ch <- liftIO newChan withFlags $ do modifyFlags (\fs -> fs { log_action = logToChan ch }) @@ -45,17 +48,17 @@ liftIO $ recalcNotesTabs notes -- | Check module source -check :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc [Note OutputMessage] -check opts pdbs m msrc = case view moduleLocation m of +check :: (MonadLog m, GhcMonad m, MonadThrow m) => [String] -> PackageDbStack -> Module -> Maybe String -> m [Note OutputMessage] +check opts pdbs m msrc = scope "check" $ case view moduleLocation m of FileModule file proj -> do ch <- liftIO newChan - pkgs <- mapExceptT liftIO $ browsePackages opts pdbs + pkgs <- browsePackages opts pdbs let dir = fromMaybe (sourceModuleRoot (view moduleName m) file) $ preview (_Just . projectPath) proj dirExist <- liftIO $ doesDirectoryExist dir - lift $ withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do + withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do _ <- setCmdOpts $ concat [ ["-Wall"], packageDbStackOpts pdbs, @@ -67,14 +70,14 @@ loadTargets [target] notes <- liftIO $ stopChan ch liftIO $ recalcNotesTabs notes - _ -> throwError "Module is not source" + _ -> scope "check" $ hsdevError $ ModuleNotSource (view moduleLocation m) -- | Check module and collect warnings and errors -checkFile :: [String] -> PackageDbStack -> Module -> ExceptT String Ghc [Note OutputMessage] +checkFile :: (MonadLog m, GhcMonad m, MonadThrow m) => [String] -> PackageDbStack -> Module -> m [Note OutputMessage] checkFile opts pdbs m = check opts pdbs m Nothing -- | Check module and collect warnings and errors -checkSource :: [String] -> PackageDbStack -> Module -> String -> ExceptT String Ghc [Note OutputMessage] +checkSource :: (MonadLog m, GhcMonad m, MonadThrow m) => [String] -> PackageDbStack -> Module -> String -> m [Note OutputMessage] checkSource opts pdbs m src = check opts pdbs m (Just src) -- Recalc tabs for notes
src/HsDev/Tools/Ghc/Types.hs view
@@ -9,6 +9,7 @@ import Control.DeepSeq import Control.Lens (over, view, set, each, preview, makeLenses, _Just) import Control.Monad +import Control.Monad.Catch (MonadThrow(..)) import Control.Monad.IO.Class import Data.Aeson import Data.Generics @@ -17,6 +18,7 @@ import Data.String (fromString) import System.Directory import System.FilePath +import System.Log.Simple (MonadLog(..), scope) import GHC hiding (exprType, Module, moduleName) import GHC.SYB.Utils (everythingStaged, Stage(TypeChecker)) @@ -29,6 +31,7 @@ import Pretty import System.Directory.Paths (canonicalize) +import HsDev.Error import HsDev.Scan.Browse (browsePackages) import HsDev.PackageDb import HsDev.Symbols @@ -98,18 +101,18 @@ v .:: "type" -- | Get all types in module -fileTypes :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc [Note TypedExpr] -fileTypes opts pdbs m msrc = case view moduleLocation m of +fileTypes :: (MonadLog m, GhcMonad m, MonadThrow m) => [String] -> PackageDbStack -> Module -> Maybe String -> m [Note TypedExpr] +fileTypes opts pdbs m msrc = scope "types" $ case view moduleLocation m of FileModule file proj -> do file' <- liftIO $ canonicalize file cts <- maybe (liftIO $ readFileUtf8 file') return msrc - pkgs <- mapExceptT liftIO $ browsePackages opts pdbs + pkgs <- browsePackages opts pdbs let dir = fromMaybe (sourceModuleRoot (view moduleName m) file') $ preview (_Just . projectPath) proj dirExist <- liftIO $ doesDirectoryExist dir - lift $ withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do + withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do _ <- setCmdOpts $ concat [ packageDbStackOpts pdbs, moduleOpts pkgs m, @@ -119,7 +122,7 @@ ts <- moduleTypes file' df <- getSessionDynFlags return $ map (setExpr cts . recalcTabs cts 8 . uncurry (toNote df)) ts - _ -> throwError "Module is not source" + _ -> hsdevError $ ModuleNotSource (view moduleLocation m) where toNote :: DynFlags -> SrcSpan -> Type -> Note String toNote df spn tp = Note { @@ -144,5 +147,5 @@ return $ set (declaration . functionType) (Just $ fromString $ view (note . typedType) tnote) d -- | Infer types in module -inferTypes :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc Module -inferTypes opts pdbs m msrc = liftM (`setModuleTypes` m) $ fileTypes opts pdbs m msrc +inferTypes :: (MonadLog m, GhcMonad m, MonadThrow m) => [String] -> PackageDbStack -> Module -> Maybe String -> m Module +inferTypes opts pdbs m msrc = scope "infer" $ liftM (`setModuleTypes` m) $ fileTypes opts pdbs m msrc
src/HsDev/Tools/Ghc/Worker.hs view
@@ -1,8 +1,9 @@-{-# LANGUAGE PatternGuards #-} +{-# LANGUAGE PatternGuards, OverloadedStrings, GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module HsDev.Tools.Ghc.Worker ( -- * Workers + GhcM(..), runGhcM, liftGhc, ghcWorker, ghciWorker, -- * Initializers and actions ghcRun, @@ -22,7 +23,9 @@ import Control.Monad import Control.Monad.Catch +import Control.Monad.CatchIO import Control.Monad.Except +import Control.Monad.Reader import Data.Dynamic import Data.Maybe import Data.Time.Clock (getCurrentTime) @@ -30,10 +33,16 @@ import Packages import StringBuffer import System.Directory (getCurrentDirectory, setCurrentDirectory) -import Text.Read +import qualified System.Log.Simple as Log +import System.Log.Simple.Monad (MonadLog(..)) +import Text.Read (readMaybe) +import Text.Format +import Exception (ExceptionMonad(..)) import GHC hiding (Warning, Module, moduleName) +import GhcMonad (Ghc(..)) import GHC.Paths +import DynFlags (HasDynFlags(..)) import Outputable import qualified ErrUtils as E import FastString (unpackFS) @@ -44,36 +53,72 @@ import HsDev.Symbols.Location (Position(..), Region(..), region, ModulePackage, ModuleLocation(..)) import HsDev.Tools.Types +instance MonadThrow Ghc where + throwM = liftIO . throwM + +instance MonadCatch Ghc where + catch = gcatch + +instance MonadCatchIO Ghc where + catch = gcatch + block act = Ghc $ block . unGhc act + unblock act = Ghc $ unblock . unGhc act + +instance ExceptionMonad m => ExceptionMonad (ReaderT r m) where + gcatch act onError = ReaderT $ \v -> gcatch (runReaderT act v) (flip runReaderT v . onError) + gmask f = ReaderT $ \v -> gmask (\h -> flip runReaderT v (f $ \act -> ReaderT (\v' -> h (runReaderT act v')))) + +instance (Monad m, HasDynFlags m) => HasDynFlags (ReaderT r m) where + getDynFlags = lift getDynFlags + +instance (Monad m, GhcMonad m) => GhcMonad (ReaderT r m) where + getSession = lift getSession + setSession = lift . setSession + +newtype GhcM a = GhcM { unGhcM :: ReaderT Log.Log Ghc a } + deriving (Functor, Applicative, Monad, MonadIO, MonadCatchIO, MonadThrow, MonadCatch, ExceptionMonad, HasDynFlags, MonadLog, GhcMonad) + +runGhcM :: MonadLog m => Maybe FilePath -> GhcM a -> m a +runGhcM dir act = do + l <- askLog + liftIO $ runGhc dir (runReaderT (unGhcM act) l) + +liftGhc :: Ghc a -> GhcM a +liftGhc = GhcM . lift + -- | Ghc worker. Pass options and initializer action -ghcWorker :: [String] -> Ghc () -> IO (Worker Ghc) -ghcWorker opts initialize = startWorker (runGhc (Just libdir)) ghcInit id where - ghcInit f = ghcRun opts (initialize >> f) +ghcWorker :: MonadLog m => [String] -> GhcM () -> m (Worker GhcM) +ghcWorker opts initialize = do + l <- askLog + liftIO $ startWorker (flip runReaderT l . runGhcM (Just libdir)) (ghcRun opts . (initialize >>)) (Log.scope "ghc") -- | Interpreter worker is worker with @preludeModules@ loaded -ghciWorker :: IO (Worker Ghc) -ghciWorker = ghcWorker [] (importModules preludeModules) +ghciWorker :: MonadLog m => m (Worker GhcM) +ghciWorker = do + l <- askLog + liftIO $ startWorker (flip runReaderT l . runGhcM (Just libdir)) (ghcRun [] . (importModules preludeModules >>)) (Log.scope "ghc") -- | Run ghc -ghcRun :: [String] -> Ghc a -> Ghc a +ghcRun :: GhcMonad m => [String] -> m a -> m a ghcRun opts f = do fs <- getSessionDynFlags defaultCleanupHandler fs $ do (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) let fs'' = fs' { ghcMode = CompManager, - -- ghcLink = LinkInMemory, - -- hscTarget = HscInterpreted } - ghcLink = NoLink, - hscTarget = HscNothing } + ghcLink = LinkInMemory, + hscTarget = HscInterpreted } + -- ghcLink = NoLink, + -- hscTarget = HscNothing } void $ setSessionDynFlags fs'' f -- | Alter @DynFlags@ temporary -withFlags :: Ghc a -> Ghc a +withFlags :: GhcMonad m => m a -> m a withFlags = gbracket getSessionDynFlags (\fs -> setSessionDynFlags fs >> return ()) . const -- | Update @DynFlags@ -modifyFlags :: (DynFlags -> DynFlags) -> Ghc () +modifyFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m () modifyFlags f = do fs <- getSessionDynFlags let @@ -83,25 +128,29 @@ return () -- | Add options without reinit session -addCmdOpts :: [String] -> Ghc () +addCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m () addCmdOpts opts = do + Log.log Log.Trace $ "setting ghc options: {}" ~~ unwords opts fs <- getSessionDynFlags (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) let fs'' = fs' { ghcMode = CompManager, - ghcLink = NoLink, - hscTarget = HscNothing } + ghcLink = LinkInMemory, + hscTarget = HscInterpreted } + -- ghcLink = NoLink, + -- hscTarget = HscNothing } void $ setSessionDynFlags fs'' -- | Set options after session reinit -setCmdOpts :: [String] -> Ghc () +setCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m () setCmdOpts opts = do + Log.log Log.Trace $ "restarting ghc session with: {}" ~~ unwords opts initGhcMonad (Just libdir) addCmdOpts opts modifyFlags (\fs -> fs { log_action = logToNull }) -- | Import some modules -importModules :: [String] -> Ghc () +importModules :: GhcMonad m => [String] -> m () importModules mods = mapM parseImportDecl ["import " ++ m | m <- mods] >>= setContext . map IIDecl -- | Default interpreter modules @@ -109,16 +158,16 @@ preludeModules = ["Prelude", "Data.List", "Control.Monad", "HsDev.Tools.Ghc.Prelude"] -- | Evaluate expression -evaluate :: String -> Ghc String -evaluate expr = liftM fromDynamic (dynCompileExpr $ "show (" ++ expr ++ ")") >>= +evaluate :: GhcMonad m => String -> m String +evaluate expr = liftM fromDynamic (dynCompileExpr $ "show ({})" ~~ expr) >>= maybe (fail "evaluate fail") return -- | Clear loaded targets -clearTargets :: Ghc () +clearTargets :: GhcMonad m => m () clearTargets = loadTargets [] -- | Make target with its source code optional -makeTarget :: String -> Maybe String -> Ghc Target +makeTarget :: GhcMonad m => String -> Maybe String -> m Target makeTarget name Nothing = guessTarget name Nothing makeTarget name (Just cts) = do t <- guessTarget name Nothing @@ -126,11 +175,11 @@ return t { targetContents = Just (stringToStringBuffer cts, tm) } -- | Load all targets -loadTargets :: [Target] -> Ghc () +loadTargets :: GhcMonad m => [Target] -> m () loadTargets ts = setTargets ts >> load LoadAllTargets >> return () -- | Get list of installed packages -listPackages :: Ghc [ModulePackage] +listPackages :: GhcMonad m => m [ModulePackage] listPackages = liftM (mapMaybe readPackage . fromMaybe [] . pkgDatabase) getSessionDynFlags readPackage :: PackageConfig -> Maybe ModulePackage @@ -142,7 +191,7 @@ spanRegion _ = Position 0 0 `region` Position 0 0 -- | Set current directory and restore it after action -withCurrentDirectory :: FilePath -> Ghc a -> Ghc a +withCurrentDirectory :: GhcMonad m => FilePath -> m a -> m a withCurrentDirectory dir act = gbracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $ const (liftIO (setCurrentDirectory dir) >> act) @@ -174,9 +223,3 @@ logToNull _ _ _ _ _ = return () -- TODO: Load target by @ModuleLocation@, which may cause updating @DynFlags@ - -instance MonadThrow Ghc where - throwM = liftIO . throwM - -instance MonadCatch Ghc where - catch = gcatch
src/HsDev/Tools/GhcMod.hs view
@@ -32,7 +32,7 @@ import Control.Arrow import Control.Concurrent import Control.DeepSeq -import Control.Exception (SomeException(..)) +import Control.Exception (SomeException(..), displayException) import Control.Lens (view, preview, _Just, over) import Control.Monad.Except import Control.Monad.Catch (MonadThrow(..), MonadCatch(..)) @@ -46,6 +46,7 @@ import Exception (gcatch) import System.Directory import System.FilePath (normalise) +import System.Log.Simple (withNoLog) import Text.Read (readMaybe) import Language.Haskell.GhcMod (GhcModT, withOptions) @@ -54,6 +55,7 @@ import qualified Language.Haskell.GhcMod.Types as GhcMod import Control.Concurrent.Worker +import HsDev.Error import HsDev.PackageDb import HsDev.Project import HsDev.Sandbox (searchPackageDbStack) @@ -259,7 +261,7 @@ locateGhcModEnv :: FilePath -> IO (Either Project PackageDbStack) locateGhcModEnv f = do mproj <- locateProject f - maybe (liftM Right $ searchPackageDbStack f) (return . Left) mproj + maybe (liftM Right $ withNoLog $ searchPackageDbStack f) (return . Left) mproj ghcModEnvPath :: FilePath -> Either Project PackageDbStack -> FilePath ghcModEnvPath defaultPath = either (view projectPath) (fromMaybe defaultPath . preview packageDb . topPackageDb) @@ -308,11 +310,11 @@ t <- pushTask w act return (M.insert envPath' w wmap, t) -waitMultiGhcMod :: Worker (ReaderT WorkerMap IO) -> FilePath -> GhcModT IO a -> ExceptT String IO a +waitMultiGhcMod :: Worker (ReaderT WorkerMap IO) -> FilePath -> GhcModT IO a -> IO a waitMultiGhcMod w f = liftIO . pushTask w . dispatch f >=> asExceptT . waitCatch >=> asExceptT . waitCatch where - asExceptT :: Monad m => m (Either SomeException a) -> ExceptT String m a - asExceptT = ExceptT . liftM (left (\(SomeException e) -> show e)) + asExceptT :: IO (Either SomeException a) -> IO a + asExceptT act = act >>= either (hsdevError . ToolError "ghc-mod" . displayException) return
+ src/HsDev/Types.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE OverloadedStrings #-} + +module HsDev.Types ( + HsDevError(..) + ) where + +import Control.Exception +import Control.DeepSeq (NFData(..)) +import Data.Aeson +import Data.Aeson.Types (Pair, Parser) +import Data.Typeable +import Text.Format + +import HsDev.Symbols.Location + +-- | hsdev exception type +data HsDevError = + ModuleNotSource ModuleLocation | + BrowseNoModuleInfo String | + FileNotFound FilePath | + ToolNotFound String | + ProjectNotFound String | + ToolError String String | + NotInspected ModuleLocation | + InspectError String | + InspectCabalError FilePath String | + IOFailed String | + GhcError String | + RequestError String String | + ResponseError String String | + OtherError String + deriving (Typeable) + +instance NFData HsDevError where + rnf (ModuleNotSource mloc) = rnf mloc + rnf (BrowseNoModuleInfo m) = rnf m + rnf (FileNotFound f) = rnf f + rnf (ToolNotFound t) = rnf t + rnf (ProjectNotFound p) = rnf p + rnf (ToolError t e) = rnf t `seq` rnf e + rnf (NotInspected mloc) = rnf mloc + rnf (InspectError e) = rnf e + rnf (InspectCabalError c e) = rnf c `seq` rnf e + rnf (IOFailed e) = rnf e + rnf (GhcError e) = rnf e + rnf (RequestError e r) = rnf e `seq` rnf r + rnf (ResponseError e r) = rnf e `seq` rnf r + rnf (OtherError e) = rnf e + +instance Show HsDevError where + show (ModuleNotSource mloc) = format "module is not source: {}" ~~ show mloc + show (BrowseNoModuleInfo m) = format "can't find module info for {}" ~~ m + show (FileNotFound f) = format "file '{}' not found" ~~ f + show (ToolNotFound t) = format "tool '{}' not found" ~~ t + show (ProjectNotFound p) = format "project '{}' not found" ~~ p + show (ToolError t e) = format "tool '{}' failed: {}" ~~ t ~~ e + show (NotInspected mloc) = "module not inspected: {}" ~~ show mloc + show (InspectError e) = format "failed to inspect: {}" ~~ e + show (InspectCabalError c e) = format "failed to inspect cabal {}: {}" ~~ c ~~ e + show (IOFailed e) = format "io exception: {}" ~~ e + show (GhcError e) = format "ghc exception: {}" ~~ e + show (RequestError e r) = format "request error: {}, request: {}" ~~ e ~~ r + show (ResponseError e r) = format "response error: {}, response: {}" ~~ e ~~ r + show (OtherError e) = e + +instance FormatBuild HsDevError where + +jsonErr :: String -> [Pair] -> Value +jsonErr e = object . (("error" .= e) :) + +instance ToJSON HsDevError where + toJSON (ModuleNotSource mloc) = jsonErr "module is not source" ["module" .= mloc] + toJSON (BrowseNoModuleInfo m) = jsonErr "no module info" ["module" .= m] + toJSON (FileNotFound f) = jsonErr "file not found" ["file" .= f] + toJSON (ToolNotFound t) = jsonErr "tool not found" ["tool" .= t] + toJSON (ProjectNotFound p) = jsonErr "project not found" ["project" .= p] + toJSON (ToolError t e) = jsonErr "tool error" ["tool" .= t, "msg" .= e] + toJSON (NotInspected mloc) = jsonErr "module not inspected" ["module" .= mloc] + toJSON (InspectError e) = jsonErr "inspect error" ["msg" .= e] + toJSON (InspectCabalError c e) = jsonErr "inspect cabal error" ["cabal" .= c, "msg" .= e] + toJSON (IOFailed e) = jsonErr "io error" ["msg" .= e] + toJSON (GhcError e) = jsonErr "ghc error" ["msg" .= e] + toJSON (RequestError e r) = jsonErr "request error" ["msg" .= e, "request" .= r] + toJSON (ResponseError e r) = jsonErr "response error" ["msg" .= e, "response" .= r] + toJSON (OtherError e) = jsonErr "other error" ["msg" .= e] + +instance FromJSON HsDevError where + parseJSON = withObject "hsdev-error" $ \v -> do + err <- v .: "error" :: Parser String + case err of + "module is not source" -> ModuleNotSource <$> v .: "module" + "no module info" -> BrowseNoModuleInfo <$> v .: "module" + "file not found" -> FileNotFound <$> v .: "file" + "tool not found" -> ToolNotFound <$> v .: "tool" + "project not found" -> ProjectNotFound <$> v .: "project" + "tool error" -> ToolError <$> v .: "tool" <*> v .: "msg" + "module not inspected" -> NotInspected <$> v .: "module" + "inspect error" -> InspectError <$> v .: "msg" + "inspect cabal error" -> InspectCabalError <$> v .: "cabal" <*> v .: "msg" + "io error" -> IOFailed <$> v .: "msg" + "ghc error" -> GhcError <$> v .: "msg" + "request error" -> RequestError <$> v .: "msg" <*> v .: "request" + "response error" -> ResponseError <$> v .: "msg" <*> v .: "response" + "other error" -> OtherError <$> v .: "msg" + _ -> fail "invalid error" + +instance Exception HsDevError
tools/Tool.hs view
@@ -2,8 +2,11 @@ module Tool ( -- * Tool - ToolM, toolMain, printExceptT, printResult, + ToolM, toolMain, printExceptT, printResult, runToolClient, + ClientM, + + toJSON, Value, module Options.Applicative, module HsDev.Util ) where @@ -13,10 +16,13 @@ import Control.Monad (liftM) import Data.Aeson import qualified Data.ByteString.Lazy.Char8 as L (putStrLn) +import Data.Default import Options.Applicative import System.Environment import System.IO +import HsDev.Client.Commands +import HsDev.Server.Base import HsDev.Tools.Base (ToolM) import HsDev.Util (cmd, parseArgs) @@ -34,3 +40,7 @@ printResult :: (ToJSON a, MonadIO m) => m a -> m () printResult act = act >>= liftIO . L.putStrLn . encode + +runToolClient :: ToJSON a => ClientM IO a -> IO () +runToolClient act = runServer silentOpts $ + runClient def act >>= liftIO . L.putStrLn . encode
tools/hsinspect.hs view
@@ -6,15 +6,14 @@ import Control.Monad (liftM, (>=>)) import Control.Monad.IO.Class -import Control.Monad.Except (ExceptT(..), runExceptT) -import Data.Aeson (toJSON) import System.Directory (canonicalizePath) import System.FilePath (takeExtension) +import HsDev.Error +import HsDev.Inspect (inspectContents, inspectDocs, getDefines) import HsDev.PackageDb import HsDev.Project (readProject) import HsDev.Scan (scanModule, scanModify) -import HsDev.Inspect (inspectContents, inspectDocs, getDefines) import HsDev.Symbols.Location (ModuleLocation(..)) import HsDev.Tools.Ghc.Types (inferTypes) import HsDev.Tools.Ghc.Worker @@ -29,23 +28,24 @@ many (strOption (metavar "GHC_OPT" <> long "ghc" <> short 'g' <> help "options to pass to GHC")) main :: IO () -main = toolMain "hsinspect" "haskell inspect" opts (printExceptT . printResult . inspect') where +main = toolMain "hsinspect" "haskell inspect" opts (runToolClient . inspect') where + inspect' :: Opts -> ClientM IO Value inspect' (Opts Nothing ghcs) = do cts <- liftIO getContents defs <- liftIO getDefines - liftM toJSON $ inspectContents "stdin" defs ghcs cts + liftM toJSON $ liftIO $ hsdevLift $ inspectContents "stdin" defs ghcs cts inspect' (Opts (Just fname@(takeExtension -> ".hs")) ghcs) = do fname' <- liftIO $ canonicalizePath fname defs <- liftIO getDefines im <- scanModule defs ghcs (FileModule fname' Nothing) Nothing - ghc <- liftIO $ ghcWorker ghcs (return ()) + ghc <- ghcWorker ghcs (return ()) let scanAdditional = - scanModify' (\opts' _ -> inspectDocs opts') >=> - scanModify' (\opts' pdbs m -> ExceptT (inWorker ghc (runExceptT $ inferTypes opts' pdbs m Nothing))) + scanModify' (\opts' _ -> liftIO . inspectDocs opts') >=> + scanModify' (\opts' pdbs m -> liftIO (inWorker ghc (inferTypes opts' pdbs m Nothing))) toJSON <$> scanAdditional im inspect' (Opts (Just fcabal@(takeExtension -> ".cabal")) _) = do fcabal' <- liftIO $ canonicalizePath fcabal - toJSON <$> readProject fcabal' + toJSON <$> liftIO (readProject fcabal') inspect' (Opts (Just mname) ghcs) = toJSON <$> scanModule [] ghcs (InstalledModule UserDb Nothing mname) Nothing scanModify' f im = scanModify f im <|> return im