hsdev 0.2.5.0 → 0.2.5.1
raw patch · 60 files changed
+9886/−9775 lines, 60 filesdep ~ghcdep ~haddock-apidep ~mmorph
Dependency ranges changed: ghc, haddock-api, mmorph
Files
- hsdev.cabal +318/−311
- src/Data/Deps.hs +70/−70
- src/Data/Group.hs +40/−40
- src/Data/Help.hs +21/−21
- src/HsDev.hs +33/−33
- src/HsDev/Cache.hs +79/−79
- src/HsDev/Cache/Structured.hs +72/−72
- src/HsDev/Client/Commands.hs +414/−414
- src/HsDev/Commands.hs +193/−193
- src/HsDev/Database.hs +253/−253
- src/HsDev/Database/Update.hs +447/−447
- src/HsDev/Display.hs +55/−55
- src/HsDev/Error.hs +63/−63
- src/HsDev/Inspect.hs +467/−467
- src/HsDev/PackageDb.hs +106/−106
- src/HsDev/Project.hs +167/−164
- src/HsDev/Project/Compat.hs +62/−0
- src/HsDev/Project/Types.hs +298/−298
- src/HsDev/Sandbox.hs +161/−161
- src/HsDev/Scan.hs +209/−209
- src/HsDev/Scan/Browse.hs +248/−251
- src/HsDev/Server/Base.hs +159/−159
- src/HsDev/Server/Commands.hs +434/−434
- src/HsDev/Server/Message.hs +115/−115
- src/HsDev/Server/Types.hs +792/−792
- src/HsDev/Stack.hs +131/−131
- src/HsDev/Symbols.hs +278/−278
- src/HsDev/Symbols/Class.hs +20/−20
- src/HsDev/Symbols/Documented.hs +24/−24
- src/HsDev/Symbols/Location.hs +277/−277
- src/HsDev/Symbols/Resolve.hs +176/−176
- src/HsDev/Symbols/Types.hs +620/−620
- src/HsDev/Symbols/Util.hs +203/−203
- src/HsDev/Tools/AutoFix.hs +92/−92
- src/HsDev/Tools/Base.hs +117/−117
- src/HsDev/Tools/Cabal.hs +85/−85
- src/HsDev/Tools/ClearImports.hs +112/−112
- src/HsDev/Tools/Ghc/Check.hs +88/−88
- src/HsDev/Tools/Ghc/Compat.hs +154/−113
- src/HsDev/Tools/Ghc/MGhc.hs +204/−204
- src/HsDev/Tools/Ghc/Session.hs +49/−49
- src/HsDev/Tools/Ghc/Types.hs +143/−145
- src/HsDev/Tools/Ghc/Worker.hs +249/−248
- src/HsDev/Tools/HDocs.hs +60/−60
- src/HsDev/Tools/HLint.hs +97/−97
- src/HsDev/Tools/Hayoo.hs +135/−135
- src/HsDev/Tools/Types.hs +99/−99
- src/HsDev/Types.hs +112/−112
- src/HsDev/Util.hs +340/−338
- src/HsDev/Watcher.hs +73/−73
- src/System/Directory/Paths.hs +31/−31
- src/System/Directory/Watcher.hs +151/−151
- src/System/Win32/PowerShell.hs +214/−214
- tests/Test.hs +47/−47
- tests/test-package/ModuleOne.hs +14/−14
- tools/hsautofix.hs +87/−87
- tools/hscabal.hs +10/−10
- tools/hsclearimports.hs +44/−44
- tools/hshayoo.hs +22/−22
- tools/hsinspect.hs +52/−52
hsdev.cabal view
@@ -1,311 +1,318 @@-name: hsdev -version: 0.2.5.0 -synopsis: Haskell development library -description: - Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc. -homepage: https://github.com/mvoidex/hsdev -license: BSD3 -license-file: LICENSE -author: Alexandr `Voidex` Ruchkin -maintainer: voidex@live.com --- copyright: -category: Development -build-type: Simple -cabal-version: >=1.8 -extra-source-files: - tests/test-package/*.hs - tests/test-package/test-package.cabal - -source-repository head - type: git - location: git://github.com/mvoidex/hsdev.git - -library - hs-source-dirs: src - ghc-options: -Wall -fno-warn-tabs - exposed-modules: - Control.Apply.Util - Control.Concurrent.FiniteChan - Control.Concurrent.Worker - Control.Concurrent.Util - Data.Async - Data.Deps - Data.Group - Data.Help - Data.Lisp - Data.Maybe.JustIf - HsDev - HsDev.Cache - HsDev.Cache.Structured - HsDev.Client.Commands - HsDev.Commands - HsDev.Database - HsDev.Database.Async - 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 - HsDev.Server.Commands - HsDev.Server.Message - HsDev.Server.Types - HsDev.Sandbox - HsDev.Stack - HsDev.Symbols - HsDev.Symbols.Class - HsDev.Symbols.Location - HsDev.Symbols.Documented - HsDev.Symbols.Resolve - HsDev.Symbols.Types - HsDev.Symbols.Util - HsDev.Tools.AutoFix - HsDev.Tools.Base - HsDev.Tools.Cabal - HsDev.Tools.ClearImports - HsDev.Tools.Ghc.Check - HsDev.Tools.Ghc.Compat - HsDev.Tools.Ghc.MGhc - HsDev.Tools.Ghc.Prelude - HsDev.Tools.Ghc.Types - HsDev.Tools.Ghc.Worker - HsDev.Tools.Ghc.Session - HsDev.Tools.Hayoo - HsDev.Tools.HDocs - HsDev.Tools.HLint - HsDev.Tools.Types - HsDev.Types - HsDev.Util - HsDev.Version - HsDev.Watcher - HsDev.Watcher.Types - System.Directory.Paths - System.Directory.Watcher - - if os(windows) - build-depends: - Win32 >= 2.3.0 - exposed-modules: - System.Win32.PowerShell - System.Win32.FileMapping.NamePool - System.Win32.FileMapping.Memory - else - build-depends: - unix >= 2.7.0 - - if impl(ghc >= 8.0) - build-depends: - haddock-api >= 2.17.0 && < 2.18.0, - ghc >= 8.0.0 && < 8.1.0, - ghc-boot, - directory >= 1.2.6.0 - if impl(ghc < 8.0) - build-depends: - haddock-api >= 2.16.0 && < 2.17.0, - ghc >= 7.10.0 && < 7.11.0, - bin-package-db, - directory == 1.2.2.* - - build-depends: - base >= 4.7 && < 5, - aeson >= 0.7.0, - aeson-pretty >= 0.7.0, - array >= 0.5.0, - async >= 2.0, - attoparsec >= 0.11.0, - bytestring >= 0.10.0, - Cabal >= 1.22.0, - containers >= 0.5.0, - cpphs >= 1.19.0, - data-default >= 0.5.0, - deepseq >= 1.4.0, - exceptions >= 0.6.0, - filepath >= 1.4.0, - fsnotify >= 0.2.1, - ghc-paths >= 0.1.0, - ghc-syb-utils >= 0.2.3, - haskell-src-exts >= 1.18.0 && < 1.20.0, - hdocs >= 0.5.0, - hformat == 0.3.*, - hlint >= 1.9.13 && < 2.1, - HTTP >= 4000.2.0, - lens >= 4.8, - lifted-base >= 0.2, - monad-control >= 1.0, - monad-loops >= 0.4, - mmorph == 1.0.*, - mtl >= 2.2.0, - network >= 2.6.0, - optparse-applicative >= 0.11, - process >= 1.2.0, - regex-pcre-builtin >= 0.94, - scientific >= 0.3, - simple-log == 0.9.*, - syb >= 0.5.1, - template-haskell, - text >= 1.2.0, - text-region == 0.3.*, - time >= 1.5.0, - transformers >= 0.4.0, - transformers-base >= 0.4.0, - uniplate >= 1.6.0, - unordered-containers >= 0.2.0, - vector >= 0.10.0 - -executable hsdev - main-is: hsdev.hs - hs-source-dirs: tools - ghc-options: -threaded -Wall -fno-warn-tabs "-with-rtsopts=-N4" - - build-depends: - base >= 4.7 && < 5, - hsdev, - aeson >= 0.7.0, - aeson-pretty >= 0.7.0, - bytestring >= 0.10.0, - containers >= 0.5.0, - deepseq >= 1.4.0, - directory, - exceptions >= 0.6.0, - filepath >= 1.4.0, - monad-loops >= 0.4.0, - mtl >= 2.2.0, - network >= 2.6.0, - optparse-applicative >= 0.11, - process >= 1.2.0, - text >= 1.2.0, - transformers >= 0.4.0, - unordered-containers >= 0.2.0 - -executable hsinspect - main-is: hsinspect.hs - hs-source-dirs: tools - ghc-options: -Wall -fno-warn-tabs - other-modules: - Tool - build-depends: - base >= 4.7 && < 5, - hsdev, - aeson >= 0.7.0, - aeson-pretty >= 0.7.0, - bytestring >= 0.10.0, - containers >= 0.5.0, - data-default >= 0.5.0, - directory, - filepath >= 1.4.0, - mtl >= 2.2.0, - optparse-applicative >= 0.11, - text >= 1.2.0, - transformers >= 0.4.0, - unordered-containers >= 0.2.0, - vector >= 0.10.0 - -executable hsclearimports - main-is: hsclearimports.hs - hs-source-dirs: tools - ghc-options: -Wall -fno-warn-tabs - other-modules: - Tool - build-depends: - base >= 4.7 && < 5, - hsdev, - aeson >= 0.7.0, - aeson-pretty >= 0.7.0, - bytestring >= 0.10.0, - containers >= 0.5.0, - data-default >= 0.5.0, - directory, - haskell-src-exts >= 1.18.0 && < 1.20.0, - lens >= 4.8, - mtl >= 2.2.0, - optparse-applicative >= 0.11, - text >= 1.2.0, - transformers >= 0.4.0, - unordered-containers >= 0.2.0 - -executable hscabal - main-is: hscabal.hs - hs-source-dirs: tools - ghc-options: -Wall -fno-warn-tabs - other-modules: - Tool - build-depends: - base >= 4.7 && < 5, - hsdev, - aeson >= 0.7.0, - 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, - transformers >= 0.4.0, - unordered-containers >= 0.2.0 - -executable hshayoo - main-is: hshayoo.hs - hs-source-dirs: tools - ghc-options: -Wall -fno-warn-tabs - other-modules: - Tool - build-depends: - base >= 4.7 && < 5, - hsdev, - aeson >= 0.7.0, - 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, - transformers >= 0.4.0, - unordered-containers >= 0.2.0 - -executable hsautofix - main-is: hsautofix.hs - hs-source-dirs: tools - ghc-options: -Wall -fno-warn-tabs - other-modules: - Tool - build-depends: - base >= 4.7 && < 5, - hsdev, - aeson >= 0.7.0, - aeson-pretty >= 0.7.0, - bytestring >= 0.10.0, - data-default >= 0.5.0, - directory, - filepath >= 1.4.0, - lens >= 4.8, - mtl >= 2.2.0, - optparse-applicative >= 0.11, - transformers >= 0.4.0 - -test-suite test - main-is: Test.hs - hs-source-dirs: tests - ghc-options: -threaded -Wall -fno-warn-tabs - type: exitcode-stdio-1.0 - build-depends: - base >= 4.7 && < 5, - hsdev, - aeson >= 0.7.0, - aeson-lens >= 0.5.0, - async >= 2.0, - data-default >= 0.5.0, - deepseq >= 1.4.0, - directory, - filepath >= 1.4.0, - containers >= 0.5.0, - hformat == 0.3.*, - hspec, - lens >= 4.8, - mtl >= 2.2.0, - text >= 1.2.0 +name: hsdev+version: 0.2.5.1+synopsis: Haskell development library+description:+ Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc.+homepage: https://github.com/mvoidex/hsdev+license: BSD3+license-file: LICENSE+author: Alexandr `Voidex` Ruchkin+maintainer: voidex@live.com+-- copyright:+category: Development+build-type: Simple+cabal-version: >=1.8+extra-source-files:+ tests/test-package/*.hs+ tests/test-package/test-package.cabal++source-repository head+ type: git+ location: git://github.com/mvoidex/hsdev.git++library+ hs-source-dirs: src+ ghc-options: -Wall -fno-warn-tabs+ exposed-modules:+ Control.Apply.Util+ Control.Concurrent.FiniteChan+ Control.Concurrent.Worker+ Control.Concurrent.Util+ Data.Async+ Data.Deps+ Data.Group+ Data.Help+ Data.Lisp+ Data.Maybe.JustIf+ HsDev+ HsDev.Cache+ HsDev.Cache.Structured+ HsDev.Client.Commands+ HsDev.Commands+ HsDev.Database+ HsDev.Database.Async+ HsDev.Database.Update+ HsDev.Database.Update.Types+ HsDev.Display+ HsDev.Error+ HsDev.Inspect+ HsDev.PackageDb+ HsDev.Project+ HsDev.Project.Compat+ HsDev.Project.Types+ HsDev.Scan+ HsDev.Scan.Browse+ HsDev.Server.Base+ HsDev.Server.Commands+ HsDev.Server.Message+ HsDev.Server.Types+ HsDev.Sandbox+ HsDev.Stack+ HsDev.Symbols+ HsDev.Symbols.Class+ HsDev.Symbols.Location+ HsDev.Symbols.Documented+ HsDev.Symbols.Resolve+ HsDev.Symbols.Types+ HsDev.Symbols.Util+ HsDev.Tools.AutoFix+ HsDev.Tools.Base+ HsDev.Tools.Cabal+ HsDev.Tools.ClearImports+ HsDev.Tools.Ghc.Check+ HsDev.Tools.Ghc.Compat+ HsDev.Tools.Ghc.MGhc+ HsDev.Tools.Ghc.Prelude+ HsDev.Tools.Ghc.Types+ HsDev.Tools.Ghc.Worker+ HsDev.Tools.Ghc.Session+ HsDev.Tools.Hayoo+ HsDev.Tools.HDocs+ HsDev.Tools.HLint+ HsDev.Tools.Types+ HsDev.Types+ HsDev.Util+ HsDev.Version+ HsDev.Watcher+ HsDev.Watcher.Types+ System.Directory.Paths+ System.Directory.Watcher++ if os(windows)+ build-depends:+ Win32 >= 2.3.0+ exposed-modules:+ System.Win32.PowerShell+ System.Win32.FileMapping.NamePool+ System.Win32.FileMapping.Memory+ else+ build-depends:+ unix >= 2.7.0++ if impl(ghc >= 8.2) && impl(ghc < 8.3)+ build-depends:+ haddock-api >= 2.18.0 && < 2.19.0,+ ghc == 8.2.*,+ ghc-boot,+ directory >= 1.2.6.0+ if impl(ghc >= 8.0) && impl(ghc < 8.2)+ build-depends:+ haddock-api >= 2.17.0 && < 2.18.0,+ ghc >= 8.0.0 && < 8.1.0,+ ghc-boot,+ directory >= 1.2.6.0+ if impl(ghc < 8.0)+ build-depends:+ haddock-api >= 2.16.0 && < 2.17.0,+ ghc >= 7.10.0 && < 7.11.0,+ bin-package-db,+ directory == 1.2.2.*++ build-depends:+ base >= 4.7 && < 5,+ aeson >= 0.7.0,+ aeson-pretty >= 0.7.0,+ array >= 0.5.0,+ async >= 2.0,+ attoparsec >= 0.11.0,+ bytestring >= 0.10.0,+ Cabal >= 1.22.0,+ containers >= 0.5.0,+ cpphs >= 1.19.0,+ data-default >= 0.5.0,+ deepseq >= 1.4.0,+ exceptions >= 0.6.0,+ filepath >= 1.4.0,+ fsnotify >= 0.2.1,+ ghc-paths >= 0.1.0,+ ghc-syb-utils >= 0.2.3,+ haskell-src-exts >= 1.18.0 && < 1.20.0,+ hdocs >= 0.5.0,+ hformat == 0.3.*,+ hlint >= 1.9.13 && < 2.1,+ HTTP >= 4000.2.0,+ lens >= 4.8,+ lifted-base >= 0.2,+ monad-control >= 1.0,+ monad-loops >= 0.4,+ mmorph >= 1.0 && < 1.2,+ mtl >= 2.2.0,+ network >= 2.6.0,+ optparse-applicative >= 0.11,+ process >= 1.2.0,+ regex-pcre-builtin >= 0.94,+ scientific >= 0.3,+ simple-log == 0.9.*,+ syb >= 0.5.1,+ template-haskell,+ text >= 1.2.0,+ text-region == 0.3.*,+ time >= 1.5.0,+ transformers >= 0.4.0,+ transformers-base >= 0.4.0,+ uniplate >= 1.6.0,+ unordered-containers >= 0.2.0,+ vector >= 0.10.0++executable hsdev+ main-is: hsdev.hs+ hs-source-dirs: tools+ ghc-options: -threaded -Wall -fno-warn-tabs "-with-rtsopts=-N4"++ build-depends:+ base >= 4.7 && < 5,+ hsdev,+ aeson >= 0.7.0,+ aeson-pretty >= 0.7.0,+ bytestring >= 0.10.0,+ containers >= 0.5.0,+ deepseq >= 1.4.0,+ directory,+ exceptions >= 0.6.0,+ filepath >= 1.4.0,+ monad-loops >= 0.4.0,+ mtl >= 2.2.0,+ network >= 2.6.0,+ optparse-applicative >= 0.11,+ process >= 1.2.0,+ text >= 1.2.0,+ transformers >= 0.4.0,+ unordered-containers >= 0.2.0++executable hsinspect+ main-is: hsinspect.hs+ hs-source-dirs: tools+ ghc-options: -Wall -fno-warn-tabs+ other-modules:+ Tool+ build-depends:+ base >= 4.7 && < 5,+ hsdev,+ aeson >= 0.7.0,+ aeson-pretty >= 0.7.0,+ bytestring >= 0.10.0,+ containers >= 0.5.0,+ data-default >= 0.5.0,+ directory,+ filepath >= 1.4.0,+ mtl >= 2.2.0,+ optparse-applicative >= 0.11,+ text >= 1.2.0,+ transformers >= 0.4.0,+ unordered-containers >= 0.2.0,+ vector >= 0.10.0++executable hsclearimports+ main-is: hsclearimports.hs+ hs-source-dirs: tools+ ghc-options: -Wall -fno-warn-tabs+ other-modules:+ Tool+ build-depends:+ base >= 4.7 && < 5,+ hsdev,+ aeson >= 0.7.0,+ aeson-pretty >= 0.7.0,+ bytestring >= 0.10.0,+ containers >= 0.5.0,+ data-default >= 0.5.0,+ directory,+ haskell-src-exts >= 1.18.0 && < 1.20.0,+ lens >= 4.8,+ mtl >= 2.2.0,+ optparse-applicative >= 0.11,+ text >= 1.2.0,+ transformers >= 0.4.0,+ unordered-containers >= 0.2.0++executable hscabal+ main-is: hscabal.hs+ hs-source-dirs: tools+ ghc-options: -Wall -fno-warn-tabs+ other-modules:+ Tool+ build-depends:+ base >= 4.7 && < 5,+ hsdev,+ aeson >= 0.7.0,+ 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,+ transformers >= 0.4.0,+ unordered-containers >= 0.2.0++executable hshayoo+ main-is: hshayoo.hs+ hs-source-dirs: tools+ ghc-options: -Wall -fno-warn-tabs+ other-modules:+ Tool+ build-depends:+ base >= 4.7 && < 5,+ hsdev,+ aeson >= 0.7.0,+ 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,+ transformers >= 0.4.0,+ unordered-containers >= 0.2.0++executable hsautofix+ main-is: hsautofix.hs+ hs-source-dirs: tools+ ghc-options: -Wall -fno-warn-tabs+ other-modules:+ Tool+ build-depends:+ base >= 4.7 && < 5,+ hsdev,+ aeson >= 0.7.0,+ aeson-pretty >= 0.7.0,+ bytestring >= 0.10.0,+ data-default >= 0.5.0,+ directory,+ filepath >= 1.4.0,+ lens >= 4.8,+ mtl >= 2.2.0,+ optparse-applicative >= 0.11,+ transformers >= 0.4.0++test-suite test+ main-is: Test.hs+ hs-source-dirs: tests+ ghc-options: -threaded -Wall -fno-warn-tabs+ type: exitcode-stdio-1.0+ build-depends:+ base >= 4.7 && < 5,+ hsdev,+ aeson >= 0.7.0,+ aeson-lens >= 0.5.0,+ async >= 2.0,+ data-default >= 0.5.0,+ deepseq >= 1.4.0,+ directory,+ filepath >= 1.4.0,+ containers >= 0.5.0,+ hformat == 0.3.*,+ hspec,+ lens >= 4.8,+ mtl >= 2.2.0,+ text >= 1.2.0
src/Data/Deps.hs view
@@ -1,70 +1,70 @@-{-# LANGUAGE FlexibleContexts, TypeFamilies #-} - -module Data.Deps ( - Deps(..), depsMap, - mapDeps, - dep, deps, - inverse, flatten - ) where - -import Control.Lens -import Control.Monad.State -import Data.List (nub) -import Data.Map (Map) -import qualified Data.Map as M -import Data.Maybe (fromMaybe) - --- | Dependency map -data Deps a = Deps { - _depsMap :: Map a [a] } - -depsMap :: Lens (Deps a) (Deps b) (Map a [a]) (Map b [b]) -depsMap = lens _depsMap (const Deps) - -instance Ord a => Monoid (Deps a) where - mempty = Deps mempty - mappend (Deps l) (Deps r) = Deps $ M.unionWith nubConcat l r - -type instance Index (Deps a) = a -type instance IxValue (Deps a) = [a] - -instance Ord a => Ixed (Deps a) where - ix k = depsMap . ix k - -instance Ord a => At (Deps a) where - at k = depsMap . at k - -mapDeps :: Ord b => (a -> b) -> Deps a -> Deps b -mapDeps f = Deps . M.mapKeys f . M.map (map f) . _depsMap - --- | Make single dependency -dep :: a -> a -> Deps a -dep x y = deps x [y] - --- | Make dependency for one target, note that order of dependencies is matter -deps :: a -> [a] -> Deps a -deps x ys = Deps $ M.singleton x ys - --- | Inverse dependencies, i.e. make map where keys are dependencies and elements are targets depends on it -inverse :: Ord a => Deps a -> Deps a -inverse = mconcat . map (uncurry dep) . concatMap inverse' . M.toList . _depsMap where - inverse' :: (a, [a]) -> [(a, a)] - inverse' (m, ds) = zip ds (repeat m) - --- | Flatten dependencies so that there will be no indirect dependencies -flatten :: Ord a => Deps a -> Deps a -flatten (Deps ds) = flip execState mempty . mapM_ flatten' . M.keys $ ds where - -- flatten' :: a -> State (Deps a) [a] - flatten' n = do - d <- gets (M.lookup n . _depsMap) - case d of - Just d' -> return d' - Nothing -> do - let - deps' = fromMaybe [] $ M.lookup n ds - d'' <- (nub . concat . (++ [deps'])) <$> mapM flatten' deps' - modify $ mappend (deps n d'') - return d'' - -nubConcat :: Ord a => [a] -> [a] -> [a] -nubConcat xs ys = nub $ xs ++ ys +{-# LANGUAGE FlexibleContexts, TypeFamilies #-}++module Data.Deps (+ Deps(..), depsMap,+ mapDeps,+ dep, deps,+ inverse, flatten+ ) where++import Control.Lens+import Control.Monad.State+import Data.List (nub)+import Data.Map (Map)+import qualified Data.Map as M+import Data.Maybe (fromMaybe)++-- | Dependency map+data Deps a = Deps {+ _depsMap :: Map a [a] }++depsMap :: Lens (Deps a) (Deps b) (Map a [a]) (Map b [b])+depsMap = lens _depsMap (const Deps)++instance Ord a => Monoid (Deps a) where+ mempty = Deps mempty+ mappend (Deps l) (Deps r) = Deps $ M.unionWith nubConcat l r++type instance Index (Deps a) = a+type instance IxValue (Deps a) = [a]++instance Ord a => Ixed (Deps a) where+ ix k = depsMap . ix k++instance Ord a => At (Deps a) where+ at k = depsMap . at k++mapDeps :: Ord b => (a -> b) -> Deps a -> Deps b+mapDeps f = Deps . M.mapKeys f . M.map (map f) . _depsMap++-- | Make single dependency+dep :: a -> a -> Deps a+dep x y = deps x [y]++-- | Make dependency for one target, note that order of dependencies is matter+deps :: a -> [a] -> Deps a+deps x ys = Deps $ M.singleton x ys++-- | Inverse dependencies, i.e. make map where keys are dependencies and elements are targets depends on it+inverse :: Ord a => Deps a -> Deps a+inverse = mconcat . map (uncurry dep) . concatMap inverse' . M.toList . _depsMap where+ inverse' :: (a, [a]) -> [(a, a)]+ inverse' (m, ds) = zip ds (repeat m)++-- | Flatten dependencies so that there will be no indirect dependencies+flatten :: Ord a => Deps a -> Deps a+flatten (Deps ds) = flip execState mempty . mapM_ flatten' . M.keys $ ds where+ -- flatten' :: a -> State (Deps a) [a]+ flatten' n = do+ d <- gets (M.lookup n . _depsMap)+ case d of+ Just d' -> return d'+ Nothing -> do+ let+ deps' = fromMaybe [] $ M.lookup n ds+ d'' <- (nub . concat . (++ [deps'])) <$> mapM flatten' deps'+ modify $ mappend (deps n d'')+ return d''++nubConcat :: Ord a => [a] -> [a] -> [a]+nubConcat xs ys = nub $ xs ++ ys
src/Data/Group.hs view
@@ -1,40 +1,40 @@-{-# LANGUAGE TypeSynonymInstances #-} - -module Data.Group ( - Group(..), - groupSum - ) where - -import Data.List ((\\)) -import Data.Map (Map) -import qualified Data.Map as M -import Data.Set (Set) -import qualified Data.Set as S - --- | Group is monoid with invertibility --- But for our purposes we prefer two functions: `add` and `sub`. -class Eq a => Group a where - add :: a -> a -> a - sub :: a -> a -> a - zero :: a - -instance Eq a => Group [a] where - add = (++) - sub = (\\) - zero = [] - -instance Ord a => Group (Set a) where - add = S.union - sub = S.difference - zero = S.empty - -instance (Ord k, Group a) => Group (Map k a) where - add = M.unionWith add - sub = M.differenceWith sub' where - sub' x y = if z == zero then Nothing else Just z where - z = sub x y - zero = M.empty - --- | Sums list -groupSum :: Group a => [a] -> a -groupSum = foldr add zero +{-# LANGUAGE TypeSynonymInstances #-}++module Data.Group (+ Group(..),+ groupSum+ ) where++import Data.List ((\\))+import Data.Map (Map)+import qualified Data.Map as M+import Data.Set (Set)+import qualified Data.Set as S++-- | Group is monoid with invertibility+-- But for our purposes we prefer two functions: `add` and `sub`.+class Eq a => Group a where+ add :: a -> a -> a+ sub :: a -> a -> a+ zero :: a++instance Eq a => Group [a] where+ add = (++)+ sub = (\\)+ zero = []++instance Ord a => Group (Set a) where+ add = S.union+ sub = S.difference+ zero = S.empty++instance (Ord k, Group a) => Group (Map k a) where+ add = M.unionWith add+ sub = M.differenceWith sub' where+ sub' x y = if z == zero then Nothing else Just z where+ z = sub x y+ zero = M.empty++-- | Sums list+groupSum :: Group a => [a] -> a+groupSum = foldr add zero
src/Data/Help.hs view
@@ -1,21 +1,21 @@-module Data.Help ( - Help(..), - indent, indentHelp, detailed, indented - ) where - -class Help a where - brief :: a -> String - help :: a -> [String] - -indent :: String -> String -indent = ('\t':) - -indentHelp :: [String] -> [String] -indentHelp [] = [] -indentHelp (h:hs) = h : map indent hs - -detailed :: Help a => a -> [String] -detailed v = brief v : help v - -indented :: Help a => a -> [String] -indented = indentHelp . detailed +module Data.Help (+ Help(..),+ indent, indentHelp, detailed, indented+ ) where++class Help a where+ brief :: a -> String+ help :: a -> [String]++indent :: String -> String+indent = ('\t':)++indentHelp :: [String] -> [String]+indentHelp [] = []+indentHelp (h:hs) = h : map indent hs++detailed :: Help a => a -> [String]+detailed v = brief v : help v++indented :: Help a => a -> [String]+indented = indentHelp . detailed
src/HsDev.hs view
@@ -1,33 +1,33 @@-module HsDev ( - module Data.Default, - - module HsDev.Types, - module HsDev.Error, - module HsDev.Server.Base, - module HsDev.Server.Commands, - module HsDev.Client.Commands, - module HsDev.Cache, - module HsDev.Commands, - module HsDev.Database, - module HsDev.Inspect, - module HsDev.Project, - module HsDev.Scan, - module HsDev.Symbols, - module HsDev.Util - ) where - -import Data.Default - -import HsDev.Types -import HsDev.Error -import HsDev.Server.Base -import HsDev.Server.Commands -import HsDev.Client.Commands -import HsDev.Cache -import HsDev.Commands -import HsDev.Database -import HsDev.Inspect -import HsDev.Project -import HsDev.Scan -import HsDev.Symbols -import HsDev.Util +module HsDev (+ module Data.Default,++ module HsDev.Types,+ module HsDev.Error,+ module HsDev.Server.Base,+ module HsDev.Server.Commands,+ module HsDev.Client.Commands,+ module HsDev.Cache,+ module HsDev.Commands,+ module HsDev.Database,+ module HsDev.Inspect,+ module HsDev.Project,+ module HsDev.Scan,+ module HsDev.Symbols,+ module HsDev.Util+ ) where++import Data.Default++import HsDev.Types+import HsDev.Error+import HsDev.Server.Base+import HsDev.Server.Commands+import HsDev.Client.Commands+import HsDev.Cache+import HsDev.Commands+import HsDev.Database+import HsDev.Inspect+import HsDev.Project+import HsDev.Scan+import HsDev.Symbols+import HsDev.Util
src/HsDev/Cache.hs view
@@ -1,79 +1,79 @@-{-# LANGUAGE OverloadedStrings #-} - -module HsDev.Cache ( - escapePath, - versionCache, - packageDbCache, - projectCache, - standaloneCache, - dump, - load, - writeVersion, - readVersion, - - -- * Reexports - Database - ) where - -import Control.DeepSeq (force) -import Control.Exception -import Control.Lens (view) -import Data.Aeson (encode, eitherDecode) -import Data.Aeson.Encode.Pretty (encodePretty) -import qualified Data.ByteString.Lazy.Char8 as BS -import Data.Char (isAlphaNum) -import Data.List (intercalate) -import System.FilePath - -import HsDev.PackageDb -import HsDev.Project -import HsDev.Database (Database) -import HsDev.Util (version) - --- | Escape path -escapePath :: FilePath -> FilePath -escapePath = intercalate "." . map (filter isAlphaNum) . splitDirectories - --- | Name of cache for version -versionCache :: FilePath -versionCache = "version" <.> "json" - --- | Name of cache for cabal -packageDbCache :: PackageDb -> FilePath -packageDbCache GlobalDb = "global" <.> "json" -packageDbCache UserDb = "user" <.> "json" -packageDbCache (PackageDb p) = escapePath p <.> "json" - --- | Name of cache for projects -projectCache :: Project -> FilePath -projectCache p = (escapePath . view projectPath $ p) <.> "json" - --- | Name of cache for standalone files -standaloneCache :: FilePath -standaloneCache = "standalone" <.> "json" - --- | Dump database to file -dump :: FilePath -> Database -> IO () -dump file = BS.writeFile file . encodePretty - --- | Load database from file, strict -load :: FilePath -> IO (Either String Database) -load file = handle onIO $ do - cts <- BS.readFile file - return $ force $ eitherDecode cts - where - onIO :: IOException -> IO (Either String Database) - onIO _ = return $ Left $ "IO exception while reading cache from " ++ file - --- | Write version -writeVersion :: FilePath -> IO () -writeVersion file = BS.writeFile file $ encode version - --- | Read version -readVersion :: FilePath -> IO (Maybe [Int]) -readVersion file = handle onIO $ do - cts <- BS.readFile file - return $ either (const Nothing) id $ eitherDecode cts - where - onIO :: IOException -> IO (Maybe [Int]) - onIO _ = return Nothing +{-# LANGUAGE OverloadedStrings #-}++module HsDev.Cache (+ escapePath,+ versionCache,+ packageDbCache,+ projectCache,+ standaloneCache,+ dump,+ load,+ writeVersion,+ readVersion,++ -- * Reexports+ Database+ ) where++import Control.DeepSeq (force)+import Control.Exception+import Control.Lens (view)+import Data.Aeson (encode, eitherDecode)+import Data.Aeson.Encode.Pretty (encodePretty)+import qualified Data.ByteString.Lazy.Char8 as BS+import Data.Char (isAlphaNum)+import Data.List (intercalate)+import System.FilePath++import HsDev.PackageDb+import HsDev.Project+import HsDev.Database (Database)+import HsDev.Util (version)++-- | Escape path+escapePath :: FilePath -> FilePath+escapePath = intercalate "." . map (filter isAlphaNum) . splitDirectories++-- | Name of cache for version+versionCache :: FilePath+versionCache = "version" <.> "json"++-- | Name of cache for cabal+packageDbCache :: PackageDb -> FilePath+packageDbCache GlobalDb = "global" <.> "json"+packageDbCache UserDb = "user" <.> "json"+packageDbCache (PackageDb p) = escapePath p <.> "json"++-- | Name of cache for projects+projectCache :: Project -> FilePath+projectCache p = (escapePath . view projectPath $ p) <.> "json"++-- | Name of cache for standalone files+standaloneCache :: FilePath+standaloneCache = "standalone" <.> "json"++-- | Dump database to file+dump :: FilePath -> Database -> IO ()+dump file = BS.writeFile file . encodePretty++-- | Load database from file, strict+load :: FilePath -> IO (Either String Database)+load file = handle onIO $ do+ cts <- BS.readFile file+ return $ force $ eitherDecode cts+ where+ onIO :: IOException -> IO (Either String Database)+ onIO _ = return $ Left $ "IO exception while reading cache from " ++ file++-- | Write version+writeVersion :: FilePath -> IO ()+writeVersion file = BS.writeFile file $ encode version++-- | Read version+readVersion :: FilePath -> IO (Maybe [Int])+readVersion file = handle onIO $ do+ cts <- BS.readFile file+ return $ either (const Nothing) id $ eitherDecode cts+ where+ onIO :: IOException -> IO (Maybe [Int])+ onIO _ = return Nothing
src/HsDev/Cache/Structured.hs view
@@ -1,72 +1,72 @@-module HsDev.Cache.Structured ( - dump, load, - loadPackageDb, loadProject, loadFiles - ) where - -import Control.DeepSeq -import Control.Exception -import Control.Lens (preview) -import Control.Monad.Except -import qualified Data.Map as M (assocs) -import System.Directory -import System.FilePath - -import Data.Group (Group(zero)) -import qualified HsDev.Cache as Cache -import HsDev.Database -import HsDev.Symbols hiding (loadProject) -import HsDev.Util - --- | Write cache -dump :: FilePath -> Structured -> IO () -dump dir db = do - createDirectoryIfMissing True (dir </> "cabal") - createDirectoryIfMissing True (dir </> "projects") - forM_ (M.assocs $ structuredPackageDbs db) $ \(c, cdb) -> Cache.dump - (dir </> "cabal" </> Cache.packageDbCache c) - cdb - forM_ (M.assocs $ structuredProjects db) $ \(p, pdb) -> Cache.dump - (dir </> "projects" </> Cache.projectCache (project p)) - pdb - files' <- either (const zero) id <$> - handle wrapIO - (Cache.load (dir </> Cache.standaloneCache)) - files' `deepseq` Cache.dump (dir </> Cache.standaloneCache) (files' `mappend` structuredFiles db) - where - wrapIO :: SomeException -> IO (Either String Database) - wrapIO = return . Left . show - --- | Load all cache -load :: FilePath -> IO (Either String Structured) -load dir = runExceptT $ join $ either throwError return <$> (structured <$> loadPackageDbs <*> loadProjects <*> loadStandaloneFiles) where - loadPackageDbs = loadDir (dir </> "cabal") - loadProjects = loadDir (dir </> "projects") - loadStandaloneFiles = ExceptT $ Cache.load (dir </> Cache.standaloneCache) - - loadDir p = do - fs <- liftE $ liftM (filter ((== ".json") . takeExtension)) $ directoryContents p - mapM (ExceptT . Cache.load) fs - --- | Load data from cache -loadData :: FilePath -> ExceptT String IO Database -loadData = liftExceptionM . ExceptT . Cache.load - --- | Load cabal from cache -loadPackageDb :: PackageDb -> FilePath -> ExceptT String IO Structured -loadPackageDb c dir = do - dat <- loadData (dir </> "cabal" </> Cache.packageDbCache c) - ExceptT $ return $ structured [dat] [] mempty - --- | Load project from cache -loadProject :: FilePath -> FilePath -> ExceptT String IO Structured -loadProject p dir = do - dat <- loadData (dir </> "projects" </> Cache.projectCache (project p)) - ExceptT $ return $ structured [] [dat] mempty - --- | Load standalone files -loadFiles :: (FilePath -> Bool) -> FilePath -> ExceptT String IO Structured -loadFiles f dir = do - dat <- loadData (dir </> Cache.standaloneCache) - ExceptT $ return $ structured [] [] $ filterDB f' (const False) dat - where - f' = maybe False f . preview (moduleIdLocation . moduleFile) +module HsDev.Cache.Structured (+ dump, load,+ loadPackageDb, loadProject, loadFiles+ ) where++import Control.DeepSeq+import Control.Exception+import Control.Lens (preview)+import Control.Monad.Except+import qualified Data.Map as M (assocs)+import System.Directory+import System.FilePath++import Data.Group (Group(zero))+import qualified HsDev.Cache as Cache+import HsDev.Database+import HsDev.Symbols hiding (loadProject)+import HsDev.Util++-- | Write cache+dump :: FilePath -> Structured -> IO ()+dump dir db = do+ createDirectoryIfMissing True (dir </> "cabal")+ createDirectoryIfMissing True (dir </> "projects")+ forM_ (M.assocs $ structuredPackageDbs db) $ \(c, cdb) -> Cache.dump+ (dir </> "cabal" </> Cache.packageDbCache c)+ cdb+ forM_ (M.assocs $ structuredProjects db) $ \(p, pdb) -> Cache.dump+ (dir </> "projects" </> Cache.projectCache (project p))+ pdb+ files' <- either (const zero) id <$>+ handle wrapIO+ (Cache.load (dir </> Cache.standaloneCache))+ files' `deepseq` Cache.dump (dir </> Cache.standaloneCache) (files' `mappend` structuredFiles db)+ where+ wrapIO :: SomeException -> IO (Either String Database)+ wrapIO = return . Left . show++-- | Load all cache+load :: FilePath -> IO (Either String Structured)+load dir = runExceptT $ join $ either throwError return <$> (structured <$> loadPackageDbs <*> loadProjects <*> loadStandaloneFiles) where+ loadPackageDbs = loadDir (dir </> "cabal")+ loadProjects = loadDir (dir </> "projects")+ loadStandaloneFiles = ExceptT $ Cache.load (dir </> Cache.standaloneCache)++ loadDir p = do+ fs <- liftE $ liftM (filter ((== ".json") . takeExtension)) $ directoryContents p+ mapM (ExceptT . Cache.load) fs++-- | Load data from cache+loadData :: FilePath -> ExceptT String IO Database+loadData = liftExceptionM . ExceptT . Cache.load++-- | Load cabal from cache+loadPackageDb :: PackageDb -> FilePath -> ExceptT String IO Structured+loadPackageDb c dir = do+ dat <- loadData (dir </> "cabal" </> Cache.packageDbCache c)+ ExceptT $ return $ structured [dat] [] mempty++-- | Load project from cache+loadProject :: FilePath -> FilePath -> ExceptT String IO Structured+loadProject p dir = do+ dat <- loadData (dir </> "projects" </> Cache.projectCache (project p))+ ExceptT $ return $ structured [] [dat] mempty++-- | Load standalone files+loadFiles :: (FilePath -> Bool) -> FilePath -> ExceptT String IO Structured+loadFiles f dir = do+ dat <- loadData (dir </> Cache.standaloneCache)+ ExceptT $ return $ structured [] [] $ filterDB f' (const False) dat+ where+ f' = maybe False f . preview (moduleIdLocation . moduleFile)
src/HsDev/Client/Commands.hs view
@@ -1,414 +1,414 @@-{-# LANGUAGE OverloadedStrings, FlexibleContexts #-} - -module HsDev.Client.Commands ( - runClient, runCommand - ) where - -import Control.Applicative -import Control.Concurrent.MVar -import Control.Exception (displayException) -import Control.Lens (view, preview, _Just, (^..), each) -import Control.Monad -import Control.Monad.Except -import Control.Monad.Reader -import qualified Control.Monad.State as State -import Control.Monad.Catch (try, catch, bracket, SomeException(..)) -import Data.Aeson hiding (Result, Error) -import Data.List -import Data.Foldable (toList) -import Data.Maybe -import qualified Data.Map as M -import Data.String (fromString) -import Data.Text (pack, unpack) -import qualified Data.Text as T (isInfixOf, isPrefixOf, isSuffixOf) -import System.Directory -import System.FilePath -import qualified System.Log.Simple as Log -import qualified System.Log.Simple.Base as Log -import Text.Regex.PCRE ((=~)) - -import qualified System.Directory.Watcher as W -import qualified Data.Async as A -import System.Directory.Paths -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 -import HsDev.Sandbox hiding (findSandbox) -import qualified HsDev.Sandbox as S (findSandbox) -import HsDev.Symbols -import HsDev.Symbols.Resolve (resolveOne, scopeModule, exportsModule) -import HsDev.Symbols.Util -import qualified HsDev.Tools.AutoFix as AutoFix -import qualified HsDev.Tools.Cabal as Cabal -import HsDev.Tools.Ghc.Session -import qualified HsDev.Tools.Ghc.Compat as Compat -import qualified HsDev.Tools.Ghc.Check as Check -import qualified HsDev.Tools.Ghc.Types as Types -import qualified HsDev.Tools.Hayoo as Hayoo -import qualified HsDev.Tools.HLint as HLint -import qualified HsDev.Tools.Types as Tools -import HsDev.Util -import HsDev.Watcher - -import qualified HsDev.Database.Update as Update - -runClient :: (ToJSON a, ServerMonadBase m) => CommandOptions -> ClientM m a -> ServerM m Result -runClient copts = mapServerM toResult . runClientM where - toResult :: (ToJSON a, ServerMonadBase m) => ReaderT CommandOptions m a -> m Result - toResult act = liftM (either Error (Result . toJSON)) $ runReaderT (try act) copts - mapServerM :: (m a -> n b) -> ServerM m a -> ServerM n b - mapServerM f = ServerM . mapReaderT f . runServerM - -toValue :: (ToJSON a, Monad m) => m a -> m Value -toValue = liftM toJSON - -runCommand :: ServerMonadBase m => Command -> ClientM m Value -runCommand Ping = toValue $ return $ object ["message" .= ("pong" :: String)] -runCommand (Listen (Just l)) = case Log.level (pack l) of - Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l - Just lev -> bracket (serverSetLogLevel lev) serverSetLogLevel $ \_ -> runCommand (Listen Nothing) -runCommand (Listen Nothing) = toValue $ do - serverListen >>= mapM_ (\msg -> commandNotify (Notification $ object ["message" .= msg])) -runCommand (SetLogLevel l) = case Log.level (pack l) of - Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l - Just lev -> toValue $ do - lev' <- serverSetLogLevel lev - Log.sendLog Log.Debug $ "log level changed from '{}' to '{}'" ~~ show lev' ~~ show lev - Log.sendLog Log.Info $ "log level updated to: {}" ~~ show lev -runCommand (AddData cts) = toValue $ mapM_ updateData cts where - updateData (AddedDatabase db) = toValue $ serverUpdateDB db - updateData (AddedModule m) = toValue $ serverUpdateDB $ fromModule m - updateData (AddedProject p) = toValue $ serverUpdateDB $ fromProject p -runCommand (Scan projs cabal sboxes fs paths' ghcs' docs' infer') = toValue $ do - sboxes' <- getSandboxes sboxes - updateProcess (Update.UpdateOptions [] ghcs' docs' infer') $ concat [ - map (\(FileSource f mcts) -> Update.scanFileContents ghcs' f mcts) fs, - map (Update.scanProject ghcs') projs, - map (Update.scanDirectory ghcs') paths', - [Update.scanCabal ghcs' | cabal], - map (Update.scanSandbox ghcs') sboxes'] -runCommand (RefineDocs projs fs ms) = toValue $ do - projects <- traverse findProject projs - dbval <- getDb - let - filters = anyOf $ concat [ - map inProject projects, - map inFile fs, - map inModule ms] - mods = selectModules (filters . view moduleId) dbval - updateProcess (Update.UpdateOptions [] [] False False) [Update.scanDocs $ map (getInspected dbval) mods] -runCommand (InferTypes projs fs ms) = toValue $ do - projects <- traverse findProject projs - dbval <- getDb - let - filters = anyOf $ concat [ - map inProject projects, - map inFile fs, - map inModule ms] - mods = selectModules (filters . view moduleId) dbval - updateProcess (Update.UpdateOptions [] [] False False) [Update.inferModTypes $ map (getInspected dbval) mods] -runCommand (Remove projs cabal sboxes files) = toValue $ do - db <- askSession sessionDatabase - dbval <- getDb - w <- askSession sessionWatcher - projects <- traverse findProject projs - sboxes' <- getSandboxes sboxes - forM_ projects $ \proj -> do - DB.clear db (return $ projectDB proj dbval) - liftIO $ unwatchProject w proj - dbPDbs <- mapM restorePackageDbStack $ databasePackageDbs dbval - flip State.evalStateT dbPDbs $ do - when cabal $ removePackageDbStack userDb - forM_ sboxes' $ \sbox -> do - pdbs <- lift $ sandboxPackageDbStack sbox - removePackageDbStack pdbs - forM_ files $ \file -> do - DB.clear db (return $ filterDB (inFile file) (const False) dbval) - let - mloc = fmap (view moduleLocation) $ lookupFile file dbval - maybe (return ()) (liftIO . unwatchModule w) mloc - where - -- We can safely remove package-db from db iff doesn't used by some of other package-dbs - -- For example, we can't remove global-db if there are any other package-dbs, because all of them uses global-db - -- We also can't remove stack snapshot package-db if there are some local package-db not yet removed - canRemove pdbs = do - from <- State.get - return $ null $ filter (pdbs `isSubStack`) $ delete pdbs from - -- Remove top of package-db stack if possible - removePackageDb pdbs = do - db <- lift $ askSession sessionDatabase - dbval <- lift getDb - w <- lift $ askSession sessionWatcher - can <- canRemove pdbs - when can $ do - State.modify (delete pdbs) - DB.clear db (return $ packageDbDB (topPackageDb pdbs) dbval) - liftIO $ unwatchPackageDb w $ topPackageDb pdbs - -- Remove package-db stack when possible - removePackageDbStack = mapM_ removePackageDb . packageDbStacks -runCommand RemoveAll = toValue $ do - db <- askSession sessionDatabase - liftIO $ A.modifyAsync db A.Clear - w <- askSession sessionWatcher - wdirs <- liftIO $ readMVar (W.watcherDirs w) - liftIO $ forM_ (M.toList wdirs) $ \(dir, (isTree, _)) -> (if isTree then W.unwatchTree else W.unwatchDir) w dir -runCommand (InfoModules fs) = toValue $ do - dbval <- getDb - filter' <- targetFilters fs - return $ map (view moduleId) $ newestPackage $ selectModules (filter' . view moduleId) dbval -runCommand InfoPackages = toValue $ (ordNub . sort . mapMaybe (preview (moduleLocation . modulePackage . _Just)) . allModules) <$> getDb -runCommand InfoProjects = toValue $ (toList . databaseProjects) <$> getDb -runCommand InfoSandboxes = toValue $ databasePackageDbs <$> getDb -runCommand (InfoSymbol sq fs locals') = toValue $ do - dbval <- liftM (localsDatabase locals') $ getDb - filter' <- targetFilters fs - return $ newestPackage $ filterMatch sq $ - concatMap moduleModuleDeclarations $ filter (filter' . view moduleId) $ allModules dbval -runCommand (InfoModule sq fs) = toValue $ do - dbval <- getDb - filter' <- targetFilters fs - return $ newestPackage $ filterMatch sq $ filter (filter' . view moduleId) $ allModules dbval -runCommand (InfoResolve fpath exports) = toValue $ do - dbval <- getSDb fpath - let - getScope - | exports = exportsModule - | otherwise = scopeModule - 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 - liftIO $ hsdevLift $ lookupSymbol dbval fpath nm -runCommand (Whois nm fpath) = toValue $ do - dbval <- getSDb fpath - liftIO $ hsdevLift $ whois dbval fpath nm -runCommand (ResolveScopeModules sq fpath) = toValue $ do - dbval <- getSDb 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) $ liftIO $ hsdevLift $ scope dbval fpath global -runCommand (Complete input wide fpath) = toValue $ do - dbval <- getSDb fpath - 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) $ - liftIO $ hsdevLift $ Hayoo.hayoo hq (Just i) -runCommand (CabalList packages) = toValue $ liftIO $ hsdevLift $ Cabal.cabalList packages -runCommand (Lint fs) = toValue $ do - liftIO $ hsdevLift $ liftM concat $ mapM (\(FileSource f c) -> HLint.hlint f c) fs -runCommand (Check fs ghcs') = toValue $ Log.scope "check" $ do - -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs - let - checkSome file fn = Log.scope "checkSome" $ do - m <- setFileSourceSession ghcs' file - inSessionGhc $ fn m - liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check ghcs' m c)) fs -runCommand (CheckLint fs ghcs') = toValue $ do - -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs - let - checkSome file fn = Log.scope "checkSome" $ do - m <- setFileSourceSession ghcs' file - inSessionGhc $ fn m - checkMsgs <- liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check ghcs' m c)) fs - lintMsgs <- liftIO $ hsdevLift $ liftM concat $ mapM (\(FileSource f c) -> HLint.hlint f c) fs - return $ checkMsgs ++ lintMsgs -runCommand (Types fs ghcs') = toValue $ do - -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs - liftM concat $ forM fs $ \(FileSource file msrc) -> do - m <- setFileSourceSession ghcs' file - inSessionGhc $ Types.fileTypes ghcs' m msrc -runCommand (AutoFix (AutoFixShow ns)) = toValue $ return $ AutoFix.corrections ns -runCommand (AutoFix (AutoFixFix ns rest isPure)) = toValue $ do - files <- liftM (ordNub . sort) $ mapM findPath $ mapMaybe (preview $ Tools.noteSource . moduleFile) ns - let - doFix :: FilePath -> Maybe String -> ([Tools.Note AutoFix.Correction], Maybe String) - doFix file mcts = AutoFix.autoFix fCorrs (fUpCorrs, mcts) where - findCorrs :: FilePath -> [Tools.Note AutoFix.Correction] -> [Tools.Note AutoFix.Correction] - findCorrs f = filter ((== Just f) . preview (Tools.noteSource . moduleFile)) - fCorrs = findCorrs file ns - fUpCorrs = findCorrs file rest - runFix file - | isPure = return $ fst $ doFix file Nothing - | otherwise = do - (corrs', Just cts') <- liftM (doFix file) $ liftIO $ Just <$> readFileUtf8 file - liftIO $ writeFileUtf8 file cts' - return corrs' - liftM concat $ mapM runFix files -runCommand (GhcEval exprs mfile) = toValue $ do - -- ensureUpToDate (Update.UpdateOptions [] [] False False) (maybeToList mfile) - ghcw <- askSession sessionGhc - case mfile of - Nothing -> inSessionGhc ghciSession - Just (FileSource f mcts) -> do - m <- setFileSourceSession [] f - inSessionGhc $ interpretModule m mcts - async' <- liftIO $ pushTask ghcw $ do - mapM (try . evaluate) exprs - res <- waitAsync async' - return $ map toValue' res - where - waitAsync :: CommandMonad m => Async a -> m a - 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 -runCommand Langs = toValue $ return $ Compat.languages -runCommand Flags = toValue $ return ["-f" ++ prefix ++ f | - f <- Compat.flags, - prefix <- ["", "no-"]] -runCommand (Link hold) = toValue $ commandLink >> when hold commandHold -runCommand Exit = toValue serverExit - -targetFilters :: CommandMonad m => [TargetFilter] -> m (ModuleId -> Bool) -targetFilters fs = do - fs_ <- mapM targetFilter fs - return $ foldr (liftM2 (&&)) (const True) fs_ - -targetFilter :: CommandMonad m => TargetFilter -> m (ModuleId -> Bool) -targetFilter f = case f of - TargetProject proj -> liftM inProject $ findProject proj - TargetFile file -> liftM inFile $ refineSourceFile file - TargetModule mname -> return $ inModule mname - TargetDepsOf dep -> liftM inDeps $ findDep dep - TargetPackageDb pdb -> return $ inPackageDb pdb - TargetCabal -> return $ inPackageDbStack userDb - TargetSandbox sbox -> liftM inPackageDbStack $ findSandbox sbox >>= sandboxPackageDbStack - TargetPackage pkg -> liftM inPackage $ refinePackage pkg - TargetSourced -> return byFile - TargetStandalone -> return standalone - --- Helper functions - --- | Canonicalize paths -findPath :: (CommandMonad m, Paths a) => a -> m a -findPath = paths findPath' where - findPath' :: CommandMonad m => FilePath -> m FilePath - findPath' f = do - r <- commandRoot - liftIO $ canonicalizePath (normalise $ if isRelative f then r </> f else f) - --- | Find sandbox by path -findSandbox :: CommandMonad m => FilePath -> m Sandbox -findSandbox fpath = do - fpath' <- findPath fpath - sbox <- liftIO $ S.findSandbox fpath' - maybe (hsdevError $ FileNotFound fpath') return sbox - --- | Get source file -refineSourceFile :: CommandMonad m => FilePath -> m FilePath -refineSourceFile fpath = do - fpath' <- findPath fpath - db' <- getDb - maybe (hsdevError (NotInspected $ FileModule fpath' Nothing)) return $ do - m' <- lookupFile fpath' db' - preview (moduleLocation . moduleFile) m' - --- | Get module by source -refineSourceModule :: CommandMonad m => FilePath -> m Module -refineSourceModule fpath = do - fpath' <- findPath fpath - db' <- getDb - maybe (hsdevError (NotInspected $ FileModule fpath' Nothing)) return $ lookupFile fpath' db' - --- | Set session by source -setFileSourceSession :: CommandMonad m => [String] -> FilePath -> m Module -setFileSourceSession opts fpath = do - m <- refineSourceModule fpath - inSessionGhc $ targetSession opts m - return m - --- | Ensure package exists -refinePackage :: CommandMonad m => String -> m String -refinePackage pkg = do - db' <- getDb - if pkg `elem` (allPackages db' ^.. each . packageName) - then return pkg - else hsdevError (PackageNotFound pkg) - --- | Get list of enumerated sandboxes -getSandboxes :: CommandMonad m => [FilePath] -> m [Sandbox] -getSandboxes = traverse (findPath >=> findSandbox) - --- | Find project by name or path -findProject :: CommandMonad m => String -> m Project -findProject proj = do - db' <- getDb - proj' <- liftM addCabal $ findPath proj - let - resultProj = - refineProject db' (project proj') <|> - find ((== proj) . view projectName) (databaseProjects db') - maybe (hsdevError $ ProjectNotFound proj) return resultProj - where - addCabal p - | takeExtension p == ".cabal" = p - | otherwise = p </> (takeBaseName p <.> "cabal") - --- | Find dependency: it may be source, project file or project name, also returns sandbox found -findDep :: CommandMonad m => String -> m (Project, Maybe FilePath, PackageDbStack) -findDep depName = do - depPath <- findPath depName - proj <- msum [ - do - p <- liftIO (locateProject depPath) - p' <- maybe (hsdevError $ ProjectNotFound depName) return p - liftIO $ loadProject p', - findProject depName] - let - src - | takeExtension depPath == ".hs" = Just depPath - | otherwise = Nothing - pdbs <- searchPackageDbStack $ view projectPath proj - return (proj, src, pdbs) - --- FIXME: Doesn't work for file without project --- | Check if project or source depends from this module -inDeps :: (Project, Maybe FilePath, PackageDbStack) -> ModuleId -> Bool -inDeps (proj, src, pdbs) = liftM2 (&&) (restrictPackageDbStack pdbs) deps' where - deps' = case src of - Nothing -> inDepsOfProject proj - Just src' -> inDepsOfFile proj src' - --- | Bring locals to top scope to search within them if 'locals' flag set -localsDatabase :: Bool -> Database -> Database -localsDatabase True = databaseLocals -localsDatabase False = id - --- | Get actual DB state -getDb :: SessionMonad m => m Database -getDb = askSession sessionDatabase >>= liftIO . DB.readAsync - --- | Get DB with filtered sanxboxes for file -getSDb :: SessionMonad m => FilePath -> m Database -getSDb fpath = do - dbval <- getDb - pdbs <- searchPackageDbStack fpath - return $ filterDB (restrictPackageDbStack pdbs) (const True) dbval - --- | Run DB update action -updateProcess :: ServerMonadBase m => Update.UpdateOptions -> [Update.UpdateM m ()] -> ClientM m () -updateProcess uopts acts = Update.runUpdate uopts $ mapM_ runAct acts where - runAct act = catch act onError - onError e = Log.sendLog Log.Error $ "{}" ~~ (e :: HsDevError) - --- | Ensure file is up to date --- ensureUpToDate :: ServerMonadBase m => Update.UpdateOptions -> [FileSource] -> ClientM m () --- ensureUpToDate uopts fs = updateProcess uopts [Update.scanFileContents (view Update.updateGhcOpts uopts) f mcts | FileSource f mcts <- fs] - --- | Filter declarations with prefix and infix -filterMatch :: Symbol a => SearchQuery -> [a] -> [a] -filterMatch (SearchQuery q st) = filter match' where - match' m = case st of - SearchExact -> fromString q == symbolName m - SearchPrefix -> fromString q `T.isPrefixOf` symbolName m - SearchInfix -> fromString q `T.isInfixOf` symbolName m - SearchSuffix -> fromString q `T.isSuffixOf` symbolName m - SearchRegex -> unpack (symbolName m) =~ q +{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}++module HsDev.Client.Commands (+ runClient, runCommand+ ) where++import Control.Applicative+import Control.Concurrent.MVar+import Control.Exception (displayException)+import Control.Lens (view, preview, _Just, (^..), each)+import Control.Monad+import Control.Monad.Except+import Control.Monad.Reader+import qualified Control.Monad.State as State+import Control.Monad.Catch (try, catch, bracket, SomeException(..))+import Data.Aeson hiding (Result, Error)+import Data.List+import Data.Foldable (toList)+import Data.Maybe+import qualified Data.Map as M+import Data.String (fromString)+import Data.Text (pack, unpack)+import qualified Data.Text as T (isInfixOf, isPrefixOf, isSuffixOf)+import System.Directory+import System.FilePath+import qualified System.Log.Simple as Log+import qualified System.Log.Simple.Base as Log+import Text.Regex.PCRE ((=~))++import qualified System.Directory.Watcher as W+import qualified Data.Async as A+import System.Directory.Paths+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+import HsDev.Sandbox hiding (findSandbox)+import qualified HsDev.Sandbox as S (findSandbox)+import HsDev.Symbols+import HsDev.Symbols.Resolve (resolveOne, scopeModule, exportsModule)+import HsDev.Symbols.Util+import qualified HsDev.Tools.AutoFix as AutoFix+import qualified HsDev.Tools.Cabal as Cabal+import HsDev.Tools.Ghc.Session+import qualified HsDev.Tools.Ghc.Compat as Compat+import qualified HsDev.Tools.Ghc.Check as Check+import qualified HsDev.Tools.Ghc.Types as Types+import qualified HsDev.Tools.Hayoo as Hayoo+import qualified HsDev.Tools.HLint as HLint+import qualified HsDev.Tools.Types as Tools+import HsDev.Util+import HsDev.Watcher++import qualified HsDev.Database.Update as Update++runClient :: (ToJSON a, ServerMonadBase m) => CommandOptions -> ClientM m a -> ServerM m Result+runClient copts = mapServerM toResult . runClientM where+ toResult :: (ToJSON a, ServerMonadBase m) => ReaderT CommandOptions m a -> m Result+ toResult act = liftM (either Error (Result . toJSON)) $ runReaderT (try act) copts+ mapServerM :: (m a -> n b) -> ServerM m a -> ServerM n b+ mapServerM f = ServerM . mapReaderT f . runServerM++toValue :: (ToJSON a, Monad m) => m a -> m Value+toValue = liftM toJSON++runCommand :: ServerMonadBase m => Command -> ClientM m Value+runCommand Ping = toValue $ return $ object ["message" .= ("pong" :: String)]+runCommand (Listen (Just l)) = case Log.level (pack l) of+ Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l+ Just lev -> bracket (serverSetLogLevel lev) serverSetLogLevel $ \_ -> runCommand (Listen Nothing)+runCommand (Listen Nothing) = toValue $ do+ serverListen >>= mapM_ (\msg -> commandNotify (Notification $ object ["message" .= msg]))+runCommand (SetLogLevel l) = case Log.level (pack l) of+ Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l+ Just lev -> toValue $ do+ lev' <- serverSetLogLevel lev+ Log.sendLog Log.Debug $ "log level changed from '{}' to '{}'" ~~ show lev' ~~ show lev+ Log.sendLog Log.Info $ "log level updated to: {}" ~~ show lev+runCommand (AddData cts) = toValue $ mapM_ updateData cts where+ updateData (AddedDatabase db) = toValue $ serverUpdateDB db+ updateData (AddedModule m) = toValue $ serverUpdateDB $ fromModule m+ updateData (AddedProject p) = toValue $ serverUpdateDB $ fromProject p+runCommand (Scan projs cabal sboxes fs paths' ghcs' docs' infer') = toValue $ do+ sboxes' <- getSandboxes sboxes+ updateProcess (Update.UpdateOptions [] ghcs' docs' infer') $ concat [+ map (\(FileSource f mcts) -> Update.scanFileContents ghcs' f mcts) fs,+ map (Update.scanProject ghcs') projs,+ map (Update.scanDirectory ghcs') paths',+ [Update.scanCabal ghcs' | cabal],+ map (Update.scanSandbox ghcs') sboxes']+runCommand (RefineDocs projs fs ms) = toValue $ do+ projects <- traverse findProject projs+ dbval <- getDb+ let+ filters = anyOf $ concat [+ map inProject projects,+ map inFile fs,+ map inModule ms]+ mods = selectModules (filters . view moduleId) dbval+ updateProcess (Update.UpdateOptions [] [] False False) [Update.scanDocs $ map (getInspected dbval) mods]+runCommand (InferTypes projs fs ms) = toValue $ do+ projects <- traverse findProject projs+ dbval <- getDb+ let+ filters = anyOf $ concat [+ map inProject projects,+ map inFile fs,+ map inModule ms]+ mods = selectModules (filters . view moduleId) dbval+ updateProcess (Update.UpdateOptions [] [] False False) [Update.inferModTypes $ map (getInspected dbval) mods]+runCommand (Remove projs cabal sboxes files) = toValue $ do+ db <- askSession sessionDatabase+ dbval <- getDb+ w <- askSession sessionWatcher+ projects <- traverse findProject projs+ sboxes' <- getSandboxes sboxes+ forM_ projects $ \proj -> do+ DB.clear db (return $ projectDB proj dbval)+ liftIO $ unwatchProject w proj+ dbPDbs <- mapM restorePackageDbStack $ databasePackageDbs dbval+ flip State.evalStateT dbPDbs $ do+ when cabal $ removePackageDbStack userDb+ forM_ sboxes' $ \sbox -> do+ pdbs <- lift $ sandboxPackageDbStack sbox+ removePackageDbStack pdbs+ forM_ files $ \file -> do+ DB.clear db (return $ filterDB (inFile file) (const False) dbval)+ let+ mloc = fmap (view moduleLocation) $ lookupFile file dbval+ maybe (return ()) (liftIO . unwatchModule w) mloc+ where+ -- We can safely remove package-db from db iff doesn't used by some of other package-dbs+ -- For example, we can't remove global-db if there are any other package-dbs, because all of them uses global-db+ -- We also can't remove stack snapshot package-db if there are some local package-db not yet removed+ canRemove pdbs = do+ from <- State.get+ return $ null $ filter (pdbs `isSubStack`) $ delete pdbs from+ -- Remove top of package-db stack if possible+ removePackageDb pdbs = do+ db <- lift $ askSession sessionDatabase+ dbval <- lift getDb+ w <- lift $ askSession sessionWatcher+ can <- canRemove pdbs+ when can $ do+ State.modify (delete pdbs)+ DB.clear db (return $ packageDbDB (topPackageDb pdbs) dbval)+ liftIO $ unwatchPackageDb w $ topPackageDb pdbs+ -- Remove package-db stack when possible+ removePackageDbStack = mapM_ removePackageDb . packageDbStacks+runCommand RemoveAll = toValue $ do+ db <- askSession sessionDatabase+ liftIO $ A.modifyAsync db A.Clear+ w <- askSession sessionWatcher+ wdirs <- liftIO $ readMVar (W.watcherDirs w)+ liftIO $ forM_ (M.toList wdirs) $ \(dir, (isTree, _)) -> (if isTree then W.unwatchTree else W.unwatchDir) w dir+runCommand (InfoModules fs) = toValue $ do+ dbval <- getDb+ filter' <- targetFilters fs+ return $ map (view moduleId) $ newestPackage $ selectModules (filter' . view moduleId) dbval+runCommand InfoPackages = toValue $ (ordNub . sort . mapMaybe (preview (moduleLocation . modulePackage . _Just)) . allModules) <$> getDb+runCommand InfoProjects = toValue $ (toList . databaseProjects) <$> getDb+runCommand InfoSandboxes = toValue $ databasePackageDbs <$> getDb+runCommand (InfoSymbol sq fs locals') = toValue $ do+ dbval <- liftM (localsDatabase locals') $ getDb+ filter' <- targetFilters fs+ return $ newestPackage $ filterMatch sq $+ concatMap moduleModuleDeclarations $ filter (filter' . view moduleId) $ allModules dbval+runCommand (InfoModule sq fs) = toValue $ do+ dbval <- getDb+ filter' <- targetFilters fs+ return $ newestPackage $ filterMatch sq $ filter (filter' . view moduleId) $ allModules dbval+runCommand (InfoResolve fpath exports) = toValue $ do+ dbval <- getSDb fpath+ let+ getScope+ | exports = exportsModule+ | otherwise = scopeModule+ 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+ liftIO $ hsdevLift $ lookupSymbol dbval fpath nm+runCommand (Whois nm fpath) = toValue $ do+ dbval <- getSDb fpath+ liftIO $ hsdevLift $ whois dbval fpath nm+runCommand (ResolveScopeModules sq fpath) = toValue $ do+ dbval <- getSDb 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) $ liftIO $ hsdevLift $ scope dbval fpath global+runCommand (Complete input wide fpath) = toValue $ do+ dbval <- getSDb fpath+ 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) $+ liftIO $ hsdevLift $ Hayoo.hayoo hq (Just i)+runCommand (CabalList packages) = toValue $ liftIO $ hsdevLift $ Cabal.cabalList packages+runCommand (Lint fs) = toValue $ do+ liftIO $ hsdevLift $ liftM concat $ mapM (\(FileSource f c) -> HLint.hlint f c) fs+runCommand (Check fs ghcs') = toValue $ Log.scope "check" $ do+ -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs+ let+ checkSome file fn = Log.scope "checkSome" $ do+ m <- setFileSourceSession ghcs' file+ inSessionGhc $ fn m+ liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check ghcs' m c)) fs+runCommand (CheckLint fs ghcs') = toValue $ do+ -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs+ let+ checkSome file fn = Log.scope "checkSome" $ do+ m <- setFileSourceSession ghcs' file+ inSessionGhc $ fn m+ checkMsgs <- liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check ghcs' m c)) fs+ lintMsgs <- liftIO $ hsdevLift $ liftM concat $ mapM (\(FileSource f c) -> HLint.hlint f c) fs+ return $ checkMsgs ++ lintMsgs+runCommand (Types fs ghcs') = toValue $ do+ -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs+ liftM concat $ forM fs $ \(FileSource file msrc) -> do+ m <- setFileSourceSession ghcs' file+ inSessionGhc $ Types.fileTypes ghcs' m msrc+runCommand (AutoFix (AutoFixShow ns)) = toValue $ return $ AutoFix.corrections ns+runCommand (AutoFix (AutoFixFix ns rest isPure)) = toValue $ do+ files <- liftM (ordNub . sort) $ mapM findPath $ mapMaybe (preview $ Tools.noteSource . moduleFile) ns+ let+ doFix :: FilePath -> Maybe String -> ([Tools.Note AutoFix.Correction], Maybe String)+ doFix file mcts = AutoFix.autoFix fCorrs (fUpCorrs, mcts) where+ findCorrs :: FilePath -> [Tools.Note AutoFix.Correction] -> [Tools.Note AutoFix.Correction]+ findCorrs f = filter ((== Just f) . preview (Tools.noteSource . moduleFile))+ fCorrs = findCorrs file ns+ fUpCorrs = findCorrs file rest+ runFix file+ | isPure = return $ fst $ doFix file Nothing+ | otherwise = do+ (corrs', Just cts') <- liftM (doFix file) $ liftIO $ Just <$> readFileUtf8 file+ liftIO $ writeFileUtf8 file cts'+ return corrs'+ liftM concat $ mapM runFix files+runCommand (GhcEval exprs mfile) = toValue $ do+ -- ensureUpToDate (Update.UpdateOptions [] [] False False) (maybeToList mfile)+ ghcw <- askSession sessionGhc+ case mfile of+ Nothing -> inSessionGhc ghciSession+ Just (FileSource f mcts) -> do+ m <- setFileSourceSession [] f+ inSessionGhc $ interpretModule m mcts+ async' <- liftIO $ pushTask ghcw $ do+ mapM (try . evaluate) exprs+ res <- waitAsync async'+ return $ map toValue' res+ where+ waitAsync :: CommandMonad m => Async a -> m a+ 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+runCommand Langs = toValue $ return $ Compat.languages+runCommand Flags = toValue $ return ["-f" ++ prefix ++ f |+ f <- Compat.flags,+ prefix <- ["", "no-"]]+runCommand (Link hold) = toValue $ commandLink >> when hold commandHold+runCommand Exit = toValue serverExit++targetFilters :: CommandMonad m => [TargetFilter] -> m (ModuleId -> Bool)+targetFilters fs = do+ fs_ <- mapM targetFilter fs+ return $ foldr (liftM2 (&&)) (const True) fs_++targetFilter :: CommandMonad m => TargetFilter -> m (ModuleId -> Bool)+targetFilter f = case f of+ TargetProject proj -> liftM inProject $ findProject proj+ TargetFile file -> liftM inFile $ refineSourceFile file+ TargetModule mname -> return $ inModule mname+ TargetDepsOf dep -> liftM inDeps $ findDep dep+ TargetPackageDb pdb -> return $ inPackageDb pdb+ TargetCabal -> return $ inPackageDbStack userDb+ TargetSandbox sbox -> liftM inPackageDbStack $ findSandbox sbox >>= sandboxPackageDbStack+ TargetPackage pkg -> liftM inPackage $ refinePackage pkg+ TargetSourced -> return byFile+ TargetStandalone -> return standalone++-- Helper functions++-- | Canonicalize paths+findPath :: (CommandMonad m, Paths a) => a -> m a+findPath = paths findPath' where+ findPath' :: CommandMonad m => FilePath -> m FilePath+ findPath' f = do+ r <- commandRoot+ liftIO $ canonicalizePath (normalise $ if isRelative f then r </> f else f)++-- | Find sandbox by path+findSandbox :: CommandMonad m => FilePath -> m Sandbox+findSandbox fpath = do+ fpath' <- findPath fpath+ sbox <- liftIO $ S.findSandbox fpath'+ maybe (hsdevError $ FileNotFound fpath') return sbox++-- | Get source file+refineSourceFile :: CommandMonad m => FilePath -> m FilePath+refineSourceFile fpath = do+ fpath' <- findPath fpath+ db' <- getDb+ maybe (hsdevError (NotInspected $ FileModule fpath' Nothing)) return $ do+ m' <- lookupFile fpath' db'+ preview (moduleLocation . moduleFile) m'++-- | Get module by source+refineSourceModule :: CommandMonad m => FilePath -> m Module+refineSourceModule fpath = do+ fpath' <- findPath fpath+ db' <- getDb+ maybe (hsdevError (NotInspected $ FileModule fpath' Nothing)) return $ lookupFile fpath' db'++-- | Set session by source+setFileSourceSession :: CommandMonad m => [String] -> FilePath -> m Module+setFileSourceSession opts fpath = do+ m <- refineSourceModule fpath+ inSessionGhc $ targetSession opts m+ return m++-- | Ensure package exists+refinePackage :: CommandMonad m => String -> m String+refinePackage pkg = do+ db' <- getDb+ if pkg `elem` (allPackages db' ^.. each . packageName)+ then return pkg+ else hsdevError (PackageNotFound pkg)++-- | Get list of enumerated sandboxes+getSandboxes :: CommandMonad m => [FilePath] -> m [Sandbox]+getSandboxes = traverse (findPath >=> findSandbox)++-- | Find project by name or path+findProject :: CommandMonad m => String -> m Project+findProject proj = do+ db' <- getDb+ proj' <- liftM addCabal $ findPath proj+ let+ resultProj =+ refineProject db' (project proj') <|>+ find ((== proj) . view projectName) (databaseProjects db')+ maybe (hsdevError $ ProjectNotFound proj) return resultProj+ where+ addCabal p+ | takeExtension p == ".cabal" = p+ | otherwise = p </> (takeBaseName p <.> "cabal")++-- | Find dependency: it may be source, project file or project name, also returns sandbox found+findDep :: CommandMonad m => String -> m (Project, Maybe FilePath, PackageDbStack)+findDep depName = do+ depPath <- findPath depName+ proj <- msum [+ do+ p <- liftIO (locateProject depPath)+ p' <- maybe (hsdevError $ ProjectNotFound depName) return p+ liftIO $ loadProject p',+ findProject depName]+ let+ src+ | takeExtension depPath == ".hs" = Just depPath+ | otherwise = Nothing+ pdbs <- searchPackageDbStack $ view projectPath proj+ return (proj, src, pdbs)++-- FIXME: Doesn't work for file without project+-- | Check if project or source depends from this module+inDeps :: (Project, Maybe FilePath, PackageDbStack) -> ModuleId -> Bool+inDeps (proj, src, pdbs) = liftM2 (&&) (restrictPackageDbStack pdbs) deps' where+ deps' = case src of+ Nothing -> inDepsOfProject proj+ Just src' -> inDepsOfFile proj src'++-- | Bring locals to top scope to search within them if 'locals' flag set+localsDatabase :: Bool -> Database -> Database+localsDatabase True = databaseLocals+localsDatabase False = id++-- | Get actual DB state+getDb :: SessionMonad m => m Database+getDb = askSession sessionDatabase >>= liftIO . DB.readAsync++-- | Get DB with filtered sanxboxes for file+getSDb :: SessionMonad m => FilePath -> m Database+getSDb fpath = do+ dbval <- getDb+ pdbs <- searchPackageDbStack fpath+ return $ filterDB (restrictPackageDbStack pdbs) (const True) dbval++-- | Run DB update action+updateProcess :: ServerMonadBase m => Update.UpdateOptions -> [Update.UpdateM m ()] -> ClientM m ()+updateProcess uopts acts = Update.runUpdate uopts $ mapM_ runAct acts where+ runAct act = catch act onError+ onError e = Log.sendLog Log.Error $ "{}" ~~ (e :: HsDevError)++-- | Ensure file is up to date+-- ensureUpToDate :: ServerMonadBase m => Update.UpdateOptions -> [FileSource] -> ClientM m ()+-- ensureUpToDate uopts fs = updateProcess uopts [Update.scanFileContents (view Update.updateGhcOpts uopts) f mcts | FileSource f mcts <- fs]++-- | Filter declarations with prefix and infix+filterMatch :: Symbol a => SearchQuery -> [a] -> [a]+filterMatch (SearchQuery q st) = filter match' where+ match' m = case st of+ SearchExact -> fromString q == symbolName m+ SearchPrefix -> fromString q `T.isPrefixOf` symbolName m+ SearchInfix -> fromString q `T.isInfixOf` symbolName m+ SearchSuffix -> fromString q `T.isSuffixOf` symbolName m+ SearchRegex -> unpack (symbolName m) =~ q
src/HsDev/Commands.hs view
@@ -1,193 +1,193 @@-{-# LANGUAGE RankNTypes, FlexibleContexts #-} - -module HsDev.Commands ( - -- * Commands - findDeclaration, findModule, - fileModule, - lookupSymbol, - whois, - scopeModules, scope, - completions, - moduleCompletions, - - -- * Filters - checkModule, checkDeclaration, restrictPackageDb, restrictPackageDbStack, visibleFrom, - splitIdentifier, - - -- * Helpers - fileCtx, fileCtxMaybe, - - -- * Reexports - module HsDev.Database, - module HsDev.Symbols.Types, - module Control.Monad.Except - ) where - -import Control.Applicative -import Control.Lens (view, set, each, toListOf) -import Control.Monad.Except -import Data.List (delete) -import Data.Maybe -import Data.String (fromString) -import qualified Data.Text as T (isPrefixOf, split, unpack) -import System.Directory (canonicalizePath) - -import System.Directory.Paths - -import HsDev.Database -import HsDev.Project -import HsDev.Symbols -import HsDev.Symbols.Resolve -import HsDev.Symbols.Types -import HsDev.Symbols.Util -import HsDev.Tools.Base (matchRx, at_) -import HsDev.Util (liftE, ordNub) - --- | Find declaration by name -findDeclaration :: Database -> String -> ExceptT String IO [ModuleDeclaration] -findDeclaration db ident = return $ selectDeclarations checkName db where - checkName :: ModuleDeclaration -> Bool - checkName m = - (view (moduleDeclaration . declarationName) m == fromString iname) && - (maybe True ((view (declarationModuleId . moduleIdName) m ==) . fromString) qname) - - (qname, iname) = splitIdentifier ident - --- | Find module by name -findModule :: Database -> String -> ExceptT String IO [Module] -findModule db mname = return $ selectModules ((== fromString mname) . view moduleName) db - --- | Find module in file -fileModule :: Database -> FilePath -> ExceptT String IO Module -fileModule db src = do - src' <- liftE $ canonicalizePath src - maybe (throwError $ "File '" ++ src' ++ "' not found") return $ lookupFile src' db - --- | Find project of module -getProject :: Database -> Project -> ExceptT String IO Project -getProject db p = do - p' <- liftE $ canonicalize p - maybe (throwError $ "Project " ++ view projectCabal p' ++ " not found") return $ - refineProject db p' - --- | Lookup visible within project/cabal symbol -lookupSymbol :: Database -> FilePath -> String -> ExceptT String IO [ModuleDeclaration] -lookupSymbol db file ident = do - (_, mthis, mproj) <- fileCtx db file - liftM - (filter $ checkModule $ allOf [ - visibleFrom mproj mthis, - maybe (const True) inModule qname]) - (newestPackage <$> findDeclaration db iname) - where - (qname, iname) = splitIdentifier ident - --- | Whois symbol in scope -whois :: Database -> FilePath -> String -> ExceptT String IO [Declaration] -whois db file ident = do - (_, mthis, mproj) <- fileCtx db file - return $ - newestPackage $ filter checkDecl $ - view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $ - moduleLocals mthis - where - (qname, iname) = splitIdentifier ident - checkDecl d = fmap fromString qname `elem` scopes d && view declarationName d == fromString iname - --- | Accessible modules -scopeModules :: Database -> FilePath -> ExceptT String IO [Module] -scopeModules db file = do - (file', mthis, mproj) <- fileCtxMaybe db file - newestPackage <$> case mproj of - Nothing -> return $ maybe id (:) mthis $ selectModules (installed . view moduleId) db - Just proj -> let deps' = deps file' proj in - return $ concatMap (\p -> selectModules (p . view moduleId) db) [ - inProject proj, - \m -> any (`inPackage` m) deps'] - where - deps f p = delete (view projectName p) $ concatMap (view infoDepends) $ fileTargets p f - --- | Symbols in scope -scope :: Database -> FilePath -> Bool -> ExceptT String IO [Declaration] -scope db file False = do - (_, mthis, mproj) <- fileCtx db file - return $ view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) mthis -scope db file True = concatMap (view moduleDeclarations) <$> scopeModules db file - --- | Completions -completions :: Database -> FilePath -> String -> Bool -> ExceptT String IO [Declaration] -completions db file prefix wide = do - (_, mthis, mproj) <- fileCtx db file - return $ - toListOf (each . minimalDecl) $ newestPackage $ filter checkDecl $ - view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $ - dropImportLists mthis - where - (qname, iname) = splitIdentifier prefix - checkDecl d = fmap fromString qname `elem` scopes d && fromString iname `T.isPrefixOf` view declarationName d - dropImportLists m - | wide = set (moduleImports . each . importList) Nothing m - | otherwise = m - --- | Module completions -moduleCompletions :: Database -> [Module] -> String -> ExceptT String IO [String] -moduleCompletions _ ms prefix = return $ map T.unpack $ ordNub $ completions' $ map (view moduleName) ms where - completions' = mapMaybe getNext where - getNext m - | fromString prefix `T.isPrefixOf` m = listToMaybe $ map snd $ dropWhile (uncurry (==)) $ zip (T.split (== '.') $ fromString prefix) (T.split (== '.') m) - | otherwise = Nothing - --- | Check module -checkModule :: (ModuleId -> Bool) -> (ModuleDeclaration -> Bool) -checkModule = (. view declarationModuleId) - --- | Check declaration -checkDeclaration :: (Declaration -> Bool) -> (ModuleDeclaration -> Bool) -checkDeclaration = (. view moduleDeclaration) - --- | Allow only selected cabal sandbox -restrictPackageDb :: PackageDb -> ModuleId -> Bool -restrictPackageDb pdb m = inPackageDb pdb m || not (installed m) - --- | Allow only selected cabal sandboxes -restrictPackageDbStack :: PackageDbStack -> ModuleId -> Bool -restrictPackageDbStack pdbs m = any (`inPackageDb` m) (packageDbs pdbs) || not (installed m) - --- | Check whether module is visible from source file -visibleFrom :: Maybe Project -> Module -> ModuleId -> Bool -visibleFrom (Just p) this m = visible p (view moduleId this) m -visibleFrom Nothing this m = view moduleId this == m || installed m - --- | Split identifier into module name and identifier itself -splitIdentifier :: String -> (Maybe String, String) -splitIdentifier name = fromMaybe (Nothing, name) $ do - groups <- matchRx "(([A-Z][\\w']*\\.)*)(.*)" name - return (dropDot <$> groups 1, groups `at_` 3) - where - dropDot :: String -> String - dropDot "" = "" - dropDot s = init s - --- | Get context file and project -fileCtx :: Database -> FilePath -> ExceptT String IO (FilePath, Module, Maybe Project) -fileCtx db file = do - file' <- liftE $ canonicalizePath file - mthis <- fileModule db file' - mproj <- traverse (getProject db) $ projectOf $ view moduleId mthis - return (file', mthis, mproj) - --- | Try get context file -fileCtxMaybe :: Database -> FilePath -> ExceptT String IO (FilePath, Maybe Module, Maybe Project) -fileCtxMaybe db file = ((\(f, m, p) -> (f, Just m, p)) <$> fileCtx db file) <|> onlyProj where - onlyProj = do - file' <- liftE $ canonicalizePath file - mproj <- liftE $ locateProject file' - mproj' <- traverse (getProject db) mproj - return (file', Nothing, mproj') - --- | Restrict only modules file depends on -fileDeps :: FilePath -> Maybe Project -> Database -> Database -fileDeps file mproj = filterDB fileDeps' (const True) where - fileDeps' = liftM2 (||) - (maybe (const True) inProject mproj) - (\m -> any (`inDepsOfTarget` m) (fromMaybe [] $ fileTargets <$> mproj <*> pure file)) +{-# LANGUAGE RankNTypes, FlexibleContexts #-}++module HsDev.Commands (+ -- * Commands+ findDeclaration, findModule,+ fileModule,+ lookupSymbol,+ whois,+ scopeModules, scope,+ completions,+ moduleCompletions,++ -- * Filters+ checkModule, checkDeclaration, restrictPackageDb, restrictPackageDbStack, visibleFrom,+ splitIdentifier,++ -- * Helpers+ fileCtx, fileCtxMaybe,++ -- * Reexports+ module HsDev.Database,+ module HsDev.Symbols.Types,+ module Control.Monad.Except+ ) where++import Control.Applicative+import Control.Lens (view, set, each, toListOf)+import Control.Monad.Except+import Data.List (delete)+import Data.Maybe+import Data.String (fromString)+import qualified Data.Text as T (isPrefixOf, split, unpack)+import System.Directory (canonicalizePath)++import System.Directory.Paths++import HsDev.Database+import HsDev.Project+import HsDev.Symbols+import HsDev.Symbols.Resolve+import HsDev.Symbols.Types+import HsDev.Symbols.Util+import HsDev.Tools.Base (matchRx, at_)+import HsDev.Util (liftE, ordNub)++-- | Find declaration by name+findDeclaration :: Database -> String -> ExceptT String IO [ModuleDeclaration]+findDeclaration db ident = return $ selectDeclarations checkName db where+ checkName :: ModuleDeclaration -> Bool+ checkName m =+ (view (moduleDeclaration . declarationName) m == fromString iname) &&+ (maybe True ((view (declarationModuleId . moduleIdName) m ==) . fromString) qname)++ (qname, iname) = splitIdentifier ident++-- | Find module by name+findModule :: Database -> String -> ExceptT String IO [Module]+findModule db mname = return $ selectModules ((== fromString mname) . view moduleName) db++-- | Find module in file+fileModule :: Database -> FilePath -> ExceptT String IO Module+fileModule db src = do+ src' <- liftE $ canonicalizePath src+ maybe (throwError $ "File '" ++ src' ++ "' not found") return $ lookupFile src' db++-- | Find project of module+getProject :: Database -> Project -> ExceptT String IO Project+getProject db p = do+ p' <- liftE $ canonicalize p+ maybe (throwError $ "Project " ++ view projectCabal p' ++ " not found") return $+ refineProject db p'++-- | Lookup visible within project/cabal symbol+lookupSymbol :: Database -> FilePath -> String -> ExceptT String IO [ModuleDeclaration]+lookupSymbol db file ident = do+ (_, mthis, mproj) <- fileCtx db file+ liftM+ (filter $ checkModule $ allOf [+ visibleFrom mproj mthis,+ maybe (const True) inModule qname])+ (newestPackage <$> findDeclaration db iname)+ where+ (qname, iname) = splitIdentifier ident++-- | Whois symbol in scope+whois :: Database -> FilePath -> String -> ExceptT String IO [Declaration]+whois db file ident = do+ (_, mthis, mproj) <- fileCtx db file+ return $+ newestPackage $ filter checkDecl $+ view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $+ moduleLocals mthis+ where+ (qname, iname) = splitIdentifier ident+ checkDecl d = fmap fromString qname `elem` scopes d && view declarationName d == fromString iname++-- | Accessible modules+scopeModules :: Database -> FilePath -> ExceptT String IO [Module]+scopeModules db file = do+ (file', mthis, mproj) <- fileCtxMaybe db file+ newestPackage <$> case mproj of+ Nothing -> return $ maybe id (:) mthis $ selectModules (installed . view moduleId) db+ Just proj -> let deps' = deps file' proj in+ return $ concatMap (\p -> selectModules (p . view moduleId) db) [+ inProject proj,+ \m -> any (`inPackage` m) deps']+ where+ deps f p = delete (view projectName p) $ concatMap (view infoDepends) $ fileTargets p f++-- | Symbols in scope+scope :: Database -> FilePath -> Bool -> ExceptT String IO [Declaration]+scope db file False = do+ (_, mthis, mproj) <- fileCtx db file+ return $ view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) mthis+scope db file True = concatMap (view moduleDeclarations) <$> scopeModules db file++-- | Completions+completions :: Database -> FilePath -> String -> Bool -> ExceptT String IO [Declaration]+completions db file prefix wide = do+ (_, mthis, mproj) <- fileCtx db file+ return $+ toListOf (each . minimalDecl) $ newestPackage $ filter checkDecl $+ view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $+ dropImportLists mthis+ where+ (qname, iname) = splitIdentifier prefix+ checkDecl d = fmap fromString qname `elem` scopes d && fromString iname `T.isPrefixOf` view declarationName d+ dropImportLists m+ | wide = set (moduleImports . each . importList) Nothing m+ | otherwise = m++-- | Module completions+moduleCompletions :: Database -> [Module] -> String -> ExceptT String IO [String]+moduleCompletions _ ms prefix = return $ map T.unpack $ ordNub $ completions' $ map (view moduleName) ms where+ completions' = mapMaybe getNext where+ getNext m+ | fromString prefix `T.isPrefixOf` m = listToMaybe $ map snd $ dropWhile (uncurry (==)) $ zip (T.split (== '.') $ fromString prefix) (T.split (== '.') m)+ | otherwise = Nothing++-- | Check module+checkModule :: (ModuleId -> Bool) -> (ModuleDeclaration -> Bool)+checkModule = (. view declarationModuleId)++-- | Check declaration+checkDeclaration :: (Declaration -> Bool) -> (ModuleDeclaration -> Bool)+checkDeclaration = (. view moduleDeclaration)++-- | Allow only selected cabal sandbox+restrictPackageDb :: PackageDb -> ModuleId -> Bool+restrictPackageDb pdb m = inPackageDb pdb m || not (installed m)++-- | Allow only selected cabal sandboxes+restrictPackageDbStack :: PackageDbStack -> ModuleId -> Bool+restrictPackageDbStack pdbs m = any (`inPackageDb` m) (packageDbs pdbs) || not (installed m)++-- | Check whether module is visible from source file+visibleFrom :: Maybe Project -> Module -> ModuleId -> Bool+visibleFrom (Just p) this m = visible p (view moduleId this) m+visibleFrom Nothing this m = view moduleId this == m || installed m++-- | Split identifier into module name and identifier itself+splitIdentifier :: String -> (Maybe String, String)+splitIdentifier name = fromMaybe (Nothing, name) $ do+ groups <- matchRx "(([A-Z][\\w']*\\.)*)(.*)" name+ return (dropDot <$> groups 1, groups `at_` 3)+ where+ dropDot :: String -> String+ dropDot "" = ""+ dropDot s = init s++-- | Get context file and project+fileCtx :: Database -> FilePath -> ExceptT String IO (FilePath, Module, Maybe Project)+fileCtx db file = do+ file' <- liftE $ canonicalizePath file+ mthis <- fileModule db file'+ mproj <- traverse (getProject db) $ projectOf $ view moduleId mthis+ return (file', mthis, mproj)++-- | Try get context file+fileCtxMaybe :: Database -> FilePath -> ExceptT String IO (FilePath, Maybe Module, Maybe Project)+fileCtxMaybe db file = ((\(f, m, p) -> (f, Just m, p)) <$> fileCtx db file) <|> onlyProj where+ onlyProj = do+ file' <- liftE $ canonicalizePath file+ mproj <- liftE $ locateProject file'+ mproj' <- traverse (getProject db) mproj+ return (file', Nothing, mproj')++-- | Restrict only modules file depends on+fileDeps :: FilePath -> Maybe Project -> Database -> Database+fileDeps file mproj = filterDB fileDeps' (const True) where+ fileDeps' = liftM2 (||)+ (maybe (const True) inProject mproj)+ (\m -> any (`inDepsOfTarget` m) (fromMaybe [] $ fileTargets <$> mproj <*> pure file))
src/HsDev/Database.hs view
@@ -1,253 +1,253 @@-{-# LANGUAGE OverloadedStrings #-} - -module HsDev.Database ( - Database(..), - databaseIntersection, nullDatabase, databaseLocals, databasePackageDbs, allModules, allDeclarations, allPackages, - fromModule, fromProject, - filterDB, - projectDB, packageDbDB, packageDbStackDB, standaloneDB, - selectModules, selectDeclarations, lookupModule, lookupInspected, lookupFile, refineProject, - getInspected, - - append, remove, - - Structured(..), - structured, structurize, merge, - - Map - ) where - -import Control.Lens (set, view, preview, _Just, _Right, each, (^..)) -import Control.Monad (msum, join) -import Control.DeepSeq (NFData(..)) -import Data.Aeson -import Data.Either (rights) -import Data.Foldable (find) -import Data.Function (on) -import Data.Group (Group(..)) -import Data.Map (Map) -import Data.Maybe -import qualified Data.Map as M - -import HsDev.Symbols -import HsDev.Symbols.Util -import HsDev.Util ((.::), ordNub, mapBy) - --- | HsDev database -data Database = Database { - databaseModules :: [InspectedModule], - databaseProjects :: [Project] } - deriving (Eq, Ord) - -instance NFData Database where - rnf (Database ms ps) = rnf ms `seq` rnf ps - -instance Group Database where - add old new = Database { - databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules new) `M.union` mapBy (view inspectedId) (databaseModules old), - databaseProjects = M.elems $ M.unionWith mergeProject - (mapBy (view projectCabal) (databaseProjects new)) - (mapBy (view projectCabal) (databaseProjects old)) } - where - mergeProject pl pr = set projectDescription (msum [view projectDescription pl, view projectDescription pr]) pl - sub old new = Database { - databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules old) `M.difference` mapBy (view inspectedId) (databaseModules new), - databaseProjects = M.elems $ mapBy (view projectCabal) (databaseProjects old) `M.difference` mapBy (view projectCabal) (databaseProjects new) } - zero = Database [] [] - -instance Monoid Database where - mempty = zero - mappend = add - -instance ToJSON Database where - toJSON (Database ms ps) = object [ - "modules" .= ms, - "projects" .= ps] - -instance FromJSON Database where - parseJSON = withObject "database" $ \v -> Database <$> - (v .:: "modules") <*> - (v .:: "projects") - --- | Database intersection, prefers first database data -databaseIntersection :: Database -> Database -> Database -databaseIntersection l r = mempty { - databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules l) `M.intersection` mapBy (view inspectedId) (databaseModules r), - databaseProjects = M.elems $ mapBy (view projectCabal) (databaseProjects l) `M.intersection` mapBy (view projectCabal) (databaseProjects r) } - --- | Check if database is empty -nullDatabase :: Database -> Bool -nullDatabase db = null (databaseModules db) && null (databaseProjects db) - --- | Bring all locals to scope -databaseLocals :: Database -> Database -databaseLocals db = db { - databaseModules = fmap (fmap moduleLocals) (databaseModules db) } - --- | All scanned sandboxes -databasePackageDbs :: Database -> [PackageDb] -databasePackageDbs db = ordNub $ databaseModules db ^.. each . inspectionResult . _Right . moduleLocation . modulePackageDb - --- | All modules -allModules :: Database -> [Module] -allModules = rights . fmap (view inspectionResult) . databaseModules - --- | All declarations -allDeclarations :: Database -> [ModuleDeclaration] -allDeclarations db = do - m <- allModules db - moduleModuleDeclarations m - --- | All packages -allPackages :: Database -> [ModulePackage] -allPackages = ordNub . mapMaybe (preview (moduleLocation . modulePackage . _Just)) . allModules - --- | Make database from module -fromModule :: InspectedModule -> Database -fromModule m = zero { - databaseModules = [m] } - --- | Make database from project -fromProject :: Project -> Database -fromProject p = zero { - databaseProjects = [p] } - --- | Filter database by predicate -filterDB :: (ModuleId -> Bool) -> (Project -> Bool) -> Database -> Database -filterDB m p db = mempty { - databaseModules = filter (either (const False) (m . view moduleId) . view inspectionResult) (databaseModules db), - databaseProjects = filter p (databaseProjects db) } - --- | Project database -projectDB :: Project -> Database -> Database -projectDB proj = filterDB (inProject proj) (((==) `on` view projectCabal) proj) - --- | Package-db database -packageDbDB :: PackageDb -> Database -> Database -packageDbDB pdb = filterDB (inPackageDb pdb) (const False) - -packageDbStackDB :: PackageDbStack -> Database -> Database -packageDbStackDB pdbs = filterDB (inPackageDbStack pdbs) (const False) - --- | Standalone database -standaloneDB :: Database -> Database -standaloneDB = filterDB check' (const False) where - check' m = standalone m && byFile m - --- | Select module by predicate -selectModules :: (Module -> Bool) -> Database -> [Module] -selectModules p = filter p . allModules - --- | Select declaration by predicate -selectDeclarations :: (ModuleDeclaration -> Bool) -> Database -> [ModuleDeclaration] -selectDeclarations p = filter p . allDeclarations - --- | Lookup module by its location and name -lookupModule :: ModuleLocation -> Database -> Maybe Module -lookupModule mloc db = do - m <- find ((== mloc) . view inspectedId) $ databaseModules db - either (const Nothing) Just $ view inspectionResult m - --- | Lookup inspected module -lookupInspected :: ModuleLocation -> Database -> Maybe InspectedModule -lookupInspected mloc db = find ((== mloc) . view inspectedId) $ databaseModules db - --- | Lookup module by its source file -lookupFile :: FilePath -> Database -> Maybe Module -lookupFile f = listToMaybe . selectModules (inFile f . view moduleId) - --- | Refine project -refineProject :: Database -> Project -> Maybe Project -refineProject db proj = find ((== view projectCabal proj) . view projectCabal) $ databaseProjects db - --- | Get inspected module -getInspected :: Database -> Module -> InspectedModule -getInspected db m = fromMaybe err $ find ((== view moduleLocation m) . view inspectedId) $ databaseModules db where - err = error "Impossible happened: getInspected" - --- | Append database -append :: Database -> Database -> Database -append = add - --- | Remove database -remove :: Database -> Database -> Database -remove = sub - --- | Structured database -data Structured = Structured { - structuredPackageDbs :: Map PackageDb Database, - structuredProjects :: Map FilePath Database, - structuredFiles :: Database } - deriving (Eq, Ord) - -instance NFData Structured where - rnf (Structured cs ps fs) = rnf cs `seq` rnf ps `seq` rnf fs - -instance Group Structured where - add old new = Structured { - structuredPackageDbs = structuredPackageDbs new `M.union` structuredPackageDbs old, - structuredProjects = structuredProjects new `M.union` structuredProjects old, - structuredFiles = structuredFiles old `add` structuredFiles new } - sub old new = Structured { - structuredPackageDbs = structuredPackageDbs old `M.difference` structuredPackageDbs new, - structuredProjects = structuredProjects old `M.difference` structuredProjects new, - structuredFiles = structuredFiles old `sub` structuredFiles new } - zero = Structured zero zero zero - -instance Monoid Structured where - mempty = zero - mappend = add - -instance ToJSON Structured where - toJSON (Structured cs ps fs) = object [ - "cabals" .= M.elems cs, - "projects" .= M.elems ps, - "files" .= fs] - -instance FromJSON Structured where - parseJSON = withObject "structured" $ \v -> join $ - either fail return <$> (structured <$> - (v .:: "cabals") <*> - (v .:: "projects") <*> - (v .:: "files")) - -structured :: [Database] -> [Database] -> Database -> Either String Structured -structured cs ps fs = Structured <$> mkMap keyCabal cs <*> mkMap keyProj ps <*> pure fs where - mkMap :: Ord a => (Database -> Either String a) -> [Database] -> Either String (Map a Database) - mkMap key dbs = do - keys <- mapM key dbs - return $ M.fromList $ zip keys dbs - keyCabal :: Database -> Either String PackageDb - keyCabal db = unique - "No cabal" - "Different module cabals" - (ordNub <$> mapM getPackageDb (allModules db)) - where - getPackageDb m = case view moduleLocation m of - InstalledModule c _ _ -> Right c - _ -> Left "Module have no cabal" - keyProj :: Database -> Either String FilePath - keyProj db = unique - "No project" - "Different module projects" - (return (databaseProjects db ^.. each . projectCabal)) - -- Check that list results in one element - unique :: String -> String -> Either String [a] -> Either String a - unique _ _ (Left e) = Left e - unique no _ (Right []) = Left no - unique _ _ (Right [x]) = Right x - unique _ much (Right _) = Left much - -structurize :: Database -> Structured -structurize db = Structured cs ps fs where - cs = M.fromList [(c, packageDbDB c db) | c <- ordNub (mapMaybe modPackageDb (allModules db))] - ps = M.fromList [(pname, projectDB (project pname) db) | pname <- databaseProjects db ^.. each . projectCabal] - fs = standaloneDB db - -merge :: Structured -> Database -merge (Structured cs ps fs) = mconcat $ M.elems cs ++ M.elems ps ++ [fs] - -modPackageDb :: Module -> Maybe PackageDb -modPackageDb m = case view moduleLocation m of - InstalledModule c _ _ -> Just c - _ -> Nothing +{-# LANGUAGE OverloadedStrings #-}++module HsDev.Database (+ Database(..),+ databaseIntersection, nullDatabase, databaseLocals, databasePackageDbs, allModules, allDeclarations, allPackages,+ fromModule, fromProject,+ filterDB,+ projectDB, packageDbDB, packageDbStackDB, standaloneDB,+ selectModules, selectDeclarations, lookupModule, lookupInspected, lookupFile, refineProject,+ getInspected,++ append, remove,++ Structured(..),+ structured, structurize, merge,++ Map+ ) where++import Control.Lens (set, view, preview, _Just, _Right, each, (^..))+import Control.Monad (msum, join)+import Control.DeepSeq (NFData(..))+import Data.Aeson+import Data.Either (rights)+import Data.Foldable (find)+import Data.Function (on)+import Data.Group (Group(..))+import Data.Map (Map)+import Data.Maybe+import qualified Data.Map as M++import HsDev.Symbols+import HsDev.Symbols.Util+import HsDev.Util ((.::), ordNub, mapBy)++-- | HsDev database+data Database = Database {+ databaseModules :: [InspectedModule],+ databaseProjects :: [Project] }+ deriving (Eq, Ord)++instance NFData Database where+ rnf (Database ms ps) = rnf ms `seq` rnf ps++instance Group Database where+ add old new = Database {+ databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules new) `M.union` mapBy (view inspectedId) (databaseModules old),+ databaseProjects = M.elems $ M.unionWith mergeProject+ (mapBy (view projectCabal) (databaseProjects new))+ (mapBy (view projectCabal) (databaseProjects old)) }+ where+ mergeProject pl pr = set projectDescription (msum [view projectDescription pl, view projectDescription pr]) pl+ sub old new = Database {+ databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules old) `M.difference` mapBy (view inspectedId) (databaseModules new),+ databaseProjects = M.elems $ mapBy (view projectCabal) (databaseProjects old) `M.difference` mapBy (view projectCabal) (databaseProjects new) }+ zero = Database [] []++instance Monoid Database where+ mempty = zero+ mappend = add++instance ToJSON Database where+ toJSON (Database ms ps) = object [+ "modules" .= ms,+ "projects" .= ps]++instance FromJSON Database where+ parseJSON = withObject "database" $ \v -> Database <$>+ (v .:: "modules") <*>+ (v .:: "projects")++-- | Database intersection, prefers first database data+databaseIntersection :: Database -> Database -> Database+databaseIntersection l r = mempty {+ databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules l) `M.intersection` mapBy (view inspectedId) (databaseModules r),+ databaseProjects = M.elems $ mapBy (view projectCabal) (databaseProjects l) `M.intersection` mapBy (view projectCabal) (databaseProjects r) }++-- | Check if database is empty+nullDatabase :: Database -> Bool+nullDatabase db = null (databaseModules db) && null (databaseProjects db)++-- | Bring all locals to scope+databaseLocals :: Database -> Database+databaseLocals db = db {+ databaseModules = fmap (fmap moduleLocals) (databaseModules db) }++-- | All scanned sandboxes+databasePackageDbs :: Database -> [PackageDb]+databasePackageDbs db = ordNub $ databaseModules db ^.. each . inspectionResult . _Right . moduleLocation . modulePackageDb++-- | All modules+allModules :: Database -> [Module]+allModules = rights . fmap (view inspectionResult) . databaseModules++-- | All declarations+allDeclarations :: Database -> [ModuleDeclaration]+allDeclarations db = do+ m <- allModules db+ moduleModuleDeclarations m++-- | All packages+allPackages :: Database -> [ModulePackage]+allPackages = ordNub . mapMaybe (preview (moduleLocation . modulePackage . _Just)) . allModules++-- | Make database from module+fromModule :: InspectedModule -> Database+fromModule m = zero {+ databaseModules = [m] }++-- | Make database from project+fromProject :: Project -> Database+fromProject p = zero {+ databaseProjects = [p] }++-- | Filter database by predicate+filterDB :: (ModuleId -> Bool) -> (Project -> Bool) -> Database -> Database+filterDB m p db = mempty {+ databaseModules = filter (either (const False) (m . view moduleId) . view inspectionResult) (databaseModules db),+ databaseProjects = filter p (databaseProjects db) }++-- | Project database+projectDB :: Project -> Database -> Database+projectDB proj = filterDB (inProject proj) (((==) `on` view projectCabal) proj)++-- | Package-db database+packageDbDB :: PackageDb -> Database -> Database+packageDbDB pdb = filterDB (inPackageDb pdb) (const False)++packageDbStackDB :: PackageDbStack -> Database -> Database+packageDbStackDB pdbs = filterDB (inPackageDbStack pdbs) (const False)++-- | Standalone database+standaloneDB :: Database -> Database+standaloneDB = filterDB check' (const False) where+ check' m = standalone m && byFile m++-- | Select module by predicate+selectModules :: (Module -> Bool) -> Database -> [Module]+selectModules p = filter p . allModules++-- | Select declaration by predicate+selectDeclarations :: (ModuleDeclaration -> Bool) -> Database -> [ModuleDeclaration]+selectDeclarations p = filter p . allDeclarations++-- | Lookup module by its location and name+lookupModule :: ModuleLocation -> Database -> Maybe Module+lookupModule mloc db = do+ m <- find ((== mloc) . view inspectedId) $ databaseModules db+ either (const Nothing) Just $ view inspectionResult m++-- | Lookup inspected module+lookupInspected :: ModuleLocation -> Database -> Maybe InspectedModule+lookupInspected mloc db = find ((== mloc) . view inspectedId) $ databaseModules db++-- | Lookup module by its source file+lookupFile :: FilePath -> Database -> Maybe Module+lookupFile f = listToMaybe . selectModules (inFile f . view moduleId)++-- | Refine project+refineProject :: Database -> Project -> Maybe Project+refineProject db proj = find ((== view projectCabal proj) . view projectCabal) $ databaseProjects db++-- | Get inspected module+getInspected :: Database -> Module -> InspectedModule+getInspected db m = fromMaybe err $ find ((== view moduleLocation m) . view inspectedId) $ databaseModules db where+ err = error "Impossible happened: getInspected"++-- | Append database+append :: Database -> Database -> Database+append = add++-- | Remove database+remove :: Database -> Database -> Database+remove = sub++-- | Structured database+data Structured = Structured {+ structuredPackageDbs :: Map PackageDb Database,+ structuredProjects :: Map FilePath Database,+ structuredFiles :: Database }+ deriving (Eq, Ord)++instance NFData Structured where+ rnf (Structured cs ps fs) = rnf cs `seq` rnf ps `seq` rnf fs++instance Group Structured where+ add old new = Structured {+ structuredPackageDbs = structuredPackageDbs new `M.union` structuredPackageDbs old,+ structuredProjects = structuredProjects new `M.union` structuredProjects old,+ structuredFiles = structuredFiles old `add` structuredFiles new }+ sub old new = Structured {+ structuredPackageDbs = structuredPackageDbs old `M.difference` structuredPackageDbs new,+ structuredProjects = structuredProjects old `M.difference` structuredProjects new,+ structuredFiles = structuredFiles old `sub` structuredFiles new }+ zero = Structured zero zero zero++instance Monoid Structured where+ mempty = zero+ mappend = add++instance ToJSON Structured where+ toJSON (Structured cs ps fs) = object [+ "cabals" .= M.elems cs,+ "projects" .= M.elems ps,+ "files" .= fs]++instance FromJSON Structured where+ parseJSON = withObject "structured" $ \v -> join $+ either fail return <$> (structured <$>+ (v .:: "cabals") <*>+ (v .:: "projects") <*>+ (v .:: "files"))++structured :: [Database] -> [Database] -> Database -> Either String Structured+structured cs ps fs = Structured <$> mkMap keyCabal cs <*> mkMap keyProj ps <*> pure fs where+ mkMap :: Ord a => (Database -> Either String a) -> [Database] -> Either String (Map a Database)+ mkMap key dbs = do+ keys <- mapM key dbs+ return $ M.fromList $ zip keys dbs+ keyCabal :: Database -> Either String PackageDb+ keyCabal db = unique+ "No cabal"+ "Different module cabals"+ (ordNub <$> mapM getPackageDb (allModules db))+ where+ getPackageDb m = case view moduleLocation m of+ InstalledModule c _ _ -> Right c+ _ -> Left "Module have no cabal"+ keyProj :: Database -> Either String FilePath+ keyProj db = unique+ "No project"+ "Different module projects"+ (return (databaseProjects db ^.. each . projectCabal))+ -- Check that list results in one element+ unique :: String -> String -> Either String [a] -> Either String a+ unique _ _ (Left e) = Left e+ unique no _ (Right []) = Left no+ unique _ _ (Right [x]) = Right x+ unique _ much (Right _) = Left much++structurize :: Database -> Structured+structurize db = Structured cs ps fs where+ cs = M.fromList [(c, packageDbDB c db) | c <- ordNub (mapMaybe modPackageDb (allModules db))]+ ps = M.fromList [(pname, projectDB (project pname) db) | pname <- databaseProjects db ^.. each . projectCabal]+ fs = standaloneDB db++merge :: Structured -> Database+merge (Structured cs ps fs) = mconcat $ M.elems cs ++ M.elems ps ++ [fs]++modPackageDb :: Module -> Maybe PackageDb+modPackageDb m = case view moduleLocation m of+ InstalledModule c _ _ -> Just c+ _ -> Nothing
src/HsDev/Database/Update.hs view
@@ -1,447 +1,447 @@-{-# LANGUAGE FlexibleContexts, OverloadedStrings, MultiParamTypeClasses #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Database.Update ( - Status(..), Progress(..), Task(..), isStatus, - UpdateOptions(..), - - UpdateM(..), - runUpdate, - - postStatus, waiter, updater, loadCache, getCache, runTask, runTasks, - readDB, - - scanModule, scanModules, scanFile, scanFileContents, scanCabal, prepareSandbox, scanSandbox, scanPackageDb, scanProjectFile, scanProjectStack, scanProject, scanDirectory, scanContents, - scanDocs, inferModTypes, - scan, - updateEvent, processEvent, - - module HsDev.Database.Update.Types, - - module HsDev.Watcher, - - module Control.Monad.Except - ) where - -import Control.Applicative ((<|>)) -import Control.Arrow -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 -import Data.Aeson -import Data.Aeson.Types -import Data.Foldable (toList) -import qualified Data.Map as M -import Data.Maybe (mapMaybe, isJust, fromMaybe) -import qualified Data.Text as T (unpack) -import System.Directory (canonicalizePath, doesFileExist) -import System.FilePath -import qualified System.Log.Simple as Log - -import HsDev.Error -import qualified HsDev.Cache.Structured as Cache -import HsDev.Database -import HsDev.Database.Async hiding (Event) -import HsDev.Display -import HsDev.Inspect (inspectDocs, inspectDocsGhc) -import HsDev.Project -import HsDev.Sandbox -import HsDev.Stack -import HsDev.Symbols -import HsDev.Tools.Ghc.Session hiding (wait) -import HsDev.Tools.Ghc.Types (inferTypes) -import HsDev.Tools.HDocs -import qualified HsDev.Scan as S -import HsDev.Scan.Browse -import HsDev.Util (isParent, ordNub) -import qualified HsDev.Util as Util (withCurrentDirectory) -import HsDev.Server.Types (commandNotify, serverWriteCache, serverReadCache) -import HsDev.Server.Message -import HsDev.Database.Update.Types -import HsDev.Watcher -import Text.Format - -onStatus :: UpdateMonad m => m () -onStatus = asks (view updateTasks) >>= commandNotify . Notification . toJSON . reverse - -childTask :: UpdateMonad m => Task -> m a -> m a -childTask t = local (over updateTasks (t:)) - -isStatus :: Value -> Bool -isStatus = isJust . parseMaybe (parseJSON :: Value -> Parser Task) - -runUpdate :: ServerMonadBase m => UpdateOptions -> UpdateM m a -> ClientM m a -runUpdate uopts act = Log.scope "update" $ do - (r, updatedMods) <- runWriterT (runUpdateM act' `runReaderT` uopts) - db <- askSession sessionDatabase - wait db - dbval <- liftIO $ readAsync db - let - dbs = ordNub $ mapMaybe (preview modulePackageDb) updatedMods - projs = ordNub $ mapMaybe (preview $ moduleProject . _Just) updatedMods - stand = any moduleStandalone updatedMods - - modifiedDb = mconcat $ concat [ - map (`packageDbDB` dbval) dbs, - map (`projectDB` dbval) projs, - [standaloneDB dbval | stand]] - serverWriteCache modifiedDb - return r - where - act' = do - (r, mlocs') <- liftM (second $ filter (isJust . preview moduleFile)) $ listen act - db <- askSession sessionDatabase - wait db - let - getMods :: (MonadIO m) => m [InspectedModule] - getMods = do - db' <- liftIO $ readAsync db - return $ filter ((`elem` mlocs') . view inspectedId) $ toList $ databaseModules db' - when (view updateDocs uopts) $ do - Log.sendLog Log.Trace "forking inspecting source docs" - void $ fork (getMods >>= waiter . mapM_ scanDocs_) - when (view updateInfer uopts) $ do - Log.sendLog Log.Trace "forking inferring types" - void $ fork (getMods >>= waiter . mapM_ inferModTypes_) - return r - scanDocs_ :: UpdateMonad m => InspectedModule -> m () - scanDocs_ im = do - im' <- (S.scanModify (\opts _ -> liftIO . inspectDocs opts) im) <|> return im - updater $ fromModule im' - inferModTypes_ :: UpdateMonad m => InspectedModule -> m () - inferModTypes_ im = do - -- TODO: locate sandbox - s <- getSession - im' <- (S.scanModify (infer' s) im) <|> return im - updater $ fromModule im' - infer' :: UpdateMonad m => Session -> [String] -> PackageDbStack -> Module -> m Module - infer' s opts _ m = case preview (moduleLocation . moduleFile) m of - Nothing -> return m - Just _ -> liftIO $ inWorker (sessionGhc s) $ do - targetSession opts m - inferTypes opts m Nothing - --- | Post status -postStatus :: UpdateMonad m => Task -> m () -postStatus t = childTask t onStatus - --- | Wait DB to complete actions -waiter :: UpdateMonad m => m () -> m () -waiter act = do - db <- askSession sessionDatabase - act - wait db - --- | Update task result to database -updater :: UpdateMonad m => Database -> m () -updater db' = do - db <- askSession sessionDatabase - update db $ return $!! db' - tell $!! map (view moduleLocation) $ allModules db' - --- | Clear obsolete data from database -cleaner :: UpdateMonad m => m Database -> m () -cleaner act = do - db <- askSession sessionDatabase - db' <- act - clear db $ return $!! db' - --- | Get data from cache without updating DB -loadCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> m Database -loadCache act = do - mdat <- serverReadCache act - return $ fromMaybe mempty mdat - --- | Load data from cache if not loaded yet and wait -getCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> (Database -> Database) -> m Database -getCache act check = do - dbval <- liftM check readDB - if nullDatabase dbval - then do - db <- loadCache act - waiter $ updater db - return db - else - return dbval - --- | Run one task -runTask :: (Display t, UpdateMonad m, NFData a) => String -> t -> m a -> m a -runTask action subj act = Log.scope "task" $ do - postStatus $ set taskStatus StatusWorking task - x <- childTask task act - x `deepseq` postStatus (set taskStatus StatusOk task) - return x - `catch` - (\e -> postStatus (set taskStatus (StatusError e) task) >> hsdevError e) - where - task = Task { - _taskName = action, - _taskStatus = StatusWorking, - _taskSubjectType = displayType subj, - _taskSubjectName = display subj, - _taskProgress = Nothing } - --- | Run many tasks with numeration -runTasks :: UpdateMonad m => [m ()] -> m () -runTasks ts = zipWithM_ taskNum [1..] (map noErr ts) where - total = length ts - taskNum n = local setProgress where - setProgress = set (updateTasks . _head . taskProgress) (Just (Progress n total)) - noErr v = v `mplus` return () - --- | Get database value -readDB :: SessionMonad m => m Database -readDB = askSession sessionDatabase >>= liftIO . readAsync - --- | Scan module -scanModule :: UpdateMonad m => [String] -> ModuleLocation -> Maybe String -> m () -scanModule opts mloc mcts = runTask "scanning" mloc $ Log.scope "module" $ do - defs <- askSession sessionDefines - im <- S.scanModule defs opts mloc mcts - updater $ fromModule im - _ <- return $ view inspectionResult im - return () - --- | Scan modules -scanModules :: UpdateMonad m => [String] -> [S.ModuleToScan] -> m () -scanModules opts ms = runTasks $ - [scanProjectFile opts p >> return () | p <- ps] ++ - [scanModule (opts ++ mopts) m mcts | (m, mopts, mcts) <- ms] - where - ps = ordNub $ mapMaybe (toProj . view _1) ms - toProj (FileModule _ p) = fmap (view projectCabal) p - toProj _ = Nothing - --- | Scan source file -scanFile :: UpdateMonad m => [String] -> FilePath -> m () -scanFile opts fpath = scanFileContents opts fpath Nothing - --- | Scan source file with contents -scanFileContents :: UpdateMonad m => [String] -> FilePath -> Maybe String -> m () -scanFileContents opts fpath mcts = Log.scope "file" $ hsdevLiftIO $ do - dbval <- readDB - 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 <- liftIO $ locateProject fpath' - return $ FileModule fpath' mproj - return [(mloc, [], mcts)] - else return [] - mapMOf_ (each . _1) (watch . flip watchModule) mlocs - scan - (Cache.loadFiles (== fpath')) - (filterDB (inFile fpath') (const False) . standaloneDB) - mlocs - opts - (scanModules opts) - where - inFile f = maybe False (== f) . preview (moduleIdLocation . moduleFile) - --- | Scan cabal modules, doesn't rescan if already scanned -scanCabal :: UpdateMonad m => [String] -> m () -scanCabal opts = Log.scope "cabal" $ do - dbval <- readDB - let - scannedDbs = databasePackageDbs dbval - unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks userDb - if null unscannedDbs - then Log.sendLog Log.Trace $ "cabal (global-db and user-db) already scanned" - else runTasks $ map (scanPackageDb opts) unscannedDbs - --- | 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 $ Util.withCurrentDirectory dir $ buildDeps Nothing, - runTask "configuring" sbox $ void $ Util.withCurrentDirectory dir $ configure Nothing] - where - dir = takeDirectory fpath -prepareSandbox _ = return () - --- | Scan sandbox modules, doesn't rescan if already scanned -scanSandbox :: UpdateMonad m => [String] -> Sandbox -> m () -scanSandbox opts sbox = Log.scope "sandbox" $ do - dbval <- readDB - prepareSandbox sbox - pdbs <- sandboxPackageDbStack sbox - let - scannedDbs = databasePackageDbs dbval - unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks pdbs - if null unscannedDbs - then Log.sendLog Log.Trace $ "sandbox already scanned" - else runTasks $ map (scanPackageDb opts) unscannedDbs - --- | Scan top of package-db stack, usable for rescan -scanPackageDb :: UpdateMonad m => [String] -> PackageDbStack -> m () -scanPackageDb opts pdbs = runTask "scanning" (topPackageDb pdbs) $ Log.scope "package-db" $ do - watch (\w -> watchPackageDb w pdbs opts) - mlocs <- liftM - (filter (\mloc -> preview modulePackageDb mloc == Just (topPackageDb pdbs))) $ - listModules opts pdbs - scan (Cache.loadPackageDb (topPackageDb pdbs)) (packageDbDB (topPackageDb pdbs)) ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do - ms <- browseModules opts pdbs (mlocs' ^.. each . _1) - docs <- liftIO $ hsdevLiftWith (ToolError "hdocs") $ hdocsCabal pdbs opts - updater $ mconcat $ map (fromModule . fmap (setDocs' docs)) ms - where - setDocs' :: Map String (Map String String) -> Module -> Module - setDocs' docs m = maybe m (`setDocs` m) $ M.lookup (T.unpack $ view moduleName m) docs - --- | Scan project file -scanProjectFile :: UpdateMonad m => [String] -> FilePath -> m Project -scanProjectFile opts cabal = runTask "scanning" cabal $ do - proj <- S.scanProjectFile opts cabal - updater $ fromProject proj - return proj - --- | Scan project and related package-db stack -scanProjectStack :: UpdateMonad m => [String] -> FilePath -> m () -scanProjectStack opts cabal = do - proj <- scanProjectFile opts cabal - scanProject opts cabal - sbox <- liftIO $ projectSandbox (view projectPath proj) - maybe (scanCabal opts) (scanSandbox opts) sbox - --- | Scan project -scanProject :: UpdateMonad m => [String] -> FilePath -> m () -scanProject opts cabal = runTask "scanning" (project cabal) $ Log.scope "project" $ do - proj <- scanProjectFile opts cabal - watch (\w -> watchProject w proj opts) - S.ScanContents _ [(_, sources)] _ <- S.enumProject proj - scan (Cache.loadProject $ view projectCabal proj) (projectDB proj) sources opts $ scanModules opts - --- | 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 <- 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 - scan (Cache.loadFiles (dir `isParent`)) (filterDB inDir (const False) . standaloneDB) standSrcs opts $ scanModules opts - where - inDir = maybe False (dir `isParent`) . preview (moduleIdLocation . moduleFile) - -scanContents :: UpdateMonad m => [String] -> S.ScanContents -> m () -scanContents opts (S.ScanContents standSrcs projSrcs pdbss) = do - dbval <- readDB - let - projs = databaseProjects dbval ^.. each . projectCabal - pdbs = databasePackageDbs dbval - files = allModules (standaloneDB dbval) ^.. each . moduleLocation . moduleFile - srcs = standSrcs ^.. each . _1 . moduleFile - inSrcs src = src `elem` srcs && src `notElem` files - inFiles = maybe False inSrcs . preview (moduleIdLocation . moduleFile) - mapMOf_ (each . _1 . projectCabal) (\p -> Log.sendLog Log.Trace ("scanning project: {}" ~~ p)) projSrcs - runTasks [scanProject opts (view projectCabal p) | (p, _) <- projSrcs, view projectCabal p `notElem` projs] - mapMOf_ (each . _1 . moduleFile) (\f -> Log.sendLog Log.Trace ("scanning file: {}" ~~ f)) standSrcs - mapMOf_ (each . _1) (watch . flip watchModule) standSrcs - scan (Cache.loadFiles inSrcs) (filterDB inFiles (const False) . standaloneDB) standSrcs opts $ scanModules opts - mapMOf_ each (\s -> Log.sendLog Log.Trace ("scanning package-db: {}" ~~ topPackageDb s)) pdbss - runTasks [scanPackageDb opts pdbs' | pdbs' <- pdbss, topPackageDb pdbs' `notElem` pdbs] - --- | Scan docs for inspected modules -scanDocs :: UpdateMonad m => [InspectedModule] -> m () -scanDocs ims = do - -- w <- liftIO $ ghcWorker ["-haddock"] (return ()) - -- w <- askSession sessionGhc - runTasks $ map scanDocs' ims - where - scanDocs' im - | not $ hasTag RefinedDocsTag im = runTask "scanning docs" (view inspectedId im) $ Log.scope "docs" $ do - Log.sendLog Log.Trace $ "Scanning docs for {}" ~~ view inspectedId im - im' <- (liftM (setTag RefinedDocsTag) $ S.scanModify doScan im) - <|> return im - Log.sendLog Log.Trace $ "Docs for {} updated: documented {} declarations" ~~ - view inspectedId im' ~~ - length (im' ^.. inspectionResult . _Right . moduleDeclarations . each . declarationDocs . _Just) - updater $ fromModule im' - | otherwise = Log.sendLog Log.Trace $ "Docs for {} already scanned" ~~ view inspectedId im - doScan _ _ m = do - w <- askSession sessionGhc - liftIO $ inWorker w $ do - opts' <- getModuleOpts [] m - haddockSession opts' - liftGhc $ inspectDocsGhc opts' m - -inferModTypes :: UpdateMonad m => [InspectedModule] -> m () -inferModTypes = runTasks . map inferModTypes' where - inferModTypes' im - | not $ hasTag InferredTypesTag im = runTask "inferring types" (view inspectedId im) $ Log.scope "docs" $ do - w <- askSession sessionGhc - Log.sendLog Log.Trace $ "Inferring types for {}" ~~ view inspectedId im - im' <- (liftM (setTag InferredTypesTag) $ - S.scanModify (\opts _ m -> liftIO (inWorker w (targetSession opts m >> inferTypes opts m Nothing))) im) - <|> return im - Log.sendLog Log.Trace $ "Types for {} inferred" ~~ view inspectedId im - updater $ fromModule im' - | otherwise = Log.sendLog 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 - => (FilePath -> ExceptT String IO Structured) - -- ^ Read data from cache - -> (Database -> Database) - -- ^ Get data from database - -> [S.ModuleToScan] - -- ^ Actual modules. Other modules will be removed from database - -> [String] - -- ^ Extra scan options - -> ([S.ModuleToScan] -> m ()) - -- ^ Function to update changed modules - -> m () -scan cache' part' mlocs opts act = Log.scope "scan" $ do - dbval <- getCache cache' part' - let - obsolete = filterDB (\m -> view moduleIdLocation m `notElem` (mlocs ^.. each . _1)) (const False) dbval - changed <- liftIO $ S.changedModules dbval opts mlocs - cleaner $ return obsolete - act changed - -updateEvent :: ServerMonadBase m => Watched -> Event -> UpdateM m () -updateEvent (WatchedProject proj projOpts) e - | isSource e = do - Log.sendLog Log.Info $ "File '{file}' in project {proj} changed" - ~~ ("file" ~% view eventPath e) - ~~ ("proj" ~% view projectName proj) - dbval <- readDB - let - opts = fromMaybe [] $ do - m <- lookupFile (view eventPath e) dbval - preview (inspection . inspectionOpts) $ getInspected dbval m - scanFile opts $ view eventPath e - | isCabal e = do - Log.sendLog Log.Info $ "Project {proj} changed" - ~~ ("proj" ~% view projectName proj) - scanProject projOpts $ view projectCabal proj - | otherwise = return () -updateEvent (WatchedPackageDb pdbs opts) e - | isConf e = do - Log.sendLog Log.Info $ "Package db {package} changed" - ~~ ("package" ~% topPackageDb pdbs) - scanPackageDb opts pdbs - | otherwise = return () -updateEvent WatchedModule e - | isSource e = do - Log.sendLog Log.Info $ "Module {file} changed" - ~~ ("file" ~% view eventPath e) - dbval <- readDB - let - opts = fromMaybe [] $ do - m <- lookupFile (view eventPath e) dbval - preview (inspection . inspectionOpts) $ getInspected dbval m - scanFile opts $ view eventPath e - | otherwise = return () - -processEvent :: UpdateOptions -> Watched -> Event -> ClientM IO () -processEvent uopts w e = runUpdate uopts $ updateEvent w e - -watch :: SessionMonad m => (Watcher -> IO ()) -> m () -watch f = do - w <- askSession sessionWatcher - liftIO $ f w +{-# LANGUAGE FlexibleContexts, OverloadedStrings, MultiParamTypeClasses #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Database.Update (+ Status(..), Progress(..), Task(..), isStatus,+ UpdateOptions(..),++ UpdateM(..),+ runUpdate,++ postStatus, waiter, updater, loadCache, getCache, runTask, runTasks,+ readDB,++ scanModule, scanModules, scanFile, scanFileContents, scanCabal, prepareSandbox, scanSandbox, scanPackageDb, scanProjectFile, scanProjectStack, scanProject, scanDirectory, scanContents,+ scanDocs, inferModTypes,+ scan,+ updateEvent, processEvent,++ module HsDev.Database.Update.Types,++ module HsDev.Watcher,++ module Control.Monad.Except+ ) where++import Control.Applicative ((<|>))+import Control.Arrow+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+import Data.Aeson+import Data.Aeson.Types+import Data.Foldable (toList)+import qualified Data.Map as M+import Data.Maybe (mapMaybe, isJust, fromMaybe)+import qualified Data.Text as T (unpack)+import System.Directory (canonicalizePath, doesFileExist)+import System.FilePath+import qualified System.Log.Simple as Log++import HsDev.Error+import qualified HsDev.Cache.Structured as Cache+import HsDev.Database+import HsDev.Database.Async hiding (Event)+import HsDev.Display+import HsDev.Inspect (inspectDocs, inspectDocsGhc)+import HsDev.Project+import HsDev.Sandbox+import HsDev.Stack+import HsDev.Symbols+import HsDev.Tools.Ghc.Session hiding (wait)+import HsDev.Tools.Ghc.Types (inferTypes)+import HsDev.Tools.HDocs+import qualified HsDev.Scan as S+import HsDev.Scan.Browse+import HsDev.Util (isParent, ordNub)+import qualified HsDev.Util as Util (withCurrentDirectory)+import HsDev.Server.Types (commandNotify, serverWriteCache, serverReadCache)+import HsDev.Server.Message+import HsDev.Database.Update.Types+import HsDev.Watcher+import Text.Format++onStatus :: UpdateMonad m => m ()+onStatus = asks (view updateTasks) >>= commandNotify . Notification . toJSON . reverse++childTask :: UpdateMonad m => Task -> m a -> m a+childTask t = local (over updateTasks (t:))++isStatus :: Value -> Bool+isStatus = isJust . parseMaybe (parseJSON :: Value -> Parser Task)++runUpdate :: ServerMonadBase m => UpdateOptions -> UpdateM m a -> ClientM m a+runUpdate uopts act = Log.scope "update" $ do+ (r, updatedMods) <- runWriterT (runUpdateM act' `runReaderT` uopts)+ db <- askSession sessionDatabase+ wait db+ dbval <- liftIO $ readAsync db+ let+ dbs = ordNub $ mapMaybe (preview modulePackageDb) updatedMods+ projs = ordNub $ mapMaybe (preview $ moduleProject . _Just) updatedMods+ stand = any moduleStandalone updatedMods++ modifiedDb = mconcat $ concat [+ map (`packageDbDB` dbval) dbs,+ map (`projectDB` dbval) projs,+ [standaloneDB dbval | stand]]+ serverWriteCache modifiedDb+ return r+ where+ act' = do+ (r, mlocs') <- liftM (second $ filter (isJust . preview moduleFile)) $ listen act+ db <- askSession sessionDatabase+ wait db+ let+ getMods :: (MonadIO m) => m [InspectedModule]+ getMods = do+ db' <- liftIO $ readAsync db+ return $ filter ((`elem` mlocs') . view inspectedId) $ toList $ databaseModules db'+ when (view updateDocs uopts) $ do+ Log.sendLog Log.Trace "forking inspecting source docs"+ void $ fork (getMods >>= waiter . mapM_ scanDocs_)+ when (view updateInfer uopts) $ do+ Log.sendLog Log.Trace "forking inferring types"+ void $ fork (getMods >>= waiter . mapM_ inferModTypes_)+ return r+ scanDocs_ :: UpdateMonad m => InspectedModule -> m ()+ scanDocs_ im = do+ im' <- (S.scanModify (\opts _ -> liftIO . inspectDocs opts) im) <|> return im+ updater $ fromModule im'+ inferModTypes_ :: UpdateMonad m => InspectedModule -> m ()+ inferModTypes_ im = do+ -- TODO: locate sandbox+ s <- getSession+ im' <- (S.scanModify (infer' s) im) <|> return im+ updater $ fromModule im'+ infer' :: UpdateMonad m => Session -> [String] -> PackageDbStack -> Module -> m Module+ infer' s opts _ m = case preview (moduleLocation . moduleFile) m of+ Nothing -> return m+ Just _ -> liftIO $ inWorker (sessionGhc s) $ do+ targetSession opts m+ inferTypes opts m Nothing++-- | Post status+postStatus :: UpdateMonad m => Task -> m ()+postStatus t = childTask t onStatus++-- | Wait DB to complete actions+waiter :: UpdateMonad m => m () -> m ()+waiter act = do+ db <- askSession sessionDatabase+ act+ wait db++-- | Update task result to database+updater :: UpdateMonad m => Database -> m ()+updater db' = do+ db <- askSession sessionDatabase+ update db $ return $!! db'+ tell $!! map (view moduleLocation) $ allModules db'++-- | Clear obsolete data from database+cleaner :: UpdateMonad m => m Database -> m ()+cleaner act = do+ db <- askSession sessionDatabase+ db' <- act+ clear db $ return $!! db'++-- | Get data from cache without updating DB+loadCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> m Database+loadCache act = do+ mdat <- serverReadCache act+ return $ fromMaybe mempty mdat++-- | Load data from cache if not loaded yet and wait+getCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> (Database -> Database) -> m Database+getCache act check = do+ dbval <- liftM check readDB+ if nullDatabase dbval+ then do+ db <- loadCache act+ waiter $ updater db+ return db+ else+ return dbval++-- | Run one task+runTask :: (Display t, UpdateMonad m, NFData a) => String -> t -> m a -> m a+runTask action subj act = Log.scope "task" $ do+ postStatus $ set taskStatus StatusWorking task+ x <- childTask task act+ x `deepseq` postStatus (set taskStatus StatusOk task)+ return x+ `catch`+ (\e -> postStatus (set taskStatus (StatusError e) task) >> hsdevError e)+ where+ task = Task {+ _taskName = action,+ _taskStatus = StatusWorking,+ _taskSubjectType = displayType subj,+ _taskSubjectName = display subj,+ _taskProgress = Nothing }++-- | Run many tasks with numeration+runTasks :: UpdateMonad m => [m ()] -> m ()+runTasks ts = zipWithM_ taskNum [1..] (map noErr ts) where+ total = length ts+ taskNum n = local setProgress where+ setProgress = set (updateTasks . _head . taskProgress) (Just (Progress n total))+ noErr v = v `mplus` return ()++-- | Get database value+readDB :: SessionMonad m => m Database+readDB = askSession sessionDatabase >>= liftIO . readAsync++-- | Scan module+scanModule :: UpdateMonad m => [String] -> ModuleLocation -> Maybe String -> m ()+scanModule opts mloc mcts = runTask "scanning" mloc $ Log.scope "module" $ do+ defs <- askSession sessionDefines+ im <- S.scanModule defs opts mloc mcts+ updater $ fromModule im+ _ <- return $ view inspectionResult im+ return ()++-- | Scan modules+scanModules :: UpdateMonad m => [String] -> [S.ModuleToScan] -> m ()+scanModules opts ms = runTasks $+ [scanProjectFile opts p >> return () | p <- ps] +++ [scanModule (opts ++ mopts) m mcts | (m, mopts, mcts) <- ms]+ where+ ps = ordNub $ mapMaybe (toProj . view _1) ms+ toProj (FileModule _ p) = fmap (view projectCabal) p+ toProj _ = Nothing++-- | Scan source file+scanFile :: UpdateMonad m => [String] -> FilePath -> m ()+scanFile opts fpath = scanFileContents opts fpath Nothing++-- | Scan source file with contents+scanFileContents :: UpdateMonad m => [String] -> FilePath -> Maybe String -> m ()+scanFileContents opts fpath mcts = Log.scope "file" $ hsdevLiftIO $ do+ dbval <- readDB+ 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 <- liftIO $ locateProject fpath'+ return $ FileModule fpath' mproj+ return [(mloc, [], mcts)]+ else return []+ mapMOf_ (each . _1) (watch . flip watchModule) mlocs+ scan+ (Cache.loadFiles (== fpath'))+ (filterDB (inFile fpath') (const False) . standaloneDB)+ mlocs+ opts+ (scanModules opts)+ where+ inFile f = maybe False (== f) . preview (moduleIdLocation . moduleFile)++-- | Scan cabal modules, doesn't rescan if already scanned+scanCabal :: UpdateMonad m => [String] -> m ()+scanCabal opts = Log.scope "cabal" $ do+ dbval <- readDB+ let+ scannedDbs = databasePackageDbs dbval+ unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks userDb+ if null unscannedDbs+ then Log.sendLog Log.Trace $ "cabal (global-db and user-db) already scanned"+ else runTasks $ map (scanPackageDb opts) unscannedDbs++-- | 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 $ Util.withCurrentDirectory dir $ buildDeps Nothing,+ runTask "configuring" sbox $ void $ Util.withCurrentDirectory dir $ configure Nothing]+ where+ dir = takeDirectory fpath+prepareSandbox _ = return ()++-- | Scan sandbox modules, doesn't rescan if already scanned+scanSandbox :: UpdateMonad m => [String] -> Sandbox -> m ()+scanSandbox opts sbox = Log.scope "sandbox" $ do+ dbval <- readDB+ prepareSandbox sbox+ pdbs <- sandboxPackageDbStack sbox+ let+ scannedDbs = databasePackageDbs dbval+ unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks pdbs+ if null unscannedDbs+ then Log.sendLog Log.Trace $ "sandbox already scanned"+ else runTasks $ map (scanPackageDb opts) unscannedDbs++-- | Scan top of package-db stack, usable for rescan+scanPackageDb :: UpdateMonad m => [String] -> PackageDbStack -> m ()+scanPackageDb opts pdbs = runTask "scanning" (topPackageDb pdbs) $ Log.scope "package-db" $ do+ watch (\w -> watchPackageDb w pdbs opts)+ mlocs <- liftM+ (filter (\mloc -> preview modulePackageDb mloc == Just (topPackageDb pdbs))) $+ listModules opts pdbs+ scan (Cache.loadPackageDb (topPackageDb pdbs)) (packageDbDB (topPackageDb pdbs)) ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do+ ms <- browseModules opts pdbs (mlocs' ^.. each . _1)+ docs <- liftIO $ hsdevLiftWith (ToolError "hdocs") $ hdocsCabal pdbs opts+ updater $ mconcat $ map (fromModule . fmap (setDocs' docs)) ms+ where+ setDocs' :: Map String (Map String String) -> Module -> Module+ setDocs' docs m = maybe m (`setDocs` m) $ M.lookup (T.unpack $ view moduleName m) docs++-- | Scan project file+scanProjectFile :: UpdateMonad m => [String] -> FilePath -> m Project+scanProjectFile opts cabal = runTask "scanning" cabal $ do+ proj <- S.scanProjectFile opts cabal+ updater $ fromProject proj+ return proj++-- | Scan project and related package-db stack+scanProjectStack :: UpdateMonad m => [String] -> FilePath -> m ()+scanProjectStack opts cabal = do+ proj <- scanProjectFile opts cabal+ scanProject opts cabal+ sbox <- liftIO $ projectSandbox (view projectPath proj)+ maybe (scanCabal opts) (scanSandbox opts) sbox++-- | Scan project+scanProject :: UpdateMonad m => [String] -> FilePath -> m ()+scanProject opts cabal = runTask "scanning" (project cabal) $ Log.scope "project" $ do+ proj <- scanProjectFile opts cabal+ watch (\w -> watchProject w proj opts)+ S.ScanContents _ [(_, sources)] _ <- S.enumProject proj+ scan (Cache.loadProject $ view projectCabal proj) (projectDB proj) sources opts $ scanModules opts++-- | 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 <- 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+ scan (Cache.loadFiles (dir `isParent`)) (filterDB inDir (const False) . standaloneDB) standSrcs opts $ scanModules opts+ where+ inDir = maybe False (dir `isParent`) . preview (moduleIdLocation . moduleFile)++scanContents :: UpdateMonad m => [String] -> S.ScanContents -> m ()+scanContents opts (S.ScanContents standSrcs projSrcs pdbss) = do+ dbval <- readDB+ let+ projs = databaseProjects dbval ^.. each . projectCabal+ pdbs = databasePackageDbs dbval+ files = allModules (standaloneDB dbval) ^.. each . moduleLocation . moduleFile+ srcs = standSrcs ^.. each . _1 . moduleFile+ inSrcs src = src `elem` srcs && src `notElem` files+ inFiles = maybe False inSrcs . preview (moduleIdLocation . moduleFile)+ mapMOf_ (each . _1 . projectCabal) (\p -> Log.sendLog Log.Trace ("scanning project: {}" ~~ p)) projSrcs+ runTasks [scanProject opts (view projectCabal p) | (p, _) <- projSrcs, view projectCabal p `notElem` projs]+ mapMOf_ (each . _1 . moduleFile) (\f -> Log.sendLog Log.Trace ("scanning file: {}" ~~ f)) standSrcs+ mapMOf_ (each . _1) (watch . flip watchModule) standSrcs+ scan (Cache.loadFiles inSrcs) (filterDB inFiles (const False) . standaloneDB) standSrcs opts $ scanModules opts+ mapMOf_ each (\s -> Log.sendLog Log.Trace ("scanning package-db: {}" ~~ topPackageDb s)) pdbss+ runTasks [scanPackageDb opts pdbs' | pdbs' <- pdbss, topPackageDb pdbs' `notElem` pdbs]++-- | Scan docs for inspected modules+scanDocs :: UpdateMonad m => [InspectedModule] -> m ()+scanDocs ims = do+ -- w <- liftIO $ ghcWorker ["-haddock"] (return ())+ -- w <- askSession sessionGhc+ runTasks $ map scanDocs' ims+ where+ scanDocs' im+ | not $ hasTag RefinedDocsTag im = runTask "scanning docs" (view inspectedId im) $ Log.scope "docs" $ do+ Log.sendLog Log.Trace $ "Scanning docs for {}" ~~ view inspectedId im+ im' <- (liftM (setTag RefinedDocsTag) $ S.scanModify doScan im)+ <|> return im+ Log.sendLog Log.Trace $ "Docs for {} updated: documented {} declarations" ~~+ view inspectedId im' ~~+ length (im' ^.. inspectionResult . _Right . moduleDeclarations . each . declarationDocs . _Just)+ updater $ fromModule im'+ | otherwise = Log.sendLog Log.Trace $ "Docs for {} already scanned" ~~ view inspectedId im+ doScan _ _ m = do+ w <- askSession sessionGhc+ liftIO $ inWorker w $ do+ opts' <- getModuleOpts [] m+ haddockSession opts'+ liftGhc $ inspectDocsGhc opts' m++inferModTypes :: UpdateMonad m => [InspectedModule] -> m ()+inferModTypes = runTasks . map inferModTypes' where+ inferModTypes' im+ | not $ hasTag InferredTypesTag im = runTask "inferring types" (view inspectedId im) $ Log.scope "docs" $ do+ w <- askSession sessionGhc+ Log.sendLog Log.Trace $ "Inferring types for {}" ~~ view inspectedId im+ im' <- (liftM (setTag InferredTypesTag) $+ S.scanModify (\opts _ m -> liftIO (inWorker w (targetSession opts m >> inferTypes opts m Nothing))) im)+ <|> return im+ Log.sendLog Log.Trace $ "Types for {} inferred" ~~ view inspectedId im+ updater $ fromModule im'+ | otherwise = Log.sendLog 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+ => (FilePath -> ExceptT String IO Structured)+ -- ^ Read data from cache+ -> (Database -> Database)+ -- ^ Get data from database+ -> [S.ModuleToScan]+ -- ^ Actual modules. Other modules will be removed from database+ -> [String]+ -- ^ Extra scan options+ -> ([S.ModuleToScan] -> m ())+ -- ^ Function to update changed modules+ -> m ()+scan cache' part' mlocs opts act = Log.scope "scan" $ do+ dbval <- getCache cache' part'+ let+ obsolete = filterDB (\m -> view moduleIdLocation m `notElem` (mlocs ^.. each . _1)) (const False) dbval+ changed <- liftIO $ S.changedModules dbval opts mlocs+ cleaner $ return obsolete+ act changed++updateEvent :: ServerMonadBase m => Watched -> Event -> UpdateM m ()+updateEvent (WatchedProject proj projOpts) e+ | isSource e = do+ Log.sendLog Log.Info $ "File '{file}' in project {proj} changed"+ ~~ ("file" ~% view eventPath e)+ ~~ ("proj" ~% view projectName proj)+ dbval <- readDB+ let+ opts = fromMaybe [] $ do+ m <- lookupFile (view eventPath e) dbval+ preview (inspection . inspectionOpts) $ getInspected dbval m+ scanFile opts $ view eventPath e+ | isCabal e = do+ Log.sendLog Log.Info $ "Project {proj} changed"+ ~~ ("proj" ~% view projectName proj)+ scanProject projOpts $ view projectCabal proj+ | otherwise = return ()+updateEvent (WatchedPackageDb pdbs opts) e+ | isConf e = do+ Log.sendLog Log.Info $ "Package db {package} changed"+ ~~ ("package" ~% topPackageDb pdbs)+ scanPackageDb opts pdbs+ | otherwise = return ()+updateEvent WatchedModule e+ | isSource e = do+ Log.sendLog Log.Info $ "Module {file} changed"+ ~~ ("file" ~% view eventPath e)+ dbval <- readDB+ let+ opts = fromMaybe [] $ do+ m <- lookupFile (view eventPath e) dbval+ preview (inspection . inspectionOpts) $ getInspected dbval m+ scanFile opts $ view eventPath e+ | otherwise = return ()++processEvent :: UpdateOptions -> Watched -> Event -> ClientM IO ()+processEvent uopts w e = runUpdate uopts $ updateEvent w e++watch :: SessionMonad m => (Watcher -> IO ()) -> m ()+watch f = do+ w <- askSession sessionWatcher+ liftIO $ f w
src/HsDev/Display.hs view
@@ -1,55 +1,55 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Display ( - Display(..) - ) where - -import Control.Lens (view) -import Data.Maybe (fromMaybe) - -import Text.Format - -import HsDev.PackageDb -import HsDev.Project -import HsDev.Sandbox -import HsDev.Symbols.Location - -class Display a where - display :: a -> String - displayType :: a -> String - -instance Display PackageDb where - display GlobalDb = "global-db" - display UserDb = "user-db" - display (PackageDb p) = "package-db " ++ p - displayType _ = "package-db" - -instance Display ModuleLocation where - display (FileModule f _) = f - display (InstalledModule _ _ n) = n - display (ModuleSource s) = fromMaybe "" s - displayType _ = "module" - -instance Display Project where - display = view projectName - displayType _ = "project" - -instance Display Sandbox where - display (Sandbox _ fpath) = fpath - displayType (Sandbox CabalSandbox _) = "cabal-sandbox" - displayType (Sandbox StackWork _) = "stack-work" - -instance Display FilePath where - display = id - displayType _ = "path" - -instance Formattable PackageDb where - formattable = formattable . display - -instance Formattable ModuleLocation where - formattable = formattable . display - -instance Formattable Project where - formattable = formattable . display - +{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Display (+ Display(..)+ ) where++import Control.Lens (view)+import Data.Maybe (fromMaybe)++import Text.Format++import HsDev.PackageDb+import HsDev.Project+import HsDev.Sandbox+import HsDev.Symbols.Location++class Display a where+ display :: a -> String+ displayType :: a -> String++instance Display PackageDb where+ display GlobalDb = "global-db"+ display UserDb = "user-db"+ display (PackageDb p) = "package-db " ++ p+ displayType _ = "package-db"++instance Display ModuleLocation where+ display (FileModule f _) = f+ display (InstalledModule _ _ n) = n+ display (ModuleSource s) = fromMaybe "" s+ displayType _ = "module"++instance Display Project where+ display = view projectName+ displayType _ = "project"++instance Display Sandbox where+ display (Sandbox _ fpath) = fpath+ displayType (Sandbox CabalSandbox _) = "cabal-sandbox"+ displayType (Sandbox StackWork _) = "stack-work"++instance Display FilePath where+ display = id+ displayType _ = "path"++instance Formattable PackageDb where+ formattable = formattable . display++instance Formattable ModuleLocation where+ formattable = formattable . display++instance Formattable Project where+ formattable = formattable . display+
src/HsDev/Error.hs view
@@ -1,63 +1,63 @@-{-# LANGUAGE FlexibleContexts #-} - -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, sendLog, 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 => Level -> m a -> m a -hsdevLog lev act = hsdevCatch act >>= either logError return where - logError e = sendLog lev (fromString $ show e) >> hsdevError e +{-# LANGUAGE FlexibleContexts #-}++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, sendLog, 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 => Level -> m a -> m a+hsdevLog lev act = hsdevCatch act >>= either logError return where+ logError e = sendLog lev (fromString $ show e) >> hsdevError e
src/HsDev/Inspect.hs view
@@ -1,467 +1,467 @@-{-# LANGUAGE TypeSynonymInstances #-} - -module HsDev.Inspect ( - analyzeModule, inspectDocsChunk, inspectDocs, inspectDocsGhc, - inspectContents, contentsInspection, - inspectFile, fileInspection, - projectDirs, projectSources, - inspectProject, - getDefines, - preprocess, preprocess_, - - module Control.Monad.Except - ) where - -import Control.Arrow -import Control.Applicative -import Control.DeepSeq -import qualified Control.Exception as E -import Control.Lens (view, preview, set, over) -import Control.Lens.At (ix) -import Control.Monad -import Control.Monad.Except -import Data.Char (isSpace) -import Data.Function (on) -import Data.List -import Data.Map (Map) -import Data.Maybe (fromMaybe, mapMaybe, catMaybes, listToMaybe, isJust) -import Data.Ord (comparing) -import Data.String (IsString, fromString) -import Data.Text (Text) -import qualified Data.Text as T (unpack) -import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, getPOSIXTime) -import qualified Data.Map as M -import qualified Language.Haskell.Exts as H -import qualified Language.Preprocessor.Cpphs as Cpphs -import qualified System.Directory as Dir -import System.FilePath -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 - --- | Analize source contents -analyzeModule :: [String] -> Maybe FilePath -> String -> Either String Module -analyzeModule exts file source = case H.parseFileContentsWithMode (parseMode file exts) source' of - H.ParseFailed loc reason -> Left $ "Parse failed at " ++ show loc ++ ": " ++ reason - H.ParseOk (H.Module _ (Just (H.ModuleHead _ (H.ModuleName _ mname) _ mexports)) _ imports declarations) -> Right Module { - _moduleName = fromString mname, - _moduleDocs = Nothing, - _moduleLocation = ModuleSource Nothing, - _moduleExports = fmap (concatMap getExports . getSpec) mexports, - _moduleImports = map getImport imports, - _moduleDeclarations = sortDeclarations $ getDecls declarations } - _ -> Left "Unknown module" - where - -- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces - source' = map untab source - untab '\t' = ' ' - untab ch = ch - getSpec (H.ExportSpecList _ es) = es - --- | Analize source contents -analyzeModule_ :: [String] -> Maybe FilePath -> String -> Either String Module -analyzeModule_ exts file source = do - mname <- parseModuleName source' - return Module { - _moduleName = fromString mname, - _moduleDocs = Nothing, - _moduleLocation = ModuleSource Nothing, - _moduleExports = do - H.PragmasAndModuleHead _ _ mhead <- parseModuleHead' source' - H.ModuleHead _ _ _ mexports <- mhead - H.ExportSpecList _ exports <- mexports - return $ concatMap getExports exports, - _moduleImports = map getImport $ mapMaybe (uncurry parseImport') parts, - _moduleDeclarations = sortDeclarations $ getDecls $ mapMaybe (uncurry parseDecl') parts } - where - parts :: [(Int, String)] - parts = zip offsets (map unlines parts') where - parts' :: [[String]] - parts' = unfoldr break' $ lines source' - offsets = scanl (+) 0 $ map length parts' - break' :: [String] -> Maybe ([String], [String]) - break' [] = Nothing - break' (l:ls) = Just $ first (l:) $ span (maybe True isSpace . listToMaybe) ls - - parseModuleName :: String -> Either String String - parseModuleName cts = maybe (Left "match fail") Right $ do - g <- matchRx "^module\\s+([\\w\\.]+)" cts - g 1 - - parseDecl' :: Int -> String -> Maybe (H.Decl H.SrcSpanInfo) - parseDecl' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $ - H.parseDeclWithMode (parseMode file exts) cts - - parseImport' :: Int -> String -> Maybe (H.ImportDecl H.SrcSpanInfo) - parseImport' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $ - H.parseImportDeclWithMode (parseMode file exts) cts - - parseModuleHead' :: String -> Maybe (H.PragmasAndModuleHead H.SrcSpanInfo) - parseModuleHead' = maybeResult . fmap H.unNonGreedy . H.parseWithMode (parseMode file exts) - - maybeResult :: H.ParseResult a -> Maybe a - maybeResult (H.ParseFailed _ _) = Nothing - maybeResult (H.ParseOk r) = Just r - - addOffset :: Int -> H.SrcSpan -> H.SrcSpan - addOffset offset src = src { H.srcSpanStartLine = H.srcSpanStartLine src + offset, H.srcSpanEndLine = H.srcSpanEndLine src + offset } - - -- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces - source' = map untab source - untab '\t' = ' ' - untab ch = ch - -parseMode :: Maybe FilePath -> [String] -> H.ParseMode -parseMode file exts = H.defaultParseMode { - H.parseFilename = fromMaybe (H.parseFilename H.defaultParseMode) file, - H.baseLanguage = H.Haskell2010, - H.extensions = H.glasgowExts ++ map H.parseExtension exts, - H.fixities = Just H.baseFixities } - --- | Get exports -getExports :: H.ExportSpec H.SrcSpanInfo -> [Export] -getExports (H.EModuleContents _ (H.ModuleName _ m)) = [ExportModule $ fromString m] -getExports (H.EVar _ n) = [uncurry ExportName (identOfQName n) ThingNothing] -getExports (H.EAbs _ _ n) = [uncurry ExportName (identOfQName n) ThingNothing] -getExports (H.EThingWith _ _ n ns) = [uncurry ExportName (identOfQName n) $ ThingWith (map toStr ns)] where - toStr :: H.CName H.SrcSpanInfo -> Text - toStr (H.VarName _ cn) = identOfName cn - toStr (H.ConName _ cn) = identOfName cn - --- | Get import -getImport :: H.ImportDecl H.SrcSpanInfo -> Import -getImport d = Import - (mname (H.importModule d)) - (H.importQualified d) - (mname <$> H.importAs d) - (importLst <$> H.importSpecs d) - (Just $ toPosition $ H.ann d) - where - mname (H.ModuleName _ n) = fromString n - importLst (H.ImportSpecList _ hiding specs) = ImportList hiding $ map impSpec specs - impSpec (H.IVar _ n) = ImportSpec (identOfName n) ThingNothing - impSpec (H.IAbs _ _ n) = ImportSpec (identOfName n) ThingNothing - impSpec (H.IThingAll _ n) = ImportSpec (identOfName n) ThingAll - impSpec (H.IThingWith _ n ns) = ImportSpec (identOfName n) $ ThingWith $ map identOfName (concatMap childrenBi ns :: [H.Name H.SrcSpanInfo]) - --- | Decl declarations -getDecls :: [H.Decl H.SrcSpanInfo] -> [Declaration] -getDecls decls = - map mergeDecls . - groupBy ((==) `on` view declarationName) . - sortBy (comparing (view declarationName)) $ - concatMap getDecl decls ++ concatMap getDef decls - where - mergeDecls :: [Declaration] -> Declaration - mergeDecls [] = error "Impossible" - mergeDecls ds = Declaration - (view declarationName $ head ds) - Nothing - Nothing - (msum $ map (view declarationDocs) ds) - (minimum <$> mapM (view declarationPosition) ds) - (foldr1 mergeInfos $ map (view declaration) ds) - - mergeInfos :: DeclarationInfo -> DeclarationInfo -> DeclarationInfo - mergeInfos (Function ln ld lr) (Function rn rd rr) = Function (ln `mplus` rn) (ld ++ rd) (lr `mplus` rr) - mergeInfos l _ = l - --- | Get local binds -getLocalDecls :: H.Decl H.SrcSpanInfo -> [Declaration] -getLocalDecls decl' = concatMap getDecls' binds' where - binds' :: [H.Binds H.SrcSpanInfo] - binds' = universeBi decl' - getDecls' :: H.Binds H.SrcSpanInfo -> [Declaration] - getDecls' (H.BDecls _ decls) = getDecls decls - getDecls' _ = [] - --- | Get declaration and child declarations -getDecl :: H.Decl H.SrcSpanInfo -> [Declaration] -getDecl decl' = case decl' of - H.TypeSig loc names typeSignature -> [mkFun loc n (Function (Just $ oneLinePrint typeSignature) [] Nothing) | n <- names] - H.TypeDecl loc h _ -> [mkType loc (tyName h) Type (tyArgs h) `withDef` decl'] - H.DataDecl loc dataOrNew mctx h cons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getConDecl (tyName h) (tyArgs h)) cons - H.GDataDecl loc dataOrNew mctx h _ gcons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getGConDecl) gcons - H.ClassDecl loc ctx h _ _ -> [mkType loc (tyName h) (Class `withCtx` ctx) (tyArgs h) `withDef` decl'] - _ -> [] - where - mkType :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> (TypeInfo -> DeclarationInfo) -> [H.TyVarBind H.SrcSpanInfo] -> Declaration - mkType loc n ctor' args = setPosition loc $ decl (identOfName n) $ ctor' $ TypeInfo Nothing (map oneLinePrint args) Nothing [] - - withDef :: H.Pretty a => Declaration -> a -> Declaration - withDef tyDecl' tyDef = set (declaration . typeInfo . typeInfoDefinition) (Just $ prettyPrint tyDef) tyDecl' - - withCtx :: (TypeInfo -> DeclarationInfo) -> Maybe (H.Context H.SrcSpanInfo) -> TypeInfo -> DeclarationInfo - withCtx ctor' mctx = ctor' . set typeInfoContext (fmap makeCtx mctx) - - ctor :: H.DataOrNew H.SrcSpanInfo -> TypeInfo -> DeclarationInfo - ctor (H.DataType _) = Data - ctor (H.NewType _) = NewType - - makeCtx ctx = fromString $ oneLinePrint ctx - - addRel :: H.Name H.SrcSpanInfo -> Declaration -> Declaration - addRel n = set (declaration . related) (Just $ identOfName n) - - tyName :: H.DeclHead H.SrcSpanInfo -> H.Name H.SrcSpanInfo - tyName (H.DHead _ n) = n - tyName (H.DHInfix _ _ n) = n - tyName (H.DHParen _ h) = tyName h - tyName (H.DHApp _ h _) = tyName h - - tyArgs :: H.DeclHead H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo] - tyArgs = universeBi - --- | Get constructor and record fields declarations -getConDecl :: H.Name H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo] -> H.QualConDecl H.SrcSpanInfo -> [Declaration] -getConDecl t as (H.QualConDecl loc _ _ cdecl) = case cdecl of - H.ConDecl _ n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ cts `tyFun` dataRes) [] Nothing)] - H.InfixConDecl _ ct n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ (ct : [cts]) `tyFun` dataRes) [] Nothing)] - H.RecDecl _ n fields -> mkFun loc n (Function (Just $ oneLinePrint $ map tyField fields `tyFun` dataRes) [] Nothing) : concatMap (getRec loc dataRes) fields - where - dataRes :: H.Type H.SrcSpanInfo - dataRes = foldr (H.TyApp loc . H.TyVar loc . nameOf) (H.TyCon loc (H.UnQual loc t)) as where - nameOf :: H.TyVarBind H.SrcSpanInfo -> H.Name H.SrcSpanInfo - nameOf (H.KindedVar _ n' _) = n' - nameOf (H.UnkindedVar _ n') = n' - -tyField :: H.FieldDecl H.SrcSpanInfo -> H.Type H.SrcSpanInfo -tyField (H.FieldDecl _ _ t) = t - --- | Get GADT constructor and record fields declarations -getGConDecl :: H.GadtDecl H.SrcSpanInfo -> [Declaration] -getGConDecl (H.GadtDecl loc n mfields r) = mkFun loc n (Function (Just $ oneLinePrint $ map tyField (fromMaybe [] mfields) `tyFun` r) [] Nothing) : concatMap (getRec loc r) (fromMaybe [] mfields) - --- | Get record field declaration -getRec :: H.SrcSpanInfo -> H.Type H.SrcSpanInfo -> H.FieldDecl H.SrcSpanInfo -> [Declaration] -getRec loc t (H.FieldDecl _ ns rt) = [mkFun loc n (Function (Just $ oneLinePrint $ H.TyFun loc t rt) [] Nothing) | n <- ns] - --- | Get definitions -getDef :: H.Decl H.SrcSpanInfo -> [Declaration] -getDef (H.FunBind _ []) = [] -getDef d@(H.FunBind _ (m : _)) = [setPosition (H.ann m) $ decl (identOfName $ identOfMatch m) fun] where - identOfMatch (H.Match _ n _ _ _) = n - identOfMatch (H.InfixMatch _ _ n _ _ _) = n - fun = Function Nothing (getLocalDecls d) Nothing -getDef d@(H.PatBind loc pat _ _) = map (\name -> setPosition loc (decl (identOfName name) (Function Nothing (getLocalDecls d) Nothing))) (names pat) where - names :: H.Pat H.SrcSpanInfo -> [H.Name H.SrcSpanInfo] - names (H.PVar _ n) = [n] - names (H.PNPlusK _ n _) = [n] - names (H.PInfixApp _ l _ r) = names l ++ names r - names (H.PApp _ _ ns) = concatMap names ns - names (H.PTuple _ _ ns) = concatMap names ns - names (H.PList _ ns) = concatMap names ns - names (H.PParen _ n) = names n - names (H.PRec _ _ pf) = concatMap fieldNames pf - names (H.PAsPat _ n ns) = n : names ns - names (H.PWildCard _) = [] - names (H.PIrrPat _ n) = names n - names (H.PatTypeSig _ n _) = names n - names (H.PViewPat _ _ n) = names n - names (H.PBangPat _ n) = names n - names _ = [] - - fieldNames :: H.PatField H.SrcSpanInfo -> [H.Name H.SrcSpanInfo] - fieldNames (H.PFieldPat _ _ n) = names n - fieldNames (H.PFieldPun _ n) = case n of - H.Qual _ _ n' -> [n'] - H.UnQual _ n' -> [n'] - _ -> [] - fieldNames (H.PFieldWildcard _) = [] -getDef _ = [] - --- | Make function declaration by location, name and function type -mkFun :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> DeclarationInfo -> Declaration -mkFun loc n = setPosition loc . decl (identOfName n) - --- | Make function from arguments and result --- --- @[a, b, c...] `tyFun` r == a `TyFun` b `TyFun` c ... `TyFun` r@ -tyFun :: [H.Type H.SrcSpanInfo] -> H.Type H.SrcSpanInfo -> H.Type H.SrcSpanInfo -tyFun as' r' = foldr (H.TyFun H.noSrcSpan) r' as' - --- | Get name of qualified name -identOfQName :: H.QName H.SrcSpanInfo -> (Maybe Text, Text) -identOfQName (H.Qual _ (H.ModuleName _ mname) name) = (Just $ fromString mname, identOfName name) -identOfQName (H.UnQual _ name) = (Nothing, identOfName name) -identOfQName (H.Special _ sname) = (Nothing, fromString $ H.prettyPrint sname) - --- | Get name of @H.Name@ -identOfName :: H.Name H.SrcSpanInfo -> Text -identOfName name = fromString $ case name of - H.Ident _ s -> s - H.Symbol _ s -> s - --- | Print something in one line -oneLinePrint :: (H.Pretty a, IsString s) => a -> s -oneLinePrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.OneLineMode }) H.defaultMode - --- | Print something -prettyPrint :: (H.Pretty a, IsString s) => a -> s -prettyPrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.PageMode }) mode' where - mode' = H.PPHsMode { - H.classIndent = 4, - H.doIndent = 4, - H.multiIfIndent = 4, - H.caseIndent = 4, - H.letIndent = 4, - H.whereIndent = 4, - H.onsideIndent = 2, - H.spacing = False, - H.layout = H.PPOffsideRule, - H.linePragmas = False } - --- | Convert @H.SrcSpanInfo@ to @Position -toPosition :: H.SrcSpanInfo -> Position -toPosition s = Position (H.startLine s) (H.startColumn s) - --- | Set @Declaration@ position -setPosition :: H.SrcSpanInfo -> Declaration -> Declaration -setPosition loc = set declarationPosition (Just $ toPosition loc) - --- | Adds documentation to declaration -addDoc :: Map String String -> Declaration -> Declaration -addDoc docsMap decl' = set declarationDocs (preview (ix (view declarationName decl')) docsMap') decl' where - docsMap' = M.mapKeys fromString . M.map fromString $ docsMap - --- | Adds documentation to all declarations in module -addDocs :: Map String String -> Module -> Module -addDocs docsMap = over moduleDeclarations (map $ addDoc docsMap) - --- | Extract files docs and set them to declarations -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 -> IO Module -inspectDocs opts m = do - let - hdocsWorkaround = False - docsMap <- 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 -> Ghc Module -inspectDocsGhc opts m = case view moduleLocation m of - FileModule fpath _ -> do - docsMap <- liftM (fmap (formatDocs . snd) . listToMaybe) $ hsdevLift $ readSourcesGhc opts [fpath] - return $ maybe id addDocs docsMap m - _ -> hsdevError $ ModuleNotSource (view moduleLocation m) - --- | Inspect contents -inspectContents :: String -> [(String, String)] -> [String] -> String -> ExceptT String IO InspectedModule -inspectContents name defines opts cts = inspect (ModuleSource $ Just name) (contentsInspection cts opts) $ do - cts' <- lift $ preprocess_ defines exts name cts - analyzed <- ExceptT $ return $ analyzeModule exts (Just name) cts' <|> analyzeModule_ exts (Just name) cts' - return $ set moduleLocation (ModuleSource $ Just name) analyzed - where - exts = mapMaybe flagExtension opts - -contentsInspection :: String -> [String] -> ExceptT String IO Inspection -contentsInspection _ _ = return InspectionNone -- crc or smth - --- | Inspect file -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 <- 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 - -- return $ setLoc absFilename proj . maybe id addDocs docsMap $ forced - return $ set moduleLocation (FileModule absFilename proj) forced - where - onError :: E.ErrorCall -> IO (Either String Module) - onError = return . Left . show - - exts = mapMaybe flagExtension opts - --- | File inspection data -fileInspection :: FilePath -> [String] -> IO Inspection -fileInspection f opts = do - tm <- Dir.getModificationTime f - return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ sort $ ordNub opts - --- | File contents inspection data -fileContentsInspection :: FilePath -> [String] -> IO Inspection -fileContentsInspection _ opts = do - tm <- getPOSIXTime - return $ InspectionAt tm $ sort $ ordNub opts - --- | Enumerate project dirs -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 -> 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 (enumCabals) dirs' - let - enumHs = liftM (filter thisProjectSource) . traverseDirectory - thisProjectSource h = haskellSource h && not (any (`isParent` h) subProjs) - liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (enumHs)) dirs - --- | Inspect project -inspectProject :: [(String, String)] -> [String] -> Project -> IO (Project, [InspectedModule]) -inspectProject defines opts p = hsdevLiftIO $ do - p' <- loadProject p - srcs <- projectSources p' - modules <- mapM inspectFile' srcs - return (p', catMaybes modules) - where - inspectFile' exts = liftM return (inspectFile defines (opts ++ extensionsOpts exts) (view entity exts) Nothing) <|> return Nothing - --- | Get actual defines -getDefines :: IO [(String, String)] -getDefines = E.handle onIO $ do - tmp <- Dir.getTemporaryDirectory - writeFile (tmp </> "defines.hs") "" - _ <- runWait "ghc" ["-E", "-optP-dM", "-cpp", tmp </> "defines.hs"] "" - cts <- readFileUtf8 (tmp </> "defines.hspp") - Dir.removeFile (tmp </> "defines.hs") - Dir.removeFile (tmp </> "defines.hspp") - return $ mapMaybe (\g -> (,) <$> g 1 <*> g 2) $ mapMaybe (matchRx rx) $ lines cts - where - rx = "#define ([^\\s]+) (.*)" - onIO :: E.IOException -> IO [(String, String)] - onIO _ = return [] - -preprocess :: [(String, String)] -> FilePath -> String -> IO String -preprocess defines fpath cts = do - 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 = preprocess defines fpath cts - | otherwise = return cts - where - exts' = map H.parseExtension exts ++ maybe [] snd (H.readExtensions cts) - hasCPP = H.EnableExtension H.CPP `elem` exts' +{-# LANGUAGE TypeSynonymInstances #-}++module HsDev.Inspect (+ analyzeModule, inspectDocsChunk, inspectDocs, inspectDocsGhc,+ inspectContents, contentsInspection,+ inspectFile, fileInspection,+ projectDirs, projectSources,+ inspectProject,+ getDefines,+ preprocess, preprocess_,++ module Control.Monad.Except+ ) where++import Control.Arrow+import Control.Applicative+import Control.DeepSeq+import qualified Control.Exception as E+import Control.Lens (view, preview, set, over)+import Control.Lens.At (ix)+import Control.Monad+import Control.Monad.Except+import Data.Char (isSpace)+import Data.Function (on)+import Data.List+import Data.Map (Map)+import Data.Maybe (fromMaybe, mapMaybe, catMaybes, listToMaybe, isJust)+import Data.Ord (comparing)+import Data.String (IsString, fromString)+import Data.Text (Text)+import qualified Data.Text as T (unpack)+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, getPOSIXTime)+import qualified Data.Map as M+import qualified Language.Haskell.Exts as H+import qualified Language.Preprocessor.Cpphs as Cpphs+import qualified System.Directory as Dir+import System.FilePath+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++-- | Analize source contents+analyzeModule :: [String] -> Maybe FilePath -> String -> Either String Module+analyzeModule exts file source = case H.parseFileContentsWithMode (parseMode file exts) source' of+ H.ParseFailed loc reason -> Left $ "Parse failed at " ++ show loc ++ ": " ++ reason+ H.ParseOk (H.Module _ (Just (H.ModuleHead _ (H.ModuleName _ mname) _ mexports)) _ imports declarations) -> Right Module {+ _moduleName = fromString mname,+ _moduleDocs = Nothing,+ _moduleLocation = ModuleSource Nothing,+ _moduleExports = fmap (concatMap getExports . getSpec) mexports,+ _moduleImports = map getImport imports,+ _moduleDeclarations = sortDeclarations $ getDecls declarations }+ _ -> Left "Unknown module"+ where+ -- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces+ source' = map untab source+ untab '\t' = ' '+ untab ch = ch+ getSpec (H.ExportSpecList _ es) = es++-- | Analize source contents+analyzeModule_ :: [String] -> Maybe FilePath -> String -> Either String Module+analyzeModule_ exts file source = do+ mname <- parseModuleName source'+ return Module {+ _moduleName = fromString mname,+ _moduleDocs = Nothing,+ _moduleLocation = ModuleSource Nothing,+ _moduleExports = do+ H.PragmasAndModuleHead _ _ mhead <- parseModuleHead' source'+ H.ModuleHead _ _ _ mexports <- mhead+ H.ExportSpecList _ exports <- mexports+ return $ concatMap getExports exports,+ _moduleImports = map getImport $ mapMaybe (uncurry parseImport') parts,+ _moduleDeclarations = sortDeclarations $ getDecls $ mapMaybe (uncurry parseDecl') parts }+ where+ parts :: [(Int, String)]+ parts = zip offsets (map unlines parts') where+ parts' :: [[String]]+ parts' = unfoldr break' $ lines source'+ offsets = scanl (+) 0 $ map length parts'+ break' :: [String] -> Maybe ([String], [String])+ break' [] = Nothing+ break' (l:ls) = Just $ first (l:) $ span (maybe True isSpace . listToMaybe) ls++ parseModuleName :: String -> Either String String+ parseModuleName cts = maybe (Left "match fail") Right $ do+ g <- matchRx "^module\\s+([\\w\\.]+)" cts+ g 1++ parseDecl' :: Int -> String -> Maybe (H.Decl H.SrcSpanInfo)+ parseDecl' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $+ H.parseDeclWithMode (parseMode file exts) cts++ parseImport' :: Int -> String -> Maybe (H.ImportDecl H.SrcSpanInfo)+ parseImport' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $+ H.parseImportDeclWithMode (parseMode file exts) cts++ parseModuleHead' :: String -> Maybe (H.PragmasAndModuleHead H.SrcSpanInfo)+ parseModuleHead' = maybeResult . fmap H.unNonGreedy . H.parseWithMode (parseMode file exts)++ maybeResult :: H.ParseResult a -> Maybe a+ maybeResult (H.ParseFailed _ _) = Nothing+ maybeResult (H.ParseOk r) = Just r++ addOffset :: Int -> H.SrcSpan -> H.SrcSpan+ addOffset offset src = src { H.srcSpanStartLine = H.srcSpanStartLine src + offset, H.srcSpanEndLine = H.srcSpanEndLine src + offset }++ -- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces+ source' = map untab source+ untab '\t' = ' '+ untab ch = ch++parseMode :: Maybe FilePath -> [String] -> H.ParseMode+parseMode file exts = H.defaultParseMode {+ H.parseFilename = fromMaybe (H.parseFilename H.defaultParseMode) file,+ H.baseLanguage = H.Haskell2010,+ H.extensions = H.glasgowExts ++ map H.parseExtension exts,+ H.fixities = Just H.baseFixities }++-- | Get exports+getExports :: H.ExportSpec H.SrcSpanInfo -> [Export]+getExports (H.EModuleContents _ (H.ModuleName _ m)) = [ExportModule $ fromString m]+getExports (H.EVar _ n) = [uncurry ExportName (identOfQName n) ThingNothing]+getExports (H.EAbs _ _ n) = [uncurry ExportName (identOfQName n) ThingNothing]+getExports (H.EThingWith _ _ n ns) = [uncurry ExportName (identOfQName n) $ ThingWith (map toStr ns)] where+ toStr :: H.CName H.SrcSpanInfo -> Text+ toStr (H.VarName _ cn) = identOfName cn+ toStr (H.ConName _ cn) = identOfName cn++-- | Get import+getImport :: H.ImportDecl H.SrcSpanInfo -> Import+getImport d = Import+ (mname (H.importModule d))+ (H.importQualified d)+ (mname <$> H.importAs d)+ (importLst <$> H.importSpecs d)+ (Just $ toPosition $ H.ann d)+ where+ mname (H.ModuleName _ n) = fromString n+ importLst (H.ImportSpecList _ hiding specs) = ImportList hiding $ map impSpec specs+ impSpec (H.IVar _ n) = ImportSpec (identOfName n) ThingNothing+ impSpec (H.IAbs _ _ n) = ImportSpec (identOfName n) ThingNothing+ impSpec (H.IThingAll _ n) = ImportSpec (identOfName n) ThingAll+ impSpec (H.IThingWith _ n ns) = ImportSpec (identOfName n) $ ThingWith $ map identOfName (concatMap childrenBi ns :: [H.Name H.SrcSpanInfo])++-- | Decl declarations+getDecls :: [H.Decl H.SrcSpanInfo] -> [Declaration]+getDecls decls =+ map mergeDecls .+ groupBy ((==) `on` view declarationName) .+ sortBy (comparing (view declarationName)) $+ concatMap getDecl decls ++ concatMap getDef decls+ where+ mergeDecls :: [Declaration] -> Declaration+ mergeDecls [] = error "Impossible"+ mergeDecls ds = Declaration+ (view declarationName $ head ds)+ Nothing+ Nothing+ (msum $ map (view declarationDocs) ds)+ (minimum <$> mapM (view declarationPosition) ds)+ (foldr1 mergeInfos $ map (view declaration) ds)++ mergeInfos :: DeclarationInfo -> DeclarationInfo -> DeclarationInfo+ mergeInfos (Function ln ld lr) (Function rn rd rr) = Function (ln `mplus` rn) (ld ++ rd) (lr `mplus` rr)+ mergeInfos l _ = l++-- | Get local binds+getLocalDecls :: H.Decl H.SrcSpanInfo -> [Declaration]+getLocalDecls decl' = concatMap getDecls' binds' where+ binds' :: [H.Binds H.SrcSpanInfo]+ binds' = universeBi decl'+ getDecls' :: H.Binds H.SrcSpanInfo -> [Declaration]+ getDecls' (H.BDecls _ decls) = getDecls decls+ getDecls' _ = []++-- | Get declaration and child declarations+getDecl :: H.Decl H.SrcSpanInfo -> [Declaration]+getDecl decl' = case decl' of+ H.TypeSig loc names typeSignature -> [mkFun loc n (Function (Just $ oneLinePrint typeSignature) [] Nothing) | n <- names]+ H.TypeDecl loc h _ -> [mkType loc (tyName h) Type (tyArgs h) `withDef` decl']+ H.DataDecl loc dataOrNew mctx h cons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getConDecl (tyName h) (tyArgs h)) cons+ H.GDataDecl loc dataOrNew mctx h _ gcons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getGConDecl) gcons+ H.ClassDecl loc ctx h _ _ -> [mkType loc (tyName h) (Class `withCtx` ctx) (tyArgs h) `withDef` decl']+ _ -> []+ where+ mkType :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> (TypeInfo -> DeclarationInfo) -> [H.TyVarBind H.SrcSpanInfo] -> Declaration+ mkType loc n ctor' args = setPosition loc $ decl (identOfName n) $ ctor' $ TypeInfo Nothing (map oneLinePrint args) Nothing []++ withDef :: H.Pretty a => Declaration -> a -> Declaration+ withDef tyDecl' tyDef = set (declaration . typeInfo . typeInfoDefinition) (Just $ prettyPrint tyDef) tyDecl'++ withCtx :: (TypeInfo -> DeclarationInfo) -> Maybe (H.Context H.SrcSpanInfo) -> TypeInfo -> DeclarationInfo+ withCtx ctor' mctx = ctor' . set typeInfoContext (fmap makeCtx mctx)++ ctor :: H.DataOrNew H.SrcSpanInfo -> TypeInfo -> DeclarationInfo+ ctor (H.DataType _) = Data+ ctor (H.NewType _) = NewType++ makeCtx ctx = fromString $ oneLinePrint ctx++ addRel :: H.Name H.SrcSpanInfo -> Declaration -> Declaration+ addRel n = set (declaration . related) (Just $ identOfName n)++ tyName :: H.DeclHead H.SrcSpanInfo -> H.Name H.SrcSpanInfo+ tyName (H.DHead _ n) = n+ tyName (H.DHInfix _ _ n) = n+ tyName (H.DHParen _ h) = tyName h+ tyName (H.DHApp _ h _) = tyName h++ tyArgs :: H.DeclHead H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo]+ tyArgs = universeBi++-- | Get constructor and record fields declarations+getConDecl :: H.Name H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo] -> H.QualConDecl H.SrcSpanInfo -> [Declaration]+getConDecl t as (H.QualConDecl loc _ _ cdecl) = case cdecl of+ H.ConDecl _ n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ cts `tyFun` dataRes) [] Nothing)]+ H.InfixConDecl _ ct n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ (ct : [cts]) `tyFun` dataRes) [] Nothing)]+ H.RecDecl _ n fields -> mkFun loc n (Function (Just $ oneLinePrint $ map tyField fields `tyFun` dataRes) [] Nothing) : concatMap (getRec loc dataRes) fields+ where+ dataRes :: H.Type H.SrcSpanInfo+ dataRes = foldr (H.TyApp loc . H.TyVar loc . nameOf) (H.TyCon loc (H.UnQual loc t)) as where+ nameOf :: H.TyVarBind H.SrcSpanInfo -> H.Name H.SrcSpanInfo+ nameOf (H.KindedVar _ n' _) = n'+ nameOf (H.UnkindedVar _ n') = n'++tyField :: H.FieldDecl H.SrcSpanInfo -> H.Type H.SrcSpanInfo+tyField (H.FieldDecl _ _ t) = t++-- | Get GADT constructor and record fields declarations+getGConDecl :: H.GadtDecl H.SrcSpanInfo -> [Declaration]+getGConDecl (H.GadtDecl loc n mfields r) = mkFun loc n (Function (Just $ oneLinePrint $ map tyField (fromMaybe [] mfields) `tyFun` r) [] Nothing) : concatMap (getRec loc r) (fromMaybe [] mfields)++-- | Get record field declaration+getRec :: H.SrcSpanInfo -> H.Type H.SrcSpanInfo -> H.FieldDecl H.SrcSpanInfo -> [Declaration]+getRec loc t (H.FieldDecl _ ns rt) = [mkFun loc n (Function (Just $ oneLinePrint $ H.TyFun loc t rt) [] Nothing) | n <- ns]++-- | Get definitions+getDef :: H.Decl H.SrcSpanInfo -> [Declaration]+getDef (H.FunBind _ []) = []+getDef d@(H.FunBind _ (m : _)) = [setPosition (H.ann m) $ decl (identOfName $ identOfMatch m) fun] where+ identOfMatch (H.Match _ n _ _ _) = n+ identOfMatch (H.InfixMatch _ _ n _ _ _) = n+ fun = Function Nothing (getLocalDecls d) Nothing+getDef d@(H.PatBind loc pat _ _) = map (\name -> setPosition loc (decl (identOfName name) (Function Nothing (getLocalDecls d) Nothing))) (names pat) where+ names :: H.Pat H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]+ names (H.PVar _ n) = [n]+ names (H.PNPlusK _ n _) = [n]+ names (H.PInfixApp _ l _ r) = names l ++ names r+ names (H.PApp _ _ ns) = concatMap names ns+ names (H.PTuple _ _ ns) = concatMap names ns+ names (H.PList _ ns) = concatMap names ns+ names (H.PParen _ n) = names n+ names (H.PRec _ _ pf) = concatMap fieldNames pf+ names (H.PAsPat _ n ns) = n : names ns+ names (H.PWildCard _) = []+ names (H.PIrrPat _ n) = names n+ names (H.PatTypeSig _ n _) = names n+ names (H.PViewPat _ _ n) = names n+ names (H.PBangPat _ n) = names n+ names _ = []++ fieldNames :: H.PatField H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]+ fieldNames (H.PFieldPat _ _ n) = names n+ fieldNames (H.PFieldPun _ n) = case n of+ H.Qual _ _ n' -> [n']+ H.UnQual _ n' -> [n']+ _ -> []+ fieldNames (H.PFieldWildcard _) = []+getDef _ = []++-- | Make function declaration by location, name and function type+mkFun :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> DeclarationInfo -> Declaration+mkFun loc n = setPosition loc . decl (identOfName n)++-- | Make function from arguments and result+--+-- @[a, b, c...] `tyFun` r == a `TyFun` b `TyFun` c ... `TyFun` r@+tyFun :: [H.Type H.SrcSpanInfo] -> H.Type H.SrcSpanInfo -> H.Type H.SrcSpanInfo+tyFun as' r' = foldr (H.TyFun H.noSrcSpan) r' as'++-- | Get name of qualified name+identOfQName :: H.QName H.SrcSpanInfo -> (Maybe Text, Text)+identOfQName (H.Qual _ (H.ModuleName _ mname) name) = (Just $ fromString mname, identOfName name)+identOfQName (H.UnQual _ name) = (Nothing, identOfName name)+identOfQName (H.Special _ sname) = (Nothing, fromString $ H.prettyPrint sname)++-- | Get name of @H.Name@+identOfName :: H.Name H.SrcSpanInfo -> Text+identOfName name = fromString $ case name of+ H.Ident _ s -> s+ H.Symbol _ s -> s++-- | Print something in one line+oneLinePrint :: (H.Pretty a, IsString s) => a -> s+oneLinePrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.OneLineMode }) H.defaultMode++-- | Print something+prettyPrint :: (H.Pretty a, IsString s) => a -> s+prettyPrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.PageMode }) mode' where+ mode' = H.PPHsMode {+ H.classIndent = 4,+ H.doIndent = 4,+ H.multiIfIndent = 4,+ H.caseIndent = 4,+ H.letIndent = 4,+ H.whereIndent = 4,+ H.onsideIndent = 2,+ H.spacing = False,+ H.layout = H.PPOffsideRule,+ H.linePragmas = False }++-- | Convert @H.SrcSpanInfo@ to @Position+toPosition :: H.SrcSpanInfo -> Position+toPosition s = Position (H.startLine s) (H.startColumn s)++-- | Set @Declaration@ position+setPosition :: H.SrcSpanInfo -> Declaration -> Declaration+setPosition loc = set declarationPosition (Just $ toPosition loc)++-- | Adds documentation to declaration+addDoc :: Map String String -> Declaration -> Declaration+addDoc docsMap decl' = set declarationDocs (preview (ix (view declarationName decl')) docsMap') decl' where+ docsMap' = M.mapKeys fromString . M.map fromString $ docsMap++-- | Adds documentation to all declarations in module+addDocs :: Map String String -> Module -> Module+addDocs docsMap = over moduleDeclarations (map $ addDoc docsMap)++-- | Extract files docs and set them to declarations+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 -> IO Module+inspectDocs opts m = do+ let+ hdocsWorkaround = False+ docsMap <- 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 -> Ghc Module+inspectDocsGhc opts m = case view moduleLocation m of+ FileModule fpath _ -> do+ docsMap <- liftM (fmap (formatDocs . snd) . listToMaybe) $ hsdevLift $ readSourcesGhc opts [fpath]+ return $ maybe id addDocs docsMap m+ _ -> hsdevError $ ModuleNotSource (view moduleLocation m)++-- | Inspect contents+inspectContents :: String -> [(String, String)] -> [String] -> String -> ExceptT String IO InspectedModule+inspectContents name defines opts cts = inspect (ModuleSource $ Just name) (contentsInspection cts opts) $ do+ cts' <- lift $ preprocess_ defines exts name cts+ analyzed <- ExceptT $ return $ analyzeModule exts (Just name) cts' <|> analyzeModule_ exts (Just name) cts'+ return $ set moduleLocation (ModuleSource $ Just name) analyzed+ where+ exts = mapMaybe flagExtension opts++contentsInspection :: String -> [String] -> ExceptT String IO Inspection+contentsInspection _ _ = return InspectionNone -- crc or smth++-- | Inspect file+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 <- 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+ -- return $ setLoc absFilename proj . maybe id addDocs docsMap $ forced+ return $ set moduleLocation (FileModule absFilename proj) forced+ where+ onError :: E.ErrorCall -> IO (Either String Module)+ onError = return . Left . show++ exts = mapMaybe flagExtension opts++-- | File inspection data+fileInspection :: FilePath -> [String] -> IO Inspection+fileInspection f opts = do+ tm <- Dir.getModificationTime f+ return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ sort $ ordNub opts++-- | File contents inspection data+fileContentsInspection :: FilePath -> [String] -> IO Inspection+fileContentsInspection _ opts = do+ tm <- getPOSIXTime+ return $ InspectionAt tm $ sort $ ordNub opts++-- | Enumerate project dirs+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 -> 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 (enumCabals) dirs'+ let+ enumHs = liftM (filter thisProjectSource) . traverseDirectory+ thisProjectSource h = haskellSource h && not (any (`isParent` h) subProjs)+ liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (enumHs)) dirs++-- | Inspect project+inspectProject :: [(String, String)] -> [String] -> Project -> IO (Project, [InspectedModule])+inspectProject defines opts p = hsdevLiftIO $ do+ p' <- loadProject p+ srcs <- projectSources p'+ modules <- mapM inspectFile' srcs+ return (p', catMaybes modules)+ where+ inspectFile' exts = liftM return (inspectFile defines (opts ++ extensionsOpts exts) (view entity exts) Nothing) <|> return Nothing++-- | Get actual defines+getDefines :: IO [(String, String)]+getDefines = E.handle onIO $ do+ tmp <- Dir.getTemporaryDirectory+ writeFile (tmp </> "defines.hs") ""+ _ <- runWait "ghc" ["-E", "-optP-dM", "-cpp", tmp </> "defines.hs"] ""+ cts <- readFileUtf8 (tmp </> "defines.hspp")+ Dir.removeFile (tmp </> "defines.hs")+ Dir.removeFile (tmp </> "defines.hspp")+ return $ mapMaybe (\g -> (,) <$> g 1 <*> g 2) $ mapMaybe (matchRx rx) $ lines cts+ where+ rx = "#define ([^\\s]+) (.*)"+ onIO :: E.IOException -> IO [(String, String)]+ onIO _ = return []++preprocess :: [(String, String)] -> FilePath -> String -> IO String+preprocess defines fpath cts = do+ 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 = preprocess defines fpath cts+ | otherwise = return cts+ where+ exts' = map H.parseExtension exts ++ maybe [] snd (H.readExtensions cts)+ hasCPP = H.EnableExtension H.CPP `elem` exts'
src/HsDev/PackageDb.hs view
@@ -1,106 +1,106 @@-{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} - -module HsDev.PackageDb ( - PackageDb(..), packageDb, - PackageDbStack(..), packageDbStack, globalDb, userDb, fromPackageDb, fromPackageDbs, - topPackageDb, packageDbs, packageDbStacks, - isSubStack, - - packageDbOpt, packageDbStackOpts - ) where - -import Control.Applicative -import Control.Monad (guard) -import Control.Lens (makeLenses, each) -import Control.DeepSeq (NFData(..)) -import Data.Aeson -import Data.List (tails, isSuffixOf) - -import System.Directory.Paths -import HsDev.Util ((.::)) - -data PackageDb = GlobalDb | UserDb | PackageDb { _packageDb :: FilePath } deriving (Eq, Ord, Read, Show) - -makeLenses ''PackageDb - -instance NFData PackageDb where - rnf GlobalDb = () - rnf UserDb = () - rnf (PackageDb p) = rnf p - -instance ToJSON PackageDb where - toJSON GlobalDb = "global-db" - toJSON UserDb = "user-db" - toJSON (PackageDb p) = object ["package-db" .= p] - -instance FromJSON PackageDb where - parseJSON v = globalP v <|> userP v <|> dbP v where - globalP = withText "global-db" (\s -> guard (s == "global-db") >> return GlobalDb) - userP = withText "user-db" (\s -> guard (s == "user-db") >> return UserDb) - dbP = withObject "package-db" pathP where - pathP obj = PackageDb <$> obj .:: "package-db" - -instance Paths PackageDb where - paths _ GlobalDb = pure GlobalDb - paths _ UserDb = pure UserDb - paths f (PackageDb p) = PackageDb <$> f p - --- | Stack of PackageDb in reverse order -newtype PackageDbStack = PackageDbStack { _packageDbStack :: [PackageDb] } deriving (Eq, Ord, Read, Show) - -makeLenses ''PackageDbStack - -instance NFData PackageDbStack where - rnf (PackageDbStack ps) = rnf ps - -instance ToJSON PackageDbStack where - toJSON (PackageDbStack ps) = toJSON ps - -instance FromJSON PackageDbStack where - parseJSON = fmap PackageDbStack . parseJSON - -instance Paths PackageDbStack where - paths f (PackageDbStack ps) = PackageDbStack <$> (each . paths) f ps - --- | Global db stack -globalDb :: PackageDbStack -globalDb = PackageDbStack [] - --- | User db stack -userDb :: PackageDbStack -userDb = PackageDbStack [UserDb] - --- | Make package-db from one package-db -fromPackageDb :: FilePath -> PackageDbStack -fromPackageDb = PackageDbStack . return . PackageDb - --- | Make package-db stack from paths -fromPackageDbs :: [FilePath] -> PackageDbStack -fromPackageDbs = PackageDbStack . map PackageDb . reverse - --- | Get top package-db for package-db stack -topPackageDb :: PackageDbStack -> PackageDb -topPackageDb (PackageDbStack []) = GlobalDb -topPackageDb (PackageDbStack (d:_)) = d - --- | Get list of package-db in stack, adds additional global-db at bottom -packageDbs :: PackageDbStack -> [PackageDb] -packageDbs = (GlobalDb :) . reverse . _packageDbStack - --- | Get stacks for each package-db in stack -packageDbStacks :: PackageDbStack -> [PackageDbStack] -packageDbStacks = map PackageDbStack . tails . _packageDbStack - --- | Is one package-db stack substack of another -isSubStack :: PackageDbStack -> PackageDbStack -> Bool -isSubStack (PackageDbStack l) (PackageDbStack r) = l `isSuffixOf` r - --- | Get ghc options for package-db -packageDbOpt :: PackageDb -> String -packageDbOpt GlobalDb = "-global-package-db" -packageDbOpt UserDb = "-user-package-db" -packageDbOpt (PackageDb p) = "-package-db " ++ p - --- | Get ghc options for package-db stack -packageDbStackOpts :: PackageDbStack -> [String] -packageDbStackOpts (PackageDbStack ps) = "-no-user-package-db" : map packageDbOpt (reverse ps) +{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}++module HsDev.PackageDb (+ PackageDb(..), packageDb,+ PackageDbStack(..), packageDbStack, globalDb, userDb, fromPackageDb, fromPackageDbs,+ topPackageDb, packageDbs, packageDbStacks,+ isSubStack,++ packageDbOpt, packageDbStackOpts+ ) where++import Control.Applicative+import Control.Monad (guard)+import Control.Lens (makeLenses, each)+import Control.DeepSeq (NFData(..))+import Data.Aeson+import Data.List (tails, isSuffixOf)++import System.Directory.Paths+import HsDev.Util ((.::))++data PackageDb = GlobalDb | UserDb | PackageDb { _packageDb :: FilePath } deriving (Eq, Ord, Read, Show)++makeLenses ''PackageDb++instance NFData PackageDb where+ rnf GlobalDb = ()+ rnf UserDb = ()+ rnf (PackageDb p) = rnf p++instance ToJSON PackageDb where+ toJSON GlobalDb = "global-db"+ toJSON UserDb = "user-db"+ toJSON (PackageDb p) = object ["package-db" .= p]++instance FromJSON PackageDb where+ parseJSON v = globalP v <|> userP v <|> dbP v where+ globalP = withText "global-db" (\s -> guard (s == "global-db") >> return GlobalDb)+ userP = withText "user-db" (\s -> guard (s == "user-db") >> return UserDb)+ dbP = withObject "package-db" pathP where+ pathP obj = PackageDb <$> obj .:: "package-db"++instance Paths PackageDb where+ paths _ GlobalDb = pure GlobalDb+ paths _ UserDb = pure UserDb+ paths f (PackageDb p) = PackageDb <$> f p++-- | Stack of PackageDb in reverse order+newtype PackageDbStack = PackageDbStack { _packageDbStack :: [PackageDb] } deriving (Eq, Ord, Read, Show)++makeLenses ''PackageDbStack++instance NFData PackageDbStack where+ rnf (PackageDbStack ps) = rnf ps++instance ToJSON PackageDbStack where+ toJSON (PackageDbStack ps) = toJSON ps++instance FromJSON PackageDbStack where+ parseJSON = fmap PackageDbStack . parseJSON++instance Paths PackageDbStack where+ paths f (PackageDbStack ps) = PackageDbStack <$> (each . paths) f ps++-- | Global db stack+globalDb :: PackageDbStack+globalDb = PackageDbStack []++-- | User db stack+userDb :: PackageDbStack+userDb = PackageDbStack [UserDb]++-- | Make package-db from one package-db+fromPackageDb :: FilePath -> PackageDbStack+fromPackageDb = PackageDbStack . return . PackageDb++-- | Make package-db stack from paths+fromPackageDbs :: [FilePath] -> PackageDbStack+fromPackageDbs = PackageDbStack . map PackageDb . reverse++-- | Get top package-db for package-db stack+topPackageDb :: PackageDbStack -> PackageDb+topPackageDb (PackageDbStack []) = GlobalDb+topPackageDb (PackageDbStack (d:_)) = d++-- | Get list of package-db in stack, adds additional global-db at bottom+packageDbs :: PackageDbStack -> [PackageDb]+packageDbs = (GlobalDb :) . reverse . _packageDbStack++-- | Get stacks for each package-db in stack+packageDbStacks :: PackageDbStack -> [PackageDbStack]+packageDbStacks = map PackageDbStack . tails . _packageDbStack++-- | Is one package-db stack substack of another+isSubStack :: PackageDbStack -> PackageDbStack -> Bool+isSubStack (PackageDbStack l) (PackageDbStack r) = l `isSuffixOf` r++-- | Get ghc options for package-db+packageDbOpt :: PackageDb -> String+packageDbOpt GlobalDb = "-global-package-db"+packageDbOpt UserDb = "-user-package-db"+packageDbOpt (PackageDb p) = "-package-db " ++ p++-- | Get ghc options for package-db stack+packageDbStackOpts :: PackageDbStack -> [String]+packageDbStackOpts (PackageDbStack ps) = "-no-user-package-db" : map packageDbOpt (reverse ps)
src/HsDev/Project.hs view
@@ -1,164 +1,167 @@-module HsDev.Project ( - module HsDev.Project.Types, - - infoSourceDirsDef, - analyzeCabal, - readProject, loadProject, - withExtensions, - infos, inTarget, fileTarget, fileTargets, findSourceDir, sourceDirs, - targetOpts, - - -- * Helpers - showExtension, flagExtension, extensionFlag, - extensionsOpts - ) where - -import Control.Arrow -import Control.Lens (Simple, Lens, view, lens) -import Control.Monad.Except -import Data.List -import Data.Maybe -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) -import Language.Haskell.Extension -import System.FilePath - -import HsDev.Project.Types -import HsDev.Error -import HsDev.Util - --- | infoSourceDirs lens with default -infoSourceDirsDef :: Simple Lens Info [FilePath] -infoSourceDirsDef = lens get' set' where - get' i = case _infoSourceDirs i of - [] -> ["."] - dirs -> dirs - set' i ["."] = i { _infoSourceDirs = [] } - set' i dirs = i { _infoSourceDirs = dirs } - --- | Analyze cabal file -analyzeCabal :: String -> Either String ProjectDescription -analyzeCabal source = case liftM flattenDescr $ parsePackageDescription source of - ParseOk _ r -> Right ProjectDescription { - _projectVersion = showVersion $ P.pkgVersion $ PD.package r, - _projectLibrary = fmap toLibrary $ PD.library r, - _projectExecutables = fmap toExecutable $ PD.executables r, - _projectTests = fmap toTest $ PD.testSuites r } - ParseFailed e -> Left $ "Parse failed: " ++ show e - where - toLibrary (PD.Library exposeds _ _ _ _ info) = Library (map components exposeds) (toInfo info) - toExecutable (PD.Executable name path info) = Executable name path (toInfo info) - toTest (PD.TestSuite name _ info enabled) = Test name enabled (toInfo info) - toInfo info = Info { - _infoDepends = map pkgName (PD.targetBuildDepends info), - _infoLanguage = PD.defaultLanguage info, - _infoExtensions = PD.defaultExtensions info ++ PD.otherExtensions info ++ PD.oldExtensions info, - _infoGHCOptions = fromMaybe [] $ lookup GHC (PD.options info), - _infoSourceDirs = PD.hsSourceDirs info } - - pkgName :: P.Dependency -> String - pkgName (P.Dependency (P.PackageName s) _) = s - - flattenDescr :: PD.GenericPackageDescription -> PD.PackageDescription - flattenDescr (PD.GenericPackageDescription pkg _ mlib mexes mtests _) = pkg { - PD.library = flip fmap mlib $ flattenTree - (insertInfo PD.libBuildInfo (\i l -> l { PD.libBuildInfo = i })), - PD.executables = flip fmap mexes $ - second (flattenTree (insertInfo PD.buildInfo (\i l -> l { PD.buildInfo = i }))) >>> - (\(n, e) -> e { PD.exeName = n }), - PD.testSuites = flip fmap mtests $ - second (flattenTree (insertInfo PD.testBuildInfo (\i l -> l { PD.testBuildInfo = i }))) >>> - (\(n, t) -> t { PD.testName = n }) } - where - insertInfo :: (a -> PD.BuildInfo) -> (PD.BuildInfo -> a -> a) -> [P.Dependency] -> a -> a - insertInfo f s deps' x = s ((f x) { PD.targetBuildDepends = deps' }) x - - flattenTree :: Monoid a => (c -> a -> a) -> PD.CondTree v c a -> a - flattenTree f (PD.CondNode x cs cmps) = f cs x `mappend` mconcat (concatMap flattenBranch cmps) where - flattenBranch (_, t, mb) = flattenTree f t : map (flattenTree f) (maybeToList mb) - --- | Read project info from .cabal -readProject :: FilePath -> IO Project -readProject file = do - 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 -> IO Project -loadProject p - | isJust (_projectDescription p) = return p - | otherwise = readProject (_projectCabal p) - --- | Extensions for target -withExtensions :: a -> Info -> Extensions a -withExtensions x i = Extensions { - _extensions = _infoExtensions i, - _ghcOptions = _infoGHCOptions i, - _entity = x } - --- | Returns build targets infos -infos :: ProjectDescription -> [Info] -infos p = - maybe [] (return . _libraryBuildInfo) (_projectLibrary p) ++ - map _executableBuildInfo (_projectExecutables p) ++ - map _testBuildInfo (_projectTests p) - --- | Check if source related to target, source must be relative to project directory -inTarget :: FilePath -> Info -> Bool -inTarget src info = any (`isParent` src) $ view infoSourceDirsDef info - --- | Get first target for source file -fileTarget :: Project -> FilePath -> Maybe Info -fileTarget p f = listToMaybe $ fileTargets p f - --- | Get possible targets for source file --- There can be many candidates in case of module related to several executables or tests -fileTargets :: Project -> FilePath -> [Info] -fileTargets p f = case filter ((`isSuffixOf` f') . normalise . _executablePath) exes of - [] -> filter (f' `inTarget`) $ maybe [] infos $ _projectDescription p - exes' -> map _executableBuildInfo exes' - where - f' = makeRelative (_projectPath p) f - exes = maybe [] _projectExecutables $ _projectDescription p - --- | Finds source dir file belongs to -findSourceDir :: Project -> FilePath -> Maybe (Extensions FilePath) -findSourceDir p f = do - info <- listToMaybe $ fileTargets p f - fmap (`withExtensions` info) $ listToMaybe $ filter (`isParent` f) $ map (_projectPath p </>) $ view infoSourceDirsDef info - --- | Returns source dirs for library, executables and tests -sourceDirs :: ProjectDescription -> [Extensions FilePath] -sourceDirs = ordNub . concatMap dirs . infos where - dirs i = map (`withExtensions` i) $ view infoSourceDirsDef i - --- | Get options for specific target -targetOpts :: Info -> [String] -targetOpts info' = concat [ - ["-i" ++ s | s <- _infoSourceDirs info'], - extensionsOpts $ withExtensions () info', - ["-package " ++ p | p <- _infoDepends info']] - --- | Extension as flag name -showExtension :: Extension -> String -showExtension = display - --- | Convert -Xext to ext -flagExtension :: String -> Maybe String -flagExtension = stripPrefix "-X" - --- | Convert ext to -Xext -extensionFlag :: String -> String -extensionFlag = ("-X" ++) - --- | Extensions as opts to GHC -extensionsOpts :: Extensions a -> [String] -extensionsOpts e = map (extensionFlag . showExtension) (_extensions e) ++ _ghcOptions e +{-# LANGUAGE CPP #-}++module HsDev.Project (+ module HsDev.Project.Types,++ infoSourceDirsDef,+ analyzeCabal,+ readProject, loadProject,+ withExtensions,+ infos, inTarget, fileTarget, fileTargets, findSourceDir, sourceDirs,+ targetOpts,++ -- * Helpers+ showExtension, flagExtension, extensionFlag,+ extensionsOpts+ ) where++import Control.Arrow+import Control.Lens (Simple, Lens, view, lens)+import Control.Monad.Except+import Data.List+import Data.Maybe+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)+import Language.Haskell.Extension+import System.FilePath++import HsDev.Project.Compat+import HsDev.Project.Types+import HsDev.Error+import HsDev.Util++-- | infoSourceDirs lens with default+infoSourceDirsDef :: Simple Lens Info [FilePath]+infoSourceDirsDef = lens get' set' where+ get' i = case _infoSourceDirs i of+ [] -> ["."]+ dirs -> dirs+ set' i ["."] = i { _infoSourceDirs = [] }+ set' i dirs = i { _infoSourceDirs = dirs }++-- | Analyze cabal file+analyzeCabal :: String -> Either String ProjectDescription+analyzeCabal source = case liftM flattenDescr $ parsePackageDesc source of+ ParseOk _ r -> Right ProjectDescription {+ _projectVersion = showVer $ P.pkgVersion $ PD.package r,+ _projectLibrary = fmap toLibrary $ PD.library r,+ _projectExecutables = fmap toExecutable $ PD.executables r,+ _projectTests = fmap toTest $ PD.testSuites r }+ ParseFailed e -> Left $ "Parse failed: " ++ show e+ where+ toLibrary lib = Library (map components $ PD.exposedModules lib) (toInfo $ PD.libBuildInfo lib)+ toExecutable exe = Executable (componentName $ PD.exeName exe) (PD.modulePath exe) (toInfo $ PD.buildInfo exe)+ toTest test = Test (componentName $ PD.testName test) (testSuiteEnabled test) (toInfo $ PD.testBuildInfo test)+ toInfo info = Info {+ _infoDepends = map pkgName (PD.targetBuildDepends info),+ _infoLanguage = PD.defaultLanguage info,+ _infoExtensions = PD.defaultExtensions info ++ PD.otherExtensions info ++ PD.oldExtensions info,+ _infoGHCOptions = fromMaybe [] $ lookup GHC (PD.options info),+ _infoSourceDirs = PD.hsSourceDirs info }++ pkgName :: P.Dependency -> String+ pkgName (P.Dependency dep _) = P.unPackageName dep++ flattenDescr :: PD.GenericPackageDescription -> PD.PackageDescription+ flattenDescr gpkg = pkg {+ PD.library = flip fmap mlib $ flattenCondTree+ (insertInfo PD.libBuildInfo (\i l -> l { PD.libBuildInfo = i })),+ PD.executables = flip fmap mexes $+ second (flattenCondTree (insertInfo PD.buildInfo (\i l -> l { PD.buildInfo = i }))) >>>+ (\(n, e) -> e { PD.exeName = n }),+ PD.testSuites = flip fmap mtests $+ second (flattenCondTree (insertInfo PD.testBuildInfo (\i l -> l { PD.testBuildInfo = i }))) >>>+ (\(n, t) -> t { PD.testName = n }) }+ where+ pkg = PD.packageDescription gpkg+ mlib = PD.condLibrary gpkg+ mexes = PD.condExecutables gpkg+ mtests = PD.condTestSuites gpkg++ insertInfo :: (a -> PD.BuildInfo) -> (PD.BuildInfo -> a -> a) -> [P.Dependency] -> a -> a+ insertInfo f s deps' x = s ((f x) { PD.targetBuildDepends = deps' }) x++-- | Read project info from .cabal+readProject :: FilePath -> IO Project+readProject file = do+ 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 -> IO Project+loadProject p+ | isJust (_projectDescription p) = return p+ | otherwise = readProject (_projectCabal p)++-- | Extensions for target+withExtensions :: a -> Info -> Extensions a+withExtensions x i = Extensions {+ _extensions = _infoExtensions i,+ _ghcOptions = _infoGHCOptions i,+ _entity = x }++-- | Returns build targets infos+infos :: ProjectDescription -> [Info]+infos p =+ maybe [] (return . _libraryBuildInfo) (_projectLibrary p) +++ map _executableBuildInfo (_projectExecutables p) +++ map _testBuildInfo (_projectTests p)++-- | Check if source related to target, source must be relative to project directory+inTarget :: FilePath -> Info -> Bool+inTarget src info = any (`isParent` src) $ view infoSourceDirsDef info++-- | Get first target for source file+fileTarget :: Project -> FilePath -> Maybe Info+fileTarget p f = listToMaybe $ fileTargets p f++-- | Get possible targets for source file+-- There can be many candidates in case of module related to several executables or tests+fileTargets :: Project -> FilePath -> [Info]+fileTargets p f = case filter ((`isSuffixOf` f') . normalise . _executablePath) exes of+ [] -> filter (f' `inTarget`) $ maybe [] infos $ _projectDescription p+ exes' -> map _executableBuildInfo exes'+ where+ f' = makeRelative (_projectPath p) f+ exes = maybe [] _projectExecutables $ _projectDescription p++-- | Finds source dir file belongs to+findSourceDir :: Project -> FilePath -> Maybe (Extensions FilePath)+findSourceDir p f = do+ info <- listToMaybe $ fileTargets p f+ fmap (`withExtensions` info) $ listToMaybe $ filter (`isParent` f) $ map (_projectPath p </>) $ view infoSourceDirsDef info++-- | Returns source dirs for library, executables and tests+sourceDirs :: ProjectDescription -> [Extensions FilePath]+sourceDirs = ordNub . concatMap dirs . infos where+ dirs i = map (`withExtensions` i) $ view infoSourceDirsDef i++-- | Get options for specific target+targetOpts :: Info -> [String]+targetOpts info' = concat [+ ["-i" ++ s | s <- _infoSourceDirs info'],+ extensionsOpts $ withExtensions () info',+ ["-package " ++ p | p <- _infoDepends info']]++-- | Extension as flag name+showExtension :: Extension -> String+showExtension = display++-- | Convert -Xext to ext+flagExtension :: String -> Maybe String+flagExtension = stripPrefix "-X"++-- | Convert ext to -Xext+extensionFlag :: String -> String+extensionFlag = ("-X" ++)++-- | Extensions as opts to GHC+extensionsOpts :: Extensions a -> [String]+extensionsOpts e = map (extensionFlag . showExtension) (_extensions e) ++ _ghcOptions e
+ src/HsDev/Project/Compat.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE CPP #-}++module HsDev.Project.Compat (+ showVer, componentName, testSuiteEnabled,+ flattenCondTree,+ parsePackageDesc+ ) where++import Data.Maybe (maybeToList)+import qualified Distribution.PackageDescription as PD+import Distribution.PackageDescription.Parse+import Distribution.Version (Version)+import Distribution.Text (display)+#if MIN_VERSION_Cabal(2,0,0)+import Distribution.Types.CondTree+#else +import Distribution.PackageDescription (CondTree(..))+#endif++#if MIN_VERSION_Cabal(2,0,0)+import Distribution.Types.UnqualComponentName+#else+import Data.Version (showVersion)+#endif++showVer :: Version -> String+#if MIN_VERSION_Cabal(2,0,0)+showVer = display+#else+showVer = showVersion+#endif++#if MIN_VERSION_Cabal(2,0,0)+componentName :: UnqualComponentName -> String+componentName = unUnqualComponentName+#else+componentName :: String -> String+componentName = id+#endif++testSuiteEnabled :: PD.TestSuite -> Bool+#if MIN_VERSION_Cabal(2,0,0)+testSuiteEnabled _ = True+#else+testSuiteEnabled = PD.testEnabled+#endif++flattenCondTree :: Monoid a => (c -> a -> a) -> CondTree v c a -> a+flattenCondTree f (PD.CondNode x cs cmps) = f cs x `mappend` mconcat (concatMap flattenBranch cmps) where+#if MIN_VERSION_Cabal(2,0,0)+ flattenBranch (CondBranch _ t mb) = go t mb+#else+ flattenBranch (_, t, mb) = go t mb+#endif+ go t mb = flattenCondTree f t : map (flattenCondTree f) (maybeToList mb)++parsePackageDesc :: String -> ParseResult PD.GenericPackageDescription+#if MIN_VERSION_Cabal(2,0,0)+parsePackageDesc = parseGenericPackageDescription+#else+parsePackageDesc = parsePackageDescription+#endif
src/HsDev/Project/Types.hs view
@@ -1,298 +1,298 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} - -module HsDev.Project.Types ( - Project(..), projectName, projectPath, projectCabal, projectDescription, project, absolutiseProjectPaths, relativiseProjectPaths, - 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 System.Directory.Paths -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" - -instance Paths Project where - paths f (Project nm p c desc) = Project nm <$> f p <*> f c <*> traverse (paths f) desc - --- | 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") - --- | Make paths absolute, not relative -absolutiseProjectPaths :: Project -> Project -absolutiseProjectPaths proj = absolutise (_projectPath proj) proj - --- | Make paths relative -relativiseProjectPaths :: Project -> Project -relativiseProjectPaths proj = relativise (_projectPath proj) proj - -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" - -instance Paths ProjectDescription where - paths f (ProjectDescription v lib exes tests) = ProjectDescription v <$> traverse (paths f) lib <*> traverse (paths f) exes <*> traverse (paths f) 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 (== '.')) - -instance Paths Library where - paths f (Library ms info) = Library ms <$> paths f info - --- | 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" - -instance Paths Executable where - paths f (Executable n p info) = Executable n <$> f p <*> paths f 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" - -instance Paths Test where - paths f (Test n e info) = Test n e <$> paths f 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 Ord Info where - compare l r = compare (_infoSourceDirs l, _infoDepends l, _infoGHCOptions l) (_infoSourceDirs r, _infoDepends r, _infoGHCOptions 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 - -instance Paths Info where - paths f (Info deps lang exts opts dirs) = Info deps lang exts opts <$> traverse f dirs - --- | 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 +{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}++module HsDev.Project.Types (+ Project(..), projectName, projectPath, projectCabal, projectDescription, project, absolutiseProjectPaths, relativiseProjectPaths,+ 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 System.Directory.Paths+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"++instance Paths Project where+ paths f (Project nm p c desc) = Project nm <$> f p <*> f c <*> traverse (paths f) desc++-- | 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")++-- | Make paths absolute, not relative+absolutiseProjectPaths :: Project -> Project+absolutiseProjectPaths proj = absolutise (_projectPath proj) proj++-- | Make paths relative+relativiseProjectPaths :: Project -> Project+relativiseProjectPaths proj = relativise (_projectPath proj) proj++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"++instance Paths ProjectDescription where+ paths f (ProjectDescription v lib exes tests) = ProjectDescription v <$> traverse (paths f) lib <*> traverse (paths f) exes <*> traverse (paths f) 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 (== '.'))++instance Paths Library where+ paths f (Library ms info) = Library ms <$> paths f info++-- | 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"++instance Paths Executable where+ paths f (Executable n p info) = Executable n <$> f p <*> paths f 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"++instance Paths Test where+ paths f (Test n e info) = Test n e <$> paths f 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 Ord Info where+ compare l r = compare (_infoSourceDirs l, _infoDepends l, _infoGHCOptions l) (_infoSourceDirs r, _infoDepends r, _infoGHCOptions 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++instance Paths Info where+ paths f (Info deps lang exts opts dirs) = Info deps lang exts opts <$> traverse f dirs++-- | 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
@@ -1,161 +1,161 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} - -module HsDev.Sandbox ( - SandboxType(..), Sandbox(..), sandboxType, sandbox, - isSandbox, guessSandboxType, sandboxFromPath, - findSandbox, searchSandbox, projectSandbox, sandboxPackageDbStack, searchPackageDbStack, restorePackageDbStack, - - -- * cabal-sandbox util - cabalSandboxLib, cabalSandboxPackageDb, - - getModuleOpts - ) where - -import Control.Applicative -import Control.Arrow -import Control.DeepSeq (NFData(..)) -import Control.Monad.Trans.Maybe -import Control.Monad.Except -import Control.Lens (view, makeLenses) -import Data.Aeson -import Data.List (find) -import Data.Maybe (isJust, fromMaybe) -import qualified Data.Text as T (unpack) -import Distribution.Compiler -import Distribution.System -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 -import HsDev.Scan.Browse (withPackages, browsePackages) -import HsDev.Stack -import HsDev.Symbols (moduleOpts) -import HsDev.Symbols.Types (Module(..), ModuleLocation(..), moduleLocation) -import HsDev.Tools.Ghc.Compat as Compat -import HsDev.Util (searchPath, directoryContents, cabalFile) - -import qualified Packages as GHC - -data SandboxType = CabalSandbox | StackWork deriving (Eq, Ord, Read, Show, Enum, Bounded) - -data Sandbox = Sandbox { _sandboxType :: SandboxType, _sandbox :: FilePath } deriving (Eq, Ord) - -makeLenses ''Sandbox - -instance NFData SandboxType where - rnf CabalSandbox = () - rnf StackWork = () - -instance NFData Sandbox where - rnf (Sandbox t p) = rnf t `seq` rnf p - -instance Show Sandbox where - show (Sandbox _ p) = p - -instance ToJSON Sandbox where - toJSON (Sandbox _ p) = toJSON p - -instance FromJSON Sandbox where - parseJSON = withText "sandbox" sandboxPath where - sandboxPath = maybe (fail "Not a sandbox") return . sandboxFromPath . T.unpack - -instance Paths Sandbox where - paths f (Sandbox st p) = Sandbox st <$> f p - -isSandbox :: FilePath -> Bool -isSandbox = isJust . guessSandboxType - -guessSandboxType :: FilePath -> Maybe SandboxType -guessSandboxType fpath - | takeFileName fpath == ".cabal-sandbox" = Just CabalSandbox - | takeFileName fpath == ".stack-work" = Just StackWork - | otherwise = Nothing - -sandboxFromPath :: FilePath -> Maybe Sandbox -sandboxFromPath fpath = Sandbox <$> guessSandboxType fpath <*> pure fpath - --- | Find sandbox in path -findSandbox :: FilePath -> IO (Maybe Sandbox) -findSandbox fpath = do - fpath' <- canonicalize fpath - isDir <- doesDirectoryExist fpath' - if isDir - then do - dirs <- liftM (fpath' :) $ directoryContents fpath' - return $ msum $ map sandboxFromDir dirs - else return Nothing - where - sandboxFromDir :: FilePath -> Maybe Sandbox - sandboxFromDir fdir - | takeFileName fdir == "stack.yaml" = sandboxFromPath (takeDirectory fdir </> ".stack-work") - | otherwise = sandboxFromPath fdir - --- | Search sandbox by parent directory -searchSandbox :: FilePath -> IO (Maybe Sandbox) -searchSandbox p = runMaybeT $ searchPath p (MaybeT . findSandbox) - --- | Get project sandbox: search up for .cabal, then search for stack.yaml in current directory and cabal sandbox in current + parents -projectSandbox :: FilePath -> IO (Maybe Sandbox) -projectSandbox fpath = runMaybeT $ do - p <- searchPath fpath (MaybeT . getCabalFile) - MaybeT (findSandbox p) <|> searchPath p (MaybeT . findSbox') - where - getCabalFile = directoryContents >=> return . find cabalFile - findSbox' = directoryContents >=> return . msum . map sandboxFromPath - --- | Get package-db stack for sandbox -sandboxPackageDbStack :: MonadLog m => Sandbox -> m PackageDbStack -sandboxPackageDbStack (Sandbox CabalSandbox fpath) = do - dir <- cabalSandboxPackageDb - return $ PackageDbStack [PackageDb $ fpath </> dir] -sandboxPackageDbStack (Sandbox StackWork fpath) = liftM (view stackPackageDbStack) $ projectEnv $ takeDirectory fpath - --- | Search package-db stack with user-db as default -searchPackageDbStack :: MonadLog m => FilePath -> m PackageDbStack -searchPackageDbStack p = do - mbox <- liftIO $ projectSandbox p - case mbox of - Nothing -> return userDb - Just sbox -> sandboxPackageDbStack sbox - --- | Restore package-db stack by package-db -restorePackageDbStack :: MonadLog m => PackageDb -> m PackageDbStack -restorePackageDbStack GlobalDb = return globalDb -restorePackageDbStack UserDb = return userDb -restorePackageDbStack (PackageDb p) = liftM (fromMaybe $ fromPackageDb p) $ runMaybeT $ do - sbox <- MaybeT $ liftIO $ searchSandbox p - lift $ sandboxPackageDbStack sbox - --- | Get actual sandbox build path: <arch>-<platform>-<compiler>-<version> -cabalSandboxLib :: MonadLog m => m FilePath -cabalSandboxLib = do - res <- withPackages ["-no-user-package-db"] $ - return . - map (GHC.packageNameString &&& GHC.packageVersion) . - fromMaybe [] . - Compat.pkgDatabase - let - compiler = T.display buildCompilerFlavor - CompilerId _ version = buildCompilerId - ver = maybe (T.display version) T.display $ lookup compiler res - return $ T.display buildPlatform ++ "-" ++ compiler ++ "-" ++ ver - --- | Get sandbox package-db: <arch>-<platform>-<compiler>-<version>-packages.conf.d -cabalSandboxPackageDb :: MonadLog m => m FilePath -cabalSandboxPackageDb = liftM (++ "-packages.conf.d") cabalSandboxLib - --- | Options for GHC for module and project -getModuleOpts :: MonadLog m => [String] -> Module -> m [String] -getModuleOpts opts m = do - pdbs <- case view moduleLocation m of - FileModule fpath _ -> searchPackageDbStack fpath - InstalledModule pdb _ _ -> restorePackageDbStack pdb - ModuleSource _ -> return userDb - pkgs <- browsePackages opts pdbs - return $ concat [ - packageDbStackOpts pdbs, - moduleOpts pkgs m, - opts] +{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}++module HsDev.Sandbox (+ SandboxType(..), Sandbox(..), sandboxType, sandbox,+ isSandbox, guessSandboxType, sandboxFromPath,+ findSandbox, searchSandbox, projectSandbox, sandboxPackageDbStack, searchPackageDbStack, restorePackageDbStack,++ -- * cabal-sandbox util+ cabalSandboxLib, cabalSandboxPackageDb,++ getModuleOpts+ ) where++import Control.Applicative+import Control.Arrow+import Control.DeepSeq (NFData(..))+import Control.Monad.Trans.Maybe+import Control.Monad.Except+import Control.Lens (view, makeLenses)+import Data.Aeson+import Data.List (find)+import Data.Maybe (isJust, fromMaybe)+import qualified Data.Text as T (unpack)+import Distribution.Compiler+import Distribution.System+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+import HsDev.Scan.Browse (withPackages, browsePackages)+import HsDev.Stack+import HsDev.Symbols (moduleOpts)+import HsDev.Symbols.Types (Module(..), ModuleLocation(..), moduleLocation)+import HsDev.Tools.Ghc.Compat as Compat+import HsDev.Util (searchPath, directoryContents, cabalFile)++import qualified Packages as GHC++data SandboxType = CabalSandbox | StackWork deriving (Eq, Ord, Read, Show, Enum, Bounded)++data Sandbox = Sandbox { _sandboxType :: SandboxType, _sandbox :: FilePath } deriving (Eq, Ord)++makeLenses ''Sandbox++instance NFData SandboxType where+ rnf CabalSandbox = ()+ rnf StackWork = ()++instance NFData Sandbox where+ rnf (Sandbox t p) = rnf t `seq` rnf p++instance Show Sandbox where+ show (Sandbox _ p) = p++instance ToJSON Sandbox where+ toJSON (Sandbox _ p) = toJSON p++instance FromJSON Sandbox where+ parseJSON = withText "sandbox" sandboxPath where+ sandboxPath = maybe (fail "Not a sandbox") return . sandboxFromPath . T.unpack++instance Paths Sandbox where+ paths f (Sandbox st p) = Sandbox st <$> f p++isSandbox :: FilePath -> Bool+isSandbox = isJust . guessSandboxType++guessSandboxType :: FilePath -> Maybe SandboxType+guessSandboxType fpath+ | takeFileName fpath == ".cabal-sandbox" = Just CabalSandbox+ | takeFileName fpath == ".stack-work" = Just StackWork+ | otherwise = Nothing++sandboxFromPath :: FilePath -> Maybe Sandbox+sandboxFromPath fpath = Sandbox <$> guessSandboxType fpath <*> pure fpath++-- | Find sandbox in path+findSandbox :: FilePath -> IO (Maybe Sandbox)+findSandbox fpath = do+ fpath' <- canonicalize fpath+ isDir <- doesDirectoryExist fpath'+ if isDir+ then do+ dirs <- liftM (fpath' :) $ directoryContents fpath'+ return $ msum $ map sandboxFromDir dirs+ else return Nothing+ where+ sandboxFromDir :: FilePath -> Maybe Sandbox+ sandboxFromDir fdir+ | takeFileName fdir == "stack.yaml" = sandboxFromPath (takeDirectory fdir </> ".stack-work")+ | otherwise = sandboxFromPath fdir++-- | Search sandbox by parent directory+searchSandbox :: FilePath -> IO (Maybe Sandbox)+searchSandbox p = runMaybeT $ searchPath p (MaybeT . findSandbox)++-- | Get project sandbox: search up for .cabal, then search for stack.yaml in current directory and cabal sandbox in current + parents+projectSandbox :: FilePath -> IO (Maybe Sandbox)+projectSandbox fpath = runMaybeT $ do+ p <- searchPath fpath (MaybeT . getCabalFile)+ MaybeT (findSandbox p) <|> searchPath p (MaybeT . findSbox')+ where+ getCabalFile = directoryContents >=> return . find cabalFile+ findSbox' = directoryContents >=> return . msum . map sandboxFromPath++-- | Get package-db stack for sandbox+sandboxPackageDbStack :: MonadLog m => Sandbox -> m PackageDbStack+sandboxPackageDbStack (Sandbox CabalSandbox fpath) = do+ dir <- cabalSandboxPackageDb+ return $ PackageDbStack [PackageDb $ fpath </> dir]+sandboxPackageDbStack (Sandbox StackWork fpath) = liftM (view stackPackageDbStack) $ projectEnv $ takeDirectory fpath++-- | Search package-db stack with user-db as default+searchPackageDbStack :: MonadLog m => FilePath -> m PackageDbStack+searchPackageDbStack p = do+ mbox <- liftIO $ projectSandbox p+ case mbox of+ Nothing -> return userDb+ Just sbox -> sandboxPackageDbStack sbox++-- | Restore package-db stack by package-db+restorePackageDbStack :: MonadLog m => PackageDb -> m PackageDbStack+restorePackageDbStack GlobalDb = return globalDb+restorePackageDbStack UserDb = return userDb+restorePackageDbStack (PackageDb p) = liftM (fromMaybe $ fromPackageDb p) $ runMaybeT $ do+ sbox <- MaybeT $ liftIO $ searchSandbox p+ lift $ sandboxPackageDbStack sbox++-- | Get actual sandbox build path: <arch>-<platform>-<compiler>-<version>+cabalSandboxLib :: MonadLog m => m FilePath+cabalSandboxLib = do+ res <- withPackages ["-no-user-package-db"] $+ return .+ map (GHC.packageNameString &&& GHC.packageVersion) .+ fromMaybe [] .+ Compat.pkgDatabase+ let+ compiler = T.display buildCompilerFlavor+ CompilerId _ version = buildCompilerId+ ver = maybe (T.display version) T.display $ lookup compiler res+ return $ T.display buildPlatform ++ "-" ++ compiler ++ "-" ++ ver++-- | Get sandbox package-db: <arch>-<platform>-<compiler>-<version>-packages.conf.d+cabalSandboxPackageDb :: MonadLog m => m FilePath+cabalSandboxPackageDb = liftM (++ "-packages.conf.d") cabalSandboxLib++-- | Options for GHC for module and project+getModuleOpts :: MonadLog m => [String] -> Module -> m [String]+getModuleOpts opts m = do+ pdbs <- case view moduleLocation m of+ FileModule fpath _ -> searchPackageDbStack fpath+ InstalledModule pdb _ _ -> restorePackageDbStack pdb+ ModuleSource _ -> return userDb+ pkgs <- browsePackages opts pdbs+ return $ concat [+ packageDbStackOpts pdbs,+ moduleOpts pkgs m,+ opts]
src/HsDev/Scan.hs view
@@ -1,209 +1,209 @@-{-# LANGUAGE FlexibleInstances #-} - -module HsDev.Scan ( - -- * Enumerate functions - CompileFlag, ModuleToScan, ProjectToScan, PackageDbToScan, ScanContents(..), - EnumContents(..), - enumProject, enumSandbox, enumDirectory, - - -- * Scan - scanProjectFile, - scanModule, scanModify, upToDate, rescanModule, changedModule, changedModules, - - -- * Reexportss - module HsDev.Database, - module HsDev.Symbols.Types, - module Control.Monad.Except, - ) where - -import Control.DeepSeq -import Control.Lens (view, preview, set, over, each, _Right, _1, _2, _3, (^.), (^..)) -import Control.Monad.Except -import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe) -import Data.List (intercalate) -import System.Directory -import Text.Format - -import HsDev.Error -import HsDev.Scan.Browse (browsePackages, browseModules) -import HsDev.Server.Types (FileSource(..), CommandMonad(..)) -import HsDev.Sandbox -import HsDev.Symbols -import HsDev.Symbols.Types -import HsDev.Database -import HsDev.Display -import HsDev.Inspect -import HsDev.Util - --- | Compile flags -type CompileFlag = String --- | Module with flags ready to scan -type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe String) --- | Project ready to scan -type ProjectToScan = (Project, [ModuleToScan]) --- | Package-db sandbox to scan (top of stack) -type PackageDbToScan = PackageDbStack - --- | Scan info -data ScanContents = ScanContents { - modulesToScan :: [ModuleToScan], - projectsToScan :: [ProjectToScan], - sandboxesToScan :: [PackageDbStack] } - -instance NFData ScanContents where - rnf (ScanContents ms ps ss) = rnf ms `seq` rnf ps `seq` rnf ss - -instance Monoid ScanContents where - mempty = ScanContents [] [] [] - mappend (ScanContents lm lp ls) (ScanContents rm rp rs) = ScanContents - (uniqueBy (view _1) $ lm ++ rm) - (uniqueBy (view _1) $ lp ++ rp) - (ordNub $ ls ++ rs) - -instance Formattable ScanContents where - formattable (ScanContents ms ps cs) = formattable str where - str :: String - str = format "modules: {}, projects: {}, package-dbs: {}" - ~~ (intercalate ", " $ ms ^.. each . _1 . moduleFile) - ~~ (intercalate ", " $ ps ^.. each . _1 . projectPath) - ~~ (intercalate ", " $ map (display . topPackageDb) $ cs ^.. each) - -class EnumContents a where - enumContents :: CommandMonad m => a -> m ScanContents - -instance EnumContents ModuleLocation where - enumContents mloc = return $ ScanContents [(mloc, [], Nothing)] [] [] - -instance EnumContents (Extensions ModuleLocation) where - enumContents ex = return $ ScanContents [(view entity ex, extensionsOpts ex, Nothing)] [] [] - -instance EnumContents Project where - enumContents = enumProject - -instance EnumContents PackageDbStack where - enumContents pdbs = return $ ScanContents [] [] (packageDbStacks pdbs) - -instance EnumContents Sandbox where - enumContents = enumSandbox - -instance {-# OVERLAPPING #-} EnumContents a => EnumContents [a] where - enumContents = liftM mconcat . tries . map enumContents - -instance {-# OVERLAPS #-} EnumContents FilePath where - enumContents f - | haskellSource f = hsdevLiftIO $ do - mproj <- liftIO $ locateProject f - case mproj of - Nothing -> enumContents $ FileModule f Nothing - Just proj -> do - ScanContents _ [(_, mods)] _ <- enumContents proj - return $ ScanContents (filter ((== Just f) . preview (_1 . moduleFile)) mods) [] [] - | otherwise = enumDirectory f - -instance EnumContents FileSource where - enumContents (FileSource f mcts) - | haskellSource f = do - ScanContents [(m, opts, _)] _ _ <- enumContents f - return $ ScanContents [(m, opts, mcts)] [] [] - | otherwise = return mempty - --- | Enum project sources -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] - projOpts f = concatMap makeOpts $ fileTargets p' f where - makeOpts :: Info -> [String] - makeOpts i = concat [ - ["-hide-all-packages"], - ["-package " ++ view projectName p'], - ["-package " ++ dep | dep <- view infoDepends i, dep `elem` pkgs]] - srcs <- 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 :: CommandMonad m => Sandbox -> m ScanContents -enumSandbox = sandboxPackageDbStack >=> enumContents - --- | Enum directory modules -enumDirectory :: CommandMonad m => FilePath -> m ScanContents -enumDirectory dir = hsdevLiftIO $ do - cts <- liftIO $ traverseDirectory dir - let - projects = filter cabalFile cts - sources = filter haskellSource cts - dirs <- liftIO $ filterM doesDirectoryExist cts - sboxes <- liftM catMaybes $ triesMap (liftIO . findSandbox) dirs - pdbs <- mapM enumSandbox sboxes - projs <- liftM mconcat $ triesMap (enumProject . project) projects - let - projPaths = map (view projectPath . fst) $ projectsToScan projs - standalone = map (`FileModule` Nothing) $ filter (\s -> not (any (`isParent` s) projPaths)) sources - return $ mconcat [ - ScanContents [(s, [], Nothing) | s <- standalone] [] [], - projs, - mconcat pdbs] - --- | Scan project file -scanProjectFile :: 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 :: 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 -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 :: CommandMonad m => PackageDb -> m PackageDbStack - getDbs = maybe (return userDb) searchPackageDbStack . preview packageDb -scanModule _ _ (ModuleSource _) _ = hsdevError $ InspectError "Can inspect only modules in file or cabal" - --- | Scan additional info and modify scanned module -scanModify :: CommandMonad m => ([String] -> PackageDbStack -> Module -> m Module) -> InspectedModule -> m InspectedModule -scanModify f im = traverse f' im where - f' m = do - pdbs <- case view moduleLocation m of - -- TODO: Get actual sandbox stack - FileModule fpath _ -> searchPackageDbStack fpath - InstalledModule pdb _ _ -> maybe (return userDb) searchPackageDbStack $ preview packageDb pdb - _ -> return userDb - f (fromMaybe [] $ preview (inspection . inspectionOpts) im) pdbs m - --- | Is inspected module up to date? -upToDate :: [String] -> InspectedModule -> IO Bool -upToDate opts im = case view inspectedId im of - FileModule f _ -> liftM (== view inspection im) $ fileInspection f opts - InstalledModule _ _ _ -> return $ view inspection im == InspectionAt 0 opts - _ -> return False - --- | Rescan inspected module -rescanModule :: CommandMonad m => [(String, String)] -> [String] -> InspectedModule -> m (Maybe InspectedModule) -rescanModule defines opts im = do - 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 -> 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] -> IO [ModuleToScan] -changedModules db opts = filterM $ \m -> if isJust (m ^. _3) - then return True - else changedModule db (opts ++ (m ^. _2)) (m ^. _1) +{-# LANGUAGE FlexibleInstances #-}++module HsDev.Scan (+ -- * Enumerate functions+ CompileFlag, ModuleToScan, ProjectToScan, PackageDbToScan, ScanContents(..),+ EnumContents(..),+ enumProject, enumSandbox, enumDirectory,++ -- * Scan+ scanProjectFile,+ scanModule, scanModify, upToDate, rescanModule, changedModule, changedModules,++ -- * Reexportss+ module HsDev.Database,+ module HsDev.Symbols.Types,+ module Control.Monad.Except,+ ) where++import Control.DeepSeq+import Control.Lens (view, preview, set, over, each, _Right, _1, _2, _3, (^.), (^..))+import Control.Monad.Except+import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe)+import Data.List (intercalate)+import System.Directory+import Text.Format++import HsDev.Error+import HsDev.Scan.Browse (browsePackages, browseModules)+import HsDev.Server.Types (FileSource(..), CommandMonad(..))+import HsDev.Sandbox+import HsDev.Symbols+import HsDev.Symbols.Types+import HsDev.Database+import HsDev.Display+import HsDev.Inspect+import HsDev.Util++-- | Compile flags+type CompileFlag = String+-- | Module with flags ready to scan+type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe String)+-- | Project ready to scan+type ProjectToScan = (Project, [ModuleToScan])+-- | Package-db sandbox to scan (top of stack)+type PackageDbToScan = PackageDbStack++-- | Scan info+data ScanContents = ScanContents {+ modulesToScan :: [ModuleToScan],+ projectsToScan :: [ProjectToScan],+ sandboxesToScan :: [PackageDbStack] }++instance NFData ScanContents where+ rnf (ScanContents ms ps ss) = rnf ms `seq` rnf ps `seq` rnf ss++instance Monoid ScanContents where+ mempty = ScanContents [] [] []+ mappend (ScanContents lm lp ls) (ScanContents rm rp rs) = ScanContents+ (uniqueBy (view _1) $ lm ++ rm)+ (uniqueBy (view _1) $ lp ++ rp)+ (ordNub $ ls ++ rs)++instance Formattable ScanContents where+ formattable (ScanContents ms ps cs) = formattable str where+ str :: String+ str = format "modules: {}, projects: {}, package-dbs: {}"+ ~~ (intercalate ", " $ ms ^.. each . _1 . moduleFile)+ ~~ (intercalate ", " $ ps ^.. each . _1 . projectPath)+ ~~ (intercalate ", " $ map (display . topPackageDb) $ cs ^.. each)++class EnumContents a where+ enumContents :: CommandMonad m => a -> m ScanContents++instance EnumContents ModuleLocation where+ enumContents mloc = return $ ScanContents [(mloc, [], Nothing)] [] []++instance EnumContents (Extensions ModuleLocation) where+ enumContents ex = return $ ScanContents [(view entity ex, extensionsOpts ex, Nothing)] [] []++instance EnumContents Project where+ enumContents = enumProject++instance EnumContents PackageDbStack where+ enumContents pdbs = return $ ScanContents [] [] (packageDbStacks pdbs)++instance EnumContents Sandbox where+ enumContents = enumSandbox++instance {-# OVERLAPPING #-} EnumContents a => EnumContents [a] where+ enumContents = liftM mconcat . tries . map enumContents++instance {-# OVERLAPS #-} EnumContents FilePath where+ enumContents f+ | haskellSource f = hsdevLiftIO $ do+ mproj <- liftIO $ locateProject f+ case mproj of+ Nothing -> enumContents $ FileModule f Nothing+ Just proj -> do+ ScanContents _ [(_, mods)] _ <- enumContents proj+ return $ ScanContents (filter ((== Just f) . preview (_1 . moduleFile)) mods) [] []+ | otherwise = enumDirectory f++instance EnumContents FileSource where+ enumContents (FileSource f mcts)+ | haskellSource f = do+ ScanContents [(m, opts, _)] _ _ <- enumContents f+ return $ ScanContents [(m, opts, mcts)] [] []+ | otherwise = return mempty++-- | Enum project sources+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]+ projOpts f = concatMap makeOpts $ fileTargets p' f where+ makeOpts :: Info -> [String]+ makeOpts i = concat [+ ["-hide-all-packages"],+ ["-package " ++ view projectName p'],+ ["-package " ++ dep | dep <- view infoDepends i, dep `elem` pkgs]]+ srcs <- 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 :: CommandMonad m => Sandbox -> m ScanContents+enumSandbox = sandboxPackageDbStack >=> enumContents++-- | Enum directory modules+enumDirectory :: CommandMonad m => FilePath -> m ScanContents+enumDirectory dir = hsdevLiftIO $ do+ cts <- liftIO $ traverseDirectory dir+ let+ projects = filter cabalFile cts+ sources = filter haskellSource cts+ dirs <- liftIO $ filterM doesDirectoryExist cts+ sboxes <- liftM catMaybes $ triesMap (liftIO . findSandbox) dirs+ pdbs <- mapM enumSandbox sboxes+ projs <- liftM mconcat $ triesMap (enumProject . project) projects+ let+ projPaths = map (view projectPath . fst) $ projectsToScan projs+ standalone = map (`FileModule` Nothing) $ filter (\s -> not (any (`isParent` s) projPaths)) sources+ return $ mconcat [+ ScanContents [(s, [], Nothing) | s <- standalone] [] [],+ projs,+ mconcat pdbs]++-- | Scan project file+scanProjectFile :: 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 :: 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+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 :: CommandMonad m => PackageDb -> m PackageDbStack+ getDbs = maybe (return userDb) searchPackageDbStack . preview packageDb+scanModule _ _ (ModuleSource _) _ = hsdevError $ InspectError "Can inspect only modules in file or cabal"++-- | Scan additional info and modify scanned module+scanModify :: CommandMonad m => ([String] -> PackageDbStack -> Module -> m Module) -> InspectedModule -> m InspectedModule+scanModify f im = traverse f' im where+ f' m = do+ pdbs <- case view moduleLocation m of+ -- TODO: Get actual sandbox stack+ FileModule fpath _ -> searchPackageDbStack fpath+ InstalledModule pdb _ _ -> maybe (return userDb) searchPackageDbStack $ preview packageDb pdb+ _ -> return userDb+ f (fromMaybe [] $ preview (inspection . inspectionOpts) im) pdbs m++-- | Is inspected module up to date?+upToDate :: [String] -> InspectedModule -> IO Bool+upToDate opts im = case view inspectedId im of+ FileModule f _ -> liftM (== view inspection im) $ fileInspection f opts+ InstalledModule _ _ _ -> return $ view inspection im == InspectionAt 0 opts+ _ -> return False++-- | Rescan inspected module+rescanModule :: CommandMonad m => [(String, String)] -> [String] -> InspectedModule -> m (Maybe InspectedModule)+rescanModule defines opts im = do+ 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 -> 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] -> 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
@@ -1,251 +1,248 @@-module HsDev.Scan.Browse ( - -- * List all packages - browsePackages, browsePackagesDeps, - -- * Scan cabal modules - listModules, browseModules, browse, browseDb, - -- * Helpers - withPackages, withPackages_, - readPackage, readPackageConfig, ghcPackageDb, ghcModuleLocation, - packageDbCandidate, packageDbCandidate_, - packageConfigs, packageDbModules, lookupModule_, - - module Control.Monad.Except - ) where - -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 -import Data.String (fromString) -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.Tools.Ghc.Worker (GhcM, runGhcM, SessionTarget(..), workerSession) -import HsDev.Tools.Ghc.Compat as Compat -import HsDev.Util (ordNub) - -import qualified ConLike as GHC -import qualified DataCon as GHC -import qualified DynFlags as GHC -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 -import qualified Packages as GHC -import qualified TyCon as GHC -import qualified Type as GHC -import qualified Var as GHC -import qualified Pretty - --- | Browse packages -browsePackages :: MonadLog m => [String] -> PackageDbStack -> m [PackageConfig] -browsePackages opts dbs = withPackages_ (packageDbStackOpts dbs ++ opts) $ - liftM (map readPackageConfig) packageConfigs - --- | Get packages with deps -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 (Compat.unitId &&& Compat.depends df) cfgs - where - toPkg df' = readPackageConfig . getPackageDetails df' - -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 :: 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 -> 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 :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule] -browse opts dbs = listModules opts dbs >>= browseModules opts dbs - --- | Browse modules in top of package-db stack -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 -> GhcM Module -browseModule pdb package' m = do - df <- GHC.getSessionDynFlags - mi <- GHC.getModuleInfo m >>= maybe (hsdevError $ BrowseNoModuleInfo thisModule) return - ds <- mapM (toDecl df mi) (GHC.modInfoExports mi) - return Module { - _moduleName = fromString thisModule, - _moduleDocs = Nothing, - _moduleLocation = thisLoc df, - _moduleExports = Just [ExportName Nothing (view declarationName d) ThingNothing | d <- ds], - _moduleImports = [import_ iname | iname <- ordNub (mapMaybe (preview definedModule) ds), iname /= fromString thisModule], - _moduleDeclarations = sortDeclarations ds } - where - thisModule = GHC.moduleNameString (GHC.moduleName m) - thisLoc df = view moduleIdLocation $ mloc df m - mloc df m' = ModuleId (fromString mname') $ - ghcModuleLocation pdb (fromMaybe package' $ GHC.lookupPackage df (moduleUnitId m')) m' - where - mname' = GHC.moduleNameString $ GHC.moduleName m' - toDecl df minfo n = do - 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) - (tyResult >>= showResult dflag) - return $ decl' `definedIn` mloc df (GHC.nameModule n) - definedModule = declarationDefined . _Just . moduleIdName - showResult :: GHC.DynFlags -> GHC.TyThing -> Maybe DeclarationInfo - showResult dflags (GHC.AnId i) = Just $ Function (Just $ fromString $ formatType dflags GHC.varType i) [] Nothing - showResult dflags (GHC.AConLike c) = case c of - GHC.RealDataCon d -> Just $ Function (Just $ fromString $ formatType dflags GHC.dataConRepType d) [] Nothing - GHC.PatSynCon p -> Just $ Function (Just $ fromString $ formatType dflags patSynType p) [] Nothing - showResult _ (GHC.ATyCon t) = Just $ tcon $ TypeInfo Nothing (map (fromString . GHC.getOccString) $ GHC.tyConTyVars t) Nothing [] where - tcon - | GHC.isAlgTyCon t && not (GHC.isNewTyCon t) && not (GHC.isClassTyCon t) = Data - | GHC.isNewTyCon t = NewType - | GHC.isClassTyCon t = Class - | GHC.isTypeSynonymTyCon t = Type - | otherwise = Type - showResult _ _ = Nothing - -withInitializedPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a -withInitializedPackages ghcOpts cont = runGhcM (Just GHC.libdir) $ do - workerSession $ SessionGhc ghcOpts - fs <- GHC.getSessionDynFlags - cleanupHandler fs $ do - (fs', _, _) <- GHC.parseDynamicFlags fs (map GHC.noLoc ghcOpts) - _ <- GHC.setSessionDynFlags fs' - (result, _) <- GHC.liftIO $ GHC.initPackages fs' - cont result - -withPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a -withPackages ghcOpts cont = withInitializedPackages ghcOpts cont - -withPackages_ :: MonadLog m => [String] -> GhcM a -> m a -withPackages_ ghcOpts act = withPackages ghcOpts (const act) - -inModuleSource :: GhcMonad m => GHC.Name -> m (Maybe GHC.TyThing) -inModuleSource nm = GHC.getModuleInfo (GHC.nameModule nm) >> GHC.lookupGlobalName nm - -formatType :: GHC.DynFlags -> (a -> GHC.Type) -> a -> String -formatType dflag f x = showOutputable dflag (removeForAlls $ f x) - -removeForAlls :: GHC.Type -> GHC.Type -removeForAlls ty = removeForAlls' ty' tty' where - ty' = GHC.dropForAlls ty - tty' = GHC.splitFunTy_maybe ty' - -removeForAlls' :: GHC.Type -> Maybe (GHC.Type, GHC.Type) -> GHC.Type -removeForAlls' ty Nothing = ty -removeForAlls' ty (Just (pre, ftype)) - | GHC.isPredTy pre = GHC.mkFunTy pre (GHC.dropForAlls ftype) - | otherwise = ty - -showOutputable :: GHC.Outputable a => GHC.DynFlags -> a -> String -showOutputable dflag = unwords . lines . showUnqualifiedPage dflag . GHC.ppr - -showUnqualifiedPage :: GHC.DynFlags -> GHC.SDoc -> String -showUnqualifiedPage dflag = renderStyle Pretty.LeftMode 0 . GHC.withPprStyleDoc dflag styleUnqualified - -styleUnqualified :: GHC.PprStyle -styleUnqualified = GHC.mkUserStyle GHC.neverQualify GHC.AllTheWay - -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)) - -readPackageConfig :: GHC.PackageConfig -> PackageConfig -readPackageConfig pc = PackageConfig - (readPackage pc) - (map (fromString . GHC.moduleNameString . GHC.exposedName) $ GHC.exposedModules pc) - (GHC.exposed pc) - -ghcModuleLocation :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ModuleLocation -ghcModuleLocation pdb p m = InstalledModule pdb (Just $ readPackage p) (GHC.moduleNameString $ GHC.moduleName m) - -ghcPackageDb :: GHC.PackageConfig -> IO PackageDb -ghcPackageDb = maybe (return GlobalDb) packageDbCandidate_ . listToMaybe . GHC.libraryDirs - --- | Get package-db for package library directory --- Haskish way --- global-db - library is in <path>/lib/<package> and there exists <path>/lib/package.conf.d --- user-db - library is in cabal user directory --- package-db --- cabal-sandbox - library in .../.cabal-sandbox/.../<platform-ghc-ver>/<package> --- then package-db is .../.cabal-sandbox/<platform-ghc-ver>-package.conf.d --- stack (snapshots or .stack-work) - library in <path>/lib/<platform-ghc-ver>/<package> --- then package-db is <path>/pkgdb -packageDbCandidate :: FilePath -> IO (Maybe PackageDb) -packageDbCandidate fpath = liftM Just (msum [global', user', sandbox', stack']) `mplus` return Nothing where - global' = do - guard (takeFileName (takeDirectory fpath') == "lib") - guardExist (takeDirectory fpath' </> "package.conf.d") - return GlobalDb - user' = do - cabalDir <- getAppUserDataDirectory "cabal" - guard (splitDirectories cabalDir `isPrefixOf` splitDirectories fpath') - return UserDb - sandbox' = do - guard (".cabal-sandbox" `elem` splitDirectories fpath') - let - sandboxPath = joinPath $ reverse $ dropWhile (/= ".cabal-sandbox") $ reverse $ splitDirectories fpath' - platform = takeFileName (takeDirectory fpath') - dbPath = sandboxPath </> (platform ++ "-packages.conf.d") - guardExist dbPath - return $ PackageDb dbPath - stack' = do - guard (takeFileName (takeDirectory (takeDirectory fpath')) == "lib") - let - pkgDb = takeDirectory (takeDirectory $ takeDirectory fpath') </> "pkgdb" - guardExist pkgDb - return $ PackageDb pkgDb - guardExist = doesDirectoryExist >=> guard - fpath' = normalise fpath - --- | Use global as default -packageDbCandidate_ :: FilePath -> IO PackageDb -packageDbCandidate_ = packageDbCandidate >=> maybe (return GlobalDb) return - -packageConfigs :: GhcM [GHC.PackageConfig] -packageConfigs = liftM (fromMaybe [] . pkgDatabase) GHC.getSessionDynFlags - -packageDbModules :: GhcM [(GHC.PackageConfig, GHC.Module)] -packageDbModules = do - pkgs <- packageConfigs - dflags <- GHC.getSessionDynFlags - return [(p, m) | - p <- pkgs, - mn <- map GHC.exposedName (GHC.exposedModules p), - m <- lookupModule_ dflags mn] - --- Lookup module everywhere -lookupModule_ :: GHC.DynFlags -> GHC.ModuleName -> [GHC.Module] -lookupModule_ d mn = case GHC.lookupModuleWithSuggestions d mn Nothing of - GHC.LookupFound m' _ -> [m'] - GHC.LookupMultiple ms -> map fst ms - GHC.LookupHidden ls rs -> map fst $ ls ++ rs - GHC.LookupNotFound _ -> [] +module HsDev.Scan.Browse (+ -- * List all packages+ browsePackages, browsePackagesDeps,+ -- * Scan cabal modules+ listModules, browseModules, browse, browseDb,+ -- * Helpers+ withPackages, withPackages_,+ readPackage, readPackageConfig, ghcPackageDb, ghcModuleLocation,+ packageDbCandidate, packageDbCandidate_,+ packageConfigs, packageDbModules, lookupModule_,++ module Control.Monad.Except+ ) where++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+import Data.String (fromString)+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.Tools.Ghc.Worker (GhcM, runGhcM, SessionTarget(..), workerSession)+import HsDev.Tools.Ghc.Compat as Compat+import HsDev.Util (ordNub)++import qualified ConLike as GHC+import qualified DataCon as GHC+import qualified DynFlags as GHC+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+import qualified Packages as GHC+import qualified TyCon as GHC+import qualified Type as GHC+import qualified Var as GHC+import qualified Pretty++-- | Browse packages+browsePackages :: MonadLog m => [String] -> PackageDbStack -> m [PackageConfig]+browsePackages opts dbs = withPackages_ (packageDbStackOpts dbs ++ opts) $+ liftM (map readPackageConfig) packageConfigs++-- | Get packages with deps+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 (Compat.unitId &&& Compat.depends df) cfgs+ where+ toPkg df' = readPackageConfig . getPackageDetails df'++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 :: 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 -> 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 :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule]+browse opts dbs = listModules opts dbs >>= browseModules opts dbs++-- | Browse modules in top of package-db stack+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 -> GhcM Module+browseModule pdb package' m = do+ df <- GHC.getSessionDynFlags+ mi <- GHC.getModuleInfo m >>= maybe (hsdevError $ BrowseNoModuleInfo thisModule) return+ ds <- mapM (toDecl df mi) (GHC.modInfoExports mi)+ return Module {+ _moduleName = fromString thisModule,+ _moduleDocs = Nothing,+ _moduleLocation = thisLoc df,+ _moduleExports = Just [ExportName Nothing (view declarationName d) ThingNothing | d <- ds],+ _moduleImports = [import_ iname | iname <- ordNub (mapMaybe (preview definedModule) ds), iname /= fromString thisModule],+ _moduleDeclarations = sortDeclarations ds }+ where+ thisModule = GHC.moduleNameString (GHC.moduleName m)+ thisLoc df = view moduleIdLocation $ mloc df m+ mloc df m' = ModuleId (fromString mname') $+ ghcModuleLocation pdb (fromMaybe package' $ GHC.lookupPackage df (moduleUnitId m')) m'+ where+ mname' = GHC.moduleNameString $ GHC.moduleName m'+ toDecl df minfo n = do+ 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)+ (tyResult >>= showResult dflag)+ return $ decl' `definedIn` mloc df (GHC.nameModule n)+ definedModule = declarationDefined . _Just . moduleIdName+ showResult :: GHC.DynFlags -> GHC.TyThing -> Maybe DeclarationInfo+ showResult dflags (GHC.AnId i) = Just $ Function (Just $ fromString $ formatType dflags GHC.varType i) [] Nothing+ showResult dflags (GHC.AConLike c) = case c of+ GHC.RealDataCon d -> Just $ Function (Just $ fromString $ formatType dflags GHC.dataConRepType d) [] Nothing+ GHC.PatSynCon p -> Just $ Function (Just $ fromString $ formatType dflags patSynType p) [] Nothing+ showResult _ (GHC.ATyCon t) = Just $ tcon $ TypeInfo Nothing (map (fromString . GHC.getOccString) $ GHC.tyConTyVars t) Nothing [] where+ tcon+ | GHC.isAlgTyCon t && not (GHC.isNewTyCon t) && not (GHC.isClassTyCon t) = Data+ | GHC.isNewTyCon t = NewType+ | GHC.isClassTyCon t = Class+ | GHC.isTypeSynonymTyCon t = Type+ | otherwise = Type+ showResult _ _ = Nothing++withInitializedPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a+withInitializedPackages ghcOpts cont = runGhcM (Just GHC.libdir) $ do+ workerSession $ SessionGhc ghcOpts+ fs <- GHC.getSessionDynFlags+ cleanupHandler fs $ do+ (fs', _, _) <- GHC.parseDynamicFlags fs (map GHC.noLoc ghcOpts)+ _ <- GHC.setSessionDynFlags fs'+ (result, _) <- GHC.liftIO $ GHC.initPackages fs'+ cont result++withPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a+withPackages ghcOpts cont = withInitializedPackages ghcOpts cont++withPackages_ :: MonadLog m => [String] -> GhcM a -> m a+withPackages_ ghcOpts act = withPackages ghcOpts (const act)++inModuleSource :: GhcMonad m => GHC.Name -> m (Maybe GHC.TyThing)+inModuleSource nm = GHC.getModuleInfo (GHC.nameModule nm) >> GHC.lookupGlobalName nm++formatType :: GHC.DynFlags -> (a -> GHC.Type) -> a -> String+formatType dflag f x = showOutputable dflag (removeForAlls $ f x)++removeForAlls :: GHC.Type -> GHC.Type+removeForAlls ty = removeForAlls' ty' tty' where+ ty' = GHC.dropForAlls ty+ tty' = GHC.splitFunTy_maybe ty'++removeForAlls' :: GHC.Type -> Maybe (GHC.Type, GHC.Type) -> GHC.Type+removeForAlls' ty Nothing = ty+removeForAlls' ty (Just (pre, ftype))+ | GHC.isPredTy pre = GHC.mkFunTy pre (GHC.dropForAlls ftype)+ | otherwise = ty++showOutputable :: GHC.Outputable a => GHC.DynFlags -> a -> String+showOutputable dflag = unwords . lines . showUnqualifiedPage dflag . GHC.ppr++showUnqualifiedPage :: GHC.DynFlags -> GHC.SDoc -> String+showUnqualifiedPage dflag = renderStyle Pretty.LeftMode 0 . GHC.withPprStyleDoc dflag (Compat.unqualStyle dflag)++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))++readPackageConfig :: GHC.PackageConfig -> PackageConfig+readPackageConfig pc = PackageConfig+ (readPackage pc)+ (map (fromString . GHC.moduleNameString . Compat.exposedModuleName) $ GHC.exposedModules pc)+ (GHC.exposed pc)++ghcModuleLocation :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ModuleLocation+ghcModuleLocation pdb p m = InstalledModule pdb (Just $ readPackage p) (GHC.moduleNameString $ GHC.moduleName m)++ghcPackageDb :: GHC.PackageConfig -> IO PackageDb+ghcPackageDb = maybe (return GlobalDb) packageDbCandidate_ . listToMaybe . GHC.libraryDirs++-- | Get package-db for package library directory+-- Haskish way+-- global-db - library is in <path>/lib/<package> and there exists <path>/lib/package.conf.d+-- user-db - library is in cabal user directory+-- package-db+-- cabal-sandbox - library in .../.cabal-sandbox/.../<platform-ghc-ver>/<package>+-- then package-db is .../.cabal-sandbox/<platform-ghc-ver>-package.conf.d+-- stack (snapshots or .stack-work) - library in <path>/lib/<platform-ghc-ver>/<package>+-- then package-db is <path>/pkgdb+packageDbCandidate :: FilePath -> IO (Maybe PackageDb)+packageDbCandidate fpath = liftM Just (msum [global', user', sandbox', stack']) `mplus` return Nothing where+ global' = do+ guard (takeFileName (takeDirectory fpath') == "lib")+ guardExist (takeDirectory fpath' </> "package.conf.d")+ return GlobalDb+ user' = do+ cabalDir <- getAppUserDataDirectory "cabal"+ guard (splitDirectories cabalDir `isPrefixOf` splitDirectories fpath')+ return UserDb+ sandbox' = do+ guard (".cabal-sandbox" `elem` splitDirectories fpath')+ let+ sandboxPath = joinPath $ reverse $ dropWhile (/= ".cabal-sandbox") $ reverse $ splitDirectories fpath'+ platform = takeFileName (takeDirectory fpath')+ dbPath = sandboxPath </> (platform ++ "-packages.conf.d")+ guardExist dbPath+ return $ PackageDb dbPath+ stack' = do+ guard (takeFileName (takeDirectory (takeDirectory fpath')) == "lib")+ let+ pkgDb = takeDirectory (takeDirectory $ takeDirectory fpath') </> "pkgdb"+ guardExist pkgDb+ return $ PackageDb pkgDb+ guardExist = doesDirectoryExist >=> guard+ fpath' = normalise fpath++-- | Use global as default+packageDbCandidate_ :: FilePath -> IO PackageDb+packageDbCandidate_ = packageDbCandidate >=> maybe (return GlobalDb) return++packageConfigs :: GhcM [GHC.PackageConfig]+packageConfigs = liftM (fromMaybe [] . pkgDatabase) GHC.getSessionDynFlags++packageDbModules :: GhcM [(GHC.PackageConfig, GHC.Module)]+packageDbModules = do+ pkgs <- packageConfigs+ dflags <- GHC.getSessionDynFlags+ return [(p, m) |+ p <- pkgs,+ mn <- map Compat.exposedModuleName (GHC.exposedModules p),+ m <- lookupModule_ dflags mn]++-- Lookup module everywhere+lookupModule_ :: GHC.DynFlags -> GHC.ModuleName -> [GHC.Module]+lookupModule_ d mn = case GHC.lookupModuleWithSuggestions d mn Nothing of+ GHC.LookupFound m' _ -> [m']+ GHC.LookupMultiple ms -> map fst ms+ GHC.LookupHidden ls rs -> map fst $ ls ++ rs+ GHC.LookupNotFound _ -> []
src/HsDev/Server/Base.hs view
@@ -1,159 +1,159 @@-{-# LANGUAGE CPP, OverloadedStrings #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Server.Base ( - initLog, runServer, Server, startServer, inServer, - withCache, writeCache, readCache, - - module HsDev.Server.Types, - module HsDev.Server.Message - ) where - -import Control.Applicative -import Control.Concurrent -import Control.Exception -import Control.Monad -import Control.Monad.Except -import Control.Monad.Reader -import Data.Default -import qualified Data.Map as M -import Data.Maybe -import Data.String -import Data.Text (Text) -import qualified Data.Text as T (pack, unpack) -import System.Log.Simple hiding (Level(..), Message) -import qualified System.Log.Simple.Base as Log -import System.Directory (removeDirectoryRecursive, createDirectoryIfMissing) -import System.FilePath - -import qualified Control.Concurrent.FiniteChan as F -import System.Directory.Paths (canonicalize) -import qualified System.Directory.Watcher as Watcher -import Text.Format ((~~), (~%)) - -import qualified HsDev.Cache as Cache -import qualified HsDev.Cache.Structured as SC -import qualified HsDev.Client.Commands as Client -import HsDev.Database -import qualified HsDev.Database.Async as DB -import qualified HsDev.Database.Update as Update -import HsDev.Inspect (getDefines) -import HsDev.Tools.Ghc.Worker -import HsDev.Server.Types -import HsDev.Server.Message -import HsDev.Util - -#if mingw32_HOST_OS -import System.Win32.FileMapping.NamePool -#endif - --- | Inits log chan and returns functions (print message, wait channel) -initLog :: ServerOpts -> IO SessionLog -initLog sopts = do - msgs <- F.newChan - l <- newLog (logCfg [("", Log.level_ . T.pack . serverLogLevel $ sopts)]) $ concat [ - [handler text console | not $ serverSilent sopts], - [handler text (chaner msgs)], - [handler text (file f) | f <- maybeToList (serverLog sopts)]] - let - listenLog = F.dupChan msgs >>= F.readChan - return $ SessionLog l listenLog (stopLog l) - --- | Run server -runServer :: ServerOpts -> ServerM IO () -> IO () -runServer sopts act = bracket (initLog sopts) sessionLogWait $ \slog -> Watcher.withWatcher $ \watcher -> withLog (sessionLogger slog) $ do - waitSem <- liftIO $ newQSem 0 - db <- liftIO $ DB.newAsync - withCache sopts () $ \cdir -> do - sendLog Log.Trace $ "Checking cache version in {}" ~~ cdir - ver <- liftIO $ Cache.readVersion $ cdir </> Cache.versionCache - sendLog Log.Debug $ "Cache version: {}" ~~ strVersion ver - unless (sameVersion (cutVersion version) (cutVersion ver)) $ ignoreIO $ do - sendLog Log.Info $ "Cache version ({cache}) is incompatible with hsdev version ({hsdev}), removing cache ({dir})" ~~ - ("cache" ~% strVersion ver) ~~ - ("hsdev" ~% strVersion version) ~~ - ("dir" ~% cdir) - -- drop cache - liftIO $ removeDirectoryRecursive cdir - sendLog Log.Debug $ "Writing new cache version: {}" ~~ strVersion version - liftIO $ createDirectoryIfMissing True cdir - liftIO $ Cache.writeVersion $ cdir </> Cache.versionCache - when (serverLoad sopts) $ withCache sopts () $ \cdir -> do - sendLog Log.Info $ "Loading cache from {}" ~~ cdir - dbCache <- liftA merge <$> liftIO (SC.load cdir) - case dbCache of - Left err -> sendLog Log.Error $ "Failed to load cache: {}" ~~ err - Right dbCache' -> DB.update db (return dbCache') -#if mingw32_HOST_OS - mmapPool <- Just <$> liftIO (createPool "hsdev") -#endif - ghcw <- ghcWorker - defs <- liftIO getDefines - let - session = Session - db - (writeCache sopts) - (readCache sopts) - slog - watcher -#if mingw32_HOST_OS - mmapPool -#endif - ghcw - (do - withLog (sessionLogger slog) $ sendLog Log.Trace "stopping server" - signalQSem waitSem) - (waitQSem waitSem) - defs - _ <- liftIO $ forkIO $ Update.onEvent watcher $ \w e -> withSession session $ - void $ Client.runClient def $ Update.processEvent def w e - liftIO $ runReaderT (runServerM act) session - -type Server = Worker (ServerM IO) - -startServer :: ServerOpts -> IO Server -startServer sopts = startWorker (runServer sopts) id id - -inServer :: Server -> CommandOptions -> Command -> IO Result -inServer srv copts c = do - c' <- canonicalize c - inWorker srv (Client.runClient copts $ Client.runCommand c') - -chaner :: F.Chan String -> Consumer Text -chaner ch = return $ F.putChan ch . T.unpack - --- | Perform action on cache -withCache :: Monad m => ServerOpts -> a -> (FilePath -> m a) -> m a -withCache sopts v onCache = case serverCache sopts of - Nothing -> return v - Just cdir -> onCache cdir - -writeCache :: SessionMonad m => ServerOpts -> Database -> m () -writeCache sopts db = withCache sopts () $ \cdir -> do - sendLog Log.Info $ "writing cache to {}" ~~ cdir - logIO "cache writing exception: " (sendLog Log.Error . fromString) $ do - let - sd = structurize db - liftIO $ SC.dump cdir sd - forM_ (M.keys (structuredPackageDbs sd)) $ \c -> sendLog Log.Debug ("cache write: cabal {}" ~~ show c) - forM_ (M.keys (structuredProjects sd)) $ \p -> sendLog Log.Debug ("cache write: project {}" ~~ p) - case allModules (structuredFiles sd) of - [] -> return () - ms -> sendLog Log.Debug $ "cache write: {} files" ~~ length ms - sendLog Log.Info $ "cache saved to {}" ~~ cdir - -readCache :: SessionMonad m => ServerOpts -> (FilePath -> ExceptT String IO Structured) -> m (Maybe Database) -readCache sopts act = do - s <- getSession - liftIO $ withSession s $ withCache sopts Nothing $ \fpath -> do - res <- liftIO $ runExceptT $ act fpath - either cacheErr cacheOk res - where - cacheErr e = sendLog Log.Error ("Error reading cache: {}" ~~ e) >> return Nothing - cacheOk s = do - forM_ (M.keys (structuredPackageDbs s)) $ \c -> sendLog Log.Debug ("cache read: cabal {}" ~~ show c) - forM_ (M.keys (structuredProjects s)) $ \p -> sendLog Log.Debug ("cache read: project {}" ~~ p) - case allModules (structuredFiles s) of - [] -> return () - ms -> sendLog Log.Debug $ "cache read: {} files" ~~ length ms - return $ Just $ merge s +{-# LANGUAGE CPP, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Server.Base (+ initLog, runServer, Server, startServer, inServer,+ withCache, writeCache, readCache,++ module HsDev.Server.Types,+ module HsDev.Server.Message+ ) where++import Control.Applicative+import Control.Concurrent+import Control.Exception+import Control.Monad+import Control.Monad.Except+import Control.Monad.Reader+import Data.Default+import qualified Data.Map as M+import Data.Maybe+import Data.String+import Data.Text (Text)+import qualified Data.Text as T (pack, unpack)+import System.Log.Simple hiding (Level(..), Message)+import qualified System.Log.Simple.Base as Log+import System.Directory (removeDirectoryRecursive, createDirectoryIfMissing)+import System.FilePath++import qualified Control.Concurrent.FiniteChan as F+import System.Directory.Paths (canonicalize)+import qualified System.Directory.Watcher as Watcher+import Text.Format ((~~), (~%))++import qualified HsDev.Cache as Cache+import qualified HsDev.Cache.Structured as SC+import qualified HsDev.Client.Commands as Client+import HsDev.Database+import qualified HsDev.Database.Async as DB+import qualified HsDev.Database.Update as Update+import HsDev.Inspect (getDefines)+import HsDev.Tools.Ghc.Worker+import HsDev.Server.Types+import HsDev.Server.Message+import HsDev.Util++#if mingw32_HOST_OS+import System.Win32.FileMapping.NamePool+#endif++-- | Inits log chan and returns functions (print message, wait channel)+initLog :: ServerOpts -> IO SessionLog+initLog sopts = do+ msgs <- F.newChan+ l <- newLog (logCfg [("", Log.level_ . T.pack . serverLogLevel $ sopts)]) $ concat [+ [handler text console | not $ serverSilent sopts],+ [handler text (chaner msgs)],+ [handler text (file f) | f <- maybeToList (serverLog sopts)]]+ let+ listenLog = F.dupChan msgs >>= F.readChan+ return $ SessionLog l listenLog (stopLog l)++-- | Run server+runServer :: ServerOpts -> ServerM IO () -> IO ()+runServer sopts act = bracket (initLog sopts) sessionLogWait $ \slog -> Watcher.withWatcher $ \watcher -> withLog (sessionLogger slog) $ do+ waitSem <- liftIO $ newQSem 0+ db <- liftIO $ DB.newAsync+ withCache sopts () $ \cdir -> do+ sendLog Log.Trace $ "Checking cache version in {}" ~~ cdir + ver <- liftIO $ Cache.readVersion $ cdir </> Cache.versionCache+ sendLog Log.Debug $ "Cache version: {}" ~~ strVersion ver+ unless (sameVersion (cutVersion version) (cutVersion ver)) $ ignoreIO $ do+ sendLog Log.Info $ "Cache version ({cache}) is incompatible with hsdev version ({hsdev}), removing cache ({dir})" ~~+ ("cache" ~% strVersion ver) ~~+ ("hsdev" ~% strVersion version) ~~+ ("dir" ~% cdir)+ -- drop cache+ liftIO $ removeDirectoryRecursive cdir+ sendLog Log.Debug $ "Writing new cache version: {}" ~~ strVersion version+ liftIO $ createDirectoryIfMissing True cdir+ liftIO $ Cache.writeVersion $ cdir </> Cache.versionCache+ when (serverLoad sopts) $ withCache sopts () $ \cdir -> do+ sendLog Log.Info $ "Loading cache from {}" ~~ cdir+ dbCache <- liftA merge <$> liftIO (SC.load cdir)+ case dbCache of+ Left err -> sendLog Log.Error $ "Failed to load cache: {}" ~~ err+ Right dbCache' -> DB.update db (return dbCache')+#if mingw32_HOST_OS+ mmapPool <- Just <$> liftIO (createPool "hsdev")+#endif+ ghcw <- ghcWorker+ defs <- liftIO getDefines+ let+ session = Session+ db+ (writeCache sopts)+ (readCache sopts)+ slog+ watcher+#if mingw32_HOST_OS+ mmapPool+#endif+ ghcw+ (do+ withLog (sessionLogger slog) $ sendLog Log.Trace "stopping server"+ signalQSem waitSem)+ (waitQSem waitSem)+ defs+ _ <- liftIO $ forkIO $ Update.onEvent watcher $ \w e -> withSession session $+ void $ Client.runClient def $ Update.processEvent def w e+ liftIO $ runReaderT (runServerM act) session++type Server = Worker (ServerM IO)++startServer :: ServerOpts -> IO Server+startServer sopts = startWorker (runServer sopts) id id++inServer :: Server -> CommandOptions -> Command -> IO Result+inServer srv copts c = do+ c' <- canonicalize c+ inWorker srv (Client.runClient copts $ Client.runCommand c')++chaner :: F.Chan String -> Consumer Text+chaner ch = return $ F.putChan ch . T.unpack++-- | Perform action on cache+withCache :: Monad m => ServerOpts -> a -> (FilePath -> m a) -> m a+withCache sopts v onCache = case serverCache sopts of+ Nothing -> return v+ Just cdir -> onCache cdir++writeCache :: SessionMonad m => ServerOpts -> Database -> m ()+writeCache sopts db = withCache sopts () $ \cdir -> do+ sendLog Log.Info $ "writing cache to {}" ~~ cdir+ logIO "cache writing exception: " (sendLog Log.Error . fromString) $ do+ let+ sd = structurize db+ liftIO $ SC.dump cdir sd+ forM_ (M.keys (structuredPackageDbs sd)) $ \c -> sendLog Log.Debug ("cache write: cabal {}" ~~ show c)+ forM_ (M.keys (structuredProjects sd)) $ \p -> sendLog Log.Debug ("cache write: project {}" ~~ p)+ case allModules (structuredFiles sd) of+ [] -> return ()+ ms -> sendLog Log.Debug $ "cache write: {} files" ~~ length ms+ sendLog Log.Info $ "cache saved to {}" ~~ cdir++readCache :: SessionMonad m => ServerOpts -> (FilePath -> ExceptT String IO Structured) -> m (Maybe Database)+readCache sopts act = do+ s <- getSession+ liftIO $ withSession s $ withCache sopts Nothing $ \fpath -> do+ res <- liftIO $ runExceptT $ act fpath+ either cacheErr cacheOk res+ where+ cacheErr e = sendLog Log.Error ("Error reading cache: {}" ~~ e) >> return Nothing+ cacheOk s = do+ forM_ (M.keys (structuredPackageDbs s)) $ \c -> sendLog Log.Debug ("cache read: cabal {}" ~~ show c)+ forM_ (M.keys (structuredProjects s)) $ \p -> sendLog Log.Debug ("cache read: project {}" ~~ p)+ case allModules (structuredFiles s) of+ [] -> return ()+ ms -> sendLog Log.Debug $ "cache read: {} files" ~~ length ms+ return $ Just $ merge s
src/HsDev/Server/Commands.hs view
@@ -1,434 +1,434 @@-{-# LANGUAGE OverloadedStrings, CPP, PatternGuards, LambdaCase, TemplateHaskell #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Server.Commands ( - ServerCommand(..), ServerOpts(..), ClientOpts(..), - Request(..), - Msg, isLisp, msg, jsonMsg, lispMsg, encodeMessage, decodeMessage, - sendCommand, runServerCommand, - findPath, - processRequest, processClient, processClientSocket, - module HsDev.Server.Types - ) where - -import Control.Applicative -import Control.Concurrent -import Control.Concurrent.Async -import Control.Lens (set, traverseOf, view, over, Lens', Lens, _1, _2, _Left) -import Control.Monad -import Control.Monad.Catch (bracket, finally) -import Data.Aeson hiding (Result, Error) -import Data.Aeson.Encode.Pretty -import qualified Data.ByteString.Char8 as BS -import Data.ByteString.Lazy.Char8 (ByteString) -import qualified Data.ByteString.Lazy.Char8 as L -import Data.Maybe -import Data.String (fromString) -import qualified Data.Text as T (pack) -import Network.Socket hiding (connect) -import qualified Network.Socket as Net hiding (send) -import qualified Network.Socket.ByteString as Net (send) -import qualified Network.Socket.ByteString.Lazy as Net (getContents) -import System.Directory -import System.Exit -import System.FilePath -import System.IO -import qualified System.Log.Simple as Log - -import Control.Concurrent.Util -import qualified Control.Concurrent.FiniteChan as F -import Data.Lisp -import Text.Format ((~~), (~%)) -import System.Directory.Paths - -import qualified HsDev.Client.Commands as Client -import qualified HsDev.Database.Async as DB -import HsDev.Server.Base -import HsDev.Server.Types -import HsDev.Tools.Base (runTool_) -import HsDev.Error -import HsDev.Util -import HsDev.Version - -#if mingw32_HOST_OS -import Data.Aeson.Types hiding (Result, Error) -import Data.Char -import Data.List -import System.Environment -import System.Win32.FileMapping.Memory (withMapFile, readMapFile) -import System.Win32.FileMapping.NamePool -import System.Win32.PowerShell (escape, quote, quoteDouble) -#else -import Control.Exception (SomeException, handle) -import System.Posix.Process -import System.Posix.Files (removeLink) -import System.Posix.IO -#endif - -sendCommand :: ClientOpts -> Bool -> Command -> (Notification -> IO a) -> IO Result -sendCommand copts noFile c onNotification = do - asyncAct <- async sendReceive - res <- waitCatch asyncAct - case res of - Left e -> return $ Error $ OtherError (show e) - Right r -> return r - where - sendReceive = do - curDir <- getCurrentDirectory - input <- if clientStdin copts - then Just <$> L.getContents - else return $ toUtf8 <$> Nothing -- arg "data" copts - let - parseData :: L.ByteString -> IO Value - parseData cts = case eitherDecode cts of - Left err -> putStrLn ("Invalid data: " ++ err) >> exitFailure - Right v -> return v - _ <- traverse parseData input -- FIXME: Not used! - - s <- makeSocket (clientPort copts) - addr' <- inet_addr "127.0.0.1" - Net.connect s (sockAddr (clientPort copts) addr') - bracket (socketToHandle s ReadWriteMode) hClose $ \h -> do - L.hPutStrLn h $ encode $ Message Nothing $ Request c curDir noFile (clientTimeout copts) (clientSilent copts) - hFlush h - peekResponse h - - peekResponse h = do - resp <- hGetLineBS h - parseResponse h resp - - parseResponse h str = case eitherDecode str of - Left e -> return $ Error $ ResponseError ("can't parse: {}" ~~ e) (fromUtf8 str) - Right (Message _ r) -> do - Response r' <- unMmap r - case r' of - Left n -> onNotification n >> peekResponse h - Right res -> return res - -runServerCommand :: ServerCommand -> IO () -runServerCommand Version = putStrLn $cabalVersion -runServerCommand (Start sopts) = do -#if mingw32_HOST_OS - let - args = "run" : serverOptsArgs sopts - myExe <- getExecutablePath - curDir <- getCurrentDirectory - let - -- one escape for start-process and other for callable process - -- seems, that start-process just concats arguments into one string - -- start-process foo 'bar baz' ⇒ foo bar baz -- not expected - -- start-process foo '"bar baz"' ⇒ foo "bar baz" -- ok - biescape = escape quote . escape quoteDouble - script = "try {{ start-process {process} {args} -WindowStyle Hidden -WorkingDirectory {dir} }} catch {{ $_.Exception, $_.InvocationInfo.Line }}" - ~~ ("process" ~% escape quote myExe) - ~~ ("args" ~% intercalate ", " (map biescape args)) - ~~ ("dir" ~% escape quote curDir) - r <- runTool_ "powershell" [ - "-Command", - script] - if all isSpace r - then putStrLn $ "Server started at port {}" ~~ serverPort sopts - else mapM_ putStrLn [ - "Failed to start server", - "\tCommand: {}" ~~ script, - "\tResult: {}" ~~ r] -#else - let - forkError :: SomeException -> IO () - forkError e = putStrLn $ "Failed to start server: {}" ~~ show e - - proxy :: IO () - proxy = do - _ <- createSession - _ <- forkProcess serverAction - exitImmediately ExitSuccess - - serverAction :: IO () - serverAction = do - mapM_ closeFd [stdInput, stdOutput, stdError] - nullFd <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags - mapM_ (dupTo nullFd) [stdInput, stdOutput, stdError] - closeFd nullFd - runServerCommand (Run sopts) - - handle forkError $ do - _ <- forkProcess proxy - putStrLn $ "Server started at port {}" ~~ serverPort sopts -#endif -runServerCommand (Run sopts) = runServer sopts $ do - q <- liftIO $ newQSem 0 - clientChan <- liftIO F.newChan - session <- getSession - _ <- liftIO $ async $ withSession session $ Log.scope "listener" $ flip finally serverExit $ - bracket (liftIO $ makeSocket (serverPort sopts)) (liftIO . close) $ \s -> do - liftIO $ do - setSocketOption s ReuseAddr 1 - addr' <- inet_addr "127.0.0.1" - bind s (sockAddr (serverPort sopts) addr') - listen s maxListenQueue - forever $ logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $ do - Log.sendLog Log.Trace "accepting connection..." - liftIO $ signalQSem q - (s', addr') <- liftIO $ accept s - Log.sendLog Log.Trace $ "accepted {}" ~~ show addr' - void $ liftIO $ forkIO $ withSession session $ Log.scope (T.pack $ show addr') $ - logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $ - flip finally (liftIO $ close s') $ - bracket (liftIO newEmptyMVar) (liftIO . (`putMVar` ())) $ \done -> do - me <- liftIO myThreadId - let - timeoutWait = withSession session $ do - notDone <- liftIO $ isEmptyMVar done - when notDone $ do - Log.sendLog Log.Trace $ "waiting for {} to complete" ~~ show addr' - waitAsync <- liftIO $ async $ do - threadDelay 1000000 - killThread me - liftIO $ void $ waitCatch waitAsync - liftIO $ F.putChan clientChan timeoutWait - processClientSocket (show addr') s' - - Log.sendLog Log.Trace "waiting for starting accept thread..." - liftIO $ waitQSem q - liftIO $ putStrLn $ "Server started at port {}" ~~ serverPort sopts - Log.sendLog Log.Info $ "server started at port {}" ~~ serverPort sopts - Log.sendLog Log.Trace "waiting for accept thread..." - serverWait - Log.sendLog Log.Trace "accept thread stopped" - liftIO $ unlink (serverPort sopts) - askSession sessionDatabase >>= liftIO . DB.readAsync >>= writeCache sopts - Log.sendLog Log.Trace "waiting for clients..." - liftIO (F.stopChan clientChan) >>= sequence_ - Log.sendLog Log.Info "server stopped" -runServerCommand (Stop copts) = runServerCommand (Remote copts False Exit) -runServerCommand (Connect copts) = do - curDir <- getCurrentDirectory - s <- makeSocket $ clientPort copts - addr' <- inet_addr "127.0.0.1" - Net.connect s $ sockAddr (clientPort copts) addr' - 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 $ 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 - where - waitResp h = do - resp <- hGetLineBS h - parseResp h resp - - parseResp h str = case decodeMessage str of - Left em -> putStrLn $ "Can't decode response: {}" ~~ view msg em - Right m -> do - Response r' <- unMmap $ view (msg . message) m - putStrLn $ "{id}: {response}" - ~~ ("id" ~% fromMaybe "_" (view (msg . messageId) m)) - ~~ ("response" ~% fromUtf8 (encodeMsg $ set msg (Response r') m)) - case unResponse (view (msg . message) m) of - Left _ -> waitResp h - _ -> return () -runServerCommand (Remote copts noFile c) = sendCommand copts noFile c printValue >>= printResult where - printValue :: ToJSON a => a -> IO () - printValue = L.putStrLn . encodeValue - printResult :: Result -> IO () - printResult (Result r) = printValue r - printResult e = printValue e - encodeValue :: ToJSON a => a -> L.ByteString - encodeValue = if clientPretty copts then encodePretty else encode - -findPath :: MonadIO m => CommandOptions -> FilePath -> m FilePath -findPath copts f = liftIO $ canonicalizePath (normalise f') where - f' - | isRelative f = commandOptionsRoot copts </> f - | otherwise = f - -type Msg a = (Bool, a) - -isLisp :: Lens' (Msg a) Bool -isLisp = _1 - -msg :: Lens (Msg a) (Msg b) a b -msg = _2 - -jsonMsg :: a -> Msg a -jsonMsg = (,) False - -lispMsg :: a -> Msg a -lispMsg = (,) True - --- | Decode lisp or json -decodeMsg :: FromJSON a => ByteString -> Either (Msg String) (Msg a) -decodeMsg bstr = over _Left decodeType' decodeMsg' where - decodeType' - | isLisp' = lispMsg - | otherwise = jsonMsg - decodeMsg' = (lispMsg <$> decodeLisp bstr) <|> (jsonMsg <$> eitherDecode bstr) - isLisp' = fromMaybe False $ mplus (try' eitherDecode False) (try' decodeLisp True) - try' :: (ByteString -> Either String Value) -> Bool -> Maybe Bool - try' f l = either (const Nothing) (const $ Just l) $ f bstr - --- | Encode lisp or json -encodeMsg :: ToJSON a => Msg a -> ByteString -encodeMsg m - | view isLisp m = encodeLisp $ view msg m - | otherwise = encode $ view msg m - --- | Decode lisp or json request -decodeMessage :: FromJSON a => ByteString -> Either (Msg String) (Msg (Message a)) -decodeMessage = decodeMsg - -encodeMessage :: ToJSON a => Msg (Message a) -> ByteString -encodeMessage = encodeMsg - --- | Process request, notifications can be sent during processing -processRequest :: SessionMonad m => CommandOptions -> Command -> m Result -processRequest copts c = do - c' <- paths (findPath copts) c - s <- getSession - withSession s $ Client.runClient copts $ Client.runCommand c' - --- | Process client, listen for requests and process them -processClient :: SessionMonad m => String -> F.Chan ByteString -> (ByteString -> IO ()) -> m () -processClient name rchan send' = do - Log.sendLog Log.Info "connected" - respChan <- liftIO newChan - liftIO $ void $ forkIO $ getChanContents respChan >>= mapM_ (send' . encodeMessage) - linkVar <- liftIO $ newMVar $ return () - s <- getSession - exit <- askSession sessionExit - let - answer :: SessionMonad m => Msg (Message Response) -> m () - answer m = do - unless (isNotification $ view (msg . message) m) $ - Log.sendLog Log.Trace $ "responsed << {}" ~~ ellipsis (fromUtf8 (encode $ view (msg . message) m)) - liftIO $ writeChan respChan m - where - ellipsis :: String -> String - ellipsis str - | length str < 100 = str - | otherwise = take 100 str ++ "..." - -- flip finally (disconnected linkVar) $ forever $ Log.scopeLog (commandLogger copts) (T.pack name) $ do - reqs <- liftIO $ F.readChan rchan - flip finally (disconnected linkVar) $ - forM_ reqs $ \req' -> do - Log.sendLog Log.Trace $ "received >> {}" ~~ fromUtf8 req' - case decodeMessage req' of - Left em -> do - Log.sendLog Log.Warning $ "Invalid request {}" ~~ fromUtf8 req' - answer $ set msg (Message Nothing $ responseError $ RequestError "invalid request" $ fromUtf8 req') em - Right m -> void $ liftIO $ forkIO $ withSession s $ Log.scope (T.pack name) $ Log.scope "req" $ - Log.scope (T.pack $ fromMaybe "_" (view (msg . messageId) m)) $ do - resp' <- flip (traverseOf (msg . message)) m $ \(Request c cdir noFile tm silent) -> do - let - onNotify n - | silent = return () - | otherwise = traverseOf (msg . message) (const $ mmap' noFile (Response $ Left n)) m >>= answer - Log.sendLog Log.Trace $ "requested >> {}" ~~ fromUtf8 (encode c) - resp <- liftIO $ fmap (Response . Right) $ handleTimeout tm $ hsdevLiftIO $ withSession s $ - processRequest - CommandOptions { - commandOptionsRoot = cdir, - commandOptionsNotify = withSession s . onNotify, - commandOptionsLink = void (swapMVar linkVar exit), - commandOptionsHold = forever (F.getChan rchan) } - c - mmap' noFile resp - answer resp' - where - handleTimeout :: Int -> IO Result -> IO Result - handleTimeout 0 = id - handleTimeout tm = fmap (fromMaybe $ Error $ OtherError "timeout") . timeout tm - - mmap' :: SessionMonad m => Bool -> Response -> m Response -#if mingw32_HOST_OS - mmap' False r = do - mpool <- askSession sessionMmapPool - case mpool of - Just pool -> liftIO $ mmap pool r - Nothing -> return r -#endif - mmap' _ r = return r - - -- Call on disconnected, either no action or exit command - disconnected :: SessionMonad m => MVar (IO ()) -> m () - disconnected var = do - Log.sendLog Log.Info "disconnected" - liftIO $ join $ takeMVar var - --- | Process client by socket -processClientSocket :: SessionMonad m => String -> Socket -> m () -processClientSocket name s = do - recvChan <- liftIO F.newChan - liftIO $ void $ forkIO $ finally - (Net.getContents s >>= mapM_ (F.putChan recvChan) . L.lines) - (F.closeChan recvChan) - processClient name recvChan (sendLine s) - where - -- NOTE: Network version of `sendAll` goes to infinite loop on client socket close - -- when server's send is blocked, see https://github.com/haskell/network/issues/155 - -- After that issue fixed we may revert to `processClientHandle` - sendLine :: Socket -> ByteString -> IO () - sendLine sock bs = sendAll sock $ L.toStrict $ L.snoc bs '\n' - sendAll :: Socket -> BS.ByteString -> IO () - sendAll sock bs - | BS.null bs = return () - | otherwise = do - sent <- Net.send sock bs - when (sent > 0) $ sendAll sock (BS.drop sent bs) - -#if mingw32_HOST_OS -data MmapFile = MmapFile String - -instance ToJSON MmapFile where - toJSON (MmapFile f) = object ["file" .= f] - -instance FromJSON MmapFile where - parseJSON = withObject "file" $ \v -> MmapFile <$> v .:: "file" - --- | Push message to mmap and return response which points to this mmap -mmap :: Pool -> Response -> IO Response -mmap mmapPool r - | L.length msg' <= 1024 = return r - | otherwise = do - rvar <- newEmptyMVar - _ <- forkIO $ flip finally (tryPutMVar rvar r) $ void $ withName mmapPool $ \mmapName -> runExceptT $ catchError - (withMapFile mmapName (L.toStrict msg') $ liftIO $ do - _ <- tryPutMVar rvar $ result $ MmapFile mmapName - -- give 10 seconds for client to read data - threadDelay 10000000) - (\_ -> liftIO $ void $ tryPutMVar rvar r) - takeMVar rvar - where - msg' = encode r -#endif - --- | If response points to mmap, get its contents and parse -unMmap :: Response -> IO Response -#if mingw32_HOST_OS -unMmap (Response (Right (Result v))) - | Just (MmapFile f) <- parseMaybe parseJSON v = do - cts <- runExceptT (fmap L.fromStrict (readMapFile f)) - case cts of - Left _ -> return $ responseError $ ResponseError "can't read map view of file" f - Right r' -> case eitherDecode r' of - Left e' -> return $ responseError $ ResponseError ("can't parse response: {}" ~~ e') (fromUtf8 r') - Right r'' -> return r'' -#endif -unMmap r = return r - -makeSocket :: ConnectionPort -> IO Socket -makeSocket (NetworkPort _) = socket AF_INET Stream defaultProtocol -makeSocket (UnixPort _) = socket AF_UNIX Stream defaultProtocol - -sockAddr :: ConnectionPort -> HostAddress -> SockAddr -sockAddr (NetworkPort p) addr = SockAddrInet (fromIntegral p) addr -sockAddr (UnixPort s) _ = SockAddrUnix s - -unlink :: ConnectionPort -> IO () -unlink (NetworkPort _) = return () -#if mingw32_HOST_OS -unlink (UnixPort _) = return () -#else -unlink (UnixPort s) = removeLink s -#endif +{-# LANGUAGE OverloadedStrings, CPP, PatternGuards, LambdaCase, TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Server.Commands (+ ServerCommand(..), ServerOpts(..), ClientOpts(..),+ Request(..),+ Msg, isLisp, msg, jsonMsg, lispMsg, encodeMessage, decodeMessage,+ sendCommand, runServerCommand,+ findPath,+ processRequest, processClient, processClientSocket,+ module HsDev.Server.Types+ ) where++import Control.Applicative+import Control.Concurrent+import Control.Concurrent.Async+import Control.Lens (set, traverseOf, view, over, Lens', Lens, _1, _2, _Left)+import Control.Monad+import Control.Monad.Catch (bracket, finally)+import Data.Aeson hiding (Result, Error)+import Data.Aeson.Encode.Pretty+import qualified Data.ByteString.Char8 as BS+import Data.ByteString.Lazy.Char8 (ByteString)+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Maybe+import Data.String (fromString)+import qualified Data.Text as T (pack)+import Network.Socket hiding (connect)+import qualified Network.Socket as Net hiding (send)+import qualified Network.Socket.ByteString as Net (send)+import qualified Network.Socket.ByteString.Lazy as Net (getContents)+import System.Directory+import System.Exit+import System.FilePath+import System.IO+import qualified System.Log.Simple as Log++import Control.Concurrent.Util+import qualified Control.Concurrent.FiniteChan as F+import Data.Lisp+import Text.Format ((~~), (~%))+import System.Directory.Paths++import qualified HsDev.Client.Commands as Client+import qualified HsDev.Database.Async as DB+import HsDev.Server.Base+import HsDev.Server.Types+import HsDev.Tools.Base (runTool_)+import HsDev.Error+import HsDev.Util+import HsDev.Version++#if mingw32_HOST_OS+import Data.Aeson.Types hiding (Result, Error)+import Data.Char+import Data.List+import System.Environment+import System.Win32.FileMapping.Memory (withMapFile, readMapFile)+import System.Win32.FileMapping.NamePool+import System.Win32.PowerShell (escape, quote, quoteDouble)+#else+import Control.Exception (SomeException, handle)+import System.Posix.Process+import System.Posix.Files (removeLink)+import System.Posix.IO+#endif++sendCommand :: ClientOpts -> Bool -> Command -> (Notification -> IO a) -> IO Result+sendCommand copts noFile c onNotification = do+ asyncAct <- async sendReceive+ res <- waitCatch asyncAct+ case res of+ Left e -> return $ Error $ OtherError (show e)+ Right r -> return r+ where+ sendReceive = do+ curDir <- getCurrentDirectory+ input <- if clientStdin copts+ then Just <$> L.getContents+ else return $ toUtf8 <$> Nothing -- arg "data" copts+ let+ parseData :: L.ByteString -> IO Value+ parseData cts = case eitherDecode cts of+ Left err -> putStrLn ("Invalid data: " ++ err) >> exitFailure+ Right v -> return v+ _ <- traverse parseData input -- FIXME: Not used!++ s <- makeSocket (clientPort copts)+ addr' <- inet_addr "127.0.0.1"+ Net.connect s (sockAddr (clientPort copts) addr')+ bracket (socketToHandle s ReadWriteMode) hClose $ \h -> do+ L.hPutStrLn h $ encode $ Message Nothing $ Request c curDir noFile (clientTimeout copts) (clientSilent copts)+ hFlush h+ peekResponse h++ peekResponse h = do+ resp <- hGetLineBS h+ parseResponse h resp++ parseResponse h str = case eitherDecode str of+ Left e -> return $ Error $ ResponseError ("can't parse: {}" ~~ e) (fromUtf8 str)+ Right (Message _ r) -> do+ Response r' <- unMmap r+ case r' of+ Left n -> onNotification n >> peekResponse h+ Right res -> return res++runServerCommand :: ServerCommand -> IO ()+runServerCommand Version = putStrLn $cabalVersion+runServerCommand (Start sopts) = do+#if mingw32_HOST_OS+ let+ args = "run" : serverOptsArgs sopts+ myExe <- getExecutablePath+ curDir <- getCurrentDirectory+ let+ -- one escape for start-process and other for callable process+ -- seems, that start-process just concats arguments into one string+ -- start-process foo 'bar baz' ⇒ foo bar baz -- not expected+ -- start-process foo '"bar baz"' ⇒ foo "bar baz" -- ok+ biescape = escape quote . escape quoteDouble+ script = "try {{ start-process {process} {args} -WindowStyle Hidden -WorkingDirectory {dir} }} catch {{ $_.Exception, $_.InvocationInfo.Line }}"+ ~~ ("process" ~% escape quote myExe)+ ~~ ("args" ~% intercalate ", " (map biescape args))+ ~~ ("dir" ~% escape quote curDir)+ r <- runTool_ "powershell" [+ "-Command",+ script]+ if all isSpace r+ then putStrLn $ "Server started at port {}" ~~ serverPort sopts+ else mapM_ putStrLn [+ "Failed to start server",+ "\tCommand: {}" ~~ script,+ "\tResult: {}" ~~ r]+#else+ let+ forkError :: SomeException -> IO ()+ forkError e = putStrLn $ "Failed to start server: {}" ~~ show e++ proxy :: IO ()+ proxy = do+ _ <- createSession+ _ <- forkProcess serverAction+ exitImmediately ExitSuccess++ serverAction :: IO ()+ serverAction = do+ mapM_ closeFd [stdInput, stdOutput, stdError]+ nullFd <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags+ mapM_ (dupTo nullFd) [stdInput, stdOutput, stdError]+ closeFd nullFd+ runServerCommand (Run sopts)++ handle forkError $ do+ _ <- forkProcess proxy+ putStrLn $ "Server started at port {}" ~~ serverPort sopts+#endif+runServerCommand (Run sopts) = runServer sopts $ do+ q <- liftIO $ newQSem 0+ clientChan <- liftIO F.newChan+ session <- getSession+ _ <- liftIO $ async $ withSession session $ Log.scope "listener" $ flip finally serverExit $+ bracket (liftIO $ makeSocket (serverPort sopts)) (liftIO . close) $ \s -> do+ liftIO $ do+ setSocketOption s ReuseAddr 1+ addr' <- inet_addr "127.0.0.1"+ bind s (sockAddr (serverPort sopts) addr')+ listen s maxListenQueue+ forever $ logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $ do+ Log.sendLog Log.Trace "accepting connection..."+ liftIO $ signalQSem q+ (s', addr') <- liftIO $ accept s+ Log.sendLog Log.Trace $ "accepted {}" ~~ show addr'+ void $ liftIO $ forkIO $ withSession session $ Log.scope (T.pack $ show addr') $+ logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $+ flip finally (liftIO $ close s') $+ bracket (liftIO newEmptyMVar) (liftIO . (`putMVar` ())) $ \done -> do+ me <- liftIO myThreadId+ let+ timeoutWait = withSession session $ do+ notDone <- liftIO $ isEmptyMVar done+ when notDone $ do+ Log.sendLog Log.Trace $ "waiting for {} to complete" ~~ show addr'+ waitAsync <- liftIO $ async $ do+ threadDelay 1000000+ killThread me+ liftIO $ void $ waitCatch waitAsync+ liftIO $ F.putChan clientChan timeoutWait+ processClientSocket (show addr') s'++ Log.sendLog Log.Trace "waiting for starting accept thread..."+ liftIO $ waitQSem q+ liftIO $ putStrLn $ "Server started at port {}" ~~ serverPort sopts+ Log.sendLog Log.Info $ "server started at port {}" ~~ serverPort sopts+ Log.sendLog Log.Trace "waiting for accept thread..."+ serverWait+ Log.sendLog Log.Trace "accept thread stopped"+ liftIO $ unlink (serverPort sopts)+ askSession sessionDatabase >>= liftIO . DB.readAsync >>= writeCache sopts+ Log.sendLog Log.Trace "waiting for clients..."+ liftIO (F.stopChan clientChan) >>= sequence_+ Log.sendLog Log.Info "server stopped"+runServerCommand (Stop copts) = runServerCommand (Remote copts False Exit)+runServerCommand (Connect copts) = do+ curDir <- getCurrentDirectory+ s <- makeSocket $ clientPort copts+ addr' <- inet_addr "127.0.0.1"+ Net.connect s $ sockAddr (clientPort copts) addr'+ 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 $ 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+ where+ waitResp h = do+ resp <- hGetLineBS h+ parseResp h resp++ parseResp h str = case decodeMessage str of+ Left em -> putStrLn $ "Can't decode response: {}" ~~ view msg em+ Right m -> do+ Response r' <- unMmap $ view (msg . message) m+ putStrLn $ "{id}: {response}"+ ~~ ("id" ~% fromMaybe "_" (view (msg . messageId) m))+ ~~ ("response" ~% fromUtf8 (encodeMsg $ set msg (Response r') m))+ case unResponse (view (msg . message) m) of+ Left _ -> waitResp h+ _ -> return ()+runServerCommand (Remote copts noFile c) = sendCommand copts noFile c printValue >>= printResult where+ printValue :: ToJSON a => a -> IO ()+ printValue = L.putStrLn . encodeValue+ printResult :: Result -> IO ()+ printResult (Result r) = printValue r+ printResult e = printValue e+ encodeValue :: ToJSON a => a -> L.ByteString+ encodeValue = if clientPretty copts then encodePretty else encode++findPath :: MonadIO m => CommandOptions -> FilePath -> m FilePath+findPath copts f = liftIO $ canonicalizePath (normalise f') where+ f'+ | isRelative f = commandOptionsRoot copts </> f+ | otherwise = f++type Msg a = (Bool, a)++isLisp :: Lens' (Msg a) Bool+isLisp = _1++msg :: Lens (Msg a) (Msg b) a b+msg = _2++jsonMsg :: a -> Msg a+jsonMsg = (,) False++lispMsg :: a -> Msg a+lispMsg = (,) True++-- | Decode lisp or json+decodeMsg :: FromJSON a => ByteString -> Either (Msg String) (Msg a)+decodeMsg bstr = over _Left decodeType' decodeMsg' where+ decodeType'+ | isLisp' = lispMsg+ | otherwise = jsonMsg+ decodeMsg' = (lispMsg <$> decodeLisp bstr) <|> (jsonMsg <$> eitherDecode bstr)+ isLisp' = fromMaybe False $ mplus (try' eitherDecode False) (try' decodeLisp True)+ try' :: (ByteString -> Either String Value) -> Bool -> Maybe Bool+ try' f l = either (const Nothing) (const $ Just l) $ f bstr++-- | Encode lisp or json+encodeMsg :: ToJSON a => Msg a -> ByteString+encodeMsg m+ | view isLisp m = encodeLisp $ view msg m+ | otherwise = encode $ view msg m++-- | Decode lisp or json request+decodeMessage :: FromJSON a => ByteString -> Either (Msg String) (Msg (Message a))+decodeMessage = decodeMsg++encodeMessage :: ToJSON a => Msg (Message a) -> ByteString+encodeMessage = encodeMsg++-- | Process request, notifications can be sent during processing+processRequest :: SessionMonad m => CommandOptions -> Command -> m Result+processRequest copts c = do+ c' <- paths (findPath copts) c+ s <- getSession+ withSession s $ Client.runClient copts $ Client.runCommand c'++-- | Process client, listen for requests and process them+processClient :: SessionMonad m => String -> F.Chan ByteString -> (ByteString -> IO ()) -> m ()+processClient name rchan send' = do+ Log.sendLog Log.Info "connected"+ respChan <- liftIO newChan+ liftIO $ void $ forkIO $ getChanContents respChan >>= mapM_ (send' . encodeMessage)+ linkVar <- liftIO $ newMVar $ return ()+ s <- getSession+ exit <- askSession sessionExit+ let+ answer :: SessionMonad m => Msg (Message Response) -> m ()+ answer m = do+ unless (isNotification $ view (msg . message) m) $+ Log.sendLog Log.Trace $ "responsed << {}" ~~ ellipsis (fromUtf8 (encode $ view (msg . message) m))+ liftIO $ writeChan respChan m+ where+ ellipsis :: String -> String+ ellipsis str+ | length str < 100 = str+ | otherwise = take 100 str ++ "..."+ -- flip finally (disconnected linkVar) $ forever $ Log.scopeLog (commandLogger copts) (T.pack name) $ do+ reqs <- liftIO $ F.readChan rchan+ flip finally (disconnected linkVar) $+ forM_ reqs $ \req' -> do+ Log.sendLog Log.Trace $ "received >> {}" ~~ fromUtf8 req'+ case decodeMessage req' of+ Left em -> do+ Log.sendLog Log.Warning $ "Invalid request {}" ~~ fromUtf8 req'+ answer $ set msg (Message Nothing $ responseError $ RequestError "invalid request" $ fromUtf8 req') em+ Right m -> void $ liftIO $ forkIO $ withSession s $ Log.scope (T.pack name) $ Log.scope "req" $+ Log.scope (T.pack $ fromMaybe "_" (view (msg . messageId) m)) $ do+ resp' <- flip (traverseOf (msg . message)) m $ \(Request c cdir noFile tm silent) -> do+ let+ onNotify n+ | silent = return ()+ | otherwise = traverseOf (msg . message) (const $ mmap' noFile (Response $ Left n)) m >>= answer+ Log.sendLog Log.Trace $ "requested >> {}" ~~ fromUtf8 (encode c)+ resp <- liftIO $ fmap (Response . Right) $ handleTimeout tm $ hsdevLiftIO $ withSession s $+ processRequest+ CommandOptions {+ commandOptionsRoot = cdir,+ commandOptionsNotify = withSession s . onNotify,+ commandOptionsLink = void (swapMVar linkVar exit),+ commandOptionsHold = forever (F.getChan rchan) }+ c+ mmap' noFile resp+ answer resp'+ where+ handleTimeout :: Int -> IO Result -> IO Result+ handleTimeout 0 = id+ handleTimeout tm = fmap (fromMaybe $ Error $ OtherError "timeout") . timeout tm++ mmap' :: SessionMonad m => Bool -> Response -> m Response+#if mingw32_HOST_OS+ mmap' False r = do+ mpool <- askSession sessionMmapPool+ case mpool of+ Just pool -> liftIO $ mmap pool r+ Nothing -> return r+#endif+ mmap' _ r = return r++ -- Call on disconnected, either no action or exit command+ disconnected :: SessionMonad m => MVar (IO ()) -> m ()+ disconnected var = do+ Log.sendLog Log.Info "disconnected"+ liftIO $ join $ takeMVar var++-- | Process client by socket+processClientSocket :: SessionMonad m => String -> Socket -> m ()+processClientSocket name s = do+ recvChan <- liftIO F.newChan+ liftIO $ void $ forkIO $ finally+ (Net.getContents s >>= mapM_ (F.putChan recvChan) . L.lines)+ (F.closeChan recvChan)+ processClient name recvChan (sendLine s)+ where+ -- NOTE: Network version of `sendAll` goes to infinite loop on client socket close+ -- when server's send is blocked, see https://github.com/haskell/network/issues/155+ -- After that issue fixed we may revert to `processClientHandle`+ sendLine :: Socket -> ByteString -> IO ()+ sendLine sock bs = sendAll sock $ L.toStrict $ L.snoc bs '\n'+ sendAll :: Socket -> BS.ByteString -> IO ()+ sendAll sock bs+ | BS.null bs = return ()+ | otherwise = do+ sent <- Net.send sock bs+ when (sent > 0) $ sendAll sock (BS.drop sent bs)++#if mingw32_HOST_OS+data MmapFile = MmapFile String++instance ToJSON MmapFile where+ toJSON (MmapFile f) = object ["file" .= f]++instance FromJSON MmapFile where+ parseJSON = withObject "file" $ \v -> MmapFile <$> v .:: "file"++-- | Push message to mmap and return response which points to this mmap+mmap :: Pool -> Response -> IO Response+mmap mmapPool r+ | L.length msg' <= 1024 = return r+ | otherwise = do+ rvar <- newEmptyMVar+ _ <- forkIO $ flip finally (tryPutMVar rvar r) $ void $ withName mmapPool $ \mmapName -> runExceptT $ catchError+ (withMapFile mmapName (L.toStrict msg') $ liftIO $ do+ _ <- tryPutMVar rvar $ result $ MmapFile mmapName+ -- give 10 seconds for client to read data+ threadDelay 10000000)+ (\_ -> liftIO $ void $ tryPutMVar rvar r)+ takeMVar rvar+ where+ msg' = encode r+#endif++-- | If response points to mmap, get its contents and parse+unMmap :: Response -> IO Response+#if mingw32_HOST_OS+unMmap (Response (Right (Result v)))+ | Just (MmapFile f) <- parseMaybe parseJSON v = do+ cts <- runExceptT (fmap L.fromStrict (readMapFile f))+ case cts of+ Left _ -> return $ responseError $ ResponseError "can't read map view of file" f+ Right r' -> case eitherDecode r' of+ Left e' -> return $ responseError $ ResponseError ("can't parse response: {}" ~~ e') (fromUtf8 r')+ Right r'' -> return r''+#endif+unMmap r = return r++makeSocket :: ConnectionPort -> IO Socket+makeSocket (NetworkPort _) = socket AF_INET Stream defaultProtocol+makeSocket (UnixPort _) = socket AF_UNIX Stream defaultProtocol++sockAddr :: ConnectionPort -> HostAddress -> SockAddr+sockAddr (NetworkPort p) addr = SockAddrInet (fromIntegral p) addr+sockAddr (UnixPort s) _ = SockAddrUnix s++unlink :: ConnectionPort -> IO ()+unlink (NetworkPort _) = return ()+#if mingw32_HOST_OS+unlink (UnixPort _) = return ()+#else+unlink (UnixPort s) = removeLink s+#endif
src/HsDev/Server/Message.hs view
@@ -1,115 +1,115 @@-{-# LANGUAGE OverloadedStrings, DeriveFunctor, TypeSynonymInstances, FlexibleInstances, TemplateHaskell #-} - -module HsDev.Server.Message ( - Message(..), messageId, message, - messagesById, - Notification(..), Result(..), ResultPart(..), - Response(..), isNotification, notification, result, responseError, resultPart, - groupResponses, responsesById - ) where - -import Control.Applicative -import Control.Lens (makeLenses) -import Control.Monad (join) -import Data.Aeson hiding (Error, Result) -import Data.Either (lefts, isRight) -import Data.List (unfoldr) - -import HsDev.Types -import HsDev.Util ((.::), (.::?), objectUnion) - --- | Message with id to link request and response -data Message a = Message { - _messageId :: Maybe String, - _message :: a } - deriving (Eq, Ord, Show, Functor) - -makeLenses ''Message - -instance ToJSON a => ToJSON (Message a) where - toJSON (Message i m) = object ["id" .= i] `objectUnion` toJSON m - -instance FromJSON a => FromJSON (Message a) where - parseJSON = withObject "message" $ \v -> - Message <$> fmap join (v .::? "id") <*> parseJSON (Object v) - -instance Foldable Message where - foldMap f (Message _ m) = f m - -instance Traversable Message where - traverse f (Message i m) = Message i <$> f m - --- | Get messages by id -messagesById :: Maybe String -> [Message a] -> [a] -messagesById i = map _message . filter ((== i) . _messageId) - --- | Notification from server -data Notification = Notification Value deriving (Eq, Show) - -instance ToJSON Notification where - toJSON (Notification v) = object ["notify" .= v] - -instance FromJSON Notification where - parseJSON = withObject "notification" $ \v -> Notification <$> v .:: "notify" - --- | Result from server -data Result = - Result Value | - -- ^ Result - Error HsDevError - -- ^ Error - deriving (Show) - -instance ToJSON Result where - toJSON (Result r) = object ["result" .= r] - toJSON (Error e) = toJSON e - -instance FromJSON Result where - parseJSON j = (withObject "result" (\v -> (Result <$> v .:: "result")) j) <|> (Error <$> parseJSON j) - --- | Part of result list, returns via notification -data ResultPart = ResultPart Value - -instance ToJSON ResultPart where - toJSON (ResultPart r) = object ["result-part" .= r] - -instance FromJSON ResultPart where - parseJSON = withObject "result-part" $ \v -> ResultPart <$> v .:: "result-part" - -newtype Response = Response { unResponse :: Either Notification Result } deriving (Show) - -isNotification :: Response -> Bool -isNotification = either (const True) (const False) . unResponse - -notification :: ToJSON a => a -> Response -notification = Response . Left . Notification . toJSON - -result :: ToJSON a => a -> Response -result = Response . Right . Result . toJSON - -responseError :: HsDevError -> Response -responseError = Response . Right . Error - -resultPart :: ToJSON a => a -> Notification -resultPart = Notification . toJSON . ResultPart . toJSON - -instance ToJSON Response where - toJSON (Response (Left n)) = toJSON n - toJSON (Response (Right r)) = toJSON r - -instance FromJSON Response where - parseJSON v = Response <$> ((Left <$> parseJSON v) <|> (Right <$> parseJSON v)) - -groupResponses :: [Response] -> [([Notification], Result)] -groupResponses = unfoldr break' where - break' :: [Response] -> Maybe (([Notification], Result), [Response]) - break' [] = Nothing - break' cs = Just ((lefts (map unResponse ns), r), drop 1 cs') where - (ns, cs') = break (isRight . unResponse) cs - r = case cs' of - (Response (Right r') : _) -> r' - [] -> Error $ OtherError "groupResponses: no result" - _ -> error "groupResponses: impossible happened" - -responsesById :: Maybe String -> [Message Response] -> [([Notification], Result)] -responsesById i = groupResponses . messagesById i +{-# LANGUAGE OverloadedStrings, DeriveFunctor, TypeSynonymInstances, FlexibleInstances, TemplateHaskell #-}++module HsDev.Server.Message (+ Message(..), messageId, message,+ messagesById,+ Notification(..), Result(..), ResultPart(..),+ Response(..), isNotification, notification, result, responseError, resultPart,+ groupResponses, responsesById+ ) where++import Control.Applicative+import Control.Lens (makeLenses)+import Control.Monad (join)+import Data.Aeson hiding (Error, Result)+import Data.Either (lefts, isRight)+import Data.List (unfoldr)++import HsDev.Types+import HsDev.Util ((.::), (.::?), objectUnion)++-- | Message with id to link request and response+data Message a = Message {+ _messageId :: Maybe String,+ _message :: a }+ deriving (Eq, Ord, Show, Functor)++makeLenses ''Message++instance ToJSON a => ToJSON (Message a) where+ toJSON (Message i m) = object ["id" .= i] `objectUnion` toJSON m++instance FromJSON a => FromJSON (Message a) where+ parseJSON = withObject "message" $ \v ->+ Message <$> fmap join (v .::? "id") <*> parseJSON (Object v)++instance Foldable Message where+ foldMap f (Message _ m) = f m++instance Traversable Message where+ traverse f (Message i m) = Message i <$> f m++-- | Get messages by id+messagesById :: Maybe String -> [Message a] -> [a]+messagesById i = map _message . filter ((== i) . _messageId)++-- | Notification from server+data Notification = Notification Value deriving (Eq, Show)++instance ToJSON Notification where+ toJSON (Notification v) = object ["notify" .= v]++instance FromJSON Notification where+ parseJSON = withObject "notification" $ \v -> Notification <$> v .:: "notify"++-- | Result from server+data Result =+ Result Value |+ -- ^ Result+ Error HsDevError+ -- ^ Error+ deriving (Show)++instance ToJSON Result where+ toJSON (Result r) = object ["result" .= r]+ toJSON (Error e) = toJSON e++instance FromJSON Result where+ parseJSON j = (withObject "result" (\v -> (Result <$> v .:: "result")) j) <|> (Error <$> parseJSON j)++-- | Part of result list, returns via notification+data ResultPart = ResultPart Value++instance ToJSON ResultPart where+ toJSON (ResultPart r) = object ["result-part" .= r]++instance FromJSON ResultPart where+ parseJSON = withObject "result-part" $ \v -> ResultPart <$> v .:: "result-part"++newtype Response = Response { unResponse :: Either Notification Result } deriving (Show)++isNotification :: Response -> Bool+isNotification = either (const True) (const False) . unResponse++notification :: ToJSON a => a -> Response+notification = Response . Left . Notification . toJSON++result :: ToJSON a => a -> Response+result = Response . Right . Result . toJSON++responseError :: HsDevError -> Response+responseError = Response . Right . Error++resultPart :: ToJSON a => a -> Notification+resultPart = Notification . toJSON . ResultPart . toJSON++instance ToJSON Response where+ toJSON (Response (Left n)) = toJSON n+ toJSON (Response (Right r)) = toJSON r++instance FromJSON Response where+ parseJSON v = Response <$> ((Left <$> parseJSON v) <|> (Right <$> parseJSON v))++groupResponses :: [Response] -> [([Notification], Result)]+groupResponses = unfoldr break' where+ break' :: [Response] -> Maybe (([Notification], Result), [Response])+ break' [] = Nothing+ break' cs = Just ((lefts (map unResponse ns), r), drop 1 cs') where+ (ns, cs') = break (isRight . unResponse) cs+ r = case cs' of+ (Response (Right r') : _) -> r'+ [] -> Error $ OtherError "groupResponses: no result"+ _ -> error "groupResponses: impossible happened"++responsesById :: Maybe String -> [Message Response] -> [([Notification], Result)]+responsesById i = groupResponses . messagesById i
src/HsDev/Server/Types.hs view
@@ -1,792 +1,792 @@-{-# LANGUAGE OverloadedStrings, CPP, TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, FlexibleContexts, UndecidableInstances, MultiParamTypeClasses, TypeFamilies, ConstraintKinds #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Server.Types ( - ServerMonadBase, - SessionLog(..), Session(..), SessionMonad(..), askSession, ServerM(..), - CommandOptions(..), CommandMonad(..), askOptions, ClientM(..), - withSession, serverListen, serverSetLogLevel, serverWait, serverUpdateDB, serverWriteCache, serverReadCache, inSessionGhc, serverExit, commandRoot, commandNotify, commandLink, commandHold, - ServerCommand(..), ConnectionPort(..), ServerOpts(..), silentOpts, ClientOpts(..), serverOptsArgs, Request(..), - - Command(..), AddedContents(..), - AutoFixCommand(..), - FileSource(..), TargetFilter(..), SearchQuery(..), SearchType(..), - FromCmd(..), - ) where - -import Control.Applicative -import Control.Concurrent.Worker -import Control.Lens (each, view, set) -import Control.Monad.Base -import Control.Monad.Catch -import Control.Monad.Except -import Control.Monad.Reader -import Control.Monad.Trans.Control -import Data.Aeson hiding (Result(..), Error) -import qualified Data.Aeson.Types as A -import qualified Data.ByteString.Lazy.Char8 as L -import Data.Default -import Data.Maybe (fromMaybe) -import Data.Monoid -import Data.Foldable (asum) -import Options.Applicative -import System.Log.Simple - -import System.Directory.Paths -import Text.Format (Formattable(..)) - -import HsDev.Database -import qualified HsDev.Database.Async as DB -import HsDev.Error (hsdevError) -import HsDev.Project -import HsDev.Symbols -import HsDev.Server.Message -import HsDev.Watcher.Types (Watcher) -import HsDev.Tools.Ghc.Worker (GhcWorker, GhcM) -import HsDev.Tools.Types (Note, OutputMessage) -import HsDev.Tools.AutoFix (Correction) -import HsDev.Types (HsDevError(..)) -import HsDev.Util - -#if mingw32_HOST_OS -import System.Win32.FileMapping.NamePool (Pool) -#endif - -type ServerMonadBase m = (MonadIO m, MonadMask m, MonadBaseControl IO m, Alternative m, MonadPlus m) - -data SessionLog = SessionLog { - sessionLogger :: Log, - 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 :: SessionLog, - sessionWatcher :: Watcher, -#if mingw32_HOST_OS - sessionMmapPool :: Maybe Pool, -#endif - sessionGhc :: GhcWorker, - sessionExit :: IO (), - sessionWait :: IO (), - sessionDefines :: [(String, String)] } - -class (ServerMonadBase m, MonadLog m) => SessionMonad m where - getSession :: m Session - -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, Alternative, Monad, MonadPlus, MonadIO, MonadReader Session, MonadTrans, MonadThrow, MonadCatch, MonadMask) - -instance (MonadIO m, MonadMask m) => MonadLog (ServerM m) where - askLog = ServerM $ asks (sessionLogger . sessionLog) - localLog fn = ServerM . local setLog' . runServerM where - setLog' sess = sess { sessionLog = (sessionLog sess) { sessionLogger = fn (sessionLogger (sessionLog sess)) } } - -instance ServerMonadBase m => SessionMonad (ServerM m) where - getSession = ask - -instance MonadBase b m => MonadBase b (ServerM m) where - liftBase = ServerM . liftBase - -instance MonadBaseControl b m => MonadBaseControl b (ServerM m) where - type StM (ServerM m) a = StM (ReaderT Session m) a - liftBaseWith f = ServerM $ liftBaseWith (\f' -> f (f' . runServerM)) - restoreM = ServerM . restoreM - -data CommandOptions = CommandOptions { - commandOptionsRoot :: FilePath, - commandOptionsNotify :: Notification -> IO (), - commandOptionsLink :: IO (), - commandOptionsHold :: IO () } - -instance Default CommandOptions where - def = CommandOptions "." (const $ return ()) (return ()) (return ()) - -class (SessionMonad m, MonadPlus m) => CommandMonad m where - getOptions :: m CommandOptions - -askOptions :: CommandMonad m => (CommandOptions -> a) -> m a -askOptions f = liftM f getOptions - -newtype ClientM m a = ClientM { runClientM :: ServerM (ReaderT CommandOptions m) a } - deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadThrow, MonadCatch, MonadMask) - -instance MonadTrans ClientM where - lift = ClientM . lift . lift - -instance (MonadIO m, MonadMask m) => MonadLog (ClientM m) where - askLog = ClientM askLog - localLog fn = ClientM . localLog fn . runClientM - -instance ServerMonadBase m => SessionMonad (ClientM m) where - getSession = ClientM getSession - -instance ServerMonadBase m => CommandMonad (ClientM m) where - 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 (ReaderT CommandOptions m)) a - liftBaseWith f = ClientM $ liftBaseWith (\f' -> f (f' . runClientM)) - restoreM = ClientM . restoreM - --- | Run action on session -withSession :: Session -> ServerM m a -> m a -withSession s act = runReaderT (runServerM act) s - --- | Listen server's log -serverListen :: SessionMonad m => m [String] -serverListen = join . liftM liftIO $ askSession (sessionListenLog . sessionLog) - --- | Set server's log config -serverSetLogLevel :: SessionMonad m => Level -> m Level -serverSetLogLevel lev = do - l <- askSession (sessionLogger . sessionLog) - cfg <- updateLogConfig l (set (componentCfg "") (Just lev)) - return $ fromMaybe def $ view (componentCfg "") cfg - --- | Wait for server -serverWait :: SessionMonad m => m () -serverWait = join . liftM liftIO $ askSession sessionWait - --- | Update database -serverUpdateDB :: SessionMonad m => Database -> m () -serverUpdateDB db = askSession sessionDatabase >>= (`DB.update` return db) - --- | Server write cache -serverWriteCache :: SessionMonad m => Database -> m () -serverWriteCache db = do - s <- getSession - write' <- askSession sessionWriteCache - liftIO $ withSession s $ write' db - --- | Server read cache -serverReadCache :: SessionMonad m => (FilePath -> ExceptT String IO Structured) -> m (Maybe Database) -serverReadCache act = do - s <- getSession - read' <- askSession sessionReadCache - liftIO $ withSession s $ read' act - --- | In ghc session -inSessionGhc :: SessionMonad m => GhcM a -> m a -inSessionGhc act = do - ghcw <- askSession sessionGhc - inWorkerWith (hsdevError . GhcError . displayException) ghcw act - --- | Exit session -serverExit :: SessionMonad m => m () -serverExit = join . liftM liftIO $ askSession sessionExit - -commandRoot :: CommandMonad m => m FilePath -commandRoot = askOptions commandOptionsRoot - -commandNotify :: CommandMonad m => Notification -> m () -commandNotify n = join . liftM liftIO $ askOptions commandOptionsNotify <*> pure n - -commandLink :: CommandMonad m => m () -commandLink = join . liftM liftIO $ askOptions commandOptionsLink - -commandHold :: CommandMonad m => m () -commandHold = join . liftM liftIO $ askOptions commandOptionsHold - --- | Server control command -data ServerCommand = - Version | - Start ServerOpts | - Run ServerOpts | - Stop ClientOpts | - Connect ClientOpts | - Remote ClientOpts Bool Command - deriving (Show) - -data ConnectionPort = NetworkPort Int | UnixPort String deriving (Eq, Read) - -instance Default ConnectionPort where - def = NetworkPort 4567 - -instance Show ConnectionPort where - show (NetworkPort p) = show p - show (UnixPort s) = "unix " ++ s - -instance Formattable ConnectionPort - --- | Server options -data ServerOpts = ServerOpts { - serverPort :: ConnectionPort, - serverTimeout :: Int, - serverLog :: Maybe FilePath, - serverLogLevel :: String, - serverCache :: Maybe FilePath, - serverLoad :: Bool, - serverSilent :: Bool } - deriving (Show) - -instance Default ServerOpts where - def = ServerOpts def 0 Nothing "info" Nothing False False - --- | Silent server with no connection, useful for ghci -silentOpts :: ServerOpts -silentOpts = def { serverSilent = True } - --- | Client options -data ClientOpts = ClientOpts { - clientPort :: ConnectionPort, - clientPretty :: Bool, - clientStdin :: Bool, - clientTimeout :: Int, - clientSilent :: Bool } - deriving (Show) - -instance Default ClientOpts where - def = ClientOpts def False False 0 False - -instance FromCmd ServerCommand where - cmdP = serv <|> remote where - serv = subparser $ mconcat [ - cmd "version" "hsdev version" (pure Version), - cmd "start" "start remote server" (Start <$> cmdP), - cmd "run" "run server" (Run <$> cmdP), - cmd "stop" "stop remote server" (Stop <$> cmdP), - cmd "connect" "connect to send commands directly" (Connect <$> cmdP)] - remote = Remote <$> cmdP <*> noFileFlag <*> cmdP - -instance FromCmd ServerOpts where - cmdP = ServerOpts <$> - (connectionArg <|> pure (serverPort def)) <*> - (timeoutArg <|> pure (serverTimeout def)) <*> - optional logArg <*> - (logLevelArg <|> pure (serverLogLevel def)) <*> - optional cacheArg <*> - loadFlag <*> - serverSilentFlag - -instance FromCmd ClientOpts where - cmdP = ClientOpts <$> - (connectionArg <|> pure (clientPort def)) <*> - prettyFlag <*> - stdinFlag <*> - (timeoutArg <|> pure (clientTimeout def)) <*> - silentFlag - -portArg :: Parser ConnectionPort -connectionArg :: Parser ConnectionPort -timeoutArg :: Parser Int -logArg :: Parser FilePath -logLevelArg :: Parser String -cacheArg :: Parser FilePath -noFileFlag :: Parser Bool -loadFlag :: Parser Bool -prettyFlag :: Parser Bool -serverSilentFlag :: Parser Bool -stdinFlag :: Parser Bool -silentFlag :: Parser Bool - -portArg = NetworkPort <$> option auto (long "port" <> metavar "number" <> help "connection port") -#if mingw32_HOST_OS -connectionArg = portArg -#else -unixArg :: Parser ConnectionPort -unixArg = UnixPort <$> strOption (long "unix" <> metavar "name" <> help "unix connection port") -connectionArg = portArg <|> unixArg -#endif -timeoutArg = option auto (long "timeout" <> metavar "msec" <> help "query timeout") -logArg = strOption (long "log" <> short 'l' <> metavar "file" <> help "log file") -logLevelArg = strOption (long "log-level" <> metavar "level" <> help "log level: trace/debug/info/warning/error/fatal") -cacheArg = strOption (long "cache" <> metavar "path" <> help "cache directory") -noFileFlag = switch (long "no-file" <> help "don't use mmap files") -loadFlag = switch (long "load" <> help "force load all data from cache on startup") -prettyFlag = switch (long "pretty" <> help "pretty json output") -serverSilentFlag = switch (long "silent" <> help "no stdout/stderr") -stdinFlag = switch (long "stdin" <> help "pass data to stdin") -silentFlag = switch (long "silent" <> help "supress notifications") - -serverOptsArgs :: ServerOpts -> [String] -serverOptsArgs sopts = concat [ - portArgs (serverPort sopts), - ["--timeout", show $ serverTimeout sopts], - marg "--log" (serverLog sopts), - ["--log-level", serverLogLevel sopts], - marg "--cache" (serverCache sopts), - ["--load" | serverLoad sopts], - ["--silent" | serverSilent sopts]] - where - marg :: String -> Maybe String -> [String] - marg n (Just v) = [n, v] - marg _ _ = [] - portArgs :: ConnectionPort -> [String] - portArgs (NetworkPort n) = ["--port", show n] - portArgs (UnixPort s) = ["--unix", s] - -data Request = Request { - requestCommand :: Command, - requestDirectory :: FilePath, - requestNoFile :: Bool, - requestTimeout :: Int, - requestSilent :: Bool } - deriving (Show) - -instance ToJSON Request where - toJSON (Request c dir f tm s) = object ["current-directory" .= dir, "no-file" .= f, "timeout" .= tm, "silent" .= s] `objectUnion` toJSON c - -instance FromJSON Request where - parseJSON = withObject "request" $ \v -> Request <$> - parseJSON (Object v) <*> - ((v .:: "current-directory") <|> pure ".") <*> - ((v .:: "no-file") <|> pure False) <*> - ((v .:: "timeout") <|> pure 0) <*> - ((v .:: "silent") <|> pure False) - --- | Command from client -data Command = - Ping | - Listen (Maybe String) | - SetLogLevel String | - AddData { addedContents :: [AddedContents] } | - Scan { - scanProjects :: [FilePath], - scanCabal :: Bool, - scanSandboxes :: [FilePath], - scanFiles :: [FileSource], - scanPaths :: [FilePath], - scanGhcOpts :: [String], - scanDocs :: Bool, - scanInferTypes :: Bool } | - RefineDocs { - docsProjects :: [FilePath], - docsFiles :: [FilePath], - docsModules :: [String] } | - InferTypes { - inferProjects :: [FilePath], - inferFiles :: [FilePath], - inferModules :: [String] } | - Remove { - removeProjects :: [FilePath], - removeCabal :: Bool, - removeSandboxes :: [FilePath], - removeFiles :: [FilePath] } | - RemoveAll | - InfoModules [TargetFilter] | - InfoPackages | - InfoProjects | - InfoSandboxes | - InfoSymbol SearchQuery [TargetFilter] Bool | - InfoModule SearchQuery [TargetFilter] | - InfoResolve FilePath Bool | - InfoProject (Either String FilePath) | - InfoSandbox FilePath | - Lookup String FilePath | - Whois String FilePath | - ResolveScopeModules SearchQuery FilePath | - ResolveScope SearchQuery Bool FilePath | - Complete String Bool FilePath | - Hayoo { - hayooQuery :: String, - hayooPage :: Int, - hayooPages :: Int } | - CabalList { cabalListPackages :: [String] } | - Lint { - lintFiles :: [FileSource] } | - Check { - checkFiles :: [FileSource], - checkGhcOpts :: [String] } | - CheckLint { - checkLintFiles :: [FileSource], - checkLintGhcOpts :: [String] } | - Types { - typesFiles :: [FileSource], - typesGhcOpts :: [String] } | - AutoFix { autoFixCommand :: AutoFixCommand } | - GhcEval { ghcEvalExpressions :: [String], ghcEvalSource :: Maybe FileSource } | - Langs | - Flags | - Link { linkHold :: Bool } | - Exit - deriving (Show) - -data AddedContents = - AddedDatabase Database | - AddedModule InspectedModule | - AddedProject Project - -instance Show AddedContents where - show = L.unpack . encode - -data AutoFixCommand = - AutoFixShow [Note OutputMessage] | - AutoFixFix [Note Correction] [Note Correction] Bool - deriving (Show) - -data FileSource = FileSource { fileSource :: FilePath, fileContents :: Maybe String } deriving (Show) -data TargetFilter = - TargetProject String | - TargetFile FilePath | - TargetModule String | - TargetDepsOf String | - TargetPackageDb PackageDb | - TargetCabal | - TargetSandbox FilePath | - TargetPackage String | - TargetSourced | - TargetStandalone - deriving (Eq, Show) -data SearchQuery = SearchQuery String SearchType deriving (Show) -data SearchType = SearchExact | SearchPrefix | SearchInfix | SearchSuffix | SearchRegex deriving (Show) - -instance Paths Command where - paths f (Scan projs c cs fs ps ghcs docs infer) = Scan <$> - each f projs <*> - pure c <*> - (each . paths) f cs <*> - (each . paths) f fs <*> - each f ps <*> - pure ghcs <*> - pure docs <*> - pure infer - paths f (RefineDocs projs fs ms) = RefineDocs <$> each f projs <*> each f fs <*> pure ms - paths f (InferTypes projs fs ms) = InferTypes <$> each f projs <*> each f fs <*> pure ms - paths f (Remove projs c cs fs) = Remove <$> each f projs <*> pure c <*> (each . paths) f cs <*> each f fs - paths _ RemoveAll = pure RemoveAll - paths f (InfoModules t) = InfoModules <$> paths f t - paths f (InfoSymbol q t l) = InfoSymbol <$> pure q <*> paths f t <*> pure l - paths f (InfoModule q t) = InfoModule <$> pure q <*> paths f t - paths f (InfoResolve fpath es) = InfoResolve <$> f fpath <*> pure es - paths f (InfoProject (Right proj)) = InfoProject <$> (Right <$> f proj) - paths f (InfoSandbox fpath) = InfoSandbox <$> f fpath - paths f (Lookup n fpath) = Lookup <$> pure n <*> f fpath - paths f (Whois n fpath) = Whois <$> pure n <*> f fpath - paths f (ResolveScopeModules q fpath) = ResolveScopeModules q <$> f fpath - paths f (ResolveScope q g fpath) = ResolveScope q g <$> f fpath - paths f (Complete n g fpath) = Complete n g <$> f fpath - paths f (Lint fs) = Lint <$> (each . paths) f fs - paths f (Check fs ghcs) = Check <$> (each . paths) f fs <*> pure ghcs - paths f (CheckLint fs ghcs) = CheckLint <$> (each . paths) f fs <*> pure ghcs - paths f (Types fs ghcs) = Types <$> (each . paths) f fs <*> pure ghcs - paths f (GhcEval e mf) = GhcEval e <$> traverse (paths f) mf - paths _ c = pure c - -instance Paths FileSource where - paths f (FileSource fpath mcts) = FileSource <$> f fpath <*> pure mcts - -instance Paths TargetFilter where - paths f (TargetFile fpath) = TargetFile <$> f fpath - paths f (TargetPackageDb pdb) = TargetPackageDb <$> paths f pdb - paths f (TargetSandbox c) = TargetSandbox <$> paths f c - paths _ t = pure t - -instance Paths [TargetFilter] where - paths = each . paths - -instance FromCmd Command where - cmdP = subparser $ mconcat [ - cmd "ping" "ping server" (pure Ping), - cmd "listen" "listen server log" (Listen <$> optional logLevelArg), - cmd "set-log" "set log level" (SetLogLevel <$> strArgument idm), - cmd "add" "add info to database" (AddData <$> option readJSON idm), - cmd "scan" "scan sources" $ Scan <$> - many projectArg <*> - cabalFlag <*> - many sandboxArg <*> - many cmdP <*> - many (pathArg $ help "path") <*> - ghcOpts <*> - docsFlag <*> - inferFlag, - cmd "docs" "scan docs" $ RefineDocs <$> many projectArg <*> many fileArg <*> many moduleArg, - cmd "infer" "infer types" $ InferTypes <$> many projectArg <*> many fileArg <*> many moduleArg, - cmd "remove" "remove modules info" $ Remove <$> - many projectArg <*> - cabalFlag <*> - many sandboxArg <*> - many fileArg, - cmd "remove-all" "remove all data" (pure RemoveAll), - cmd "modules" "list modules" (InfoModules <$> many cmdP), - cmd "packages" "list packages" (pure InfoPackages), - cmd "projects" "list projects" (pure InfoProjects), - cmd "sandboxes" "list sandboxes" (pure InfoSandboxes), - cmd "symbol" "get symbol info" (InfoSymbol <$> cmdP <*> many cmdP <*> localsFlag), - cmd "module" "get module info" (InfoModule <$> cmdP <*> many cmdP), - cmd "resolve" "resolve module scope (or exports)" (InfoResolve <$> fileArg <*> exportsFlag), - cmd "project" "get project info" (InfoProject <$> ((Left <$> projectArg) <|> (Right <$> pathArg idm))), - cmd "sandbox" "get sandbox info" (InfoSandbox <$> pathArg (help "locate sandbox in parent of this path")), - cmd "lookup" "lookup for symbol" (Lookup <$> strArgument idm <*> ctx), - cmd "whois" "get info for symbol" (Whois <$> strArgument idm <*> ctx), - cmd "scope" "get declarations accessible from module or within a project" ( - subparser (cmd "modules" "get modules accessible from module or within a project" (ResolveScopeModules <$> cmdP <*> ctx)) <|> - ResolveScope <$> cmdP <*> globalFlag <*> ctx), - cmd "complete" "show completions for input" (Complete <$> strArgument idm <*> wideFlag <*> ctx), - cmd "hayoo" "find declarations online via Hayoo" (Hayoo <$> strArgument idm <*> hayooPageArg <*> hayooPagesArg), - cmd "cabal" "cabal commands" (subparser $ cmd "list" "list cabal packages" (CabalList <$> many (strArgument idm))), - cmd "lint" "lint source files or file contents" (Lint <$> many cmdP), - cmd "check" "check source files or file contents" (Check <$> many cmdP <*> ghcOpts), - cmd "check-lint" "check and lint source files or file contents" (CheckLint <$> many cmdP <*> ghcOpts), - cmd "types" "get types for file expressions" (Types <$> many cmdP <*> ghcOpts), - cmd "autofix" "autofix commands" (AutoFix <$> cmdP), - cmd "ghc" "ghc commands" (subparser $ cmd "eval" "evaluate expression" (GhcEval <$> many (strArgument idm) <*> optional cmdP)), - cmd "langs" "ghc language options" (pure Langs), - cmd "flags" "ghc flags" (pure Flags), - cmd "link" "link to server" (Link <$> holdFlag), - cmd "exit" "exit" (pure Exit)] - -instance FromCmd AutoFixCommand where - cmdP = subparser $ mconcat [ - cmd "show" "generate corrections for check & lint messages" (AutoFixShow <$> option readJSON (long "data" <> metavar "message" <> help "messages to make fixes for")), - cmd "fix" "fix errors and return rest corrections with updated regions" (AutoFixFix <$> - option readJSON (long "data" <> metavar "message" <> help "messages to fix") <*> - option readJSON (long "rest" <> metavar "correction" <> short 'r' <> help "update corrections") <*> - pureFlag)] - -instance FromCmd FileSource where - cmdP = option readJSON (long "contents") <|> (FileSource <$> fileArg <*> pure Nothing) - -instance FromCmd TargetFilter where - cmdP = asum [ - TargetProject <$> projectArg, - TargetFile <$> fileArg, - TargetModule <$> moduleArg, - TargetDepsOf <$> depsArg, - TargetPackageDb <$> packageDbArg, - flag' TargetCabal (long "cabal"), - TargetSandbox <$> sandboxArg, - TargetPackage <$> packageArg, - flag' TargetSourced (long "src"), - flag' TargetStandalone (long "stand")] - -instance FromCmd SearchQuery where - cmdP = SearchQuery <$> (strArgument idm <|> pure "") <*> asum [ - flag' SearchExact (long "exact"), - flag' SearchRegex (long "regex"), - flag' SearchInfix (long "infix"), - flag' SearchSuffix (long "suffix"), - pure SearchPrefix <* switch (long "prefix")] - -readJSON :: FromJSON a => ReadM a -readJSON = str >>= maybe (readerError "Can't parse JSON argument") return . decode . L.pack - -cabalFlag :: Parser Bool -ctx :: Parser FilePath -depsArg :: Parser String -docsFlag :: Parser Bool -exportsFlag :: Parser Bool -fileArg :: Parser FilePath -ghcOpts :: Parser [String] -globalFlag :: Parser Bool -hayooPageArg :: Parser Int -hayooPagesArg :: Parser Int -holdFlag :: Parser Bool -inferFlag :: Parser Bool -localsFlag :: Parser Bool -moduleArg :: Parser String -packageDbArg :: Parser PackageDb -packageArg :: Parser String -pathArg :: Mod OptionFields String -> Parser FilePath -projectArg :: Parser String -pureFlag :: Parser Bool -sandboxArg :: Parser FilePath -wideFlag :: Parser Bool - -cabalFlag = switch (long "cabal") -ctx = fileArg -depsArg = strOption (long "deps" <> metavar "object" <> help "filter to such that in dependency of specified object (file or project)") -docsFlag = switch (long "docs" <> help "scan source file docs") -exportsFlag = switch (long "exports" <> short 'e' <> help "resolve module exports") -fileArg = strOption (long "file" <> metavar "path" <> short 'f') -ghcOpts = many (strOption (long "ghc" <> metavar "option" <> short 'g' <> help "options to pass to GHC")) -globalFlag = switch (long "global" <> help "scope of project") -hayooPageArg = option auto (long "page" <> metavar "n" <> short 'p' <> help "page number (0 by default)" <> value 0) -hayooPagesArg = option auto (long "pages" <> metavar "count" <> short 'n' <> help "pages count (1 by default)" <> value 1) -holdFlag = switch (long "hold" <> short 'h' <> help "don't return any response") -inferFlag = switch (long "infer" <> help "infer types") -localsFlag = switch (long "locals" <> short 'l' <> help "look in local declarations") -moduleArg = strOption (long "module" <> metavar "name" <> short 'm' <> help "module name") -packageArg = strOption (long "package" <> metavar "name" <> help "module package") -packageDbArg = - flag' GlobalDb (long "global-db" <> help "global package-db") <|> - flag' UserDb (long "user-db" <> help "user package-db") <|> - (PackageDb <$> strOption (long "package-db" <> metavar "path" <> help "custom package-db")) -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") -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 lev) = cmdJson "listen" ["level" .= lev] - toJSON (SetLogLevel lev) = cmdJson "set-log" ["level" .= lev] - toJSON (AddData cts) = cmdJson "add" ["data" .= cts] - toJSON (Scan projs cabal sboxes fs ps ghcs docs' infer') = cmdJson "scan" [ - "projects" .= projs, - "cabal" .= cabal, - "sandboxes" .= sboxes, - "files" .= fs, - "paths" .= ps, - "ghc-opts" .= ghcs, - "docs" .= docs', - "infer" .= infer'] - toJSON (RefineDocs projs fs ms) = cmdJson "docs" ["projects" .= projs, "files" .= fs, "modules" .= ms] - toJSON (InferTypes projs fs ms) = cmdJson "infer" ["projects" .= projs, "files" .= fs, "modules" .= ms] - toJSON (Remove projs cabal sboxes fs) = cmdJson "remove" ["projects" .= projs, "cabal" .= cabal, "sandboxes" .= sboxes, "files" .= fs] - toJSON RemoveAll = cmdJson "remove-all" [] - toJSON (InfoModules tf) = cmdJson "modules" ["filters" .= tf] - toJSON InfoPackages = cmdJson "packages" [] - toJSON InfoProjects = cmdJson "projects" [] - toJSON InfoSandboxes = cmdJson "sandboxes" [] - toJSON (InfoSymbol q tf l) = cmdJson "symbol" ["query" .= q, "filters" .= tf, "locals" .= l] - toJSON (InfoModule q tf) = cmdJson "module" ["query" .= q, "filters" .= tf] - toJSON (InfoResolve f es) = cmdJson "resolve" ["file" .= f, "exports" .= es] - toJSON (InfoProject p) = cmdJson "project" $ either (\pname -> ["name" .= pname]) (\ppath -> ["path" .= ppath]) p - toJSON (InfoSandbox p) = cmdJson "sandbox" ["path" .= p] - toJSON (Lookup n f) = cmdJson "lookup" ["name" .= n, "file" .= f] - toJSON (Whois n f) = cmdJson "whois" ["name" .= n, "file" .= f] - toJSON (ResolveScopeModules q f) = cmdJson "scope modules" ["query" .= q, "file" .= f] - toJSON (ResolveScope q g f) = cmdJson "scope" ["query" .= q, "global" .= g, "file" .= f] - toJSON (Complete q w f) = cmdJson "complete" ["prefix" .= q, "wide" .= w, "file" .= f] - toJSON (Hayoo q p ps) = cmdJson "hayoo" ["query" .= q, "page" .= p, "pages" .= ps] - toJSON (CabalList ps) = cmdJson "cabal list" ["packages" .= ps] - toJSON (Lint fs) = cmdJson "lint" ["files" .= fs] - toJSON (Check fs ghcs) = cmdJson "check" ["files" .= fs, "ghc-opts" .= ghcs] - toJSON (CheckLint fs ghcs) = cmdJson "check-lint" ["files" .= fs, "ghc-opts" .= ghcs] - toJSON (Types fs ghcs) = cmdJson "types" ["files" .= fs, "ghc-opts" .= ghcs] - toJSON (AutoFix acmd) = toJSON acmd - toJSON (GhcEval exprs f) = cmdJson "ghc eval" ["exprs" .= exprs, "file" .= f] - toJSON Langs = cmdJson "langs" [] - toJSON Flags = cmdJson "flags" [] - toJSON (Link h) = cmdJson "link" ["hold" .= h] - toJSON Exit = cmdJson "exit" [] - -instance FromJSON Command where - parseJSON = withObject "command" $ \v -> asum [ - guardCmd "ping" v *> pure Ping, - guardCmd "listen" v *> (Listen <$> v .::? "level"), - guardCmd "set-log" v *> (SetLogLevel <$> v .:: "level"), - guardCmd "add" v *> (AddData <$> v .:: "data"), - guardCmd "scan" v *> (Scan <$> - v .::?! "projects" <*> - (v .:: "cabal" <|> pure False) <*> - v .::?! "sandboxes" <*> - v .::?! "files" <*> - v .::?! "paths" <*> - v .::?! "ghc-opts" <*> - (v .:: "docs" <|> pure False) <*> - (v .:: "infer" <|> pure False)), - guardCmd "docs" v *> (RefineDocs <$> v .::?! "projects" <*> v .::?! "files" <*> v .::?! "modules"), - guardCmd "infer" v *> (InferTypes <$> v .::?! "projects" <*> v .::?! "files" <*> v .::?! "modules"), - guardCmd "remove" v *> (Remove <$> - v .::?! "projects" <*> - (v .:: "cabal" <|> pure False) <*> - v .::?! "sandboxes" <*> - v .::?! "files"), - guardCmd "remove-all" v *> pure RemoveAll, - guardCmd "modules" v *> (InfoModules <$> v .::?! "filters"), - guardCmd "packages" v *> pure InfoPackages, - guardCmd "projects" v *> pure InfoProjects, - guardCmd "sandboxes" v *> pure InfoSandboxes, - guardCmd "symbol" v *> (InfoSymbol <$> v .:: "query" <*> v .::?! "filters" <*> (v .:: "locals" <|> pure False)), - guardCmd "module" v *> (InfoModule <$> v .:: "query" <*> v .::?! "filters"), - guardCmd "resolve" v *> (InfoResolve <$> v .:: "file" <*> (v .:: "exports" <|> pure False)), - guardCmd "project" v *> (InfoProject <$> asum [Left <$> v .:: "name", Right <$> v .:: "path"]), - guardCmd "sandbox" v *> (InfoSandbox <$> v .:: "path"), - guardCmd "lookup" v *> (Lookup <$> v .:: "name" <*> v .:: "file"), - guardCmd "whois" v *> (Whois <$> v .:: "name" <*> v .:: "file"), - guardCmd "scope modules" v *> (ResolveScopeModules <$> v .:: "query" <*> v .:: "file"), - guardCmd "scope" v *> (ResolveScope <$> v .:: "query" <*> (v .:: "global" <|> pure False) <*> v .:: "file"), - guardCmd "complete" v *> (Complete <$> v .:: "prefix" <*> (v .:: "wide" <|> pure False) <*> v .:: "file"), - guardCmd "hayoo" v *> (Hayoo <$> v .:: "query" <*> (v .:: "page" <|> pure 0) <*> (v .:: "pages" <|> pure 1)), - guardCmd "cabal list" v *> (CabalList <$> v .::?! "packages"), - guardCmd "lint" v *> (Lint <$> v .::?! "files"), - guardCmd "check" v *> (Check <$> v .::?! "files" <*> v .::?! "ghc-opts"), - guardCmd "check-lint" v *> (CheckLint <$> v .::?! "files" <*> v .::?! "ghc-opts"), - guardCmd "types" v *> (Types <$> v .::?! "files" <*> v .::?! "ghc-opts"), - AutoFix <$> parseJSON (Object v), - guardCmd "ghc eval" v *> (GhcEval <$> v .::?! "exprs" <*> v .::? "file"), - guardCmd "langs" v *> pure Langs, - guardCmd "flags" v *> pure Flags, - guardCmd "link" v *> (Link <$> (v .:: "hold" <|> pure False)), - guardCmd "exit" v *> pure Exit] - -instance ToJSON AddedContents where - toJSON (AddedDatabase db) = object ["database" .= db] - toJSON (AddedModule im) = object ["module" .= im] - toJSON (AddedProject p) = object ["project" .= p] - -instance FromJSON AddedContents where - parseJSON = withObject "added-contents" $ \v -> asum [ - AddedDatabase <$> v .:: "database", - AddedModule <$> v .:: "module", - AddedProject <$> v .:: "project"] - -instance ToJSON AutoFixCommand where - toJSON (AutoFixShow ns) = cmdJson "autofix show" ["messages" .= ns] - toJSON (AutoFixFix ns rests pure') = cmdJson "autofix fix" ["messages" .= ns, "rest" .= rests, "pure" .= pure'] - -instance FromJSON AutoFixCommand where - parseJSON = withObject "auto-fix-command" $ \v -> asum [ - guardCmd "autofix show" v *> (AutoFixShow <$> v .:: "messages"), - guardCmd "autofix fix" v *> (AutoFixFix <$> v .:: "messages" <*> v .::?! "rest" <*> (v .:: "pure" <|> pure True))] - -instance ToJSON FileSource where - toJSON (FileSource fpath mcts) = object ["file" .= fpath, "contents" .= mcts] - -instance FromJSON FileSource where - parseJSON = withObject "file-contents" $ \v -> FileSource <$> v .:: "file" <*> v .::? "contents" - -instance ToJSON TargetFilter where - toJSON (TargetProject pname) = object ["project" .= pname] - toJSON (TargetFile fpath) = object ["file" .= fpath] - toJSON (TargetModule mname) = object ["module" .= mname] - toJSON (TargetDepsOf dep) = object ["deps" .= dep] - toJSON (TargetPackageDb pdb) = object ["db" .= pdb] - toJSON TargetCabal = toJSON ("cabal" :: String) - toJSON (TargetSandbox sbox) = object ["sandbox" .= sbox] - toJSON (TargetPackage pname) = object ["package" .= pname] - toJSON TargetSourced = toJSON ("sourced" :: String) - toJSON TargetStandalone = toJSON ("standalone" :: String) - -instance FromJSON TargetFilter where - parseJSON j = obj j <|> str' where - obj = withObject "target-filter" $ \v -> asum [ - TargetProject <$> v .:: "project", - TargetFile <$> v .:: "file", - TargetModule <$> v .:: "module", - TargetDepsOf <$> v .:: "deps", - TargetPackageDb <$> v .:: "db", - TargetSandbox <$> v .:: "sandbox", - TargetPackage <$> v .:: "package"] - str' = do - s <- parseJSON j :: A.Parser String - case s of - "cabal" -> return TargetCabal - "sourced" -> return TargetSourced - "standalone" -> return TargetStandalone - _ -> empty - -instance ToJSON SearchQuery where - toJSON (SearchQuery q st) = object ["input" .= q, "type" .= st] - -instance FromJSON SearchQuery where - parseJSON = withObject "search-query" $ \v -> SearchQuery <$> (v .:: "input" <|> pure "") <*> (v .:: "type" <|> pure SearchPrefix) - -instance ToJSON SearchType where - toJSON SearchExact = toJSON ("exact" :: String) - toJSON SearchPrefix = toJSON ("prefix" :: String) - toJSON SearchInfix = toJSON ("infix" :: String) - toJSON SearchSuffix = toJSON ("suffix" :: String) - toJSON SearchRegex = toJSON ("regex" :: String) - -instance FromJSON SearchType where - parseJSON v = do - str' <- parseJSON v :: A.Parser String - case str' of - "exact" -> return SearchExact - "prefix" -> return SearchPrefix - "infix" -> return SearchInfix - "suffix" -> return SearchInfix - "regex" -> return SearchRegex - _ -> empty +{-# LANGUAGE OverloadedStrings, CPP, TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, FlexibleContexts, UndecidableInstances, MultiParamTypeClasses, TypeFamilies, ConstraintKinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Server.Types (+ ServerMonadBase,+ SessionLog(..), Session(..), SessionMonad(..), askSession, ServerM(..),+ CommandOptions(..), CommandMonad(..), askOptions, ClientM(..),+ withSession, serverListen, serverSetLogLevel, serverWait, serverUpdateDB, serverWriteCache, serverReadCache, inSessionGhc, serverExit, commandRoot, commandNotify, commandLink, commandHold,+ ServerCommand(..), ConnectionPort(..), ServerOpts(..), silentOpts, ClientOpts(..), serverOptsArgs, Request(..),++ Command(..), AddedContents(..),+ AutoFixCommand(..),+ FileSource(..), TargetFilter(..), SearchQuery(..), SearchType(..),+ FromCmd(..),+ ) where++import Control.Applicative+import Control.Concurrent.Worker+import Control.Lens (each, view, set)+import Control.Monad.Base+import Control.Monad.Catch+import Control.Monad.Except+import Control.Monad.Reader+import Control.Monad.Trans.Control+import Data.Aeson hiding (Result(..), Error)+import qualified Data.Aeson.Types as A+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Default+import Data.Maybe (fromMaybe)+import Data.Monoid+import Data.Foldable (asum)+import Options.Applicative+import System.Log.Simple++import System.Directory.Paths+import Text.Format (Formattable(..))++import HsDev.Database+import qualified HsDev.Database.Async as DB+import HsDev.Error (hsdevError)+import HsDev.Project+import HsDev.Symbols+import HsDev.Server.Message+import HsDev.Watcher.Types (Watcher)+import HsDev.Tools.Ghc.Worker (GhcWorker, GhcM)+import HsDev.Tools.Types (Note, OutputMessage)+import HsDev.Tools.AutoFix (Correction)+import HsDev.Types (HsDevError(..))+import HsDev.Util++#if mingw32_HOST_OS+import System.Win32.FileMapping.NamePool (Pool)+#endif++type ServerMonadBase m = (MonadIO m, MonadMask m, MonadBaseControl IO m, Alternative m, MonadPlus m)++data SessionLog = SessionLog {+ sessionLogger :: Log,+ 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 :: SessionLog,+ sessionWatcher :: Watcher,+#if mingw32_HOST_OS+ sessionMmapPool :: Maybe Pool,+#endif+ sessionGhc :: GhcWorker,+ sessionExit :: IO (),+ sessionWait :: IO (),+ sessionDefines :: [(String, String)] }++class (ServerMonadBase m, MonadLog m) => SessionMonad m where+ getSession :: m Session++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, Alternative, Monad, MonadPlus, MonadIO, MonadReader Session, MonadTrans, MonadThrow, MonadCatch, MonadMask)++instance (MonadIO m, MonadMask m) => MonadLog (ServerM m) where+ askLog = ServerM $ asks (sessionLogger . sessionLog)+ localLog fn = ServerM . local setLog' . runServerM where+ setLog' sess = sess { sessionLog = (sessionLog sess) { sessionLogger = fn (sessionLogger (sessionLog sess)) } }++instance ServerMonadBase m => SessionMonad (ServerM m) where+ getSession = ask++instance MonadBase b m => MonadBase b (ServerM m) where+ liftBase = ServerM . liftBase++instance MonadBaseControl b m => MonadBaseControl b (ServerM m) where+ type StM (ServerM m) a = StM (ReaderT Session m) a+ liftBaseWith f = ServerM $ liftBaseWith (\f' -> f (f' . runServerM))+ restoreM = ServerM . restoreM++data CommandOptions = CommandOptions {+ commandOptionsRoot :: FilePath,+ commandOptionsNotify :: Notification -> IO (),+ commandOptionsLink :: IO (),+ commandOptionsHold :: IO () }++instance Default CommandOptions where+ def = CommandOptions "." (const $ return ()) (return ()) (return ())++class (SessionMonad m, MonadPlus m) => CommandMonad m where+ getOptions :: m CommandOptions++askOptions :: CommandMonad m => (CommandOptions -> a) -> m a+askOptions f = liftM f getOptions++newtype ClientM m a = ClientM { runClientM :: ServerM (ReaderT CommandOptions m) a }+ deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadThrow, MonadCatch, MonadMask)++instance MonadTrans ClientM where+ lift = ClientM . lift . lift++instance (MonadIO m, MonadMask m) => MonadLog (ClientM m) where+ askLog = ClientM askLog+ localLog fn = ClientM . localLog fn . runClientM++instance ServerMonadBase m => SessionMonad (ClientM m) where+ getSession = ClientM getSession++instance ServerMonadBase m => CommandMonad (ClientM m) where+ 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 (ReaderT CommandOptions m)) a+ liftBaseWith f = ClientM $ liftBaseWith (\f' -> f (f' . runClientM))+ restoreM = ClientM . restoreM++-- | Run action on session+withSession :: Session -> ServerM m a -> m a+withSession s act = runReaderT (runServerM act) s++-- | Listen server's log+serverListen :: SessionMonad m => m [String]+serverListen = join . liftM liftIO $ askSession (sessionListenLog . sessionLog)++-- | Set server's log config+serverSetLogLevel :: SessionMonad m => Level -> m Level+serverSetLogLevel lev = do+ l <- askSession (sessionLogger . sessionLog)+ cfg <- updateLogConfig l (set (componentCfg "") (Just lev))+ return $ fromMaybe def $ view (componentCfg "") cfg++-- | Wait for server+serverWait :: SessionMonad m => m ()+serverWait = join . liftM liftIO $ askSession sessionWait++-- | Update database+serverUpdateDB :: SessionMonad m => Database -> m ()+serverUpdateDB db = askSession sessionDatabase >>= (`DB.update` return db)++-- | Server write cache+serverWriteCache :: SessionMonad m => Database -> m ()+serverWriteCache db = do+ s <- getSession+ write' <- askSession sessionWriteCache+ liftIO $ withSession s $ write' db++-- | Server read cache+serverReadCache :: SessionMonad m => (FilePath -> ExceptT String IO Structured) -> m (Maybe Database)+serverReadCache act = do+ s <- getSession+ read' <- askSession sessionReadCache+ liftIO $ withSession s $ read' act++-- | In ghc session+inSessionGhc :: SessionMonad m => GhcM a -> m a+inSessionGhc act = do+ ghcw <- askSession sessionGhc+ inWorkerWith (hsdevError . GhcError . displayException) ghcw act++-- | Exit session+serverExit :: SessionMonad m => m ()+serverExit = join . liftM liftIO $ askSession sessionExit++commandRoot :: CommandMonad m => m FilePath+commandRoot = askOptions commandOptionsRoot++commandNotify :: CommandMonad m => Notification -> m ()+commandNotify n = join . liftM liftIO $ askOptions commandOptionsNotify <*> pure n++commandLink :: CommandMonad m => m ()+commandLink = join . liftM liftIO $ askOptions commandOptionsLink++commandHold :: CommandMonad m => m ()+commandHold = join . liftM liftIO $ askOptions commandOptionsHold++-- | Server control command+data ServerCommand =+ Version |+ Start ServerOpts |+ Run ServerOpts |+ Stop ClientOpts |+ Connect ClientOpts |+ Remote ClientOpts Bool Command+ deriving (Show)++data ConnectionPort = NetworkPort Int | UnixPort String deriving (Eq, Read)++instance Default ConnectionPort where+ def = NetworkPort 4567++instance Show ConnectionPort where+ show (NetworkPort p) = show p+ show (UnixPort s) = "unix " ++ s++instance Formattable ConnectionPort++-- | Server options+data ServerOpts = ServerOpts {+ serverPort :: ConnectionPort,+ serverTimeout :: Int,+ serverLog :: Maybe FilePath,+ serverLogLevel :: String,+ serverCache :: Maybe FilePath,+ serverLoad :: Bool,+ serverSilent :: Bool }+ deriving (Show)++instance Default ServerOpts where+ def = ServerOpts def 0 Nothing "info" Nothing False False++-- | Silent server with no connection, useful for ghci+silentOpts :: ServerOpts+silentOpts = def { serverSilent = True }++-- | Client options+data ClientOpts = ClientOpts {+ clientPort :: ConnectionPort,+ clientPretty :: Bool,+ clientStdin :: Bool,+ clientTimeout :: Int,+ clientSilent :: Bool }+ deriving (Show)++instance Default ClientOpts where+ def = ClientOpts def False False 0 False++instance FromCmd ServerCommand where+ cmdP = serv <|> remote where+ serv = subparser $ mconcat [+ cmd "version" "hsdev version" (pure Version),+ cmd "start" "start remote server" (Start <$> cmdP),+ cmd "run" "run server" (Run <$> cmdP),+ cmd "stop" "stop remote server" (Stop <$> cmdP),+ cmd "connect" "connect to send commands directly" (Connect <$> cmdP)]+ remote = Remote <$> cmdP <*> noFileFlag <*> cmdP++instance FromCmd ServerOpts where+ cmdP = ServerOpts <$>+ (connectionArg <|> pure (serverPort def)) <*>+ (timeoutArg <|> pure (serverTimeout def)) <*>+ optional logArg <*>+ (logLevelArg <|> pure (serverLogLevel def)) <*>+ optional cacheArg <*>+ loadFlag <*>+ serverSilentFlag++instance FromCmd ClientOpts where+ cmdP = ClientOpts <$>+ (connectionArg <|> pure (clientPort def)) <*>+ prettyFlag <*>+ stdinFlag <*>+ (timeoutArg <|> pure (clientTimeout def)) <*>+ silentFlag++portArg :: Parser ConnectionPort+connectionArg :: Parser ConnectionPort+timeoutArg :: Parser Int+logArg :: Parser FilePath+logLevelArg :: Parser String+cacheArg :: Parser FilePath+noFileFlag :: Parser Bool+loadFlag :: Parser Bool+prettyFlag :: Parser Bool+serverSilentFlag :: Parser Bool+stdinFlag :: Parser Bool+silentFlag :: Parser Bool++portArg = NetworkPort <$> option auto (long "port" <> metavar "number" <> help "connection port")+#if mingw32_HOST_OS+connectionArg = portArg+#else+unixArg :: Parser ConnectionPort+unixArg = UnixPort <$> strOption (long "unix" <> metavar "name" <> help "unix connection port")+connectionArg = portArg <|> unixArg+#endif+timeoutArg = option auto (long "timeout" <> metavar "msec" <> help "query timeout")+logArg = strOption (long "log" <> short 'l' <> metavar "file" <> help "log file")+logLevelArg = strOption (long "log-level" <> metavar "level" <> help "log level: trace/debug/info/warning/error/fatal")+cacheArg = strOption (long "cache" <> metavar "path" <> help "cache directory")+noFileFlag = switch (long "no-file" <> help "don't use mmap files")+loadFlag = switch (long "load" <> help "force load all data from cache on startup")+prettyFlag = switch (long "pretty" <> help "pretty json output")+serverSilentFlag = switch (long "silent" <> help "no stdout/stderr")+stdinFlag = switch (long "stdin" <> help "pass data to stdin")+silentFlag = switch (long "silent" <> help "supress notifications")++serverOptsArgs :: ServerOpts -> [String]+serverOptsArgs sopts = concat [+ portArgs (serverPort sopts),+ ["--timeout", show $ serverTimeout sopts],+ marg "--log" (serverLog sopts),+ ["--log-level", serverLogLevel sopts],+ marg "--cache" (serverCache sopts),+ ["--load" | serverLoad sopts],+ ["--silent" | serverSilent sopts]]+ where+ marg :: String -> Maybe String -> [String]+ marg n (Just v) = [n, v]+ marg _ _ = []+ portArgs :: ConnectionPort -> [String]+ portArgs (NetworkPort n) = ["--port", show n]+ portArgs (UnixPort s) = ["--unix", s]++data Request = Request {+ requestCommand :: Command,+ requestDirectory :: FilePath,+ requestNoFile :: Bool,+ requestTimeout :: Int,+ requestSilent :: Bool }+ deriving (Show)++instance ToJSON Request where+ toJSON (Request c dir f tm s) = object ["current-directory" .= dir, "no-file" .= f, "timeout" .= tm, "silent" .= s] `objectUnion` toJSON c++instance FromJSON Request where+ parseJSON = withObject "request" $ \v -> Request <$>+ parseJSON (Object v) <*>+ ((v .:: "current-directory") <|> pure ".") <*>+ ((v .:: "no-file") <|> pure False) <*>+ ((v .:: "timeout") <|> pure 0) <*>+ ((v .:: "silent") <|> pure False)++-- | Command from client+data Command =+ Ping |+ Listen (Maybe String) |+ SetLogLevel String |+ AddData { addedContents :: [AddedContents] } |+ Scan {+ scanProjects :: [FilePath],+ scanCabal :: Bool,+ scanSandboxes :: [FilePath],+ scanFiles :: [FileSource],+ scanPaths :: [FilePath],+ scanGhcOpts :: [String],+ scanDocs :: Bool,+ scanInferTypes :: Bool } |+ RefineDocs {+ docsProjects :: [FilePath],+ docsFiles :: [FilePath],+ docsModules :: [String] } |+ InferTypes {+ inferProjects :: [FilePath],+ inferFiles :: [FilePath],+ inferModules :: [String] } |+ Remove {+ removeProjects :: [FilePath],+ removeCabal :: Bool,+ removeSandboxes :: [FilePath],+ removeFiles :: [FilePath] } |+ RemoveAll |+ InfoModules [TargetFilter] |+ InfoPackages |+ InfoProjects |+ InfoSandboxes |+ InfoSymbol SearchQuery [TargetFilter] Bool |+ InfoModule SearchQuery [TargetFilter] |+ InfoResolve FilePath Bool |+ InfoProject (Either String FilePath) |+ InfoSandbox FilePath |+ Lookup String FilePath |+ Whois String FilePath |+ ResolveScopeModules SearchQuery FilePath |+ ResolveScope SearchQuery Bool FilePath |+ Complete String Bool FilePath |+ Hayoo {+ hayooQuery :: String,+ hayooPage :: Int,+ hayooPages :: Int } |+ CabalList { cabalListPackages :: [String] } |+ Lint {+ lintFiles :: [FileSource] } |+ Check {+ checkFiles :: [FileSource],+ checkGhcOpts :: [String] } |+ CheckLint {+ checkLintFiles :: [FileSource],+ checkLintGhcOpts :: [String] } |+ Types {+ typesFiles :: [FileSource],+ typesGhcOpts :: [String] } |+ AutoFix { autoFixCommand :: AutoFixCommand } |+ GhcEval { ghcEvalExpressions :: [String], ghcEvalSource :: Maybe FileSource } |+ Langs |+ Flags |+ Link { linkHold :: Bool } |+ Exit+ deriving (Show)++data AddedContents =+ AddedDatabase Database |+ AddedModule InspectedModule |+ AddedProject Project++instance Show AddedContents where+ show = L.unpack . encode++data AutoFixCommand =+ AutoFixShow [Note OutputMessage] |+ AutoFixFix [Note Correction] [Note Correction] Bool+ deriving (Show)++data FileSource = FileSource { fileSource :: FilePath, fileContents :: Maybe String } deriving (Show)+data TargetFilter =+ TargetProject String |+ TargetFile FilePath |+ TargetModule String |+ TargetDepsOf String |+ TargetPackageDb PackageDb |+ TargetCabal |+ TargetSandbox FilePath |+ TargetPackage String |+ TargetSourced |+ TargetStandalone+ deriving (Eq, Show)+data SearchQuery = SearchQuery String SearchType deriving (Show)+data SearchType = SearchExact | SearchPrefix | SearchInfix | SearchSuffix | SearchRegex deriving (Show)++instance Paths Command where+ paths f (Scan projs c cs fs ps ghcs docs infer) = Scan <$>+ each f projs <*>+ pure c <*>+ (each . paths) f cs <*>+ (each . paths) f fs <*>+ each f ps <*>+ pure ghcs <*>+ pure docs <*>+ pure infer+ paths f (RefineDocs projs fs ms) = RefineDocs <$> each f projs <*> each f fs <*> pure ms+ paths f (InferTypes projs fs ms) = InferTypes <$> each f projs <*> each f fs <*> pure ms+ paths f (Remove projs c cs fs) = Remove <$> each f projs <*> pure c <*> (each . paths) f cs <*> each f fs+ paths _ RemoveAll = pure RemoveAll+ paths f (InfoModules t) = InfoModules <$> paths f t+ paths f (InfoSymbol q t l) = InfoSymbol <$> pure q <*> paths f t <*> pure l+ paths f (InfoModule q t) = InfoModule <$> pure q <*> paths f t+ paths f (InfoResolve fpath es) = InfoResolve <$> f fpath <*> pure es+ paths f (InfoProject (Right proj)) = InfoProject <$> (Right <$> f proj)+ paths f (InfoSandbox fpath) = InfoSandbox <$> f fpath+ paths f (Lookup n fpath) = Lookup <$> pure n <*> f fpath+ paths f (Whois n fpath) = Whois <$> pure n <*> f fpath+ paths f (ResolveScopeModules q fpath) = ResolveScopeModules q <$> f fpath+ paths f (ResolveScope q g fpath) = ResolveScope q g <$> f fpath+ paths f (Complete n g fpath) = Complete n g <$> f fpath+ paths f (Lint fs) = Lint <$> (each . paths) f fs+ paths f (Check fs ghcs) = Check <$> (each . paths) f fs <*> pure ghcs+ paths f (CheckLint fs ghcs) = CheckLint <$> (each . paths) f fs <*> pure ghcs+ paths f (Types fs ghcs) = Types <$> (each . paths) f fs <*> pure ghcs+ paths f (GhcEval e mf) = GhcEval e <$> traverse (paths f) mf+ paths _ c = pure c++instance Paths FileSource where+ paths f (FileSource fpath mcts) = FileSource <$> f fpath <*> pure mcts++instance Paths TargetFilter where+ paths f (TargetFile fpath) = TargetFile <$> f fpath+ paths f (TargetPackageDb pdb) = TargetPackageDb <$> paths f pdb+ paths f (TargetSandbox c) = TargetSandbox <$> paths f c+ paths _ t = pure t++instance Paths [TargetFilter] where+ paths = each . paths++instance FromCmd Command where+ cmdP = subparser $ mconcat [+ cmd "ping" "ping server" (pure Ping),+ cmd "listen" "listen server log" (Listen <$> optional logLevelArg),+ cmd "set-log" "set log level" (SetLogLevel <$> strArgument idm),+ cmd "add" "add info to database" (AddData <$> option readJSON idm),+ cmd "scan" "scan sources" $ Scan <$>+ many projectArg <*>+ cabalFlag <*>+ many sandboxArg <*>+ many cmdP <*>+ many (pathArg $ help "path") <*>+ ghcOpts <*>+ docsFlag <*>+ inferFlag,+ cmd "docs" "scan docs" $ RefineDocs <$> many projectArg <*> many fileArg <*> many moduleArg,+ cmd "infer" "infer types" $ InferTypes <$> many projectArg <*> many fileArg <*> many moduleArg,+ cmd "remove" "remove modules info" $ Remove <$>+ many projectArg <*>+ cabalFlag <*>+ many sandboxArg <*>+ many fileArg,+ cmd "remove-all" "remove all data" (pure RemoveAll),+ cmd "modules" "list modules" (InfoModules <$> many cmdP),+ cmd "packages" "list packages" (pure InfoPackages),+ cmd "projects" "list projects" (pure InfoProjects),+ cmd "sandboxes" "list sandboxes" (pure InfoSandboxes),+ cmd "symbol" "get symbol info" (InfoSymbol <$> cmdP <*> many cmdP <*> localsFlag),+ cmd "module" "get module info" (InfoModule <$> cmdP <*> many cmdP),+ cmd "resolve" "resolve module scope (or exports)" (InfoResolve <$> fileArg <*> exportsFlag),+ cmd "project" "get project info" (InfoProject <$> ((Left <$> projectArg) <|> (Right <$> pathArg idm))),+ cmd "sandbox" "get sandbox info" (InfoSandbox <$> pathArg (help "locate sandbox in parent of this path")),+ cmd "lookup" "lookup for symbol" (Lookup <$> strArgument idm <*> ctx),+ cmd "whois" "get info for symbol" (Whois <$> strArgument idm <*> ctx),+ cmd "scope" "get declarations accessible from module or within a project" (+ subparser (cmd "modules" "get modules accessible from module or within a project" (ResolveScopeModules <$> cmdP <*> ctx)) <|>+ ResolveScope <$> cmdP <*> globalFlag <*> ctx),+ cmd "complete" "show completions for input" (Complete <$> strArgument idm <*> wideFlag <*> ctx),+ cmd "hayoo" "find declarations online via Hayoo" (Hayoo <$> strArgument idm <*> hayooPageArg <*> hayooPagesArg),+ cmd "cabal" "cabal commands" (subparser $ cmd "list" "list cabal packages" (CabalList <$> many (strArgument idm))),+ cmd "lint" "lint source files or file contents" (Lint <$> many cmdP),+ cmd "check" "check source files or file contents" (Check <$> many cmdP <*> ghcOpts),+ cmd "check-lint" "check and lint source files or file contents" (CheckLint <$> many cmdP <*> ghcOpts),+ cmd "types" "get types for file expressions" (Types <$> many cmdP <*> ghcOpts),+ cmd "autofix" "autofix commands" (AutoFix <$> cmdP),+ cmd "ghc" "ghc commands" (subparser $ cmd "eval" "evaluate expression" (GhcEval <$> many (strArgument idm) <*> optional cmdP)),+ cmd "langs" "ghc language options" (pure Langs),+ cmd "flags" "ghc flags" (pure Flags),+ cmd "link" "link to server" (Link <$> holdFlag),+ cmd "exit" "exit" (pure Exit)]++instance FromCmd AutoFixCommand where+ cmdP = subparser $ mconcat [+ cmd "show" "generate corrections for check & lint messages" (AutoFixShow <$> option readJSON (long "data" <> metavar "message" <> help "messages to make fixes for")),+ cmd "fix" "fix errors and return rest corrections with updated regions" (AutoFixFix <$>+ option readJSON (long "data" <> metavar "message" <> help "messages to fix") <*>+ option readJSON (long "rest" <> metavar "correction" <> short 'r' <> help "update corrections") <*>+ pureFlag)]++instance FromCmd FileSource where+ cmdP = option readJSON (long "contents") <|> (FileSource <$> fileArg <*> pure Nothing)++instance FromCmd TargetFilter where+ cmdP = asum [+ TargetProject <$> projectArg,+ TargetFile <$> fileArg,+ TargetModule <$> moduleArg,+ TargetDepsOf <$> depsArg,+ TargetPackageDb <$> packageDbArg,+ flag' TargetCabal (long "cabal"),+ TargetSandbox <$> sandboxArg,+ TargetPackage <$> packageArg,+ flag' TargetSourced (long "src"),+ flag' TargetStandalone (long "stand")]++instance FromCmd SearchQuery where+ cmdP = SearchQuery <$> (strArgument idm <|> pure "") <*> asum [+ flag' SearchExact (long "exact"),+ flag' SearchRegex (long "regex"),+ flag' SearchInfix (long "infix"),+ flag' SearchSuffix (long "suffix"),+ pure SearchPrefix <* switch (long "prefix")]++readJSON :: FromJSON a => ReadM a+readJSON = str >>= maybe (readerError "Can't parse JSON argument") return . decode . L.pack++cabalFlag :: Parser Bool+ctx :: Parser FilePath+depsArg :: Parser String+docsFlag :: Parser Bool+exportsFlag :: Parser Bool+fileArg :: Parser FilePath+ghcOpts :: Parser [String]+globalFlag :: Parser Bool+hayooPageArg :: Parser Int+hayooPagesArg :: Parser Int+holdFlag :: Parser Bool+inferFlag :: Parser Bool+localsFlag :: Parser Bool+moduleArg :: Parser String+packageDbArg :: Parser PackageDb+packageArg :: Parser String+pathArg :: Mod OptionFields String -> Parser FilePath+projectArg :: Parser String+pureFlag :: Parser Bool+sandboxArg :: Parser FilePath+wideFlag :: Parser Bool++cabalFlag = switch (long "cabal")+ctx = fileArg+depsArg = strOption (long "deps" <> metavar "object" <> help "filter to such that in dependency of specified object (file or project)")+docsFlag = switch (long "docs" <> help "scan source file docs")+exportsFlag = switch (long "exports" <> short 'e' <> help "resolve module exports")+fileArg = strOption (long "file" <> metavar "path" <> short 'f')+ghcOpts = many (strOption (long "ghc" <> metavar "option" <> short 'g' <> help "options to pass to GHC"))+globalFlag = switch (long "global" <> help "scope of project")+hayooPageArg = option auto (long "page" <> metavar "n" <> short 'p' <> help "page number (0 by default)" <> value 0)+hayooPagesArg = option auto (long "pages" <> metavar "count" <> short 'n' <> help "pages count (1 by default)" <> value 1)+holdFlag = switch (long "hold" <> short 'h' <> help "don't return any response")+inferFlag = switch (long "infer" <> help "infer types")+localsFlag = switch (long "locals" <> short 'l' <> help "look in local declarations")+moduleArg = strOption (long "module" <> metavar "name" <> short 'm' <> help "module name")+packageArg = strOption (long "package" <> metavar "name" <> help "module package")+packageDbArg =+ flag' GlobalDb (long "global-db" <> help "global package-db") <|>+ flag' UserDb (long "user-db" <> help "user package-db") <|>+ (PackageDb <$> strOption (long "package-db" <> metavar "path" <> help "custom package-db"))+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")+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 lev) = cmdJson "listen" ["level" .= lev]+ toJSON (SetLogLevel lev) = cmdJson "set-log" ["level" .= lev]+ toJSON (AddData cts) = cmdJson "add" ["data" .= cts]+ toJSON (Scan projs cabal sboxes fs ps ghcs docs' infer') = cmdJson "scan" [+ "projects" .= projs,+ "cabal" .= cabal,+ "sandboxes" .= sboxes,+ "files" .= fs,+ "paths" .= ps,+ "ghc-opts" .= ghcs,+ "docs" .= docs',+ "infer" .= infer']+ toJSON (RefineDocs projs fs ms) = cmdJson "docs" ["projects" .= projs, "files" .= fs, "modules" .= ms]+ toJSON (InferTypes projs fs ms) = cmdJson "infer" ["projects" .= projs, "files" .= fs, "modules" .= ms]+ toJSON (Remove projs cabal sboxes fs) = cmdJson "remove" ["projects" .= projs, "cabal" .= cabal, "sandboxes" .= sboxes, "files" .= fs]+ toJSON RemoveAll = cmdJson "remove-all" []+ toJSON (InfoModules tf) = cmdJson "modules" ["filters" .= tf]+ toJSON InfoPackages = cmdJson "packages" []+ toJSON InfoProjects = cmdJson "projects" []+ toJSON InfoSandboxes = cmdJson "sandboxes" []+ toJSON (InfoSymbol q tf l) = cmdJson "symbol" ["query" .= q, "filters" .= tf, "locals" .= l]+ toJSON (InfoModule q tf) = cmdJson "module" ["query" .= q, "filters" .= tf]+ toJSON (InfoResolve f es) = cmdJson "resolve" ["file" .= f, "exports" .= es]+ toJSON (InfoProject p) = cmdJson "project" $ either (\pname -> ["name" .= pname]) (\ppath -> ["path" .= ppath]) p+ toJSON (InfoSandbox p) = cmdJson "sandbox" ["path" .= p]+ toJSON (Lookup n f) = cmdJson "lookup" ["name" .= n, "file" .= f]+ toJSON (Whois n f) = cmdJson "whois" ["name" .= n, "file" .= f]+ toJSON (ResolveScopeModules q f) = cmdJson "scope modules" ["query" .= q, "file" .= f]+ toJSON (ResolveScope q g f) = cmdJson "scope" ["query" .= q, "global" .= g, "file" .= f]+ toJSON (Complete q w f) = cmdJson "complete" ["prefix" .= q, "wide" .= w, "file" .= f]+ toJSON (Hayoo q p ps) = cmdJson "hayoo" ["query" .= q, "page" .= p, "pages" .= ps]+ toJSON (CabalList ps) = cmdJson "cabal list" ["packages" .= ps]+ toJSON (Lint fs) = cmdJson "lint" ["files" .= fs]+ toJSON (Check fs ghcs) = cmdJson "check" ["files" .= fs, "ghc-opts" .= ghcs]+ toJSON (CheckLint fs ghcs) = cmdJson "check-lint" ["files" .= fs, "ghc-opts" .= ghcs]+ toJSON (Types fs ghcs) = cmdJson "types" ["files" .= fs, "ghc-opts" .= ghcs]+ toJSON (AutoFix acmd) = toJSON acmd+ toJSON (GhcEval exprs f) = cmdJson "ghc eval" ["exprs" .= exprs, "file" .= f]+ toJSON Langs = cmdJson "langs" []+ toJSON Flags = cmdJson "flags" []+ toJSON (Link h) = cmdJson "link" ["hold" .= h]+ toJSON Exit = cmdJson "exit" []++instance FromJSON Command where+ parseJSON = withObject "command" $ \v -> asum [+ guardCmd "ping" v *> pure Ping,+ guardCmd "listen" v *> (Listen <$> v .::? "level"),+ guardCmd "set-log" v *> (SetLogLevel <$> v .:: "level"),+ guardCmd "add" v *> (AddData <$> v .:: "data"),+ guardCmd "scan" v *> (Scan <$>+ v .::?! "projects" <*>+ (v .:: "cabal" <|> pure False) <*>+ v .::?! "sandboxes" <*>+ v .::?! "files" <*>+ v .::?! "paths" <*>+ v .::?! "ghc-opts" <*>+ (v .:: "docs" <|> pure False) <*>+ (v .:: "infer" <|> pure False)),+ guardCmd "docs" v *> (RefineDocs <$> v .::?! "projects" <*> v .::?! "files" <*> v .::?! "modules"),+ guardCmd "infer" v *> (InferTypes <$> v .::?! "projects" <*> v .::?! "files" <*> v .::?! "modules"),+ guardCmd "remove" v *> (Remove <$>+ v .::?! "projects" <*>+ (v .:: "cabal" <|> pure False) <*>+ v .::?! "sandboxes" <*>+ v .::?! "files"),+ guardCmd "remove-all" v *> pure RemoveAll,+ guardCmd "modules" v *> (InfoModules <$> v .::?! "filters"),+ guardCmd "packages" v *> pure InfoPackages,+ guardCmd "projects" v *> pure InfoProjects,+ guardCmd "sandboxes" v *> pure InfoSandboxes,+ guardCmd "symbol" v *> (InfoSymbol <$> v .:: "query" <*> v .::?! "filters" <*> (v .:: "locals" <|> pure False)),+ guardCmd "module" v *> (InfoModule <$> v .:: "query" <*> v .::?! "filters"),+ guardCmd "resolve" v *> (InfoResolve <$> v .:: "file" <*> (v .:: "exports" <|> pure False)),+ guardCmd "project" v *> (InfoProject <$> asum [Left <$> v .:: "name", Right <$> v .:: "path"]),+ guardCmd "sandbox" v *> (InfoSandbox <$> v .:: "path"),+ guardCmd "lookup" v *> (Lookup <$> v .:: "name" <*> v .:: "file"),+ guardCmd "whois" v *> (Whois <$> v .:: "name" <*> v .:: "file"),+ guardCmd "scope modules" v *> (ResolveScopeModules <$> v .:: "query" <*> v .:: "file"),+ guardCmd "scope" v *> (ResolveScope <$> v .:: "query" <*> (v .:: "global" <|> pure False) <*> v .:: "file"),+ guardCmd "complete" v *> (Complete <$> v .:: "prefix" <*> (v .:: "wide" <|> pure False) <*> v .:: "file"),+ guardCmd "hayoo" v *> (Hayoo <$> v .:: "query" <*> (v .:: "page" <|> pure 0) <*> (v .:: "pages" <|> pure 1)),+ guardCmd "cabal list" v *> (CabalList <$> v .::?! "packages"),+ guardCmd "lint" v *> (Lint <$> v .::?! "files"),+ guardCmd "check" v *> (Check <$> v .::?! "files" <*> v .::?! "ghc-opts"),+ guardCmd "check-lint" v *> (CheckLint <$> v .::?! "files" <*> v .::?! "ghc-opts"),+ guardCmd "types" v *> (Types <$> v .::?! "files" <*> v .::?! "ghc-opts"),+ AutoFix <$> parseJSON (Object v),+ guardCmd "ghc eval" v *> (GhcEval <$> v .::?! "exprs" <*> v .::? "file"),+ guardCmd "langs" v *> pure Langs,+ guardCmd "flags" v *> pure Flags,+ guardCmd "link" v *> (Link <$> (v .:: "hold" <|> pure False)),+ guardCmd "exit" v *> pure Exit]++instance ToJSON AddedContents where+ toJSON (AddedDatabase db) = object ["database" .= db]+ toJSON (AddedModule im) = object ["module" .= im]+ toJSON (AddedProject p) = object ["project" .= p]++instance FromJSON AddedContents where+ parseJSON = withObject "added-contents" $ \v -> asum [+ AddedDatabase <$> v .:: "database",+ AddedModule <$> v .:: "module",+ AddedProject <$> v .:: "project"]++instance ToJSON AutoFixCommand where+ toJSON (AutoFixShow ns) = cmdJson "autofix show" ["messages" .= ns]+ toJSON (AutoFixFix ns rests pure') = cmdJson "autofix fix" ["messages" .= ns, "rest" .= rests, "pure" .= pure']++instance FromJSON AutoFixCommand where+ parseJSON = withObject "auto-fix-command" $ \v -> asum [+ guardCmd "autofix show" v *> (AutoFixShow <$> v .:: "messages"),+ guardCmd "autofix fix" v *> (AutoFixFix <$> v .:: "messages" <*> v .::?! "rest" <*> (v .:: "pure" <|> pure True))]++instance ToJSON FileSource where+ toJSON (FileSource fpath mcts) = object ["file" .= fpath, "contents" .= mcts]++instance FromJSON FileSource where+ parseJSON = withObject "file-contents" $ \v -> FileSource <$> v .:: "file" <*> v .::? "contents"++instance ToJSON TargetFilter where+ toJSON (TargetProject pname) = object ["project" .= pname]+ toJSON (TargetFile fpath) = object ["file" .= fpath]+ toJSON (TargetModule mname) = object ["module" .= mname]+ toJSON (TargetDepsOf dep) = object ["deps" .= dep]+ toJSON (TargetPackageDb pdb) = object ["db" .= pdb]+ toJSON TargetCabal = toJSON ("cabal" :: String)+ toJSON (TargetSandbox sbox) = object ["sandbox" .= sbox]+ toJSON (TargetPackage pname) = object ["package" .= pname]+ toJSON TargetSourced = toJSON ("sourced" :: String)+ toJSON TargetStandalone = toJSON ("standalone" :: String)++instance FromJSON TargetFilter where+ parseJSON j = obj j <|> str' where+ obj = withObject "target-filter" $ \v -> asum [+ TargetProject <$> v .:: "project",+ TargetFile <$> v .:: "file",+ TargetModule <$> v .:: "module",+ TargetDepsOf <$> v .:: "deps",+ TargetPackageDb <$> v .:: "db",+ TargetSandbox <$> v .:: "sandbox",+ TargetPackage <$> v .:: "package"]+ str' = do+ s <- parseJSON j :: A.Parser String+ case s of+ "cabal" -> return TargetCabal+ "sourced" -> return TargetSourced+ "standalone" -> return TargetStandalone+ _ -> empty++instance ToJSON SearchQuery where+ toJSON (SearchQuery q st) = object ["input" .= q, "type" .= st]++instance FromJSON SearchQuery where+ parseJSON = withObject "search-query" $ \v -> SearchQuery <$> (v .:: "input" <|> pure "") <*> (v .:: "type" <|> pure SearchPrefix)++instance ToJSON SearchType where+ toJSON SearchExact = toJSON ("exact" :: String)+ toJSON SearchPrefix = toJSON ("prefix" :: String)+ toJSON SearchInfix = toJSON ("infix" :: String)+ toJSON SearchSuffix = toJSON ("suffix" :: String)+ toJSON SearchRegex = toJSON ("regex" :: String)++instance FromJSON SearchType where+ parseJSON v = do+ str' <- parseJSON v :: A.Parser String+ case str' of+ "exact" -> return SearchExact+ "prefix" -> return SearchPrefix+ "infix" -> return SearchInfix+ "suffix" -> return SearchInfix+ "regex" -> return SearchRegex+ _ -> empty
src/HsDev/Stack.hs view
@@ -1,131 +1,131 @@-{-# LANGUAGE TemplateHaskell, RankNTypes #-} - -module HsDev.Stack ( - stack, yaml, - path, pathOf, - build, buildDeps, configure, - StackEnv(..), stackRoot, stackProject, stackConfig, stackGhc, stackSnapshot, stackLocal, - getStackEnv, projectEnv, - stackPackageDbStack, - - stackCompiler, stackArch, - - MaybeT(..) - ) where - -import Control.Arrow -import Control.Lens (makeLenses, Lens', at, ix, lens, (^?), (^.)) -import Control.Monad -import Control.Monad.Trans.Maybe -import Control.Monad.IO.Class -import Data.Char -import Data.Maybe -import Data.Map (Map) -import qualified Data.Map as M -import Distribution.Compiler -import Distribution.System -import qualified Distribution.Text as T (display) -import System.Directory -import System.Environment -import System.FilePath -import System.Log.Simple (MonadLog(..)) - -import qualified Packages as GHC - -import HsDev.Error -import HsDev.PackageDb -import qualified HsDev.Tools.Ghc.Compat as Compat -import HsDev.Scan.Browse (withPackages) -import HsDev.Util as Util -import HsDev.Tools.Base (runTool_) - --- | Get compiler version -stackCompiler :: MonadLog m => m String -stackCompiler = do - res <- withPackages ["-no-user-package-db"] $ - return . - map (GHC.packageNameString &&& GHC.packageVersion) . - fromMaybe [] . - Compat.pkgDatabase - let - compiler = T.display buildCompilerFlavor - CompilerId _ version' = buildCompilerId - ver = maybe (T.display version') T.display $ lookup compiler res - return $ compiler ++ "-" ++ ver - --- | 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 :: MonadLog m => [String] -> m String -stack cmd' = hsdevLiftIO $ do - curExe <- liftIO getExecutablePath - stackExe <- Util.withCurrentDirectory (takeDirectory curExe) $ - liftIO (findExecutable "stack") >>= maybe (hsdevError $ ToolNotFound "stack") return - comp <- stackCompiler - liftIO $ runTool_ stackExe (["--compiler", comp, "--arch", stackArch] ++ cmd') - --- | Make yaml opts -yaml :: Maybe FilePath -> [String] -yaml Nothing = [] -yaml (Just y) = ["--stack-yaml", y] - -type Paths = Map String FilePath - --- | Stack path -path :: MonadLog 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 (== ':') - --- | Get path for -pathOf :: String -> Lens' Paths (Maybe FilePath) -pathOf = at - --- | Build stack project -build :: MonadLog m => [String] -> Maybe FilePath -> m () -build opts mcfg = void $ stack $ "build" : (opts ++ yaml mcfg) - --- | Build only dependencies -buildDeps :: MonadLog m => Maybe FilePath -> m () -buildDeps = build ["--only-dependencies"] - --- | Configure project -configure :: MonadLog m => Maybe FilePath -> m () -configure = build ["--only-configure"] - -data StackEnv = StackEnv { - _stackRoot :: FilePath, - _stackProject :: FilePath, - _stackConfig :: FilePath, - _stackGhc :: FilePath, - _stackSnapshot :: FilePath, - _stackLocal :: FilePath } - -makeLenses ''StackEnv - -getStackEnv :: Paths -> Maybe StackEnv -getStackEnv p = StackEnv <$> - (p ^. pathOf "stack-root") <*> - (p ^. pathOf "project-root") <*> - (p ^. pathOf "config-location") <*> - (p ^. pathOf "ghc-paths") <*> - (p ^. pathOf "snapshot-pkg-db") <*> - (p ^. pathOf "local-pkg-db") - --- | Projects paths -projectEnv :: MonadLog m => FilePath -> m StackEnv -projectEnv p = hsdevLiftIO $ Util.withCurrentDirectory p $ do - paths' <- path Nothing - maybe (hsdevError $ ToolError "stack" ("can't get paths for " ++ p)) return $ getStackEnv paths' - --- | Get package-db stack for stack environment -stackPackageDbStack :: Lens' StackEnv PackageDbStack -stackPackageDbStack = lens g s where - g :: StackEnv -> PackageDbStack - g env' = PackageDbStack $ map PackageDb [_stackLocal env', _stackSnapshot env'] - s :: StackEnv -> PackageDbStack -> StackEnv - s env' pdbs = env' { - _stackSnapshot = fromMaybe (_stackSnapshot env') $ pdbs ^? packageDbStack . ix 1 . packageDb, - _stackLocal = fromMaybe (_stackLocal env') $ pdbs ^? packageDbStack . ix 0 . packageDb } +{-# LANGUAGE TemplateHaskell, RankNTypes #-}++module HsDev.Stack (+ stack, yaml,+ path, pathOf,+ build, buildDeps, configure,+ StackEnv(..), stackRoot, stackProject, stackConfig, stackGhc, stackSnapshot, stackLocal,+ getStackEnv, projectEnv,+ stackPackageDbStack,++ stackCompiler, stackArch,++ MaybeT(..)+ ) where++import Control.Arrow+import Control.Lens (makeLenses, Lens', at, ix, lens, (^?), (^.))+import Control.Monad+import Control.Monad.Trans.Maybe+import Control.Monad.IO.Class+import Data.Char+import Data.Maybe+import Data.Map (Map)+import qualified Data.Map as M+import Distribution.Compiler+import Distribution.System+import qualified Distribution.Text as T (display)+import System.Directory+import System.Environment+import System.FilePath+import System.Log.Simple (MonadLog(..))++import qualified Packages as GHC++import HsDev.Error+import HsDev.PackageDb+import qualified HsDev.Tools.Ghc.Compat as Compat+import HsDev.Scan.Browse (withPackages)+import HsDev.Util as Util+import HsDev.Tools.Base (runTool_)++-- | Get compiler version+stackCompiler :: MonadLog m => m String+stackCompiler = do+ res <- withPackages ["-no-user-package-db"] $+ return .+ map (GHC.packageNameString &&& GHC.packageVersion) .+ fromMaybe [] .+ Compat.pkgDatabase+ let+ compiler = T.display buildCompilerFlavor+ CompilerId _ version' = buildCompilerId+ ver = maybe (T.display version') T.display $ lookup compiler res+ return $ compiler ++ "-" ++ ver++-- | 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 :: MonadLog m => [String] -> m String+stack cmd' = hsdevLiftIO $ do+ curExe <- liftIO getExecutablePath+ stackExe <- Util.withCurrentDirectory (takeDirectory curExe) $+ liftIO (findExecutable "stack") >>= maybe (hsdevError $ ToolNotFound "stack") return+ comp <- stackCompiler+ liftIO $ runTool_ stackExe (["--compiler", comp, "--arch", stackArch] ++ cmd')++-- | Make yaml opts+yaml :: Maybe FilePath -> [String]+yaml Nothing = []+yaml (Just y) = ["--stack-yaml", y]++type Paths = Map String FilePath++-- | Stack path+path :: MonadLog 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 (== ':')++-- | Get path for+pathOf :: String -> Lens' Paths (Maybe FilePath)+pathOf = at++-- | Build stack project+build :: MonadLog m => [String] -> Maybe FilePath -> m ()+build opts mcfg = void $ stack $ "build" : (opts ++ yaml mcfg)++-- | Build only dependencies+buildDeps :: MonadLog m => Maybe FilePath -> m ()+buildDeps = build ["--only-dependencies"]++-- | Configure project+configure :: MonadLog m => Maybe FilePath -> m ()+configure = build ["--only-configure"]++data StackEnv = StackEnv {+ _stackRoot :: FilePath,+ _stackProject :: FilePath,+ _stackConfig :: FilePath,+ _stackGhc :: FilePath,+ _stackSnapshot :: FilePath,+ _stackLocal :: FilePath }++makeLenses ''StackEnv++getStackEnv :: Paths -> Maybe StackEnv+getStackEnv p = StackEnv <$>+ (p ^. pathOf "stack-root") <*>+ (p ^. pathOf "project-root") <*>+ (p ^. pathOf "config-location") <*>+ (p ^. pathOf "ghc-paths") <*>+ (p ^. pathOf "snapshot-pkg-db") <*>+ (p ^. pathOf "local-pkg-db")++-- | Projects paths+projectEnv :: MonadLog m => FilePath -> m StackEnv+projectEnv p = hsdevLiftIO $ Util.withCurrentDirectory p $ do+ paths' <- path Nothing+ maybe (hsdevError $ ToolError "stack" ("can't get paths for " ++ p)) return $ getStackEnv paths'++-- | Get package-db stack for stack environment+stackPackageDbStack :: Lens' StackEnv PackageDbStack+stackPackageDbStack = lens g s where+ g :: StackEnv -> PackageDbStack+ g env' = PackageDbStack $ map PackageDb [_stackLocal env', _stackSnapshot env']+ s :: StackEnv -> PackageDbStack -> StackEnv+ s env' pdbs = env' {+ _stackSnapshot = fromMaybe (_stackSnapshot env') $ pdbs ^? packageDbStack . ix 1 . packageDb,+ _stackLocal = fromMaybe (_stackLocal env') $ pdbs ^? packageDbStack . ix 0 . packageDb }
src/HsDev/Symbols.hs view
@@ -1,278 +1,278 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Symbols ( - -- * Information - export, - passThingSpec, passImportSpec, imported, exported, - importNames, import_, - Symbol(..), - unnamedModuleId, - sortDeclarations, moduleLocals, - setDefinedIn, dropExternals, clearDefinedIn, - moduleLocalDeclarations, moduleModuleDeclarations, - Locals(..), - decl, definedIn, declarationLocals, scopes, - mergeExported, - - -- * Functions - importQualifier, - - -- * Utility - locateProject, searchProject, - locateSourceDir, - standaloneInfo, - moduleOpts, - - -- * Modifiers - addDeclaration, - - -- * Other - unalias, - - -- * Tags - setTag, hasTag, removeTag, dropTags, - - -- * Reexportss - module HsDev.Symbols.Types, - module HsDev.Symbols.Class, - module HsDev.Symbols.Documented - ) where - -import Control.Applicative -import Control.Arrow -import Control.Lens -import Control.Monad.Trans.Maybe -import Control.Monad.Except -import Data.Function (on) -import Data.List -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 -import System.FilePath - -import HsDev.Symbols.Types -import HsDev.Symbols.Class -import HsDev.Symbols.Documented (Documented(..)) -import HsDev.Util (searchPath, uniqueBy, directoryContents) - --- | Get name of export -export :: Export -> Text -export (ExportName Nothing n _) = n -export (ExportName (Just q) n _) = T.concat [q, ".", n] -export (ExportModule m) = m - --- | Does name pass thing spec -passThingSpec :: Text -> ThingPart -> Bool -passThingSpec _ ThingNothing = False -passThingSpec _ ThingAll = True -passThingSpec n (ThingWith ns) = n `elem` ns - --- | Does declaration pass import-list spec -passImportSpec :: Declaration -> ImportSpec -> Bool -passImportSpec decl' (ImportSpec n p) - | view declarationName decl' == n = True - | preview (declaration . related . _Just) decl' == Just n = view declarationName decl' `passThingSpec` p - | otherwise = False - --- | Check whether declaration passes import list -imported :: Declaration -> ImportList -> Bool -imported decl' (ImportList hiding specs) = invert $ any (decl' `passImportSpec`) specs where - invert = if hiding then not else id - --- | Check whether declaration passes export -exported :: Declaration -> Export -> Bool -exported decl' (ExportName q n p) - | view declarationName decl' == n = checkImport - | preview (declaration . related . _Just) decl' == Just n = view declarationName decl' `passThingSpec` p - | otherwise = False - where - checkImport = case q of - Nothing -> any (not . view importIsQualified) $ fromMaybe [] $ view declarationImported decl' - Just q' -> any ((q' `elem`) . importNames) $ fromMaybe [] $ view declarationImported decl' -exported decl' (ExportModule m) = any (unqualBy m) . fromMaybe [] . view declarationImported $ decl' where - unqualBy :: Text -> Import -> Bool - unqualBy m' i = m' `elem` importNames i && not (view importIsQualified i) - --- | Get import module names - full and synonym -importNames :: Import -> [Text] -importNames i = view importModuleName i : maybe [] return (view importAs i) - --- | Simple import -import_ :: Text -> Import -import_ n = Import n False Nothing Nothing Nothing - --- | Imported module can be accessed via qualifier -importQualifier :: Maybe Text -> Import -> Bool -importQualifier Nothing i - | not (view importIsQualified i) = True - | otherwise = False -importQualifier (Just q) i - | q == view importModuleName i = True - | Just q == view importAs i = True - | otherwise = False - -unnamedModuleId :: ModuleLocation -> ModuleId -unnamedModuleId = ModuleId "" - -sortDeclarations :: [Declaration] -> [Declaration] -sortDeclarations = sortBy (comparing (view declarationName)) - --- | Bring locals to top -moduleLocals :: Module -> Module -moduleLocals m = set moduleDeclarations (moduleLocalDeclarations m) m - --- | Set all declaration `definedIn` to this module -setDefinedIn :: Module -> Module -setDefinedIn m = over moduleDeclarations (map (`definedIn` view moduleId m)) m - --- | Drop all declarations, that not defined in this module -dropExternals :: Module -> Module -dropExternals m = over moduleDeclarations (filter ((/= Just (view moduleId m)) . view declarationDefined)) m - --- | Clear `definedIn` information -clearDefinedIn :: Module -> Module -clearDefinedIn = over moduleDeclarations (map (set declarationDefined Nothing)) - --- | Get declarations with locals -moduleLocalDeclarations :: Module -> [Declaration] -moduleLocalDeclarations = - sortDeclarations . - concatMap declarationLocals' . - view moduleDeclarations - where - declarationLocals' :: Declaration -> [Declaration] - declarationLocals' d = d : declarationLocals d - --- | Get list of declarations as ModuleDeclaration -moduleModuleDeclarations :: Module -> [ModuleDeclaration] -moduleModuleDeclarations m = [ModuleDeclaration (view moduleId m) d | d <- view moduleDeclarations m] - -class Locals a where - locals :: a -> [Declaration] - where_ :: a -> [Declaration] -> a - -instance Locals Declaration where - locals = locals . view declaration - where_ d ds = over declaration (`where_` ds) d - -decl :: Text -> DeclarationInfo -> Declaration -decl n = Declaration n Nothing Nothing Nothing Nothing - -definedIn :: Declaration -> ModuleId -> Declaration -definedIn d m = set declarationDefined (Just m) d - -declarationLocals :: Declaration -> [Declaration] -declarationLocals d = locals $ view declaration d - --- | Get scopes of @Declaration@, where @Nothing@ is global scope -scopes :: Declaration -> [Maybe Text] -scopes d = globalScope $ concatMap (map Just . importNames) is where - is = fromMaybe [] $ view declarationImported d - globalScope - | any (not . view importIsQualified) is = (Nothing :) - | otherwise = id - -instance Locals DeclarationInfo where - locals (Function _ ds _) = ds - locals _ = [] - where_ (Function n s r) ds = Function n (s ++ ds) r - where_ d _ = d - --- | Merge @ModuleDeclaration@ into @ExportedDeclaration@ -mergeExported :: [ModuleDeclaration] -> [ExportedDeclaration] -mergeExported = - map merge' . - groupBy ((==) `on` declId) . - sortBy (comparing declId) - where - declId :: ModuleDeclaration -> (Text, Maybe ModuleId) - declId = view moduleDeclaration >>> (view declarationName &&& view declarationDefined) - merge' :: [ModuleDeclaration] -> ExportedDeclaration - merge' [] = error "mergeExported: impossible" - merge' ds@(d:_) = ExportedDeclaration (map (view declarationModuleId) ds) (view moduleDeclaration d) - --- | Find project file is related to -locateProject :: FilePath -> IO (Maybe Project) -locateProject file = do - file' <- canonicalizePath file - isDir <- doesDirectoryExist file' - if isDir then locateHere file' else locateParent (takeDirectory file') - where - locateHere path = do - cts <- filter (not . null . takeBaseName) <$> directoryContents path - return $ fmap (project . (path </>)) $ find ((== ".cabal") . takeExtension) cts - locateParent dir = do - cts <- filter (not . null . takeBaseName) <$> directoryContents dir - case find ((== ".cabal") . takeExtension) cts of - Nothing -> if isDrive dir then return Nothing else locateParent (takeDirectory dir) - Just cabalf -> return $ Just $ project (dir </> cabalf) - --- | Search project up -searchProject :: FilePath -> IO (Maybe Project) -searchProject file = runMaybeT $ searchPath file (MaybeT . locateProject) <|> mzero - --- | Locate source dir of file -locateSourceDir :: FilePath -> IO (Maybe (Extensions FilePath)) -locateSourceDir f = runMaybeT $ do - file <- liftIO $ canonicalizePath f - p <- MaybeT $ locateProject file - proj <- lift $ loadProject p - MaybeT $ return $ findSourceDir proj file - --- | Make `Info` for standalone `Module` -standaloneInfo :: [PackageConfig] -> Module -> Info -standaloneInfo pkgs m = mempty { _infoDepends = pkgDeps ^.. each . package . packageName } where - pkgDeps = catMaybes [M.lookup mdep pkgMap >>= listToMaybe | mdep <- "Prelude" : (m ^.. moduleImports . each . importModuleName)] - pkgMap = M.unionsWith mergePkgs [M.singleton m' [p] | p <- pkgs, m' <- view packageModules p] - mergePkgs ls rs = if null es then hs else es where - (es, hs) = partition (view packageExposed) $ uniqueBy (view package) (ls ++ rs) - --- | Options for GHC of module and project -moduleOpts :: [PackageConfig] -> Module -> [String] -moduleOpts pkgs m = case view moduleLocation m of - FileModule file proj -> concat [ - hidePackages, - targetOpts info'] - where - infos' = maybe [standaloneInfo pkgs m] (`fileTargets` file) proj - info' = over infoDepends (filter validDep) (mconcat $ selfInfo : infos') - selfInfo - | proj ^? _Just . projectName `elem` map Just (infos' ^.. each . infoDepends . each) = fromMaybe mempty $ - proj ^? _Just . projectDescription . _Just . projectLibrary . _Just . libraryBuildInfo - | otherwise = mempty - -- filter out unavailable packages such as unix under windows - validDep d = d `elem` pkgs' - pkgs' = pkgs ^.. each . package . packageName - hidePackages - | null (info' ^. infoDepends) = [] - | otherwise = ["-hide-all-packages"] - _ -> [] - --- | Add declaration to module -addDeclaration :: Declaration -> Module -> Module -addDeclaration decl' = over moduleDeclarations (sortDeclarations . (decl' :)) - --- | 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 +{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Symbols (+ -- * Information+ export,+ passThingSpec, passImportSpec, imported, exported,+ importNames, import_,+ Symbol(..),+ unnamedModuleId,+ sortDeclarations, moduleLocals,+ setDefinedIn, dropExternals, clearDefinedIn,+ moduleLocalDeclarations, moduleModuleDeclarations,+ Locals(..),+ decl, definedIn, declarationLocals, scopes,+ mergeExported,++ -- * Functions+ importQualifier,++ -- * Utility+ locateProject, searchProject,+ locateSourceDir,+ standaloneInfo,+ moduleOpts,++ -- * Modifiers+ addDeclaration,++ -- * Other+ unalias,++ -- * Tags+ setTag, hasTag, removeTag, dropTags,++ -- * Reexportss+ module HsDev.Symbols.Types,+ module HsDev.Symbols.Class,+ module HsDev.Symbols.Documented+ ) where++import Control.Applicative+import Control.Arrow+import Control.Lens+import Control.Monad.Trans.Maybe+import Control.Monad.Except+import Data.Function (on)+import Data.List+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+import System.FilePath++import HsDev.Symbols.Types+import HsDev.Symbols.Class+import HsDev.Symbols.Documented (Documented(..))+import HsDev.Util (searchPath, uniqueBy, directoryContents)++-- | Get name of export+export :: Export -> Text+export (ExportName Nothing n _) = n+export (ExportName (Just q) n _) = T.concat [q, ".", n]+export (ExportModule m) = m++-- | Does name pass thing spec+passThingSpec :: Text -> ThingPart -> Bool+passThingSpec _ ThingNothing = False+passThingSpec _ ThingAll = True+passThingSpec n (ThingWith ns) = n `elem` ns++-- | Does declaration pass import-list spec+passImportSpec :: Declaration -> ImportSpec -> Bool+passImportSpec decl' (ImportSpec n p)+ | view declarationName decl' == n = True+ | preview (declaration . related . _Just) decl' == Just n = view declarationName decl' `passThingSpec` p+ | otherwise = False++-- | Check whether declaration passes import list+imported :: Declaration -> ImportList -> Bool+imported decl' (ImportList hiding specs) = invert $ any (decl' `passImportSpec`) specs where+ invert = if hiding then not else id++-- | Check whether declaration passes export+exported :: Declaration -> Export -> Bool+exported decl' (ExportName q n p)+ | view declarationName decl' == n = checkImport+ | preview (declaration . related . _Just) decl' == Just n = view declarationName decl' `passThingSpec` p+ | otherwise = False+ where+ checkImport = case q of+ Nothing -> any (not . view importIsQualified) $ fromMaybe [] $ view declarationImported decl'+ Just q' -> any ((q' `elem`) . importNames) $ fromMaybe [] $ view declarationImported decl'+exported decl' (ExportModule m) = any (unqualBy m) . fromMaybe [] . view declarationImported $ decl' where+ unqualBy :: Text -> Import -> Bool+ unqualBy m' i = m' `elem` importNames i && not (view importIsQualified i)++-- | Get import module names - full and synonym+importNames :: Import -> [Text]+importNames i = view importModuleName i : maybe [] return (view importAs i)++-- | Simple import+import_ :: Text -> Import+import_ n = Import n False Nothing Nothing Nothing++-- | Imported module can be accessed via qualifier+importQualifier :: Maybe Text -> Import -> Bool+importQualifier Nothing i+ | not (view importIsQualified i) = True+ | otherwise = False+importQualifier (Just q) i+ | q == view importModuleName i = True+ | Just q == view importAs i = True+ | otherwise = False++unnamedModuleId :: ModuleLocation -> ModuleId+unnamedModuleId = ModuleId ""++sortDeclarations :: [Declaration] -> [Declaration]+sortDeclarations = sortBy (comparing (view declarationName))++-- | Bring locals to top+moduleLocals :: Module -> Module+moduleLocals m = set moduleDeclarations (moduleLocalDeclarations m) m++-- | Set all declaration `definedIn` to this module+setDefinedIn :: Module -> Module+setDefinedIn m = over moduleDeclarations (map (`definedIn` view moduleId m)) m++-- | Drop all declarations, that not defined in this module+dropExternals :: Module -> Module+dropExternals m = over moduleDeclarations (filter ((/= Just (view moduleId m)) . view declarationDefined)) m++-- | Clear `definedIn` information+clearDefinedIn :: Module -> Module+clearDefinedIn = over moduleDeclarations (map (set declarationDefined Nothing))++-- | Get declarations with locals+moduleLocalDeclarations :: Module -> [Declaration]+moduleLocalDeclarations =+ sortDeclarations .+ concatMap declarationLocals' .+ view moduleDeclarations+ where+ declarationLocals' :: Declaration -> [Declaration]+ declarationLocals' d = d : declarationLocals d++-- | Get list of declarations as ModuleDeclaration+moduleModuleDeclarations :: Module -> [ModuleDeclaration]+moduleModuleDeclarations m = [ModuleDeclaration (view moduleId m) d | d <- view moduleDeclarations m]++class Locals a where+ locals :: a -> [Declaration]+ where_ :: a -> [Declaration] -> a++instance Locals Declaration where+ locals = locals . view declaration+ where_ d ds = over declaration (`where_` ds) d++decl :: Text -> DeclarationInfo -> Declaration+decl n = Declaration n Nothing Nothing Nothing Nothing++definedIn :: Declaration -> ModuleId -> Declaration+definedIn d m = set declarationDefined (Just m) d++declarationLocals :: Declaration -> [Declaration]+declarationLocals d = locals $ view declaration d++-- | Get scopes of @Declaration@, where @Nothing@ is global scope+scopes :: Declaration -> [Maybe Text]+scopes d = globalScope $ concatMap (map Just . importNames) is where+ is = fromMaybe [] $ view declarationImported d+ globalScope+ | any (not . view importIsQualified) is = (Nothing :)+ | otherwise = id++instance Locals DeclarationInfo where+ locals (Function _ ds _) = ds+ locals _ = []+ where_ (Function n s r) ds = Function n (s ++ ds) r+ where_ d _ = d++-- | Merge @ModuleDeclaration@ into @ExportedDeclaration@+mergeExported :: [ModuleDeclaration] -> [ExportedDeclaration]+mergeExported =+ map merge' .+ groupBy ((==) `on` declId) .+ sortBy (comparing declId)+ where+ declId :: ModuleDeclaration -> (Text, Maybe ModuleId)+ declId = view moduleDeclaration >>> (view declarationName &&& view declarationDefined)+ merge' :: [ModuleDeclaration] -> ExportedDeclaration+ merge' [] = error "mergeExported: impossible"+ merge' ds@(d:_) = ExportedDeclaration (map (view declarationModuleId) ds) (view moduleDeclaration d)++-- | Find project file is related to+locateProject :: FilePath -> IO (Maybe Project)+locateProject file = do+ file' <- canonicalizePath file+ isDir <- doesDirectoryExist file'+ if isDir then locateHere file' else locateParent (takeDirectory file')+ where+ locateHere path = do+ cts <- filter (not . null . takeBaseName) <$> directoryContents path+ return $ fmap (project . (path </>)) $ find ((== ".cabal") . takeExtension) cts+ locateParent dir = do+ cts <- filter (not . null . takeBaseName) <$> directoryContents dir+ case find ((== ".cabal") . takeExtension) cts of+ Nothing -> if isDrive dir then return Nothing else locateParent (takeDirectory dir)+ Just cabalf -> return $ Just $ project (dir </> cabalf)++-- | Search project up+searchProject :: FilePath -> IO (Maybe Project)+searchProject file = runMaybeT $ searchPath file (MaybeT . locateProject) <|> mzero++-- | Locate source dir of file+locateSourceDir :: FilePath -> IO (Maybe (Extensions FilePath))+locateSourceDir f = runMaybeT $ do+ file <- liftIO $ canonicalizePath f+ p <- MaybeT $ locateProject file+ proj <- lift $ loadProject p+ MaybeT $ return $ findSourceDir proj file++-- | Make `Info` for standalone `Module`+standaloneInfo :: [PackageConfig] -> Module -> Info+standaloneInfo pkgs m = mempty { _infoDepends = pkgDeps ^.. each . package . packageName } where+ pkgDeps = catMaybes [M.lookup mdep pkgMap >>= listToMaybe | mdep <- "Prelude" : (m ^.. moduleImports . each . importModuleName)]+ pkgMap = M.unionsWith mergePkgs [M.singleton m' [p] | p <- pkgs, m' <- view packageModules p]+ mergePkgs ls rs = if null es then hs else es where+ (es, hs) = partition (view packageExposed) $ uniqueBy (view package) (ls ++ rs)++-- | Options for GHC of module and project+moduleOpts :: [PackageConfig] -> Module -> [String]+moduleOpts pkgs m = case view moduleLocation m of+ FileModule file proj -> concat [+ hidePackages,+ targetOpts info']+ where+ infos' = maybe [standaloneInfo pkgs m] (`fileTargets` file) proj+ info' = over infoDepends (filter validDep) (mconcat $ selfInfo : infos')+ selfInfo+ | proj ^? _Just . projectName `elem` map Just (infos' ^.. each . infoDepends . each) = fromMaybe mempty $+ proj ^? _Just . projectDescription . _Just . projectLibrary . _Just . libraryBuildInfo+ | otherwise = mempty+ -- filter out unavailable packages such as unix under windows+ validDep d = d `elem` pkgs'+ pkgs' = pkgs ^.. each . package . packageName+ hidePackages+ | null (info' ^. infoDepends) = []+ | otherwise = ["-hide-all-packages"]+ _ -> []++-- | Add declaration to module+addDeclaration :: Declaration -> Module -> Module+addDeclaration decl' = over moduleDeclarations (sortDeclarations . (decl' :))++-- | 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/Class.hs view
@@ -1,20 +1,20 @@-module HsDev.Symbols.Class ( - Symbol(..), - symbolModuleLocation, - - module HsDev.Symbols.Location - ) where - -import Control.Lens (view) -import Data.Text (Text) - -import HsDev.Symbols.Location - -class Symbol a where - symbolName :: a -> Text - symbolQualifiedName :: a -> Text - symbolDocs :: a -> Maybe Text - symbolLocation :: a -> Location - -symbolModuleLocation :: Symbol a => a -> ModuleLocation -symbolModuleLocation = view locationModule . symbolLocation +module HsDev.Symbols.Class (+ Symbol(..),+ symbolModuleLocation,++ module HsDev.Symbols.Location+ ) where++import Control.Lens (view)+import Data.Text (Text)++import HsDev.Symbols.Location++class Symbol a where+ symbolName :: a -> Text+ symbolQualifiedName :: a -> Text+ symbolDocs :: a -> Maybe Text+ symbolLocation :: a -> Location++symbolModuleLocation :: Symbol a => a -> ModuleLocation+symbolModuleLocation = view locationModule . symbolLocation
src/HsDev/Symbols/Documented.hs view
@@ -1,24 +1,24 @@-module HsDev.Symbols.Documented ( - Documented(..), - defaultDetailed - ) where - -import Data.Text (unpack) - -import HsDev.Symbols.Class - --- | Documented symbol -class Symbol a => Documented a where - brief :: a -> String - detailed :: a -> String - detailed = unlines . defaultDetailed - --- | Default detailed docs -defaultDetailed :: Documented a => a -> [String] -defaultDetailed s = header ++ docs ++ loc where - header = [brief s, ""] - docs = maybe [] (return . unpack) $ symbolDocs s - loc - | null mloc = [] - | otherwise = ["Defined at " ++ mloc] - mloc = show (symbolLocation s) +module HsDev.Symbols.Documented (+ Documented(..),+ defaultDetailed+ ) where++import Data.Text (unpack)++import HsDev.Symbols.Class++-- | Documented symbol+class Symbol a => Documented a where+ brief :: a -> String+ detailed :: a -> String+ detailed = unlines . defaultDetailed++-- | Default detailed docs+defaultDetailed :: Documented a => a -> [String]+defaultDetailed s = header ++ docs ++ loc where+ header = [brief s, ""]+ docs = maybe [] (return . unpack) $ symbolDocs s+ loc+ | null mloc = []+ | otherwise = ["Defined at " ++ mloc]+ mloc = show (symbolLocation s)
src/HsDev/Symbols/Location.hs view
@@ -1,277 +1,277 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} - -module HsDev.Symbols.Location ( - ModulePackage(..), PackageConfig(..), ModuleLocation(..), moduleStandalone, locationId, noLocation, - Position(..), Region(..), region, regionAt, regionLines, regionStr, - Location(..), - - packageName, packageVersion, - package, packageModules, packageExposed, - moduleFile, moduleProject, modulePackageDb, modulePackage, cabalModuleName, moduleSourceName, - positionLine, positionColumn, - regionFrom, regionTo, - locationModule, locationPosition, - - sourceModuleRoot, - importedModulePath, - packageOpt, - RecalcTabs(..), - - module HsDev.PackageDb - ) where - -import Control.Applicative -import Control.DeepSeq (NFData(..)) -import Control.Lens (makeLenses, preview, view) -import Control.Monad (join) -import Data.Aeson -import Data.Char (isSpace, isDigit) -import Data.List (intercalate, findIndex) -import Data.Maybe -import Data.Text (Text) -import qualified Data.Text as T (split, unpack) -import System.FilePath -import Text.Read (readMaybe) - -import System.Directory.Paths -import HsDev.PackageDb -import HsDev.Project.Types -import HsDev.Util ((.::), (.::?), (.::?!), objectUnion) - --- | Just package name and version without its location -data ModulePackage = ModulePackage { - _packageName :: String, - _packageVersion :: String } - deriving (Eq, Ord) - -makeLenses ''ModulePackage - -instance NFData ModulePackage where - rnf (ModulePackage n v) = rnf n `seq` rnf v - -instance Show ModulePackage where - show (ModulePackage n "") = n - show (ModulePackage n v) = n ++ "-" ++ v - -instance Read ModulePackage where - readsPrec _ str = case pkg of - "" -> [] - _ -> [(ModulePackage n v, str')] - where - (pkg, str') = break isSpace str - (rv, rn) = span versionChar $ reverse pkg - v = reverse rv - n = reverse $ dropWhile (== '-') rn - - versionChar ch = isDigit ch || ch == '.' - -instance ToJSON ModulePackage where - toJSON (ModulePackage n v) = object [ - "name" .= n, - "version" .= v] - -instance FromJSON ModulePackage where - parseJSON = withObject "module package" $ \v -> - ModulePackage <$> (v .:: "name") <*> (v .:: "version") - -data PackageConfig = PackageConfig { - _package :: ModulePackage, - _packageModules :: [Text], - _packageExposed :: Bool } - deriving (Eq, Ord, Read, Show) - -makeLenses ''PackageConfig - -instance NFData PackageConfig where - rnf (PackageConfig p ms e) = rnf p `seq` rnf ms `seq` rnf e - -instance ToJSON PackageConfig where - toJSON (PackageConfig p ms e) = toJSON p `objectUnion` object ["modules" .= ms, "exposed" .= e] - -instance FromJSON PackageConfig where - parseJSON = withObject "package-config" $ \v -> PackageConfig <$> - parseJSON (Object v) <*> - (v .::?! "modules") <*> - (v .:: "exposed" <|> pure False) - --- | Location of module -data ModuleLocation = - FileModule { _moduleFile :: FilePath, _moduleProject :: Maybe Project } | - InstalledModule { _modulePackageDb :: PackageDb, _modulePackage :: Maybe ModulePackage, _cabalModuleName :: String } | - ModuleSource { _moduleSourceName :: Maybe String } - deriving (Eq, Ord) - -makeLenses ''ModuleLocation - -moduleStandalone :: ModuleLocation -> Bool -moduleStandalone = (== Just Nothing) . preview moduleProject - -locationId :: ModuleLocation -> String -locationId (FileModule fpath _) = fpath -locationId (InstalledModule cabal mpack nm) = intercalate ":" [show cabal, maybe "" show mpack, nm] -locationId (ModuleSource msrc) = fromMaybe "" msrc - -instance NFData ModuleLocation where - rnf (FileModule f p) = rnf f `seq` rnf p - rnf (InstalledModule d p n) = rnf d `seq` rnf p `seq` rnf n - rnf (ModuleSource m) = rnf m - -instance Show ModuleLocation where - show (FileModule f p) = f ++ maybe "" (" in " ++) (fmap (view projectPath) p) - show (InstalledModule _ p n) = n ++ maybe "" (" in package " ++) (fmap show p) - show (ModuleSource m) = fromMaybe "" m - -instance ToJSON ModuleLocation where - toJSON (FileModule f p) = object ["file" .= f, "project" .= fmap (view projectCabal) p] - toJSON (InstalledModule c p n) = object ["db" .= c, "package" .= fmap show p, "name" .= n] - toJSON (ModuleSource (Just s)) = object ["source" .= s] - toJSON (ModuleSource Nothing) = object [] - -instance FromJSON ModuleLocation where - parseJSON = withObject "module location" $ \v -> - (FileModule <$> v .:: "file" <*> (fmap project <$> (v .:: "project"))) <|> - (InstalledModule <$> v .:: "db" <*> fmap (join . fmap readMaybe) (v .:: "package") <*> v .:: "name") <|> - (ModuleSource <$> v .::? "source") - -instance Paths ModuleLocation where - paths f (FileModule fpath p) = FileModule <$> f fpath <*> traverse (paths f) p - paths f (InstalledModule c p n) = InstalledModule <$> paths f c <*> pure p <*> pure n - paths _ (ModuleSource m) = pure $ ModuleSource m - -noLocation :: ModuleLocation -noLocation = ModuleSource Nothing - -data Position = Position { - _positionLine :: Int, - _positionColumn :: Int } - deriving (Eq, Ord, Read) - -makeLenses ''Position - -instance NFData Position where - rnf (Position l c) = rnf l `seq` rnf c - -instance Show Position where - show (Position l c) = show l ++ ":" ++ show c - -instance ToJSON Position where - toJSON (Position l c) = object [ - "line" .= l, - "column" .= c] - -instance FromJSON Position where - parseJSON = withObject "position" $ \v -> Position <$> - v .:: "line" <*> - v .:: "column" - -data Region = Region { - _regionFrom :: Position, - _regionTo :: Position } - deriving (Eq, Ord, Read) - -makeLenses ''Region - -region :: Position -> Position -> Region -region f t = Region (min f t) (max f t) - -regionAt :: Position -> Region -regionAt f = region f f - -regionLines :: Region -> Int -regionLines (Region f t) = succ (view positionLine t - view positionLine f) - --- | Get string at region -regionStr :: Region -> String -> String -regionStr r@(Region f t) s = intercalate "\n" $ drop (pred $ view positionColumn f) fline : tl where - s' = take (regionLines r) $ drop (pred (view positionLine f)) $ lines s - (fline:tl) = init s' ++ [take (pred $ view positionColumn t) (last s')] - -instance NFData Region where - rnf (Region f t) = rnf f `seq` rnf t - -instance Show Region where - show (Region f t) = show f ++ "-" ++ show t - -instance ToJSON Region where - toJSON (Region f t) = object [ - "from" .= f, - "to" .= t] - -instance FromJSON Region where - parseJSON = withObject "region" $ \v -> Region <$> - v .:: "from" <*> - v .:: "to" - --- | Location of symbol -data Location = Location { - _locationModule :: ModuleLocation, - _locationPosition :: Maybe Position } - deriving (Eq, Ord) - -makeLenses ''Location - -instance NFData Location where - rnf (Location m p) = rnf m `seq` rnf p - -instance Show Location where - show (Location m p) = show m ++ ":" ++ show p - -instance ToJSON Location where - toJSON (Location ml p) = object [ - "module" .= ml, - "pos" .= p] - -instance FromJSON Location where - parseJSON = withObject "location" $ \v -> Location <$> - v .:: "module" <*> - v .:: "pos" - --- | Get source module root directory, i.e. for "...\src\Foo\Bar.hs" with module 'Foo.Bar' will return "...\src" -sourceModuleRoot :: Text -> FilePath -> FilePath -sourceModuleRoot mname = - joinPath . - reverse . drop (length $ T.split (== '.') mname) . reverse . - splitDirectories - --- | Get path of imported module --- >importedModulePath "Foo.Bar" "...\src\Foo\Bar.hs" "Quux.Blah" = "...\src\Quux\Blah.hs" -importedModulePath :: Text -> FilePath -> Text -> FilePath -importedModulePath mname file imp = - (`addExtension` "hs") . joinPath . - (++ ipath) . splitDirectories $ - sourceModuleRoot mname file - where - ipath = map T.unpack $ T.split (== '.') imp - -packageOpt :: Maybe ModulePackage -> [String] -packageOpt = maybeToList . fmap (("-package " ++) . view packageName) - --- | Recalc positions to interpret '\t' as one symbol instead of N -class RecalcTabs a where - -- | Interpret '\t' as one symbol instead of N - recalcTabs :: String -> Int -> a -> a - -- | Inverse of `recalcTabs`: interpret '\t' as N symbols instead of 1 - calcTabs :: String -> Int -> a -> a - -instance RecalcTabs Position where - recalcTabs cts n (Position l c) = Position l c' where - line = listToMaybe $ drop (pred l) $ lines cts - c' = case line of - Nothing -> c - Just line' -> let sizes = map charSize line' in - succ . fromMaybe (length sizes) . - findIndex (>= pred c) . - scanl (+) 0 $ sizes - charSize :: Char -> Int - charSize '\t' = n - charSize _ = 1 - calcTabs cts n (Position l c) = Position l c' where - line = listToMaybe $ drop (pred l) $ lines cts - c' = maybe c (succ . sum . map charSize . take (pred c)) line - charSize :: Char -> Int - charSize '\t' = n - charSize _ = 1 - -instance RecalcTabs Region where - recalcTabs cts n (Region f t) = Region (recalcTabs cts n f) (recalcTabs cts n t) - calcTabs cts n (Region f t) = Region (calcTabs cts n f) (calcTabs cts n t) +{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}++module HsDev.Symbols.Location (+ ModulePackage(..), PackageConfig(..), ModuleLocation(..), moduleStandalone, locationId, noLocation,+ Position(..), Region(..), region, regionAt, regionLines, regionStr,+ Location(..),++ packageName, packageVersion,+ package, packageModules, packageExposed,+ moduleFile, moduleProject, modulePackageDb, modulePackage, cabalModuleName, moduleSourceName,+ positionLine, positionColumn,+ regionFrom, regionTo,+ locationModule, locationPosition,++ sourceModuleRoot,+ importedModulePath,+ packageOpt,+ RecalcTabs(..),++ module HsDev.PackageDb+ ) where++import Control.Applicative+import Control.DeepSeq (NFData(..))+import Control.Lens (makeLenses, preview, view)+import Control.Monad (join)+import Data.Aeson+import Data.Char (isSpace, isDigit)+import Data.List (intercalate, findIndex)+import Data.Maybe+import Data.Text (Text)+import qualified Data.Text as T (split, unpack)+import System.FilePath+import Text.Read (readMaybe)++import System.Directory.Paths+import HsDev.PackageDb+import HsDev.Project.Types+import HsDev.Util ((.::), (.::?), (.::?!), objectUnion)++-- | Just package name and version without its location+data ModulePackage = ModulePackage {+ _packageName :: String,+ _packageVersion :: String }+ deriving (Eq, Ord)++makeLenses ''ModulePackage++instance NFData ModulePackage where+ rnf (ModulePackage n v) = rnf n `seq` rnf v++instance Show ModulePackage where+ show (ModulePackage n "") = n+ show (ModulePackage n v) = n ++ "-" ++ v++instance Read ModulePackage where+ readsPrec _ str = case pkg of+ "" -> []+ _ -> [(ModulePackage n v, str')]+ where+ (pkg, str') = break isSpace str+ (rv, rn) = span versionChar $ reverse pkg+ v = reverse rv+ n = reverse $ dropWhile (== '-') rn++ versionChar ch = isDigit ch || ch == '.'++instance ToJSON ModulePackage where+ toJSON (ModulePackage n v) = object [+ "name" .= n,+ "version" .= v]++instance FromJSON ModulePackage where+ parseJSON = withObject "module package" $ \v ->+ ModulePackage <$> (v .:: "name") <*> (v .:: "version")++data PackageConfig = PackageConfig {+ _package :: ModulePackage,+ _packageModules :: [Text],+ _packageExposed :: Bool }+ deriving (Eq, Ord, Read, Show)++makeLenses ''PackageConfig++instance NFData PackageConfig where+ rnf (PackageConfig p ms e) = rnf p `seq` rnf ms `seq` rnf e++instance ToJSON PackageConfig where+ toJSON (PackageConfig p ms e) = toJSON p `objectUnion` object ["modules" .= ms, "exposed" .= e]++instance FromJSON PackageConfig where+ parseJSON = withObject "package-config" $ \v -> PackageConfig <$>+ parseJSON (Object v) <*>+ (v .::?! "modules") <*>+ (v .:: "exposed" <|> pure False)++-- | Location of module+data ModuleLocation =+ FileModule { _moduleFile :: FilePath, _moduleProject :: Maybe Project } |+ InstalledModule { _modulePackageDb :: PackageDb, _modulePackage :: Maybe ModulePackage, _cabalModuleName :: String } |+ ModuleSource { _moduleSourceName :: Maybe String }+ deriving (Eq, Ord)++makeLenses ''ModuleLocation++moduleStandalone :: ModuleLocation -> Bool+moduleStandalone = (== Just Nothing) . preview moduleProject++locationId :: ModuleLocation -> String+locationId (FileModule fpath _) = fpath+locationId (InstalledModule cabal mpack nm) = intercalate ":" [show cabal, maybe "" show mpack, nm]+locationId (ModuleSource msrc) = fromMaybe "" msrc++instance NFData ModuleLocation where+ rnf (FileModule f p) = rnf f `seq` rnf p+ rnf (InstalledModule d p n) = rnf d `seq` rnf p `seq` rnf n+ rnf (ModuleSource m) = rnf m++instance Show ModuleLocation where+ show (FileModule f p) = f ++ maybe "" (" in " ++) (fmap (view projectPath) p)+ show (InstalledModule _ p n) = n ++ maybe "" (" in package " ++) (fmap show p)+ show (ModuleSource m) = fromMaybe "" m++instance ToJSON ModuleLocation where+ toJSON (FileModule f p) = object ["file" .= f, "project" .= fmap (view projectCabal) p]+ toJSON (InstalledModule c p n) = object ["db" .= c, "package" .= fmap show p, "name" .= n]+ toJSON (ModuleSource (Just s)) = object ["source" .= s]+ toJSON (ModuleSource Nothing) = object []++instance FromJSON ModuleLocation where+ parseJSON = withObject "module location" $ \v ->+ (FileModule <$> v .:: "file" <*> (fmap project <$> (v .:: "project"))) <|>+ (InstalledModule <$> v .:: "db" <*> fmap (join . fmap readMaybe) (v .:: "package") <*> v .:: "name") <|>+ (ModuleSource <$> v .::? "source")++instance Paths ModuleLocation where+ paths f (FileModule fpath p) = FileModule <$> f fpath <*> traverse (paths f) p+ paths f (InstalledModule c p n) = InstalledModule <$> paths f c <*> pure p <*> pure n+ paths _ (ModuleSource m) = pure $ ModuleSource m++noLocation :: ModuleLocation+noLocation = ModuleSource Nothing++data Position = Position {+ _positionLine :: Int,+ _positionColumn :: Int }+ deriving (Eq, Ord, Read)++makeLenses ''Position++instance NFData Position where+ rnf (Position l c) = rnf l `seq` rnf c++instance Show Position where+ show (Position l c) = show l ++ ":" ++ show c++instance ToJSON Position where+ toJSON (Position l c) = object [+ "line" .= l,+ "column" .= c]++instance FromJSON Position where+ parseJSON = withObject "position" $ \v -> Position <$>+ v .:: "line" <*>+ v .:: "column"++data Region = Region {+ _regionFrom :: Position,+ _regionTo :: Position }+ deriving (Eq, Ord, Read)++makeLenses ''Region++region :: Position -> Position -> Region+region f t = Region (min f t) (max f t)++regionAt :: Position -> Region+regionAt f = region f f++regionLines :: Region -> Int+regionLines (Region f t) = succ (view positionLine t - view positionLine f)++-- | Get string at region+regionStr :: Region -> String -> String+regionStr r@(Region f t) s = intercalate "\n" $ drop (pred $ view positionColumn f) fline : tl where+ s' = take (regionLines r) $ drop (pred (view positionLine f)) $ lines s+ (fline:tl) = init s' ++ [take (pred $ view positionColumn t) (last s')]++instance NFData Region where+ rnf (Region f t) = rnf f `seq` rnf t++instance Show Region where+ show (Region f t) = show f ++ "-" ++ show t++instance ToJSON Region where+ toJSON (Region f t) = object [+ "from" .= f,+ "to" .= t]++instance FromJSON Region where+ parseJSON = withObject "region" $ \v -> Region <$>+ v .:: "from" <*>+ v .:: "to"++-- | Location of symbol+data Location = Location {+ _locationModule :: ModuleLocation,+ _locationPosition :: Maybe Position }+ deriving (Eq, Ord)++makeLenses ''Location++instance NFData Location where+ rnf (Location m p) = rnf m `seq` rnf p++instance Show Location where+ show (Location m p) = show m ++ ":" ++ show p++instance ToJSON Location where+ toJSON (Location ml p) = object [+ "module" .= ml,+ "pos" .= p]++instance FromJSON Location where+ parseJSON = withObject "location" $ \v -> Location <$>+ v .:: "module" <*>+ v .:: "pos"++-- | Get source module root directory, i.e. for "...\src\Foo\Bar.hs" with module 'Foo.Bar' will return "...\src"+sourceModuleRoot :: Text -> FilePath -> FilePath+sourceModuleRoot mname = + joinPath .+ reverse . drop (length $ T.split (== '.') mname) . reverse .+ splitDirectories++-- | Get path of imported module+-- >importedModulePath "Foo.Bar" "...\src\Foo\Bar.hs" "Quux.Blah" = "...\src\Quux\Blah.hs"+importedModulePath :: Text -> FilePath -> Text -> FilePath+importedModulePath mname file imp =+ (`addExtension` "hs") . joinPath .+ (++ ipath) . splitDirectories $+ sourceModuleRoot mname file+ where+ ipath = map T.unpack $ T.split (== '.') imp++packageOpt :: Maybe ModulePackage -> [String]+packageOpt = maybeToList . fmap (("-package " ++) . view packageName)++-- | Recalc positions to interpret '\t' as one symbol instead of N+class RecalcTabs a where+ -- | Interpret '\t' as one symbol instead of N+ recalcTabs :: String -> Int -> a -> a+ -- | Inverse of `recalcTabs`: interpret '\t' as N symbols instead of 1+ calcTabs :: String -> Int -> a -> a++instance RecalcTabs Position where+ recalcTabs cts n (Position l c) = Position l c' where+ line = listToMaybe $ drop (pred l) $ lines cts+ c' = case line of+ Nothing -> c+ Just line' -> let sizes = map charSize line' in+ succ . fromMaybe (length sizes) .+ findIndex (>= pred c) .+ scanl (+) 0 $ sizes+ charSize :: Char -> Int+ charSize '\t' = n+ charSize _ = 1+ calcTabs cts n (Position l c) = Position l c' where+ line = listToMaybe $ drop (pred l) $ lines cts+ c' = maybe c (succ . sum . map charSize . take (pred c)) line+ charSize :: Char -> Int+ charSize '\t' = n+ charSize _ = 1++instance RecalcTabs Region where+ recalcTabs cts n (Region f t) = Region (recalcTabs cts n f) (recalcTabs cts n t)+ calcTabs cts n (Region f t) = Region (calcTabs cts n f) (calcTabs cts n t)
src/HsDev/Symbols/Resolve.hs view
@@ -1,176 +1,176 @@-{-# LANGUAGE GeneralizedNewtypeDeriving, TemplateHaskell #-} - -module HsDev.Symbols.Resolve ( - ResolveM(..),ResolvedTree, ResolvedModule(..), resolvedModule, resolvedScope, resolvedExports, - scopeModule, exportsModule, resolvedTopScope, - resolve, resolveOne, resolveModule, resolveImports, resolveImport, - mergeImported - ) where - -import Control.Applicative ((<|>)) -import Control.Arrow -import Control.Lens (makeLenses, view, preview, set, _Just, over, each) -import Control.Monad.Reader -import Control.Monad.State -import Data.Function (on) -import Data.List (sortBy, groupBy, delete) -import qualified Data.Map as M -import Data.Maybe (fromMaybe, listToMaybe, catMaybes) -import Data.Maybe.JustIf -import Data.Ord (comparing) -import qualified Data.Set as S -import Data.String (fromString) -import Data.Text (Text) - -import HsDev.Database -import HsDev.Symbols -import HsDev.Symbols.Util -import HsDev.Util (uniqueBy) - --- | Map from name to modules -type ModuleMap = M.Map Text [Module] - --- | Resolve monad uses existing @Database@ and @ResolvedTree@ as state. -newtype ResolveM a = ResolveM { runResolveM :: ReaderT (Database, ModuleMap) (State ResolvedTree) a } - deriving (Functor, Applicative, Monad, MonadState ResolvedTree, MonadReader (Database, ModuleMap)) - --- | Tree of resolved modules -type ResolvedTree = Map ModuleId ResolvedModule - --- | Module with declarations bringed to scope and with exported declarations -data ResolvedModule = ResolvedModule { - _resolvedModule :: Module, - _resolvedScope :: [Declaration], - _resolvedExports :: [Declaration] } - -makeLenses ''ResolvedModule - --- | Make @Module@ with scope declarations -scopeModule :: ResolvedModule -> Module -scopeModule r = set moduleDeclarations (view resolvedScope r) (view resolvedModule r) - --- | Make @Module@ with exported only declarations -exportsModule :: ResolvedModule -> Module -exportsModule r = set moduleDeclarations (view resolvedExports r) (view resolvedModule r) - --- | Get top-level scope -resolvedTopScope :: ResolvedModule -> [Declaration] -resolvedTopScope = filter isTop . view resolvedScope where - isTop :: Declaration -> Bool - isTop = any (not . view importIsQualified) . fromMaybe [] . view declarationImported - --- | Resolve modules, function is not IO, so all file names must be canonicalized -resolve :: Traversable t => Database -> t Module -> t ResolvedModule -resolve db = flip evalState M.empty . flip runReaderT (db, m) . runResolveM . traverse resolveModule where - m :: ModuleMap - m = M.fromList $ map ((view moduleName . head) &&& id) $ - groupBy ((==) `on` view moduleName) $ - sortBy (comparing (view moduleName)) $ allModules db - --- | Resolve one module -resolveOne :: Database -> Module -> ResolvedModule -resolveOne db = fromMaybe (error "Resolve: impossible happened") . resolve db . Just - --- | Resolve module -resolveModule :: Module -> ResolveM ResolvedModule -resolveModule m = gets (M.lookup $ view moduleId m) >>= maybe resolveModule' return where - resolveModule' = save $ case view moduleLocation m of - InstalledModule {} -> return ResolvedModule { - _resolvedModule = m, - _resolvedScope = map setSelfDefined $ view moduleDeclarations m, - _resolvedExports = map setSelfDefined $ view moduleDeclarations m } - _ -> do - scope' <- - liftM (thisDecls ++) . - resolveImports m . - (import_ (fromString "Prelude") :) . - view moduleImports $ m - let - exported' = case view moduleExports m of - Nothing -> thisDecls - Just exports' -> unique $ catMaybes $ mexported <$> scope' <*> exports' - return $ ResolvedModule m (sortDeclarations scope') (sortDeclarations exported') - thisDecls :: [Declaration] - thisDecls = map (selfDefined . selfImport) $ view moduleDeclarations m - setSelfDefined :: Declaration -> Declaration - setSelfDefined = - over (declaration . localDeclarations . each . declarationDefined) (<|> Just (view moduleId m)) . - over declarationDefined (<|> Just (view moduleId m)) - selfDefined :: Declaration -> Declaration - selfDefined = set declarationDefined (Just $ view moduleId m) - selfImport :: Declaration -> Declaration - selfImport = set declarationImported (Just [import_ $ view moduleName m]) - save :: ResolveM ResolvedModule -> ResolveM ResolvedModule - save act = do - rm <- act - modify $ M.insert (view (resolvedModule . moduleId) rm) rm - return rm - unique :: [Declaration] -> [Declaration] - unique = uniqueBy declId - declId :: Declaration -> (Text, Maybe ModuleId) - declId = view declarationName &&& view declarationDefined - mexported decl' e' = decl' `justIf` exported decl' e' - --- | Bring declarations into scope by imports -resolveImports :: Module -> [Import] -> ResolveM [Declaration] -resolveImports m is = do - db <- asks fst - let - deps = maybe S.empty S.fromList $ do - f <- preview (moduleLocation . moduleFile) m - p <- preview (moduleLocation . moduleProject . _Just) m - p' <- refineProject db p - return $ delete (view projectName p') $ concatMap (view infoDepends) $ fileTargets p' f - liftM (mergeImported . concat) $ mapM (resolveImport deps m) is - --- | Bring declarations into scope, first parameter is set of visible packages -resolveImport :: S.Set String -> Module -> Import -> ResolveM [Declaration] -resolveImport deps m i = liftM (map $ setImport i) resolveImport' where - resolveImport' :: ResolveM [Declaration] - resolveImport' = do - ms <- case view moduleLocation m of - FileModule file proj -> do - db <- asks fst - let - proj' = proj >>= refineProject db - case proj' of - Nothing -> selectImport i [ - inFile $ importedModulePath (view moduleName m) file (view importModuleName i), - installed] - Just p -> selectImport i [ - inProject p, - inDeps] - InstalledModule pdb _ _ -> selectImport i [inPackageDb pdb] - ModuleSource _ -> selectImport i [installed] - fromMaybe [] <$> traverse (liftM (filterImportList . view resolvedExports) . resolveModule) ms - setImport :: Import -> Declaration -> Declaration - setImport i' = set declarationImported (Just [i']) - selectImport :: Import -> [ModuleId -> Bool] -> ResolveM (Maybe Module) - selectImport i' fs = do - modsMap <- asks snd - let - mods = fromMaybe [] $ M.lookup (view importModuleName i') modsMap - return $ - listToMaybe $ - newestPackage $ - fromMaybe [] $ - listToMaybe $ dropWhile null - [filter (f . view moduleId) mods | f <- fs] - filterImportList :: [Declaration] -> [Declaration] - filterImportList = case view importList i of - Nothing -> id - Just il -> filter (`imported` il) - inDeps = maybe False (`S.member` deps) . preview (moduleIdLocation . modulePackage . _Just . packageName) - --- | Merge imported declarations -mergeImported :: [Declaration] -> [Declaration] -mergeImported = - map merge' . - groupBy ((==) `on` declId) . - sortBy (comparing declId) - where - declId :: Declaration -> (Text, Maybe ModuleId) - declId = view declarationName &&& view declarationDefined - merge' :: [Declaration] -> Declaration - merge' [] = error "mergeImported: impossible" - merge' ds@(d:_) = set declarationImported (mconcat $ map (view declarationImported) ds) d +{-# LANGUAGE GeneralizedNewtypeDeriving, TemplateHaskell #-}++module HsDev.Symbols.Resolve (+ ResolveM(..),ResolvedTree, ResolvedModule(..), resolvedModule, resolvedScope, resolvedExports,+ scopeModule, exportsModule, resolvedTopScope,+ resolve, resolveOne, resolveModule, resolveImports, resolveImport,+ mergeImported+ ) where++import Control.Applicative ((<|>))+import Control.Arrow+import Control.Lens (makeLenses, view, preview, set, _Just, over, each)+import Control.Monad.Reader+import Control.Monad.State+import Data.Function (on)+import Data.List (sortBy, groupBy, delete)+import qualified Data.Map as M+import Data.Maybe (fromMaybe, listToMaybe, catMaybes)+import Data.Maybe.JustIf+import Data.Ord (comparing)+import qualified Data.Set as S+import Data.String (fromString)+import Data.Text (Text)++import HsDev.Database+import HsDev.Symbols+import HsDev.Symbols.Util+import HsDev.Util (uniqueBy)++-- | Map from name to modules+type ModuleMap = M.Map Text [Module]++-- | Resolve monad uses existing @Database@ and @ResolvedTree@ as state.+newtype ResolveM a = ResolveM { runResolveM :: ReaderT (Database, ModuleMap) (State ResolvedTree) a }+ deriving (Functor, Applicative, Monad, MonadState ResolvedTree, MonadReader (Database, ModuleMap))++-- | Tree of resolved modules+type ResolvedTree = Map ModuleId ResolvedModule++-- | Module with declarations bringed to scope and with exported declarations+data ResolvedModule = ResolvedModule {+ _resolvedModule :: Module,+ _resolvedScope :: [Declaration],+ _resolvedExports :: [Declaration] }++makeLenses ''ResolvedModule++-- | Make @Module@ with scope declarations+scopeModule :: ResolvedModule -> Module+scopeModule r = set moduleDeclarations (view resolvedScope r) (view resolvedModule r)++-- | Make @Module@ with exported only declarations+exportsModule :: ResolvedModule -> Module+exportsModule r = set moduleDeclarations (view resolvedExports r) (view resolvedModule r)++-- | Get top-level scope+resolvedTopScope :: ResolvedModule -> [Declaration]+resolvedTopScope = filter isTop . view resolvedScope where+ isTop :: Declaration -> Bool+ isTop = any (not . view importIsQualified) . fromMaybe [] . view declarationImported++-- | Resolve modules, function is not IO, so all file names must be canonicalized+resolve :: Traversable t => Database -> t Module -> t ResolvedModule+resolve db = flip evalState M.empty . flip runReaderT (db, m) . runResolveM . traverse resolveModule where+ m :: ModuleMap+ m = M.fromList $ map ((view moduleName . head) &&& id) $+ groupBy ((==) `on` view moduleName) $+ sortBy (comparing (view moduleName)) $ allModules db++-- | Resolve one module+resolveOne :: Database -> Module -> ResolvedModule+resolveOne db = fromMaybe (error "Resolve: impossible happened") . resolve db . Just++-- | Resolve module+resolveModule :: Module -> ResolveM ResolvedModule+resolveModule m = gets (M.lookup $ view moduleId m) >>= maybe resolveModule' return where+ resolveModule' = save $ case view moduleLocation m of+ InstalledModule {} -> return ResolvedModule {+ _resolvedModule = m,+ _resolvedScope = map setSelfDefined $ view moduleDeclarations m,+ _resolvedExports = map setSelfDefined $ view moduleDeclarations m }+ _ -> do+ scope' <-+ liftM (thisDecls ++) .+ resolveImports m .+ (import_ (fromString "Prelude") :) .+ view moduleImports $ m+ let+ exported' = case view moduleExports m of+ Nothing -> thisDecls+ Just exports' -> unique $ catMaybes $ mexported <$> scope' <*> exports'+ return $ ResolvedModule m (sortDeclarations scope') (sortDeclarations exported')+ thisDecls :: [Declaration]+ thisDecls = map (selfDefined . selfImport) $ view moduleDeclarations m+ setSelfDefined :: Declaration -> Declaration+ setSelfDefined =+ over (declaration . localDeclarations . each . declarationDefined) (<|> Just (view moduleId m)) .+ over declarationDefined (<|> Just (view moduleId m))+ selfDefined :: Declaration -> Declaration+ selfDefined = set declarationDefined (Just $ view moduleId m)+ selfImport :: Declaration -> Declaration+ selfImport = set declarationImported (Just [import_ $ view moduleName m])+ save :: ResolveM ResolvedModule -> ResolveM ResolvedModule+ save act = do+ rm <- act+ modify $ M.insert (view (resolvedModule . moduleId) rm) rm+ return rm+ unique :: [Declaration] -> [Declaration]+ unique = uniqueBy declId+ declId :: Declaration -> (Text, Maybe ModuleId)+ declId = view declarationName &&& view declarationDefined+ mexported decl' e' = decl' `justIf` exported decl' e'++-- | Bring declarations into scope by imports+resolveImports :: Module -> [Import] -> ResolveM [Declaration]+resolveImports m is = do+ db <- asks fst+ let+ deps = maybe S.empty S.fromList $ do+ f <- preview (moduleLocation . moduleFile) m+ p <- preview (moduleLocation . moduleProject . _Just) m+ p' <- refineProject db p+ return $ delete (view projectName p') $ concatMap (view infoDepends) $ fileTargets p' f+ liftM (mergeImported . concat) $ mapM (resolveImport deps m) is++-- | Bring declarations into scope, first parameter is set of visible packages+resolveImport :: S.Set String -> Module -> Import -> ResolveM [Declaration]+resolveImport deps m i = liftM (map $ setImport i) resolveImport' where+ resolveImport' :: ResolveM [Declaration]+ resolveImport' = do+ ms <- case view moduleLocation m of+ FileModule file proj -> do+ db <- asks fst+ let+ proj' = proj >>= refineProject db+ case proj' of+ Nothing -> selectImport i [+ inFile $ importedModulePath (view moduleName m) file (view importModuleName i),+ installed]+ Just p -> selectImport i [+ inProject p,+ inDeps]+ InstalledModule pdb _ _ -> selectImport i [inPackageDb pdb]+ ModuleSource _ -> selectImport i [installed]+ fromMaybe [] <$> traverse (liftM (filterImportList . view resolvedExports) . resolveModule) ms+ setImport :: Import -> Declaration -> Declaration+ setImport i' = set declarationImported (Just [i'])+ selectImport :: Import -> [ModuleId -> Bool] -> ResolveM (Maybe Module)+ selectImport i' fs = do+ modsMap <- asks snd+ let+ mods = fromMaybe [] $ M.lookup (view importModuleName i') modsMap+ return $+ listToMaybe $+ newestPackage $+ fromMaybe [] $+ listToMaybe $ dropWhile null+ [filter (f . view moduleId) mods | f <- fs]+ filterImportList :: [Declaration] -> [Declaration]+ filterImportList = case view importList i of+ Nothing -> id+ Just il -> filter (`imported` il)+ inDeps = maybe False (`S.member` deps) . preview (moduleIdLocation . modulePackage . _Just . packageName)++-- | Merge imported declarations+mergeImported :: [Declaration] -> [Declaration]+mergeImported =+ map merge' .+ groupBy ((==) `on` declId) .+ sortBy (comparing declId)+ where+ declId :: Declaration -> (Text, Maybe ModuleId)+ declId = view declarationName &&& view declarationDefined+ merge' :: [Declaration] -> Declaration+ merge' [] = error "mergeImported: impossible"+ merge' ds@(d:_) = set declarationImported (mconcat $ map (view declarationImported) ds) d
src/HsDev/Symbols/Types.hs view
@@ -1,620 +1,620 @@-{-# LANGUAGE TemplateHaskell, TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Symbols.Types ( - ThingPart(..), - Export(..), exportQualified, exportName, exportPart, exportModule, - ImportSpec(..), importSpecName, importSpecPart, - ImportList(..), hidingList, importSpecs, - Import(..), importModuleName, importIsQualified, importAs, importList, importPosition, - ModuleId(..), moduleIdName, moduleIdLocation, - Module(..), moduleName, moduleDocs, moduleLocation, moduleExports, moduleImports, moduleDeclarations, moduleContents, moduleId, - Declaration(..), declarationName, declarationDefined, declarationImported, declarationDocs, declarationPosition, declaration, minimalDecl, - TypeInfo(..), typeInfoContext, typeInfoArgs, typeInfoDefinition, typeInfoFunctions, showTypeInfo, - DeclarationInfo(..), functionType, localDeclarations, related, typeInfo, declarationInfo, declarationTypeCtor, declarationTypeName, - ModuleDeclaration(..), declarationModuleId, moduleDeclaration, - ExportedDeclaration(..), exportedBy, exportedDeclaration, - Inspection(..), inspectionAt, inspectionOpts, - Inspected(..), inspection, inspectedId, inspectionTags, inspectionResult, noTags, - ModuleTag(..), - InspectedModule, notInspected, - - module HsDev.PackageDb, - module HsDev.Project, - module HsDev.Symbols.Class, - module HsDev.Symbols.Documented - ) where - -import Control.Applicative -import Control.Arrow -import Control.Lens (makeLenses, view, set, Simple, Lens, Lens', lens) -import Control.Monad -import Control.DeepSeq (NFData(..)) -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 -data ThingPart = ThingNothing | ThingAll | ThingWith [Text] deriving (Eq, Ord) - -instance NFData ThingPart where - rnf ThingNothing = () - rnf ThingAll = () - rnf (ThingWith ns) = rnf ns - -instance Show ThingPart where - show ThingNothing = "" - show ThingAll = "(..)" - show (ThingWith ns) = "(" ++ intercalate ", " (map unpack ns) ++ ")" - -instance ToJSON ThingPart where - toJSON ThingNothing = toJSON ("nothing" :: String) - toJSON ThingAll = toJSON ("all" :: String) - toJSON (ThingWith ns) = object [ - "with" .= ns] - -instance FromJSON ThingPart where - parseJSON v = parse' <|> parseWith v where - parse' = do - s <- parseJSON v - mplus - (guard (s == ("nothing" :: String)) >> return ThingNothing) - (guard (s == ("all" :: String)) >> return ThingAll) - parseWith = withObject "export part" $ \v' -> ThingWith <$> v' .:: "with" - --- | Module export -data Export = - ExportName { - _exportQualified :: Maybe Text, - _exportName :: Text, - _exportPart :: ThingPart } | - ExportModule { _exportModule :: Text } - deriving (Eq, Ord) - -instance NFData Export where - rnf (ExportName q n w) = rnf q `seq` rnf n `seq` rnf w - rnf (ExportModule m) = rnf m - -instance Show Export where - show (ExportName Nothing n w) = unpack n ++ show w - show (ExportName (Just q) n w) = unpack q ++ "." ++ unpack n ++ show w - show (ExportModule m) = "module " ++ unpack m - -instance ToJSON Export where - toJSON (ExportName q n w) = object ["module" .= q, "name" .= n, "part" .= w] - toJSON (ExportModule m) = object ["module" .= m] - -instance FromJSON Export where - parseJSON = withObject "export" $ \v -> - (ExportName <$> (v .:: "module") <*> (v .:: "name") <*> (v .:: "part")) <|> - (ExportModule <$> (v .:: "module")) - --- | Import spec -data ImportSpec = ImportSpec { - _importSpecName :: Text, - _importSpecPart :: ThingPart } - deriving (Eq, Ord) - -instance NFData ImportSpec where - rnf (ImportSpec n p) = rnf n `seq` rnf p - -instance Show ImportSpec where - show (ImportSpec n p) = unpack n ++ show p - -instance ToJSON ImportSpec where - toJSON (ImportSpec n p) = object ["name" .= n, "part" .= p] - -instance FromJSON ImportSpec where - parseJSON = withObject "import-spec" $ \v -> ImportSpec <$> (v .:: "name") <*> (v .:: "part") - --- | Import list -data ImportList = ImportList { - _hidingList :: Bool, - _importSpecs :: [ImportSpec] } - deriving (Eq, Ord) - -instance NFData ImportList where - rnf (ImportList h ls) = rnf h `seq` rnf ls - -instance Show ImportList where - show (ImportList h ls) = (if h then ("hiding " ++) else id) $ "(" ++ intercalate ", " (map show ls) ++ ")" - -instance ToJSON ImportList where - toJSON (ImportList h ls) = object [ - "hiding" .= h, - "specs" .= ls] - -instance FromJSON ImportList where - parseJSON = withObject "import-list" $ \v -> ImportList <$> - v .:: "hiding" <*> - v .:: "specs" - --- | Module import -data Import = Import { - _importModuleName :: Text, - _importIsQualified :: Bool, - _importAs :: Maybe Text, - _importList :: Maybe ImportList, - _importPosition :: Maybe Position } - deriving (Eq, Ord) - -instance NFData Import where - rnf (Import m q a il l) = rnf m `seq` rnf q `seq` rnf a `seq` rnf il `seq` rnf l - -instance Show Import where - show i = concat [ - "import ", - if _importIsQualified i then "qualified " else "", - unpack $ _importModuleName i, - maybe "" ((" as " ++) . unpack) (_importAs i), - maybe "" ((" " ++) . show) (_importList i)] - -instance ToJSON Import where - toJSON i = object $ noNulls [ - "name" .= _importModuleName i, - "qualified" .= _importIsQualified i, - "as" .= _importAs i, - "import-list" .= _importList i, - "pos" .= _importPosition i] - -instance FromJSON Import where - parseJSON = withObject "import" $ \v -> Import <$> - v .:: "name" <*> - v .:: "qualified" <*> - v .::? "as" <*> - v .::? "import-list" <*> - v .::? "pos" - --- | Module id -data ModuleId = ModuleId { - _moduleIdName :: Text, - _moduleIdLocation :: ModuleLocation } - deriving (Eq, Ord) - -instance NFData ModuleId where - rnf (ModuleId n l) = rnf n `seq` rnf l - -instance Show ModuleId where - show (ModuleId n l) = "module " ++ unpack n ++ " from " ++ show l - -instance ToJSON ModuleId where - toJSON m = object [ - "name" .= _moduleIdName m, - "location" .= _moduleIdLocation m] - -instance FromJSON ModuleId where - parseJSON = withObject "module id" $ \v -> ModuleId <$> - v .:: "name" <*> - v .:: "location" - --- | Module -data Module = Module { - _moduleName :: Text, - _moduleDocs :: Maybe Text, - _moduleLocation :: ModuleLocation, - _moduleExports :: Maybe [Export], - _moduleImports :: [Import], - _moduleDeclarations :: [Declaration] } - deriving (Ord) - -instance ToJSON Module where - toJSON m = object $ noNulls [ - "name" .= _moduleName m, - "docs" .= _moduleDocs m, - "location" .= _moduleLocation m, - "exports" .= _moduleExports m, - "imports" .= _moduleImports m, - "declarations" .= _moduleDeclarations m] - -instance FromJSON Module where - parseJSON = withObject "module" $ \v -> Module <$> - v .:: "name" <*> - v .::? "docs" <*> - v .:: "location" <*> - v .::? "exports" <*> - v .::?! "imports" <*> - v .::?! "declarations" - -instance NFData Module where - rnf (Module n d s e i ds) = rnf n `seq` rnf d `seq` rnf s `seq` rnf e `seq` rnf i `seq` rnf ds - -instance Eq Module where - l == r = _moduleName l == _moduleName r && _moduleLocation l == _moduleLocation r - -instance Show Module where - show m = unlines $ filter (not . null) [ - "module " ++ unpack (_moduleName m), - "\tlocation: " ++ show (_moduleLocation m), - "\texports: " ++ maybe "*" (intercalate ", " . map show) (_moduleExports m), - "\timports:", - unlines $ map (tab 2 . show) $ _moduleImports m, - "\tdeclarations:", - unlines $ map (tabs 2 . show) $ _moduleDeclarations m, - maybe "" (("\tdocs: " ++) . unpack) (_moduleDocs m)] - -moduleId :: Simple Lens Module ModuleId -moduleId = lens - (uncurry ModuleId . (_moduleName &&& _moduleLocation)) - (\m mi -> m { _moduleName = _moduleIdName mi, _moduleLocation = _moduleIdLocation mi }) - --- | Module contents -moduleContents :: Module -> [String] -moduleContents = map showDecl . _moduleDeclarations where - showDecl d = brief d ++ maybe "" ((" -- " ++) . unpack) (_declarationDocs d) - --- | Declaration -data Declaration = Declaration { - _declarationName :: Text, - _declarationDefined :: Maybe ModuleId, -- ^ Where declaration defined, @Nothing@ if here - _declarationImported :: Maybe [Import], -- ^ Declaration imported with. @Nothing@ if unknown (cabal modules) or here (source file) - _declarationDocs :: Maybe Text, - _declarationPosition :: Maybe Position, - _declaration :: DeclarationInfo } - deriving (Eq, Ord) - -instance NFData Declaration where - rnf (Declaration n def is d l x) = rnf n `seq` rnf def `seq` rnf is `seq` rnf d `seq` rnf l `seq` rnf x - -instance Show Declaration where - show d = unlines $ filter (not . null) [ - brief d, - maybe "" (("\tdocs: " ++) . unpack) $ _declarationDocs d, - maybe "" (("\tdefined in: " ++) . show) $ _declarationDefined d, - maybe "" (("\tlocation: " ++ ) . show) $ _declarationPosition d] - -instance ToJSON Declaration where - toJSON d = object $ noNulls [ - "name" .= _declarationName d, - "defined" .= _declarationDefined d, - "imported" .= _declarationImported d, - "docs" .= _declarationDocs d, - "pos" .= _declarationPosition d, - "decl" .= _declaration d] - -instance FromJSON Declaration where - parseJSON = withObject "declaration" $ \v -> Declaration <$> - v .:: "name" <*> - v .::? "defined" <*> - v .::? "imported" <*> - v .::? "docs" <*> - v .::? "pos" <*> - v .:: "decl" - --- | Minimal declaration info without defined, docs and position -minimalDecl :: Lens' Declaration Declaration -minimalDecl = lens to' from' where - to' :: Declaration -> Declaration - to' decl' = decl' { _declarationDefined = Nothing, _declarationDocs = Nothing, _declarationPosition = Nothing } - from' :: Declaration -> Declaration -> Declaration - from' decl' mdecl = decl' { _declarationName = _declarationName mdecl, _declarationImported = _declarationImported mdecl, _declaration = _declaration mdecl } - --- | Common info for type, newtype, data and class -data TypeInfo = TypeInfo { - _typeInfoContext :: Maybe Text, -- FIXME: Why not list of contexts? - _typeInfoArgs :: [Text], - _typeInfoDefinition :: Maybe Text, - _typeInfoFunctions :: [Text] } - deriving (Eq, Ord, Read, Show) - -instance NFData TypeInfo where - rnf (TypeInfo c a d f) = rnf c `seq` rnf a `seq` rnf d `seq` rnf f - -instance ToJSON TypeInfo where - toJSON t = object $ noNulls [ - "ctx" .= _typeInfoContext t, - "args" .= _typeInfoArgs t, - "def" .= _typeInfoDefinition t, - "funs" .= _typeInfoFunctions t] - -instance FromJSON TypeInfo where - parseJSON = withObject "type info" $ \v -> TypeInfo <$> - v .::? "ctx" <*> - v .::?! "args" <*> - v .::? "def" <*> - v .::?! "funs" - -showTypeInfo :: TypeInfo -> String -> String -> String -showTypeInfo ti pre name = concat [ - pre, - maybe "" ((++ " =>") . unpack) (_typeInfoContext ti), " ", - name, " ", - unwords (map unpack $ _typeInfoArgs ti), - maybe "" ((" = " ++) . unpack) (_typeInfoDefinition ti)] - --- | Declaration info -data DeclarationInfo = - Function { _functionType :: Maybe Text, _localDeclarations :: [Declaration], _related :: Maybe Text } | - Type { _typeInfo :: TypeInfo } | - NewType { _typeInfo :: TypeInfo } | - Data { _typeInfo :: TypeInfo } | - Class { _typeInfo :: TypeInfo } - deriving (Ord) - --- | Get function type of type info -declarationInfo :: DeclarationInfo -> Either (Maybe Text, [Declaration], Maybe Text) TypeInfo -declarationInfo (Function t ds r) = Left (t, ds, r) -declarationInfo (Type ti) = Right ti -declarationInfo (NewType ti) = Right ti -declarationInfo (Data ti) = Right ti -declarationInfo (Class ti) = Right ti - -declarationTypeCtor :: String -> TypeInfo -> DeclarationInfo -declarationTypeCtor "type" = Type -declarationTypeCtor "newtype" = NewType -declarationTypeCtor "data" = Data -declarationTypeCtor "class" = Class -declarationTypeCtor _ = error "Invalid type constructor name" - -declarationTypeName :: DeclarationInfo -> Maybe String -declarationTypeName (Type _) = Just "type" -declarationTypeName (NewType _) = Just "newtype" -declarationTypeName (Data _) = Just "data" -declarationTypeName (Class _) = Just "class" -declarationTypeName _ = Nothing - -instance NFData DeclarationInfo where - rnf (Function f ds r) = rnf f `seq` rnf ds `seq` rnf r - rnf (Type i) = rnf i - rnf (NewType i) = rnf i - rnf (Data i) = rnf i - rnf (Class i) = rnf i - -instance Eq DeclarationInfo where - (Function l lds lr) == (Function r rds rr) = l == r && lds == rds && lr == rr - (Type _) == (Type _) = True - (NewType _) == (NewType _) = True - (Data _) == (Data _) = True - (Class _) == (Class _) = True - _ == _ = False - -instance ToJSON DeclarationInfo where - toJSON i = case declarationInfo i of - Left (t, ds, r) -> object $ noNulls ["what" .= ("function" :: String), "type" .= t, "locals" .= ds, "related" .= r] - Right ti -> object ["what" .= declarationTypeName i, "info" .= ti] - -instance FromJSON DeclarationInfo where - parseJSON = withObject "declaration info" $ \v -> do - w <- fmap (id :: String -> String) $ v .:: "what" - if w == "function" - then Function <$> v .::? "type" <*> v .::?! "locals" <*> v .::? "related" - else declarationTypeCtor w <$> v .:: "info" - --- | Symbol in context of some module -data ModuleDeclaration = ModuleDeclaration { - _declarationModuleId :: ModuleId, - _moduleDeclaration :: Declaration } - deriving (Eq, Ord) - -instance NFData ModuleDeclaration where - rnf (ModuleDeclaration m s) = rnf m `seq` rnf s - -instance Show ModuleDeclaration where - show (ModuleDeclaration m s) = unlines $ filter (not . null) [ - show s, - "\tmodule: " ++ show (_moduleIdLocation m)] - -instance ToJSON ModuleDeclaration where - toJSON d = object [ - "module-id" .= _declarationModuleId d, - "declaration" .= _moduleDeclaration d] - -instance FromJSON ModuleDeclaration where - parseJSON = withObject "module declaration" $ \v -> ModuleDeclaration <$> - v .:: "module-id" <*> - v .:: "declaration" - --- | Symbol exported with -data ExportedDeclaration = ExportedDeclaration { - _exportedBy :: [ModuleId], - _exportedDeclaration :: Declaration } - deriving (Eq, Ord) - -instance NFData ExportedDeclaration where - rnf (ExportedDeclaration m s) = rnf m `seq` rnf s - -instance Show ExportedDeclaration where - show (ExportedDeclaration m s) = unlines $ filter (not . null) [ - show s, - "\tmodules: " ++ intercalate ", " (map (show . _moduleIdLocation) m)] - -instance ToJSON ExportedDeclaration where - toJSON d = object [ - "exported-by" .= _exportedBy d, - "declaration" .= _exportedDeclaration d] - -instance FromJSON ExportedDeclaration where - parseJSON = withObject "exported declaration" $ \v -> ExportedDeclaration <$> - v .:: "exported-by" <*> - v .:: "declaration" - --- | Inspection data -data Inspection = - -- | No inspection - InspectionNone | - -- | Time and flags of inspection - InspectionAt { - _inspectionAt :: POSIXTime, - _inspectionOpts :: [String] } - deriving (Eq, Ord) - -instance NFData Inspection where - rnf InspectionNone = () - rnf (InspectionAt t fs) = rnf t `seq` rnf fs - -instance Show Inspection where - show InspectionNone = "none" - show (InspectionAt tm fs) = "mtime " ++ show tm ++ ", flags [" ++ intercalate ", " fs ++ "]" - -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 [ - "mtime" .= (floor tm :: Integer), - "flags" .= fs] - -instance FromJSON Inspection where - parseJSON = withObject "inspection" $ \v -> - ((const InspectionNone :: Bool -> Inspection) <$> v .:: "inspected") <|> - (InspectionAt <$> (fromInteger <$> v .:: "mtime") <*> (v .:: "flags")) - --- | Inspected entity -data Inspected i t a = Inspected { - _inspection :: Inspection, - _inspectedId :: i, - _inspectionTags :: Set t, - _inspectionResult :: Either HsDevError a } - -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 t) where - foldMap f = either mempty f . _inspectionResult - -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 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 :: 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 ModuleTag Module - -instance Show InspectedModule where - 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] - -instance ToJSON InspectedModule where - 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 - symbolQualifiedName = _moduleName - symbolDocs = _moduleDocs - symbolLocation m = Location (_moduleLocation m) Nothing - -instance Symbol ModuleId where - symbolName = _moduleIdName - symbolQualifiedName = _moduleIdName - symbolDocs = const Nothing - symbolLocation m = Location (_moduleIdLocation m) Nothing - -instance Symbol Declaration where - symbolName = _declarationName - symbolQualifiedName = _declarationName - symbolDocs = _declarationDocs - symbolLocation d = Location (ModuleSource Nothing) (_declarationPosition d) - -instance Symbol ModuleDeclaration where - symbolName = _declarationName . _moduleDeclaration - symbolQualifiedName d = qualifiedName (_declarationModuleId d) (_moduleDeclaration d) where - qualifiedName :: ModuleId -> Declaration -> Text - qualifiedName m' d' = T.concat [_moduleIdName m', ".", _declarationName d'] - symbolDocs = _declarationDocs . _moduleDeclaration - symbolLocation d = set locationPosition (_declarationPosition $ _moduleDeclaration d) - (symbolLocation . _declarationModuleId $ d) - -instance Documented ModuleId where - brief m = unpack (_moduleIdName m) ++ " in " ++ show (_moduleIdLocation m) - -instance Documented Module where - brief m = unpack (_moduleName m) ++ " in " ++ show (_moduleLocation m) - detailed m = unlines $ header ++ docs ++ cts where - header = [brief m, ""] - docs = maybe [] (return . unpack) $ _moduleDocs m - cts = moduleContents m - -instance Documented Declaration where - brief d = case declarationInfo $ _declaration d of - Left (f, _, _) -> name ++ maybe "" ((" :: " ++) . unpack) f - Right ti -> showTypeInfo ti (fromMaybe err $ declarationTypeName $ _declaration d) name - where - name = unpack $ _declarationName d - err = error "Impossible happened: declarationTypeName" - -instance Documented ModuleDeclaration where - brief = brief . _moduleDeclaration - -makeLenses ''Export -makeLenses ''ImportSpec -makeLenses ''ImportList -makeLenses ''Import -makeLenses ''ModuleId -makeLenses ''DeclarationInfo -makeLenses ''TypeInfo -makeLenses ''Declaration -makeLenses ''Module -makeLenses ''ModuleDeclaration -makeLenses ''ExportedDeclaration -makeLenses ''Inspection -makeLenses ''Inspected +{-# LANGUAGE TemplateHaskell, TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Symbols.Types (+ ThingPart(..),+ Export(..), exportQualified, exportName, exportPart, exportModule,+ ImportSpec(..), importSpecName, importSpecPart,+ ImportList(..), hidingList, importSpecs,+ Import(..), importModuleName, importIsQualified, importAs, importList, importPosition,+ ModuleId(..), moduleIdName, moduleIdLocation,+ Module(..), moduleName, moduleDocs, moduleLocation, moduleExports, moduleImports, moduleDeclarations, moduleContents, moduleId,+ Declaration(..), declarationName, declarationDefined, declarationImported, declarationDocs, declarationPosition, declaration, minimalDecl,+ TypeInfo(..), typeInfoContext, typeInfoArgs, typeInfoDefinition, typeInfoFunctions, showTypeInfo,+ DeclarationInfo(..), functionType, localDeclarations, related, typeInfo, declarationInfo, declarationTypeCtor, declarationTypeName,+ ModuleDeclaration(..), declarationModuleId, moduleDeclaration,+ ExportedDeclaration(..), exportedBy, exportedDeclaration,+ Inspection(..), inspectionAt, inspectionOpts,+ Inspected(..), inspection, inspectedId, inspectionTags, inspectionResult, noTags,+ ModuleTag(..),+ InspectedModule, notInspected,++ module HsDev.PackageDb,+ module HsDev.Project,+ module HsDev.Symbols.Class,+ module HsDev.Symbols.Documented+ ) where++import Control.Applicative+import Control.Arrow+import Control.Lens (makeLenses, view, set, Simple, Lens, Lens', lens)+import Control.Monad+import Control.DeepSeq (NFData(..))+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+data ThingPart = ThingNothing | ThingAll | ThingWith [Text] deriving (Eq, Ord)++instance NFData ThingPart where+ rnf ThingNothing = ()+ rnf ThingAll = ()+ rnf (ThingWith ns) = rnf ns++instance Show ThingPart where+ show ThingNothing = ""+ show ThingAll = "(..)"+ show (ThingWith ns) = "(" ++ intercalate ", " (map unpack ns) ++ ")"++instance ToJSON ThingPart where+ toJSON ThingNothing = toJSON ("nothing" :: String)+ toJSON ThingAll = toJSON ("all" :: String)+ toJSON (ThingWith ns) = object [+ "with" .= ns]++instance FromJSON ThingPart where+ parseJSON v = parse' <|> parseWith v where+ parse' = do+ s <- parseJSON v+ mplus+ (guard (s == ("nothing" :: String)) >> return ThingNothing)+ (guard (s == ("all" :: String)) >> return ThingAll)+ parseWith = withObject "export part" $ \v' -> ThingWith <$> v' .:: "with"++-- | Module export+data Export =+ ExportName {+ _exportQualified :: Maybe Text,+ _exportName :: Text,+ _exportPart :: ThingPart } |+ ExportModule { _exportModule :: Text }+ deriving (Eq, Ord)++instance NFData Export where+ rnf (ExportName q n w) = rnf q `seq` rnf n `seq` rnf w+ rnf (ExportModule m) = rnf m++instance Show Export where+ show (ExportName Nothing n w) = unpack n ++ show w+ show (ExportName (Just q) n w) = unpack q ++ "." ++ unpack n ++ show w+ show (ExportModule m) = "module " ++ unpack m++instance ToJSON Export where+ toJSON (ExportName q n w) = object ["module" .= q, "name" .= n, "part" .= w]+ toJSON (ExportModule m) = object ["module" .= m]++instance FromJSON Export where+ parseJSON = withObject "export" $ \v ->+ (ExportName <$> (v .:: "module") <*> (v .:: "name") <*> (v .:: "part")) <|>+ (ExportModule <$> (v .:: "module"))++-- | Import spec+data ImportSpec = ImportSpec {+ _importSpecName :: Text,+ _importSpecPart :: ThingPart }+ deriving (Eq, Ord)++instance NFData ImportSpec where+ rnf (ImportSpec n p) = rnf n `seq` rnf p++instance Show ImportSpec where+ show (ImportSpec n p) = unpack n ++ show p++instance ToJSON ImportSpec where+ toJSON (ImportSpec n p) = object ["name" .= n, "part" .= p]++instance FromJSON ImportSpec where+ parseJSON = withObject "import-spec" $ \v -> ImportSpec <$> (v .:: "name") <*> (v .:: "part")++-- | Import list+data ImportList = ImportList {+ _hidingList :: Bool,+ _importSpecs :: [ImportSpec] }+ deriving (Eq, Ord)++instance NFData ImportList where+ rnf (ImportList h ls) = rnf h `seq` rnf ls++instance Show ImportList where+ show (ImportList h ls) = (if h then ("hiding " ++) else id) $ "(" ++ intercalate ", " (map show ls) ++ ")"++instance ToJSON ImportList where+ toJSON (ImportList h ls) = object [+ "hiding" .= h,+ "specs" .= ls]++instance FromJSON ImportList where+ parseJSON = withObject "import-list" $ \v -> ImportList <$>+ v .:: "hiding" <*>+ v .:: "specs"++-- | Module import+data Import = Import {+ _importModuleName :: Text,+ _importIsQualified :: Bool,+ _importAs :: Maybe Text,+ _importList :: Maybe ImportList,+ _importPosition :: Maybe Position }+ deriving (Eq, Ord)++instance NFData Import where+ rnf (Import m q a il l) = rnf m `seq` rnf q `seq` rnf a `seq` rnf il `seq` rnf l++instance Show Import where+ show i = concat [+ "import ",+ if _importIsQualified i then "qualified " else "",+ unpack $ _importModuleName i,+ maybe "" ((" as " ++) . unpack) (_importAs i),+ maybe "" ((" " ++) . show) (_importList i)]++instance ToJSON Import where+ toJSON i = object $ noNulls [+ "name" .= _importModuleName i,+ "qualified" .= _importIsQualified i,+ "as" .= _importAs i,+ "import-list" .= _importList i,+ "pos" .= _importPosition i]++instance FromJSON Import where+ parseJSON = withObject "import" $ \v -> Import <$>+ v .:: "name" <*>+ v .:: "qualified" <*>+ v .::? "as" <*>+ v .::? "import-list" <*>+ v .::? "pos"++-- | Module id+data ModuleId = ModuleId {+ _moduleIdName :: Text,+ _moduleIdLocation :: ModuleLocation }+ deriving (Eq, Ord)++instance NFData ModuleId where+ rnf (ModuleId n l) = rnf n `seq` rnf l++instance Show ModuleId where+ show (ModuleId n l) = "module " ++ unpack n ++ " from " ++ show l++instance ToJSON ModuleId where+ toJSON m = object [+ "name" .= _moduleIdName m,+ "location" .= _moduleIdLocation m]++instance FromJSON ModuleId where+ parseJSON = withObject "module id" $ \v -> ModuleId <$>+ v .:: "name" <*>+ v .:: "location"++-- | Module+data Module = Module {+ _moduleName :: Text,+ _moduleDocs :: Maybe Text,+ _moduleLocation :: ModuleLocation,+ _moduleExports :: Maybe [Export],+ _moduleImports :: [Import],+ _moduleDeclarations :: [Declaration] }+ deriving (Ord)++instance ToJSON Module where+ toJSON m = object $ noNulls [+ "name" .= _moduleName m,+ "docs" .= _moduleDocs m,+ "location" .= _moduleLocation m,+ "exports" .= _moduleExports m,+ "imports" .= _moduleImports m,+ "declarations" .= _moduleDeclarations m]++instance FromJSON Module where+ parseJSON = withObject "module" $ \v -> Module <$>+ v .:: "name" <*>+ v .::? "docs" <*>+ v .:: "location" <*>+ v .::? "exports" <*>+ v .::?! "imports" <*>+ v .::?! "declarations"++instance NFData Module where+ rnf (Module n d s e i ds) = rnf n `seq` rnf d `seq` rnf s `seq` rnf e `seq` rnf i `seq` rnf ds++instance Eq Module where+ l == r = _moduleName l == _moduleName r && _moduleLocation l == _moduleLocation r++instance Show Module where+ show m = unlines $ filter (not . null) [+ "module " ++ unpack (_moduleName m),+ "\tlocation: " ++ show (_moduleLocation m),+ "\texports: " ++ maybe "*" (intercalate ", " . map show) (_moduleExports m),+ "\timports:",+ unlines $ map (tab 2 . show) $ _moduleImports m,+ "\tdeclarations:",+ unlines $ map (tabs 2 . show) $ _moduleDeclarations m,+ maybe "" (("\tdocs: " ++) . unpack) (_moduleDocs m)]++moduleId :: Simple Lens Module ModuleId+moduleId = lens+ (uncurry ModuleId . (_moduleName &&& _moduleLocation))+ (\m mi -> m { _moduleName = _moduleIdName mi, _moduleLocation = _moduleIdLocation mi })++-- | Module contents+moduleContents :: Module -> [String]+moduleContents = map showDecl . _moduleDeclarations where+ showDecl d = brief d ++ maybe "" ((" -- " ++) . unpack) (_declarationDocs d)++-- | Declaration+data Declaration = Declaration {+ _declarationName :: Text,+ _declarationDefined :: Maybe ModuleId, -- ^ Where declaration defined, @Nothing@ if here+ _declarationImported :: Maybe [Import], -- ^ Declaration imported with. @Nothing@ if unknown (cabal modules) or here (source file)+ _declarationDocs :: Maybe Text,+ _declarationPosition :: Maybe Position,+ _declaration :: DeclarationInfo }+ deriving (Eq, Ord)++instance NFData Declaration where+ rnf (Declaration n def is d l x) = rnf n `seq` rnf def `seq` rnf is `seq` rnf d `seq` rnf l `seq` rnf x++instance Show Declaration where+ show d = unlines $ filter (not . null) [+ brief d,+ maybe "" (("\tdocs: " ++) . unpack) $ _declarationDocs d,+ maybe "" (("\tdefined in: " ++) . show) $ _declarationDefined d,+ maybe "" (("\tlocation: " ++ ) . show) $ _declarationPosition d]++instance ToJSON Declaration where+ toJSON d = object $ noNulls [+ "name" .= _declarationName d,+ "defined" .= _declarationDefined d,+ "imported" .= _declarationImported d,+ "docs" .= _declarationDocs d,+ "pos" .= _declarationPosition d,+ "decl" .= _declaration d]++instance FromJSON Declaration where+ parseJSON = withObject "declaration" $ \v -> Declaration <$>+ v .:: "name" <*>+ v .::? "defined" <*>+ v .::? "imported" <*>+ v .::? "docs" <*>+ v .::? "pos" <*>+ v .:: "decl"++-- | Minimal declaration info without defined, docs and position+minimalDecl :: Lens' Declaration Declaration+minimalDecl = lens to' from' where+ to' :: Declaration -> Declaration+ to' decl' = decl' { _declarationDefined = Nothing, _declarationDocs = Nothing, _declarationPosition = Nothing }+ from' :: Declaration -> Declaration -> Declaration+ from' decl' mdecl = decl' { _declarationName = _declarationName mdecl, _declarationImported = _declarationImported mdecl, _declaration = _declaration mdecl }++-- | Common info for type, newtype, data and class+data TypeInfo = TypeInfo {+ _typeInfoContext :: Maybe Text, -- FIXME: Why not list of contexts?+ _typeInfoArgs :: [Text],+ _typeInfoDefinition :: Maybe Text,+ _typeInfoFunctions :: [Text] }+ deriving (Eq, Ord, Read, Show)++instance NFData TypeInfo where+ rnf (TypeInfo c a d f) = rnf c `seq` rnf a `seq` rnf d `seq` rnf f++instance ToJSON TypeInfo where+ toJSON t = object $ noNulls [+ "ctx" .= _typeInfoContext t,+ "args" .= _typeInfoArgs t,+ "def" .= _typeInfoDefinition t,+ "funs" .= _typeInfoFunctions t]++instance FromJSON TypeInfo where+ parseJSON = withObject "type info" $ \v -> TypeInfo <$>+ v .::? "ctx" <*>+ v .::?! "args" <*>+ v .::? "def" <*>+ v .::?! "funs"++showTypeInfo :: TypeInfo -> String -> String -> String+showTypeInfo ti pre name = concat [+ pre,+ maybe "" ((++ " =>") . unpack) (_typeInfoContext ti), " ",+ name, " ",+ unwords (map unpack $ _typeInfoArgs ti),+ maybe "" ((" = " ++) . unpack) (_typeInfoDefinition ti)]++-- | Declaration info+data DeclarationInfo =+ Function { _functionType :: Maybe Text, _localDeclarations :: [Declaration], _related :: Maybe Text } |+ Type { _typeInfo :: TypeInfo } |+ NewType { _typeInfo :: TypeInfo } |+ Data { _typeInfo :: TypeInfo } |+ Class { _typeInfo :: TypeInfo }+ deriving (Ord)++-- | Get function type of type info+declarationInfo :: DeclarationInfo -> Either (Maybe Text, [Declaration], Maybe Text) TypeInfo+declarationInfo (Function t ds r) = Left (t, ds, r)+declarationInfo (Type ti) = Right ti+declarationInfo (NewType ti) = Right ti+declarationInfo (Data ti) = Right ti+declarationInfo (Class ti) = Right ti++declarationTypeCtor :: String -> TypeInfo -> DeclarationInfo+declarationTypeCtor "type" = Type+declarationTypeCtor "newtype" = NewType+declarationTypeCtor "data" = Data+declarationTypeCtor "class" = Class+declarationTypeCtor _ = error "Invalid type constructor name"++declarationTypeName :: DeclarationInfo -> Maybe String+declarationTypeName (Type _) = Just "type"+declarationTypeName (NewType _) = Just "newtype"+declarationTypeName (Data _) = Just "data"+declarationTypeName (Class _) = Just "class"+declarationTypeName _ = Nothing++instance NFData DeclarationInfo where+ rnf (Function f ds r) = rnf f `seq` rnf ds `seq` rnf r+ rnf (Type i) = rnf i+ rnf (NewType i) = rnf i+ rnf (Data i) = rnf i+ rnf (Class i) = rnf i++instance Eq DeclarationInfo where+ (Function l lds lr) == (Function r rds rr) = l == r && lds == rds && lr == rr+ (Type _) == (Type _) = True+ (NewType _) == (NewType _) = True+ (Data _) == (Data _) = True+ (Class _) == (Class _) = True+ _ == _ = False++instance ToJSON DeclarationInfo where+ toJSON i = case declarationInfo i of+ Left (t, ds, r) -> object $ noNulls ["what" .= ("function" :: String), "type" .= t, "locals" .= ds, "related" .= r]+ Right ti -> object ["what" .= declarationTypeName i, "info" .= ti]++instance FromJSON DeclarationInfo where+ parseJSON = withObject "declaration info" $ \v -> do+ w <- fmap (id :: String -> String) $ v .:: "what"+ if w == "function"+ then Function <$> v .::? "type" <*> v .::?! "locals" <*> v .::? "related"+ else declarationTypeCtor w <$> v .:: "info"++-- | Symbol in context of some module+data ModuleDeclaration = ModuleDeclaration {+ _declarationModuleId :: ModuleId,+ _moduleDeclaration :: Declaration }+ deriving (Eq, Ord)++instance NFData ModuleDeclaration where+ rnf (ModuleDeclaration m s) = rnf m `seq` rnf s++instance Show ModuleDeclaration where+ show (ModuleDeclaration m s) = unlines $ filter (not . null) [+ show s,+ "\tmodule: " ++ show (_moduleIdLocation m)]++instance ToJSON ModuleDeclaration where+ toJSON d = object [+ "module-id" .= _declarationModuleId d,+ "declaration" .= _moduleDeclaration d]++instance FromJSON ModuleDeclaration where+ parseJSON = withObject "module declaration" $ \v -> ModuleDeclaration <$>+ v .:: "module-id" <*>+ v .:: "declaration"++-- | Symbol exported with+data ExportedDeclaration = ExportedDeclaration {+ _exportedBy :: [ModuleId],+ _exportedDeclaration :: Declaration }+ deriving (Eq, Ord)++instance NFData ExportedDeclaration where+ rnf (ExportedDeclaration m s) = rnf m `seq` rnf s++instance Show ExportedDeclaration where+ show (ExportedDeclaration m s) = unlines $ filter (not . null) [+ show s,+ "\tmodules: " ++ intercalate ", " (map (show . _moduleIdLocation) m)]++instance ToJSON ExportedDeclaration where+ toJSON d = object [+ "exported-by" .= _exportedBy d,+ "declaration" .= _exportedDeclaration d]++instance FromJSON ExportedDeclaration where+ parseJSON = withObject "exported declaration" $ \v -> ExportedDeclaration <$>+ v .:: "exported-by" <*>+ v .:: "declaration"++-- | Inspection data+data Inspection =+ -- | No inspection+ InspectionNone |+ -- | Time and flags of inspection+ InspectionAt {+ _inspectionAt :: POSIXTime,+ _inspectionOpts :: [String] }+ deriving (Eq, Ord)++instance NFData Inspection where+ rnf InspectionNone = ()+ rnf (InspectionAt t fs) = rnf t `seq` rnf fs++instance Show Inspection where+ show InspectionNone = "none"+ show (InspectionAt tm fs) = "mtime " ++ show tm ++ ", flags [" ++ intercalate ", " fs ++ "]"++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 [+ "mtime" .= (floor tm :: Integer),+ "flags" .= fs]++instance FromJSON Inspection where+ parseJSON = withObject "inspection" $ \v ->+ ((const InspectionNone :: Bool -> Inspection) <$> v .:: "inspected") <|>+ (InspectionAt <$> (fromInteger <$> v .:: "mtime") <*> (v .:: "flags"))++-- | Inspected entity+data Inspected i t a = Inspected {+ _inspection :: Inspection,+ _inspectedId :: i,+ _inspectionTags :: Set t,+ _inspectionResult :: Either HsDevError a }++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 t) where+ foldMap f = either mempty f . _inspectionResult++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 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 :: 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 ModuleTag Module++instance Show InspectedModule where+ 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]++instance ToJSON InspectedModule where+ 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+ symbolQualifiedName = _moduleName+ symbolDocs = _moduleDocs+ symbolLocation m = Location (_moduleLocation m) Nothing++instance Symbol ModuleId where+ symbolName = _moduleIdName+ symbolQualifiedName = _moduleIdName+ symbolDocs = const Nothing+ symbolLocation m = Location (_moduleIdLocation m) Nothing++instance Symbol Declaration where+ symbolName = _declarationName+ symbolQualifiedName = _declarationName+ symbolDocs = _declarationDocs+ symbolLocation d = Location (ModuleSource Nothing) (_declarationPosition d)++instance Symbol ModuleDeclaration where+ symbolName = _declarationName . _moduleDeclaration+ symbolQualifiedName d = qualifiedName (_declarationModuleId d) (_moduleDeclaration d) where+ qualifiedName :: ModuleId -> Declaration -> Text+ qualifiedName m' d' = T.concat [_moduleIdName m', ".", _declarationName d']+ symbolDocs = _declarationDocs . _moduleDeclaration+ symbolLocation d = set locationPosition (_declarationPosition $ _moduleDeclaration d)+ (symbolLocation . _declarationModuleId $ d)++instance Documented ModuleId where+ brief m = unpack (_moduleIdName m) ++ " in " ++ show (_moduleIdLocation m)++instance Documented Module where+ brief m = unpack (_moduleName m) ++ " in " ++ show (_moduleLocation m)+ detailed m = unlines $ header ++ docs ++ cts where+ header = [brief m, ""]+ docs = maybe [] (return . unpack) $ _moduleDocs m+ cts = moduleContents m++instance Documented Declaration where+ brief d = case declarationInfo $ _declaration d of+ Left (f, _, _) -> name ++ maybe "" ((" :: " ++) . unpack) f+ Right ti -> showTypeInfo ti (fromMaybe err $ declarationTypeName $ _declaration d) name+ where+ name = unpack $ _declarationName d+ err = error "Impossible happened: declarationTypeName"++instance Documented ModuleDeclaration where+ brief = brief . _moduleDeclaration++makeLenses ''Export+makeLenses ''ImportSpec+makeLenses ''ImportList+makeLenses ''Import+makeLenses ''ModuleId+makeLenses ''DeclarationInfo+makeLenses ''TypeInfo+makeLenses ''Declaration+makeLenses ''Module+makeLenses ''ModuleDeclaration+makeLenses ''ExportedDeclaration+makeLenses ''Inspection+makeLenses ''Inspected
src/HsDev/Symbols/Util.hs view
@@ -1,203 +1,203 @@-module HsDev.Symbols.Util ( - projectOf, packageDbOf, packageOf, - inProject, inDepsOfTarget, inDepsOfFile, inDepsOfProject, inPackageDb, inPackageDbStack, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, installed, standalone, - imports, qualifier, moduleImported, visible, inScope, - newestPackage, - sourceModule, visibleModule, preferredModule, uniqueModules, - allOf, anyOf - ) where - -import Control.Arrow ((***), (&&&), first) -import Control.Lens (view) -import Control.Monad (liftM) -import Data.Function (on) -import Data.Maybe -import Data.List (maximumBy, groupBy, sortBy, partition) -import Data.Ord (comparing) -import Data.String (fromString) -import System.FilePath (normalise) - -import HsDev.Symbols -import HsDev.Util (ordNub) - --- | Get module project -projectOf :: ModuleId -> Maybe Project -projectOf m = case view moduleIdLocation m of - FileModule _ proj -> proj - _ -> Nothing - --- | Get module cabal -packageDbOf :: ModuleId -> Maybe PackageDb -packageDbOf m = case view moduleIdLocation m of - InstalledModule c _ _ -> Just c - _ -> Nothing - --- | Get module package -packageOf :: ModuleId -> Maybe ModulePackage -packageOf m = case view moduleIdLocation m of - InstalledModule _ package' _ -> package' - _ -> Nothing - --- | Check if module in project -inProject :: Project -> ModuleId -> Bool -inProject p m = projectOf m == Just p - --- | Check if module in deps of project target -inDepsOfTarget :: Info -> ModuleId -> Bool -inDepsOfTarget i m = any (`inPackage` m) $ view infoDepends i - --- | Check if module in deps of source -inDepsOfFile :: Project -> FilePath -> ModuleId -> Bool -inDepsOfFile p f m = any (`inDepsOfTarget` m) (fileTargets p f) - --- | Check if module in deps of project -inDepsOfProject :: Project -> ModuleId -> Bool -inDepsOfProject = maybe (const False) (anyPackage . ordNub . concatMap (view infoDepends) . infos) . view projectDescription where - anyPackage :: [String] -> ModuleId -> Bool - anyPackage = liftM or . mapM inPackage - --- | Check if module in package-db -inPackageDb :: PackageDb -> ModuleId -> Bool -inPackageDb c m = case view moduleIdLocation m of - InstalledModule d _ _ -> d == c - _ -> False - --- | Check if module in one of sandboxes -inPackageDbStack :: PackageDbStack -> ModuleId -> Bool -inPackageDbStack dbs m = case view moduleIdLocation m of - InstalledModule d _ _ -> d `elem` packageDbs dbs - _ -> False - --- | Check if module in package -inPackage :: String -> ModuleId -> Bool -inPackage p m = case view moduleIdLocation m of - InstalledModule _ package' _ -> Just p == fmap (view packageName) package' - _ -> False - -inVersion :: String -> ModuleId -> Bool -inVersion v m = case view moduleIdLocation m of - InstalledModule _ package' _ -> Just v == fmap (view packageVersion) package' - _ -> False - --- | Check if module in file -inFile :: FilePath -> ModuleId -> Bool -inFile fpath m = case view moduleIdLocation m of - FileModule f _ -> f == normalise fpath - _ -> False - --- | Check if module in source -inModuleSource :: Maybe String -> ModuleId -> Bool -inModuleSource src m = case view moduleIdLocation m of - ModuleSource src' -> src' == src - _ -> False - --- | Check if declaration is in module -inModule :: String -> ModuleId -> Bool -inModule mname m = fromString mname == view moduleIdName m - --- | Check if module defined in file -byFile :: ModuleId -> Bool -byFile m = case view moduleIdLocation m of - FileModule _ _ -> True - _ -> False - --- | Check if module got from cabal database -installed :: ModuleId -> Bool -installed m = case view moduleIdLocation m of - InstalledModule _ _ _ -> True - _ -> False - --- | Check if module is standalone -standalone :: ModuleId -> Bool -standalone m = case view moduleIdLocation m of - FileModule _ Nothing -> True - _ -> False - --- | Get list of imports -imports :: Module -> [Import] -imports = view moduleImports - --- | Get list of imports, which can be accessed with specified qualifier or unqualified -qualifier :: Module -> Maybe String -> [Import] -qualifier m q = filter (importQualifier (fmap fromString q)) $ - import_ (fromString "Prelude") : - import_ (view moduleName m) : - imports m - --- | Check if module imported via imports specified -moduleImported :: ModuleId -> [Import] -> Bool -moduleImported m = any (\i -> view moduleIdName m == view importModuleName i) - --- | Check if module visible from this module within this project -visible :: Project -> ModuleId -> ModuleId -> Bool -visible p (ModuleId _ (FileModule src _)) m = - inProject p m || any (`inPackage` m) deps || maybe False ((`elem` deps) . view projectName) (projectOf m) - where - deps = concatMap (view infoDepends) $ fileTargets p src -visible _ _ _ = False - --- | Check if module is in scope with qualifier -inScope :: Module -> Maybe String -> ModuleId -> Bool -inScope this q m = m `moduleImported` qualifier this q - --- | Select symbols with last package version -newestPackage :: Symbol a => [a] -> [a] -newestPackage = - uncurry (++) . - ((selectNewest . groupPackages) *** map snd) . - partition (isJust . fst) . - map ((mpackage . symbolModuleLocation) &&& id) - where - mpackage (InstalledModule _ (Just p) _) = Just p - mpackage _ = Nothing - pname = fmap (view packageName) . fst - pver = fmap (view packageVersion) . fst - groupPackages :: [(Maybe ModulePackage, a)] -> [(Maybe ModulePackage, [a])] - groupPackages = map (first head . unzip) . groupBy ((==) `on` fst) . sortBy (comparing fst) - selectNewest :: [(Maybe ModulePackage, [a])] -> [a] - selectNewest = - concatMap (snd . maximumBy (comparing pver)) . - groupBy ((==) `on` pname) . - sortBy (comparing pname) - --- | Select module, defined by sources -sourceModule :: Maybe Project -> [Module] -> Maybe Module -sourceModule proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (byFile . view moduleId) ms - --- | Select module, visible in project or cabal --- TODO: PackageDbStack? -visibleModule :: PackageDb -> Maybe Project -> [Module] -> Maybe Module -visibleModule d proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (inPackageDb d . view moduleId) ms - --- | Select preferred visible module --- TODO: PackageDbStack? -preferredModule :: PackageDb -> Maybe Project -> [ModuleId] -> Maybe ModuleId -preferredModule d proj ms = listToMaybe $ concatMap (`filter` ms) order where - order = [ - maybe (const False) inProject proj, - byFile, - inPackageDb d, - const True] - --- | Remove duplicate modules, leave only `preferredModule` --- TODO: PackageDbStack? -uniqueModules :: PackageDb -> Maybe Project -> [ModuleId] -> [ModuleId] -uniqueModules d proj = - mapMaybe (preferredModule d proj) . - groupBy ((==) `on` view moduleIdName) . - sortBy (comparing (view moduleIdName)) - --- | Select value, satisfying to all predicates -allOf :: [a -> Bool] -> a -> Bool -allOf ps x = all ($ x) ps - --- | Select value, satisfying one of predicates -anyOf :: [a -> Bool] -> a -> Bool -anyOf ps x = any ($ x) ps - --- | Is file info actual? ---isActual :: Symbol a -> IO Bool ---isActual = maybe (return False) checkStamp . symbolLocation where --- checkStamp l = do --- actualStamp <- getModificationTime (locationFile l) --- return $ Just actualStamp == locationTimeStamp l +module HsDev.Symbols.Util (+ projectOf, packageDbOf, packageOf,+ inProject, inDepsOfTarget, inDepsOfFile, inDepsOfProject, inPackageDb, inPackageDbStack, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, installed, standalone,+ imports, qualifier, moduleImported, visible, inScope,+ newestPackage,+ sourceModule, visibleModule, preferredModule, uniqueModules,+ allOf, anyOf+ ) where++import Control.Arrow ((***), (&&&), first)+import Control.Lens (view)+import Control.Monad (liftM)+import Data.Function (on)+import Data.Maybe+import Data.List (maximumBy, groupBy, sortBy, partition)+import Data.Ord (comparing)+import Data.String (fromString)+import System.FilePath (normalise)++import HsDev.Symbols+import HsDev.Util (ordNub)++-- | Get module project+projectOf :: ModuleId -> Maybe Project+projectOf m = case view moduleIdLocation m of+ FileModule _ proj -> proj+ _ -> Nothing++-- | Get module cabal+packageDbOf :: ModuleId -> Maybe PackageDb+packageDbOf m = case view moduleIdLocation m of+ InstalledModule c _ _ -> Just c+ _ -> Nothing++-- | Get module package+packageOf :: ModuleId -> Maybe ModulePackage+packageOf m = case view moduleIdLocation m of+ InstalledModule _ package' _ -> package'+ _ -> Nothing++-- | Check if module in project+inProject :: Project -> ModuleId -> Bool+inProject p m = projectOf m == Just p++-- | Check if module in deps of project target+inDepsOfTarget :: Info -> ModuleId -> Bool+inDepsOfTarget i m = any (`inPackage` m) $ view infoDepends i++-- | Check if module in deps of source+inDepsOfFile :: Project -> FilePath -> ModuleId -> Bool+inDepsOfFile p f m = any (`inDepsOfTarget` m) (fileTargets p f)++-- | Check if module in deps of project+inDepsOfProject :: Project -> ModuleId -> Bool+inDepsOfProject = maybe (const False) (anyPackage . ordNub . concatMap (view infoDepends) . infos) . view projectDescription where+ anyPackage :: [String] -> ModuleId -> Bool+ anyPackage = liftM or . mapM inPackage++-- | Check if module in package-db+inPackageDb :: PackageDb -> ModuleId -> Bool+inPackageDb c m = case view moduleIdLocation m of+ InstalledModule d _ _ -> d == c+ _ -> False++-- | Check if module in one of sandboxes+inPackageDbStack :: PackageDbStack -> ModuleId -> Bool+inPackageDbStack dbs m = case view moduleIdLocation m of+ InstalledModule d _ _ -> d `elem` packageDbs dbs+ _ -> False++-- | Check if module in package+inPackage :: String -> ModuleId -> Bool+inPackage p m = case view moduleIdLocation m of+ InstalledModule _ package' _ -> Just p == fmap (view packageName) package'+ _ -> False++inVersion :: String -> ModuleId -> Bool+inVersion v m = case view moduleIdLocation m of+ InstalledModule _ package' _ -> Just v == fmap (view packageVersion) package'+ _ -> False++-- | Check if module in file+inFile :: FilePath -> ModuleId -> Bool+inFile fpath m = case view moduleIdLocation m of+ FileModule f _ -> f == normalise fpath+ _ -> False++-- | Check if module in source+inModuleSource :: Maybe String -> ModuleId -> Bool+inModuleSource src m = case view moduleIdLocation m of+ ModuleSource src' -> src' == src+ _ -> False++-- | Check if declaration is in module+inModule :: String -> ModuleId -> Bool+inModule mname m = fromString mname == view moduleIdName m++-- | Check if module defined in file+byFile :: ModuleId -> Bool+byFile m = case view moduleIdLocation m of+ FileModule _ _ -> True+ _ -> False++-- | Check if module got from cabal database+installed :: ModuleId -> Bool+installed m = case view moduleIdLocation m of+ InstalledModule _ _ _ -> True+ _ -> False++-- | Check if module is standalone+standalone :: ModuleId -> Bool+standalone m = case view moduleIdLocation m of+ FileModule _ Nothing -> True+ _ -> False++-- | Get list of imports+imports :: Module -> [Import]+imports = view moduleImports++-- | Get list of imports, which can be accessed with specified qualifier or unqualified+qualifier :: Module -> Maybe String -> [Import]+qualifier m q = filter (importQualifier (fmap fromString q)) $+ import_ (fromString "Prelude") :+ import_ (view moduleName m) :+ imports m++-- | Check if module imported via imports specified+moduleImported :: ModuleId -> [Import] -> Bool+moduleImported m = any (\i -> view moduleIdName m == view importModuleName i)++-- | Check if module visible from this module within this project+visible :: Project -> ModuleId -> ModuleId -> Bool+visible p (ModuleId _ (FileModule src _)) m =+ inProject p m || any (`inPackage` m) deps || maybe False ((`elem` deps) . view projectName) (projectOf m)+ where+ deps = concatMap (view infoDepends) $ fileTargets p src+visible _ _ _ = False++-- | Check if module is in scope with qualifier+inScope :: Module -> Maybe String -> ModuleId -> Bool+inScope this q m = m `moduleImported` qualifier this q++-- | Select symbols with last package version+newestPackage :: Symbol a => [a] -> [a]+newestPackage =+ uncurry (++) .+ ((selectNewest . groupPackages) *** map snd) .+ partition (isJust . fst) .+ map ((mpackage . symbolModuleLocation) &&& id)+ where+ mpackage (InstalledModule _ (Just p) _) = Just p+ mpackage _ = Nothing+ pname = fmap (view packageName) . fst+ pver = fmap (view packageVersion) . fst+ groupPackages :: [(Maybe ModulePackage, a)] -> [(Maybe ModulePackage, [a])]+ groupPackages = map (first head . unzip) . groupBy ((==) `on` fst) . sortBy (comparing fst)+ selectNewest :: [(Maybe ModulePackage, [a])] -> [a]+ selectNewest =+ concatMap (snd . maximumBy (comparing pver)) .+ groupBy ((==) `on` pname) .+ sortBy (comparing pname)++-- | Select module, defined by sources+sourceModule :: Maybe Project -> [Module] -> Maybe Module+sourceModule proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (byFile . view moduleId) ms++-- | Select module, visible in project or cabal+-- TODO: PackageDbStack?+visibleModule :: PackageDb -> Maybe Project -> [Module] -> Maybe Module+visibleModule d proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (inPackageDb d . view moduleId) ms++-- | Select preferred visible module+-- TODO: PackageDbStack?+preferredModule :: PackageDb -> Maybe Project -> [ModuleId] -> Maybe ModuleId+preferredModule d proj ms = listToMaybe $ concatMap (`filter` ms) order where+ order = [+ maybe (const False) inProject proj,+ byFile,+ inPackageDb d,+ const True]++-- | Remove duplicate modules, leave only `preferredModule`+-- TODO: PackageDbStack?+uniqueModules :: PackageDb -> Maybe Project -> [ModuleId] -> [ModuleId]+uniqueModules d proj =+ mapMaybe (preferredModule d proj) .+ groupBy ((==) `on` view moduleIdName) .+ sortBy (comparing (view moduleIdName))++-- | Select value, satisfying to all predicates+allOf :: [a -> Bool] -> a -> Bool+allOf ps x = all ($ x) ps++-- | Select value, satisfying one of predicates+anyOf :: [a -> Bool] -> a -> Bool+anyOf ps x = any ($ x) ps++-- | Is file info actual?+--isActual :: Symbol a -> IO Bool+--isActual = maybe (return False) checkStamp . symbolLocation where+-- checkStamp l = do+-- actualStamp <- getModificationTime (locationFile l)+-- return $ Just actualStamp == locationTimeStamp l
src/HsDev/Tools/AutoFix.hs view
@@ -1,92 +1,92 @@-{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Tools.AutoFix ( - Correction(..), correctionMessage, corrector, - corrections, - autoFix, - CorrectorMatch, - correctors, - match, - findCorrector, - - module Data.Text.Region, - module HsDev.Tools.Types - ) where - -import Control.Applicative -import Control.Lens hiding ((.=), at) -import Data.Aeson -import Data.Maybe (listToMaybe, mapMaybe) -import Data.Text.Region hiding (Region(..)) -import qualified Data.Text.Region as R - -import HsDev.Symbols.Location (Position(..), Region(..)) -import HsDev.Tools.Base -import HsDev.Tools.Types -import HsDev.Util ((.::)) - -data Correction = Correction { - _correctionMessage :: String, - _corrector :: Replace String } - deriving (Eq, Show) - -instance ToJSON Correction where - toJSON (Correction msg cor) = object [ - "message" .= msg, - "corrector" .= cor] - -instance FromJSON Correction where - parseJSON = withObject "correction" $ \v -> Correction <$> - v .:: "message" <*> - v .:: "corrector" - -makeLenses ''Correction - -corrections :: [Note OutputMessage] -> [Note Correction] -corrections = mapMaybe toCorrection where - toCorrection :: Note OutputMessage -> Maybe (Note Correction) - toCorrection n = useSuggestion <|> findCorrector n where - -- Use existing suggestion - useSuggestion :: Maybe (Note Correction) - useSuggestion = do - sugg <- view (note . messageSuggestion) n - return $ set - note - (Correction - (view (note . message) n) - (replace (fromRegion $ view noteRegion n) sugg)) - n - --- | Apply corrections -autoFix :: [Note Correction] -> ([Note Correction], Maybe String) -> ([Note Correction], Maybe String) -autoFix ns (upd, mcts) = (over (each . note . corrector . replaceRegion) (update act) upd, over _Just (apply act) mcts) where - act = Edit (ns ^.. each . note . corrector) - -type CorrectorMatch = Note OutputMessage -> Maybe (Note Correction) - -correctors :: [CorrectorMatch] -correctors = [ - match "^The (?:qualified )?import of .([\\w\\.]+). is redundant" $ \_ rgn -> Correction -- There are different quotes in Windows/Linux - "Redundant import" - (cut - (expandLines rgn)), - match "^(.*?)\nFound:\n (.*?)\nWhy not:\n (.*?)$" $ \g rgn -> Correction - (g `at` 1) - (replace - ((rgn ^. regionFrom) `regionSize` pt 0 (length $ g `at` 2)) - (g `at` 3))] - -match :: String -> ((Int -> Maybe String) -> R.Region -> Correction) -> CorrectorMatch -match pat f n = do - g <- matchRx pat (view (note . message) n) - return $ set note (f g (fromRegion $ view noteRegion n)) n - -findCorrector :: Note OutputMessage -> Maybe (Note Correction) -findCorrector n = listToMaybe $ mapMaybe ($ n) correctors - -fromRegion :: Region -> R.Region -fromRegion (Region f t) = fromPosition f `till` fromPosition t - -fromPosition :: Position -> Point -fromPosition (Position l c) = pt (pred l) (pred c) +{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Tools.AutoFix (+ Correction(..), correctionMessage, corrector,+ corrections,+ autoFix,+ CorrectorMatch,+ correctors,+ match,+ findCorrector,++ module Data.Text.Region,+ module HsDev.Tools.Types+ ) where++import Control.Applicative+import Control.Lens hiding ((.=), at)+import Data.Aeson+import Data.Maybe (listToMaybe, mapMaybe)+import Data.Text.Region hiding (Region(..))+import qualified Data.Text.Region as R++import HsDev.Symbols.Location (Position(..), Region(..))+import HsDev.Tools.Base+import HsDev.Tools.Types+import HsDev.Util ((.::))++data Correction = Correction {+ _correctionMessage :: String,+ _corrector :: Replace String }+ deriving (Eq, Show)++instance ToJSON Correction where+ toJSON (Correction msg cor) = object [+ "message" .= msg,+ "corrector" .= cor]++instance FromJSON Correction where+ parseJSON = withObject "correction" $ \v -> Correction <$>+ v .:: "message" <*>+ v .:: "corrector"++makeLenses ''Correction++corrections :: [Note OutputMessage] -> [Note Correction]+corrections = mapMaybe toCorrection where+ toCorrection :: Note OutputMessage -> Maybe (Note Correction)+ toCorrection n = useSuggestion <|> findCorrector n where+ -- Use existing suggestion+ useSuggestion :: Maybe (Note Correction)+ useSuggestion = do+ sugg <- view (note . messageSuggestion) n+ return $ set+ note+ (Correction+ (view (note . message) n)+ (replace (fromRegion $ view noteRegion n) sugg))+ n++-- | Apply corrections+autoFix :: [Note Correction] -> ([Note Correction], Maybe String) -> ([Note Correction], Maybe String)+autoFix ns (upd, mcts) = (over (each . note . corrector . replaceRegion) (update act) upd, over _Just (apply act) mcts) where+ act = Edit (ns ^.. each . note . corrector)++type CorrectorMatch = Note OutputMessage -> Maybe (Note Correction)++correctors :: [CorrectorMatch]+correctors = [+ match "^The (?:qualified )?import of .([\\w\\.]+). is redundant" $ \_ rgn -> Correction -- There are different quotes in Windows/Linux+ "Redundant import"+ (cut+ (expandLines rgn)),+ match "^(.*?)\nFound:\n (.*?)\nWhy not:\n (.*?)$" $ \g rgn -> Correction+ (g `at` 1)+ (replace+ ((rgn ^. regionFrom) `regionSize` pt 0 (length $ g `at` 2))+ (g `at` 3))]++match :: String -> ((Int -> Maybe String) -> R.Region -> Correction) -> CorrectorMatch+match pat f n = do+ g <- matchRx pat (view (note . message) n)+ return $ set note (f g (fromRegion $ view noteRegion n)) n++findCorrector :: Note OutputMessage -> Maybe (Note Correction)+findCorrector n = listToMaybe $ mapMaybe ($ n) correctors++fromRegion :: Region -> R.Region+fromRegion (Region f t) = fromPosition f `till` fromPosition t++fromPosition :: Position -> Point+fromPosition (Position l c) = pt (pred l) (pred c)
src/HsDev/Tools/Base.hs view
@@ -1,117 +1,117 @@-module HsDev.Tools.Base ( - runTool, runTool_, - Result, ToolM, - runWait, runWait_, - tool, tool_, - matchRx, splitRx, replaceRx, - at, at_, - inspect, - -- * Read parse utils - ReadM, - readParse, parseReads, parseRead, - - module HsDev.Tools.Types - ) where - -import Control.Lens (set) -import Control.Monad.Catch (MonadCatch) -import Control.Monad.Except -import Control.Monad.State -import Data.Array (assocs) -import Data.List (unfoldr, intercalate) -import Data.Maybe (fromMaybe, listToMaybe) -import System.Exit -import System.Process -import Text.Regex.PCRE ((=~), MatchResult(..)) - -import HsDev.Error -import HsDev.Tools.Types -import HsDev.Symbols -import HsDev.Util (liftIOErrors) - --- | Run tool, throwing HsDevError on fail -runTool :: FilePath -> [String] -> String -> IO String -runTool name args input = hsdevLiftIOWith onIOError $ do - (code, out, err) <- readProcessWithExitCode name args input - case code of - ExitFailure ecode -> hsdevError $ ToolError name $ - "exited with code " ++ show ecode ++ ": " ++ err - ExitSuccess -> return out - where - onIOError s = ToolError name $ unlines [ - "args: [" ++ intercalate ", " args ++ "]", - "stdin: " ++ input, - "error: " ++ s] - --- | Run tool with not stdin -runTool_ :: FilePath -> [String] -> IO String -runTool_ name args = runTool name args "" - -type Result = Either String String -type ToolM a = ExceptT String IO a - --- | Run command and wait for result -runWait :: FilePath -> [String] -> String -> IO Result -runWait name args input = do - (code, out, err) <- readProcessWithExitCode name args input - return $ if code == ExitSuccess && not (null out) then Right out else Left err - --- | Run command with no input -runWait_ :: FilePath -> [String] -> IO Result -runWait_ name args = runWait name args "" - --- | Tool -tool :: FilePath -> [String] -> String -> ToolM String -tool name args input = liftIOErrors $ ExceptT $ runWait name args input - --- | Tool with no input -tool_ :: FilePath -> [String] -> ToolM String -tool_ name args = tool name args "" - -matchRx :: String -> String -> Maybe (Int -> Maybe String) -matchRx pat str = if matched then Just look else Nothing where - m :: MatchResult String - m = str =~ pat - matched = not $ null $ mrMatch m - groups = filter (not . null . snd) $ assocs $ mrSubs m - look i = lookup i groups - -splitRx :: String -> String -> [String] -splitRx pat = unfoldr split' . Just where - split' :: Maybe String -> Maybe (String, Maybe String) - split' Nothing = Nothing - split' (Just str) = case str =~ pat of - (pre, "", "") -> Just (pre, Nothing) - (pre, _, post) -> Just (pre, Just post) - -replaceRx :: String -> String -> String -> String -replaceRx pat w = intercalate w . splitRx pat - -at :: (Int -> Maybe String) -> Int -> String -at g i = fromMaybe (error $ "Can't find group " ++ show i) $ g i - -at_ :: (Int -> Maybe String) -> Int -> String -at_ g = fromMaybe "" . g - -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 - --- | Parse readable value -readParse :: Read a => ReadM a -readParse = StateT reads - --- | Run parser -parseReads :: String -> ReadM a -> [a] -parseReads = flip evalStateT - --- | Run parser and select first result -parseRead :: String -> ReadM a -> Maybe a -parseRead s = listToMaybe . parseReads s +module HsDev.Tools.Base (+ runTool, runTool_,+ Result, ToolM,+ runWait, runWait_,+ tool, tool_,+ matchRx, splitRx, replaceRx,+ at, at_,+ inspect,+ -- * Read parse utils+ ReadM,+ readParse, parseReads, parseRead,++ module HsDev.Tools.Types+ ) where++import Control.Lens (set)+import Control.Monad.Catch (MonadCatch)+import Control.Monad.Except+import Control.Monad.State+import Data.Array (assocs)+import Data.List (unfoldr, intercalate)+import Data.Maybe (fromMaybe, listToMaybe)+import System.Exit+import System.Process+import Text.Regex.PCRE ((=~), MatchResult(..))++import HsDev.Error+import HsDev.Tools.Types+import HsDev.Symbols+import HsDev.Util (liftIOErrors)++-- | Run tool, throwing HsDevError on fail+runTool :: FilePath -> [String] -> String -> IO String+runTool name args input = hsdevLiftIOWith onIOError $ do+ (code, out, err) <- readProcessWithExitCode name args input+ case code of+ ExitFailure ecode -> hsdevError $ ToolError name $+ "exited with code " ++ show ecode ++ ": " ++ err+ ExitSuccess -> return out+ where+ onIOError s = ToolError name $ unlines [+ "args: [" ++ intercalate ", " args ++ "]",+ "stdin: " ++ input,+ "error: " ++ s]++-- | Run tool with not stdin+runTool_ :: FilePath -> [String] -> IO String+runTool_ name args = runTool name args ""++type Result = Either String String+type ToolM a = ExceptT String IO a++-- | Run command and wait for result+runWait :: FilePath -> [String] -> String -> IO Result+runWait name args input = do+ (code, out, err) <- readProcessWithExitCode name args input+ return $ if code == ExitSuccess && not (null out) then Right out else Left err++-- | Run command with no input+runWait_ :: FilePath -> [String] -> IO Result+runWait_ name args = runWait name args ""++-- | Tool+tool :: FilePath -> [String] -> String -> ToolM String+tool name args input = liftIOErrors $ ExceptT $ runWait name args input++-- | Tool with no input+tool_ :: FilePath -> [String] -> ToolM String+tool_ name args = tool name args ""++matchRx :: String -> String -> Maybe (Int -> Maybe String)+matchRx pat str = if matched then Just look else Nothing where+ m :: MatchResult String+ m = str =~ pat+ matched = not $ null $ mrMatch m+ groups = filter (not . null . snd) $ assocs $ mrSubs m+ look i = lookup i groups++splitRx :: String -> String -> [String]+splitRx pat = unfoldr split' . Just where+ split' :: Maybe String -> Maybe (String, Maybe String)+ split' Nothing = Nothing+ split' (Just str) = case str =~ pat of+ (pre, "", "") -> Just (pre, Nothing)+ (pre, _, post) -> Just (pre, Just post)++replaceRx :: String -> String -> String -> String+replaceRx pat w = intercalate w . splitRx pat++at :: (Int -> Maybe String) -> Int -> String+at g i = fromMaybe (error $ "Can't find group " ++ show i) $ g i++at_ :: (Int -> Maybe String) -> Int -> String+at_ g = fromMaybe "" . g++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++-- | Parse readable value+readParse :: Read a => ReadM a+readParse = StateT reads++-- | Run parser+parseReads :: String -> ReadM a -> [a]+parseReads = flip evalStateT++-- | Run parser and select first result+parseRead :: String -> ReadM a -> Maybe a+parseRead s = listToMaybe . parseReads s
src/HsDev/Tools/Cabal.hs view
@@ -1,85 +1,85 @@-{-# LANGUAGE OverloadedStrings, CPP #-} - -module HsDev.Tools.Cabal ( - CabalPackage(..), - cabalList, - - -- * Reexports - Version(..), License(..) - ) where - -import Control.Arrow -import Control.Monad -import Data.Aeson -import Data.Char (isSpace) -import Data.Maybe -import Distribution.License -import Distribution.Text -import Distribution.Version - -import HsDev.Tools.Base -import HsDev.Util - -data CabalPackage = CabalPackage { - cabalPackageName :: String, - cabalPackageSynopsis :: Maybe String, - cabalPackageDefaultVersion :: Maybe Version, - cabalPackageInstalledVersions :: [Version], - cabalPackageHomepage :: Maybe String, - cabalPackageLicense :: Maybe License } - deriving (Eq, Read, Show) - -instance ToJSON CabalPackage where - toJSON cp = object [ - "name" .= cabalPackageName cp, - "synopsis" .= cabalPackageSynopsis cp, - "default-version" .= fmap display (cabalPackageDefaultVersion cp), - "installed-versions" .= map display (cabalPackageInstalledVersions cp), - "homepage" .= cabalPackageHomepage cp, - "license" .= fmap display (cabalPackageLicense cp)] - -instance FromJSON CabalPackage where - parseJSON = withObject "cabal-package" $ \v -> CabalPackage <$> - (v .:: "name") <*> - (v .:: "synopsis") <*> - ((join . fmap simpleParse) <$> (v .:: "default-version")) <*> - (mapMaybe simpleParse <$> (v .:: "installed-versions")) <*> - (v .:: "homepage") <*> - ((join . fmap simpleParse) <$> (v .:: "license")) - -cabalList :: [String] -> ToolM [CabalPackage] -cabalList queries = do -#if mingw32_HOST_OS - rs <- liftM (split (all isSpace) . lines) $ tool_ "powershell" [ - "-Command", - unwords (["&", "{", "chcp 65001 | out-null;", "cabal list"] ++ queries ++ ["}"])] -#else - rs <- liftM (split (all isSpace) . lines) $ tool_ "cabal" ("list" : queries) -#endif - return $ map toPackage $ mapMaybe parseFields rs - where - toPackage :: (String, [(String, String)]) -> CabalPackage - toPackage (name, fs) = CabalPackage { - cabalPackageName = name, - cabalPackageSynopsis = lookup "Synopsis" fs, - cabalPackageDefaultVersion = (lookup "Default available version" fs >>= simpleParse), - cabalPackageInstalledVersions = fromMaybe [] (lookup "Installed versions" fs >>= mapM (simpleParse . trim) . split (== ',')), - cabalPackageHomepage = lookup "Homepage" fs, - cabalPackageLicense = lookup "License" fs >>= simpleParse } - - parseFields :: [String] -> Maybe (String, [(String, String)]) - parseFields [] = Nothing - parseFields (('*':name):fs) = Just (trim name, mapMaybe parseField' fs) where - parseField' :: String -> Maybe (String, String) - parseField' str = case parseField str of - (fname, Just fval) -> Just (fname, fval) - _ -> Nothing - parseFields _ = Nothing - - -- foo: bar → (foo, bar) - parseField :: String -> (String, Maybe String) - parseField = (trim *** (parseValue . trim . drop 1)) . break (== ':') - -- [ ... ] → Nothing, ... → Just ... - parseValue :: String -> Maybe String - parseValue ('[':_) = Nothing - parseValue v = Just v +{-# LANGUAGE OverloadedStrings, CPP #-}++module HsDev.Tools.Cabal (+ CabalPackage(..),+ cabalList,++ -- * Reexports+ Version, License(..)+ ) where++import Control.Arrow+import Control.Monad+import Data.Aeson+import Data.Char (isSpace)+import Data.Maybe+import Distribution.License+import Distribution.Text+import Distribution.Version++import HsDev.Tools.Base+import HsDev.Util++data CabalPackage = CabalPackage {+ cabalPackageName :: String,+ cabalPackageSynopsis :: Maybe String,+ cabalPackageDefaultVersion :: Maybe Version,+ cabalPackageInstalledVersions :: [Version],+ cabalPackageHomepage :: Maybe String,+ cabalPackageLicense :: Maybe License }+ deriving (Eq, Read, Show)++instance ToJSON CabalPackage where+ toJSON cp = object [+ "name" .= cabalPackageName cp,+ "synopsis" .= cabalPackageSynopsis cp,+ "default-version" .= fmap display (cabalPackageDefaultVersion cp),+ "installed-versions" .= map display (cabalPackageInstalledVersions cp),+ "homepage" .= cabalPackageHomepage cp,+ "license" .= fmap display (cabalPackageLicense cp)]++instance FromJSON CabalPackage where+ parseJSON = withObject "cabal-package" $ \v -> CabalPackage <$>+ (v .:: "name") <*>+ (v .:: "synopsis") <*>+ ((join . fmap simpleParse) <$> (v .:: "default-version")) <*>+ (mapMaybe simpleParse <$> (v .:: "installed-versions")) <*>+ (v .:: "homepage") <*>+ ((join . fmap simpleParse) <$> (v .:: "license"))++cabalList :: [String] -> ToolM [CabalPackage]+cabalList queries = do+#if mingw32_HOST_OS+ rs <- liftM (split (all isSpace) . lines) $ tool_ "powershell" [+ "-Command",+ unwords (["&", "{", "chcp 65001 | out-null;", "cabal list"] ++ queries ++ ["}"])]+#else+ rs <- liftM (split (all isSpace) . lines) $ tool_ "cabal" ("list" : queries)+#endif+ return $ map toPackage $ mapMaybe parseFields rs+ where+ toPackage :: (String, [(String, String)]) -> CabalPackage+ toPackage (name, fs) = CabalPackage {+ cabalPackageName = name,+ cabalPackageSynopsis = lookup "Synopsis" fs,+ cabalPackageDefaultVersion = (lookup "Default available version" fs >>= simpleParse),+ cabalPackageInstalledVersions = fromMaybe [] (lookup "Installed versions" fs >>= mapM (simpleParse . trim) . split (== ',')),+ cabalPackageHomepage = lookup "Homepage" fs,+ cabalPackageLicense = lookup "License" fs >>= simpleParse }++ parseFields :: [String] -> Maybe (String, [(String, String)])+ parseFields [] = Nothing+ parseFields (('*':name):fs) = Just (trim name, mapMaybe parseField' fs) where+ parseField' :: String -> Maybe (String, String)+ parseField' str = case parseField str of+ (fname, Just fval) -> Just (fname, fval)+ _ -> Nothing+ parseFields _ = Nothing++ -- foo: bar → (foo, bar)+ parseField :: String -> (String, Maybe String)+ parseField = (trim *** (parseValue . trim . drop 1)) . break (== ':')+ -- [ ... ] → Nothing, ... → Just ...+ parseValue :: String -> Maybe String+ parseValue ('[':_) = Nothing+ parseValue v = Just v
src/HsDev/Tools/ClearImports.hs view
@@ -1,112 +1,112 @@-module HsDev.Tools.ClearImports ( - dumpMinimalImports, waitImports, cleanTmpImports, - findMinimalImports, - groupImports, splitImport, - clearImports, - - module Control.Monad.Except - ) where - -import Control.Arrow -import Control.Concurrent (threadDelay) -import Control.Exception -import Control.Monad.Except -import Data.Char -import Data.List -import Data.Maybe (mapMaybe) -import System.Directory -import System.FilePath -import qualified Language.Haskell.Exts as Exts - -import GHC -import GHC.Paths (libdir) - -import HsDev.Util -import HsDev.Tools.Ghc.Compat - --- | Dump minimal imports -dumpMinimalImports :: [String] -> FilePath -> ExceptT String IO String -dumpMinimalImports opts f = do - cur <- liftE getCurrentDirectory - file <- liftE $ canonicalizePath f - cts <- liftE $ readFileUtf8 file - - mname <- case Exts.parseFileContentsWithMode (pmode file) cts of - Exts.ParseFailed loc err -> throwError $ - "Failed to parse file at " ++ - Exts.prettyPrint loc ++ ":" ++ err - Exts.ParseOk (Exts.Module _ (Just (Exts.ModuleHead _ (Exts.ModuleName _ mname) _ _)) _ _ _) -> return mname - _ -> throwError "Error" - - void $ liftE $ runGhc (Just libdir) $ do - df <- getSessionDynFlags - let - df' = df { - ghcLink = NoLink, - hscTarget = HscNothing, - dumpDir = Just cur, - stubDir = Just cur, - objectDir = Just cur, - hiDir = Just cur } - (df'', _, _) <- parseDynamicFlags df' (map noLoc ("-ddump-minimal-imports" : opts)) - _ <- setSessionDynFlags df'' - cleanupHandler df'' $ do - t <- guessTarget file Nothing - setTargets [t] - load LoadAllTargets - - length mname `seq` return mname - where - pmode :: FilePath -> Exts.ParseMode - pmode f' = Exts.defaultParseMode { - Exts.parseFilename = f', - Exts.baseLanguage = Exts.Haskell2010, - Exts.extensions = Exts.glasgowExts ++ map Exts.parseExtension exts, - Exts.fixities = Just Exts.baseFixities } - exts = mapMaybe (stripPrefix "-X") opts - --- | Read imports from file -waitImports :: FilePath -> IO [String] -waitImports f = retry 1000 $ do - is <- liftM lines $ readFile f - length is `seq` return is - --- | Clean temporary files -cleanTmpImports :: FilePath -> IO () -cleanTmpImports dir = do - dumps <- liftM (filter ((== ".imports") . takeExtension)) $ directoryContents dir - forM_ dumps $ handle ignoreIO' . retry 1000 . removeFile - where - ignoreIO' :: IOException -> IO () - ignoreIO' _ = return () - --- | Dump and read imports -findMinimalImports :: [String] -> FilePath -> ExceptT String IO [String] -findMinimalImports opts f = do - file <- liftE $ canonicalizePath f - mname <- dumpMinimalImports opts file - is <- liftE $ waitImports (mname <.> "imports") - tmp <- liftE getCurrentDirectory - liftE $ cleanTmpImports tmp - return is - --- | Groups several lines related to one import by indents -groupImports :: [String] -> [[String]] -groupImports = unfoldr getPack where - getPack [] = Nothing - getPack (s:ss) = Just $ first (s:) $ break (null . takeWhile isSpace) ss - --- | Split import to import and import-list -splitImport :: [String] -> (String, String) -splitImport = splitBraces . unwords . map trim where - cut = twice $ reverse . drop 1 - twice f = f . f - splitBraces = (trim *** (trim . cut)) . break (== '(') - --- | Returns minimal imports for file specified -clearImports :: [String] -> FilePath -> ExceptT String IO [(String, String)] -clearImports opts = liftM (map splitImport . groupImports) . findMinimalImports opts - --- | Retry action on fail -retry :: (MonadPlus m, MonadIO m) => Int -> m a -> m a -retry dt act = msum $ act : repeat ((liftIO (threadDelay dt) >>) act) +module HsDev.Tools.ClearImports (+ dumpMinimalImports, waitImports, cleanTmpImports,+ findMinimalImports,+ groupImports, splitImport,+ clearImports,++ module Control.Monad.Except+ ) where++import Control.Arrow+import Control.Concurrent (threadDelay)+import Control.Exception+import Control.Monad.Except+import Data.Char+import Data.List+import Data.Maybe (mapMaybe)+import System.Directory+import System.FilePath+import qualified Language.Haskell.Exts as Exts++import GHC+import GHC.Paths (libdir)++import HsDev.Util+import HsDev.Tools.Ghc.Compat++-- | Dump minimal imports+dumpMinimalImports :: [String] -> FilePath -> ExceptT String IO String+dumpMinimalImports opts f = do+ cur <- liftE getCurrentDirectory+ file <- liftE $ canonicalizePath f+ cts <- liftE $ readFileUtf8 file++ mname <- case Exts.parseFileContentsWithMode (pmode file) cts of+ Exts.ParseFailed loc err -> throwError $+ "Failed to parse file at " +++ Exts.prettyPrint loc ++ ":" ++ err+ Exts.ParseOk (Exts.Module _ (Just (Exts.ModuleHead _ (Exts.ModuleName _ mname) _ _)) _ _ _) -> return mname+ _ -> throwError "Error"++ void $ liftE $ runGhc (Just libdir) $ do+ df <- getSessionDynFlags+ let+ df' = df {+ ghcLink = NoLink,+ hscTarget = HscNothing,+ dumpDir = Just cur,+ stubDir = Just cur,+ objectDir = Just cur,+ hiDir = Just cur }+ (df'', _, _) <- parseDynamicFlags df' (map noLoc ("-ddump-minimal-imports" : opts))+ _ <- setSessionDynFlags df''+ cleanupHandler df'' $ do+ t <- guessTarget file Nothing+ setTargets [t]+ load LoadAllTargets++ length mname `seq` return mname+ where+ pmode :: FilePath -> Exts.ParseMode+ pmode f' = Exts.defaultParseMode {+ Exts.parseFilename = f',+ Exts.baseLanguage = Exts.Haskell2010,+ Exts.extensions = Exts.glasgowExts ++ map Exts.parseExtension exts,+ Exts.fixities = Just Exts.baseFixities }+ exts = mapMaybe (stripPrefix "-X") opts++-- | Read imports from file+waitImports :: FilePath -> IO [String]+waitImports f = retry 1000 $ do+ is <- liftM lines $ readFile f+ length is `seq` return is++-- | Clean temporary files+cleanTmpImports :: FilePath -> IO ()+cleanTmpImports dir = do+ dumps <- liftM (filter ((== ".imports") . takeExtension)) $ directoryContents dir+ forM_ dumps $ handle ignoreIO' . retry 1000 . removeFile+ where+ ignoreIO' :: IOException -> IO ()+ ignoreIO' _ = return ()++-- | Dump and read imports+findMinimalImports :: [String] -> FilePath -> ExceptT String IO [String]+findMinimalImports opts f = do+ file <- liftE $ canonicalizePath f+ mname <- dumpMinimalImports opts file+ is <- liftE $ waitImports (mname <.> "imports")+ tmp <- liftE getCurrentDirectory+ liftE $ cleanTmpImports tmp+ return is++-- | Groups several lines related to one import by indents+groupImports :: [String] -> [[String]]+groupImports = unfoldr getPack where+ getPack [] = Nothing+ getPack (s:ss) = Just $ first (s:) $ break (null . takeWhile isSpace) ss++-- | Split import to import and import-list+splitImport :: [String] -> (String, String)+splitImport = splitBraces . unwords . map trim where+ cut = twice $ reverse . drop 1+ twice f = f . f+ splitBraces = (trim *** (trim . cut)) . break (== '(')++-- | Returns minimal imports for file specified+clearImports :: [String] -> FilePath -> ExceptT String IO [(String, String)]+clearImports opts = liftM (map splitImport . groupImports) . findMinimalImports opts++-- | Retry action on fail+retry :: (MonadPlus m, MonadIO m) => Int -> m a -> m a+retry dt act = msum $ act : repeat ((liftIO (threadDelay dt) >>) act)
src/HsDev/Tools/Ghc/Check.hs view
@@ -1,88 +1,88 @@-{-# LANGUAGE OverloadedStrings #-} - -module HsDev.Tools.Ghc.Check ( - checkFiles, check, checkFile, checkSource, - - Ghc, - module HsDev.Tools.Types, - module HsDev.Symbols.Types, - PackageDb(..), PackageDbStack(..), Project(..), - - recalcNotesTabs, - - module Control.Monad.Except - ) where - -import Control.Lens (preview, view, each, _Just, (^..)) -import Control.Monad.Except -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.Symbols.Location -import HsDev.Symbols.Types -import HsDev.Project (fileTarget) -import HsDev.Tools.Base -import HsDev.Tools.Ghc.Worker -import HsDev.Tools.Ghc.Compat -import HsDev.Tools.Types -import HsDev.Util (readFileUtf8, ordNub) - --- | Check files and collect warnings and errors -checkFiles :: (MonadLog m, GhcMonad m) => [String] -> [FilePath] -> Maybe Project -> m [Note OutputMessage] -checkFiles opts files _ = scope "check-files" $ do - ch <- liftIO newChan - withFlags $ do - modifyFlags $ setLogAction $ logToChan ch - addCmdOpts opts - clearTargets - mapM (`makeTarget` Nothing) files >>= loadTargets - notes <- liftIO $ stopChan ch - liftIO $ recalcNotesTabs notes - --- | Check module source -check :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note OutputMessage] -check opts m msrc = scope "check" $ case view moduleLocation m of - FileModule file proj -> do - ch <- liftIO newChan - let - dir = fromMaybe - (sourceModuleRoot (view moduleName m) file) $ - preview (_Just . projectPath) proj - dirExist <- liftIO $ doesDirectoryExist dir - withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do - addCmdOpts opts - modifyFlags $ setLogAction $ logToChan ch - clearTargets - target <- makeTarget (makeRelative dir file) msrc - loadTargets [target] - notes <- liftIO $ stopChan ch - liftIO $ recalcNotesTabs notes - _ -> scope "check" $ hsdevError $ ModuleNotSource (view moduleLocation m) - --- | Check module and collect warnings and errors -checkFile :: (MonadLog m, GhcMonad m) => [String] -> Module -> m [Note OutputMessage] -checkFile opts m = check opts m Nothing - --- | Check module and collect warnings and errors -checkSource :: (MonadLog m, GhcMonad m) => [String] -> Module -> String -> m [Note OutputMessage] -checkSource opts m src = check opts m (Just src) - --- Recalc tabs for notes -recalcNotesTabs :: [Note OutputMessage] -> IO [Note OutputMessage] -recalcNotesTabs notes = do - cts <- mapM readFileUtf8 files - let - recalc' n = fromMaybe n $ do - fname <- preview (noteSource . moduleFile) n - cts' <- lookup fname (zip files cts) - return $ recalcTabs cts' 8 n - return $ map recalc' notes - where - files = ordNub $ notes ^.. each . noteSource . moduleFile +{-# LANGUAGE OverloadedStrings #-}++module HsDev.Tools.Ghc.Check (+ checkFiles, check, checkFile, checkSource,++ Ghc,+ module HsDev.Tools.Types,+ module HsDev.Symbols.Types,+ PackageDb(..), PackageDbStack(..), Project(..),++ recalcNotesTabs,++ module Control.Monad.Except+ ) where++import Control.Lens (preview, view, each, _Just, (^..))+import Control.Monad.Except+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.Symbols.Location+import HsDev.Symbols.Types+import HsDev.Project (fileTarget)+import HsDev.Tools.Base+import HsDev.Tools.Ghc.Worker+import HsDev.Tools.Ghc.Compat as C+import HsDev.Tools.Types+import HsDev.Util (readFileUtf8, ordNub)++-- | Check files and collect warnings and errors+checkFiles :: (MonadLog m, GhcMonad m) => [String] -> [FilePath] -> Maybe Project -> m [Note OutputMessage]+checkFiles opts files _ = scope "check-files" $ do+ ch <- liftIO newChan+ withFlags $ do+ modifyFlags $ C.setLogAction $ logToChan ch+ addCmdOpts opts+ clearTargets+ mapM (`makeTarget` Nothing) files >>= loadTargets+ notes <- liftIO $ stopChan ch+ liftIO $ recalcNotesTabs notes++-- | Check module source+check :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note OutputMessage]+check opts m msrc = scope "check" $ case view moduleLocation m of+ FileModule file proj -> do+ ch <- liftIO newChan+ let+ dir = fromMaybe+ (sourceModuleRoot (view moduleName m) file) $+ preview (_Just . projectPath) proj+ dirExist <- liftIO $ doesDirectoryExist dir+ withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do+ addCmdOpts opts+ modifyFlags $ C.setLogAction $ logToChan ch+ clearTargets+ target <- makeTarget (makeRelative dir file) msrc+ loadTargets [target]+ notes <- liftIO $ stopChan ch+ liftIO $ recalcNotesTabs notes+ _ -> scope "check" $ hsdevError $ ModuleNotSource (view moduleLocation m)++-- | Check module and collect warnings and errors+checkFile :: (MonadLog m, GhcMonad m) => [String] -> Module -> m [Note OutputMessage]+checkFile opts m = check opts m Nothing++-- | Check module and collect warnings and errors+checkSource :: (MonadLog m, GhcMonad m) => [String] -> Module -> String -> m [Note OutputMessage]+checkSource opts m src = check opts m (Just src)++-- Recalc tabs for notes+recalcNotesTabs :: [Note OutputMessage] -> IO [Note OutputMessage]+recalcNotesTabs notes = do+ cts <- mapM readFileUtf8 files+ let+ recalc' n = fromMaybe n $ do+ fname <- preview (noteSource . moduleFile) n+ cts' <- lookup fname (zip files cts)+ return $ recalcTabs cts' 8 n+ return $ map recalc' notes+ where+ files = ordNub $ notes ^.. each . noteSource . moduleFile
src/HsDev/Tools/Ghc/Compat.hs view
@@ -1,113 +1,154 @@-{-# LANGUAGE CPP #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Tools.Ghc.Compat ( - pkgDatabase, UnitId, unitId, moduleUnitId, depends, getPackageDetails, patSynType, cleanupHandler, renderStyle, - LogAction, setLogAction, - languages, flags - ) where - -import qualified DynFlags as GHC -import qualified ErrUtils -import qualified GHC -import qualified Module -import qualified Packages as GHC -import qualified PatSyn as GHC -import qualified Pretty - -#if __GLASGOW_HASKELL__ == 710 -import Exception (ExceptionMonad) -import Control.Monad.Reader -#endif - -pkgDatabase :: GHC.DynFlags -> Maybe [GHC.PackageConfig] -#if __GLASGOW_HASKELL__ == 800 -pkgDatabase = fmap (concatMap snd) . GHC.pkgDatabase -#elif __GLASGOW_HASKELL__ == 710 -pkgDatabase = GHC.pkgDatabase -#endif - -#if __GLASGOW_HASKELL__ == 800 -type UnitId = Module.UnitId -#elif __GLASGOW_HASKELL__ == 710 -type UnitId = Module.PackageKey -#endif - -unitId :: GHC.PackageConfig -> UnitId -#if __GLASGOW_HASKELL__ == 800 -unitId = GHC.unitId -#elif __GLASGOW_HASKELL__ == 710 -unitId = GHC.packageKey -#endif - -moduleUnitId :: GHC.Module -> UnitId -#if __GLASGOW_HASKELL__ == 800 -moduleUnitId = GHC.moduleUnitId -#elif __GLASGOW_HASKELL__ == 710 -moduleUnitId = GHC.modulePackageKey -#endif - -depends :: GHC.DynFlags -> GHC.PackageConfig -> [UnitId] -#if __GLASGOW_HASKELL__ == 800 -depends _ = GHC.depends -#elif __GLASGOW_HASKELL__ == 710 -depends df = map (GHC.resolveInstalledPackageId df) . GHC.depends -#endif - -getPackageDetails :: GHC.DynFlags -> UnitId -> GHC.PackageConfig -getPackageDetails = GHC.getPackageDetails - -patSynType :: GHC.PatSyn -> GHC.Type -patSynType p = GHC.patSynInstResTy p (GHC.patSynArgs p) - -#if __GLASGOW_HASKELL__ == 800 -cleanupHandler :: GHC.DynFlags -> m a -> m a -cleanupHandler _ = id -#elif __GLASGOW_HASKELL__ == 710 -cleanupHandler :: (ExceptionMonad m) => GHC.DynFlags -> m a -> m a -cleanupHandler = GHC.defaultCleanupHandler -#endif - -renderStyle :: Pretty.Mode -> Int -> Pretty.Doc -> String -#if __GLASGOW_HASKELL__ == 800 -renderStyle m cols = Pretty.renderStyle (Pretty.Style m cols 1.5) -#elif __GLASGOW_HASKELL__ == 710 -renderStyle = Pretty.showDoc -#endif - -type LogAction = GHC.DynFlags -> GHC.Severity -> GHC.SrcSpan -> ErrUtils.MsgDoc -> IO () - -setLogAction :: LogAction -> GHC.DynFlags -> GHC.DynFlags -setLogAction act fs = fs { GHC.log_action = act' } where - act' :: GHC.LogAction -#if __GLASGOW_HASKELL__ == 800 - act' df _ sev src _ msg = act df sev src msg -#elif __GLASGOW_HASKELL__ == 710 - act' df sev src _ msg = act df sev src msg -#endif - -#if __GLASGOW_HASKELL__ == 710 -instance (Monad m, GHC.HasDynFlags m) => GHC.HasDynFlags (ReaderT r m) where - getDynFlags = lift GHC.getDynFlags -#endif - -flags :: [String] -#if __GLASGOW_HASKELL__ >= 800 -flags = concat [ - [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags], - ["warn-" ++ option | (GHC.FlagSpec option _ _ _) <- GHC.wWarningFlags], - [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]] -#elif __GLASGOW_HASKELL__ >= 710 -flags = concat [ - [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags], - [option | (GHC.FlagSpec option _ _ _) <- GHC.fWarningFlags], - [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]] -#elif __GLASGOW_HASKELL__ >= 704 -flags = concat [ - [option | (option, _, _) <- GHC.fFlags], - [option | (option, _, _) <- GHC.fWarningFlags], - [option | (option, _, _) <- GHC.fLangFlags]] -#endif - -languages :: [String] -languages = GHC.supportedLanguagesAndExtensions +{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Tools.Ghc.Compat (+ pkgDatabase,+ UnitId, InstalledUnitId, toInstalledUnitId,+ unitId, moduleUnitId, depends, getPackageDetails, patSynType, cleanupHandler, renderStyle,+ LogAction, setLogAction,+ languages, flags,+ unqualStyle,+ exposedModuleName+ ) where++import qualified DynFlags as GHC+import qualified ErrUtils+import qualified GHC+import qualified Module+import qualified Packages as GHC+import qualified PatSyn as GHC+import qualified Pretty+import Outputable++#if __GLASGOW_HASKELL__ == 710+import Exception (ExceptionMonad)+import Control.Monad.Reader+#endif++#if __GLASGOW_HASKELL__ <= 800+import qualified GHC.PackageDb as GHC+#endif++pkgDatabase :: GHC.DynFlags -> Maybe [GHC.PackageConfig]+#if __GLASGOW_HASKELL__ >= 800+pkgDatabase = fmap (concatMap snd) . GHC.pkgDatabase+#elif __GLASGOW_HASKELL__ == 710+pkgDatabase = GHC.pkgDatabase+#endif++#if __GLASGOW_HASKELL__ >= 800+type UnitId = Module.UnitId+#elif __GLASGOW_HASKELL__ == 710+type UnitId = Module.PackageKey+#endif++#if __GLASGOW_HASKELL__ == 802+type InstalledUnitId = Module.InstalledUnitId+#else+type InstalledUnitId = UnitId+#endif++toInstalledUnitId :: UnitId -> InstalledUnitId+#if __GLASGOW_HASKELL__ == 802+toInstalledUnitId = Module.toInstalledUnitId+#else+toInstalledUnitId = id+#endif++unitId :: GHC.PackageConfig -> InstalledUnitId+#if __GLASGOW_HASKELL__ >= 800+unitId = GHC.unitId+#elif __GLASGOW_HASKELL__ == 710+unitId = GHC.packageKey+#endif++moduleUnitId :: GHC.Module -> UnitId+#if __GLASGOW_HASKELL__ >= 800+moduleUnitId = GHC.moduleUnitId+#elif __GLASGOW_HASKELL__ == 710+moduleUnitId = GHC.modulePackageKey+#endif++depends :: GHC.DynFlags -> GHC.PackageConfig -> [InstalledUnitId]+#if __GLASGOW_HASKELL__ >= 800+depends _ = GHC.depends+#elif __GLASGOW_HASKELL__ == 710+depends df = map (GHC.resolveInstalledPackageId df) . GHC.depends+#endif++getPackageDetails :: GHC.DynFlags -> InstalledUnitId -> GHC.PackageConfig+#if __GLASGOW_HASKELL__ == 802+getPackageDetails = GHC.getInstalledPackageDetails+#else+getPackageDetails = GHC.getPackageDetails+#endif++patSynType :: GHC.PatSyn -> GHC.Type+patSynType p = GHC.patSynInstResTy p (GHC.patSynArgs p)++#if __GLASGOW_HASKELL__ >= 800+cleanupHandler :: GHC.DynFlags -> m a -> m a+cleanupHandler _ = id+#elif __GLASGOW_HASKELL__ == 710+cleanupHandler :: (ExceptionMonad m) => GHC.DynFlags -> m a -> m a+cleanupHandler = GHC.defaultCleanupHandler+#endif++renderStyle :: Pretty.Mode -> Int -> Pretty.Doc -> String+#if __GLASGOW_HASKELL__ >= 800+renderStyle m cols = Pretty.renderStyle (Pretty.Style m cols 1.5)+#elif __GLASGOW_HASKELL__ == 710+renderStyle = Pretty.showDoc+#endif++type LogAction = GHC.DynFlags -> GHC.Severity -> GHC.SrcSpan -> ErrUtils.MsgDoc -> IO ()++setLogAction :: LogAction -> GHC.DynFlags -> GHC.DynFlags+setLogAction act fs = fs { GHC.log_action = act' } where+ act' :: GHC.LogAction+#if __GLASGOW_HASKELL__ >= 800+ act' df _ sev src _ msg = act df sev src msg+#elif __GLASGOW_HASKELL__ == 710+ act' df sev src _ msg = act df sev src msg+#endif++#if __GLASGOW_HASKELL__ == 710+instance (Monad m, GHC.HasDynFlags m) => GHC.HasDynFlags (ReaderT r m) where+ getDynFlags = lift GHC.getDynFlags+#endif++flags :: [String]+#if __GLASGOW_HASKELL__ >= 800+flags = concat [+ [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags],+ ["warn-" ++ option | (GHC.FlagSpec option _ _ _) <- GHC.wWarningFlags],+ [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]]+#elif __GLASGOW_HASKELL__ >= 710+flags = concat [+ [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags],+ [option | (GHC.FlagSpec option _ _ _) <- GHC.fWarningFlags],+ [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]]+#elif __GLASGOW_HASKELL__ >= 704+flags = concat [+ [option | (option, _, _) <- GHC.fFlags],+ [option | (option, _, _) <- GHC.fWarningFlags],+ [option | (option, _, _) <- GHC.fLangFlags]]+#endif++languages :: [String]+languages = GHC.supportedLanguagesAndExtensions++unqualStyle :: GHC.DynFlags -> PprStyle+#if __GLASGOW_HASKELL__ == 802+unqualStyle df = mkUserStyle df neverQualify AllTheWay+#else+unqualStyle _ = mkUserStyle neverQualify AllTheWay+#endif++#if __GLASGOW_HASKELL__ == 802+exposedModuleName :: (a, Maybe b) -> a+exposedModuleName = fst+#else+exposedModuleName :: GHC.ExposedModule unit mname -> mname+exposedModuleName = GHC.exposedName+#endif
src/HsDev/Tools/Ghc/MGhc.hs view
@@ -1,204 +1,204 @@-{-# LANGUAGE UnicodeSyntax, GeneralizedNewtypeDeriving, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Tools.Ghc.MGhc ( - SessionState(..), sessionActive, sessionMap, - MGhcT(..), runMGhcT, liftGhc, - hasSession, findSession, findSessionBy, saveSession, - initSession, newSession, - switchSession, switchSession_, - deleteSession, restoreSession, usingSession - ) where - -import Control.Lens -import Control.Monad.Morph -import Control.Monad.Catch -import Control.Monad.Reader -import Control.Monad.State -import Data.Default -import Data.IORef -import Data.List (find) -import Data.Map (Map) -import qualified Data.Map as M -import Data.Maybe (fromMaybe, isJust) -import System.Log.Simple.Monad (MonadLog(..)) - -import DynFlags -import Exception hiding (catch, mask, uninterruptibleMask, bracket) -import GHC -import GhcMonad -import HscTypes -import Outputable -import SysTools - -data SessionState s = SessionState { - _sessionActive :: Maybe s, - _sessionMap :: Map s HscEnv } - -instance Default (SessionState s) where - def = SessionState Nothing M.empty - -sessionActive :: Lens' (SessionState s) (Maybe s) -sessionActive = lens g s where - g = _sessionActive - s st nm = st { _sessionActive = nm } - -sessionMap :: Lens' (SessionState s) (Map s HscEnv) -sessionMap = lens g s where - g = _sessionMap - s st m = st { _sessionMap = m } - -instance ExceptionMonad m => ExceptionMonad (StateT s m) where - gcatch act onError = StateT $ \st -> gcatch (runStateT act st) (\e -> runStateT (onError e) st) - gmask f = StateT $ gmask . f' where - f' st' act' = runStateT (f act) st' where - act st = StateT $ act' . runStateT st - -instance ExceptionMonad m => ExceptionMonad (ReaderT r m) where - gcatch act onError = ReaderT $ \v -> gcatch (runReaderT act v) (\e -> runReaderT (onError e) v) - gmask f = ReaderT $ gmask . f' where - f' v' act' = runReaderT (f act) v' where - act v = ReaderT $ act' . runReaderT v - --- | Multi-session ghc monad -newtype MGhcT s m a = MGhcT { unMGhcT :: GhcT (ReaderT (Maybe FilePath) (StateT (SessionState s) m)) a } - deriving (Functor, Applicative, Monad, MonadIO, ExceptionMonad, HasDynFlags, GhcMonad, MonadState (SessionState s), MonadReader (Maybe FilePath), MonadThrow, MonadCatch, MonadMask, MonadLog) - -instance MonadTrans GhcT where - lift = liftGhcT - -instance MFunctor GhcT where - hoist fn = GhcT . (fn .) . unGhcT - -instance MonadState st m => MonadState st (GhcT m) where - get = lift get - put = lift . put - state = lift . state - -instance MonadReader r m => MonadReader r (GhcT m) where - ask = lift ask - local f act = GhcT $ local f . unGhcT act - -instance MonadThrow m => MonadThrow (GhcT m) where - throwM = lift . throwM - -instance MonadCatch m => MonadCatch (GhcT m) where - catch act onError = GhcT $ \sess -> catch (unGhcT act sess) (flip unGhcT sess . onError) - -instance MonadMask m => MonadMask (GhcT m) where - mask f = GhcT $ \s -> mask $ \g -> unGhcT (f $ q g) s where - q g' act = GhcT $ g' . unGhcT act - uninterruptibleMask f = GhcT $ \s -> uninterruptibleMask $ \g -> unGhcT (f $ q g) s where - q g' act = GhcT $ g' . unGhcT act - --- | Run multi-session ghc -runMGhcT :: (MonadIO m, ExceptionMonad m, Ord s) => Maybe FilePath -> MGhcT s m a -> m a -runMGhcT lib act = do - ref <- liftIO $ newIORef (panic "empty session") - let - session = Session ref - flip evalStateT def $ flip runReaderT lib $ flip unGhcT session $ unMGhcT $ act `gfinally` cleanup - where - cleanup :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m () - cleanup = do - void saveSession - sessions <- gets (M.elems . view sessionMap) - liftIO $ mapM_ cleanupSession sessions - modify (set sessionMap M.empty) - --- | Lift `Ghc` monad onto `MGhc` -liftGhc :: MonadIO m => Ghc a -> MGhcT s m a -liftGhc (Ghc act) = MGhcT $ GhcT $ liftIO . act - --- | Does session exist -hasSession :: (MonadIO m, Ord s) => s -> MGhcT s m Bool -hasSession key = do - msess <- gets (preview (sessionMap . ix key)) - return $ isJust msess - --- | Find session -findSession :: (MonadIO m, Ord s) => s -> MGhcT s m (Maybe s) -findSession key = do - mkeys <- gets (M.keys . view sessionMap) - return $ find (== key) mkeys - --- | Find session by -findSessionBy :: MonadIO m => (s -> Bool) -> MGhcT s m [s] -findSessionBy p = do - mkeys <- gets (M.keys . view sessionMap) - return $ filter p mkeys - --- | Save current session -saveSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m (Maybe s) -saveSession = do - key <- gets (view sessionActive) - case key of - Just key' -> do - sess <- getSession - modify (set (sessionMap . at key') (Just sess)) - Nothing -> return () - return key - --- | Initialize new session -initSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m () -initSession = do - lib <- ask - initGhcMonad lib - void saveSession - -activateSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m (Maybe HscEnv) -activateSession key = do - void saveSession - modify (set sessionActive $ Just key) - gets (view (sessionMap . at key)) - --- | Create new named session, deleting existing session -newSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m () -newSession key = do - msess <- activateSession key - maybe (return ()) (liftIO . cleanupSession) msess - initSession - --- | Switch to session, creating if not exist, returns True if session was created -switchSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m Bool -switchSession key = do - msess <- activateSession key - case msess of - Nothing -> initSession >> return True - Just sess -> setSession sess >> return False - --- | Switch to session, creating if not exist and initializing with passed function -switchSession_ :: (MonadIO m, ExceptionMonad m, Ord s) => s -> Maybe (MGhcT s m ()) -> MGhcT s m () -switchSession_ key f = do - new <- switchSession key - when new $ fromMaybe (return ()) f - --- | Delete existing session -deleteSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m () -deleteSession key = do - cur <- saveSession - when (cur == Just key) $ - modify (set sessionActive Nothing) - msess <- gets (view (sessionMap . at key)) - modify (set (sessionMap . at key) Nothing) - case msess of - Nothing -> return () - Just sess -> liftIO $ cleanupSession sess - --- | Save and restore session -restoreSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => MGhcT s m a -> MGhcT s m a -restoreSession act = bracket saveSession (maybe (return ()) (void . switchSession)) $ const act - --- | Run action using session, restoring session back -usingSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => s -> MGhcT s m a -> MGhcT s m a -usingSession key act = restoreSession $ do - void $ switchSession key - act - --- | Cleanup session -cleanupSession :: HscEnv -> IO () -cleanupSession env = do - cleanTempFiles df - cleanTempDirs df - where - df = hsc_dflags env +{-# LANGUAGE UnicodeSyntax, GeneralizedNewtypeDeriving, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Tools.Ghc.MGhc (+ SessionState(..), sessionActive, sessionMap,+ MGhcT(..), runMGhcT, liftGhc,+ hasSession, findSession, findSessionBy, saveSession,+ initSession, newSession,+ switchSession, switchSession_,+ deleteSession, restoreSession, usingSession+ ) where++import Control.Lens+import Control.Monad.Morph+import Control.Monad.Catch+import Control.Monad.Reader+import Control.Monad.State+import Data.Default+import Data.IORef+import Data.List (find)+import Data.Map (Map)+import qualified Data.Map as M+import Data.Maybe (fromMaybe, isJust)+import System.Log.Simple.Monad (MonadLog(..))++import DynFlags+import Exception hiding (catch, mask, uninterruptibleMask, bracket)+import GHC+import GhcMonad+import HscTypes+import Outputable+import SysTools++data SessionState s = SessionState {+ _sessionActive :: Maybe s,+ _sessionMap :: Map s HscEnv }++instance Default (SessionState s) where+ def = SessionState Nothing M.empty++sessionActive :: Lens' (SessionState s) (Maybe s)+sessionActive = lens g s where+ g = _sessionActive+ s st nm = st { _sessionActive = nm }++sessionMap :: Lens' (SessionState s) (Map s HscEnv)+sessionMap = lens g s where+ g = _sessionMap+ s st m = st { _sessionMap = m }++instance ExceptionMonad m => ExceptionMonad (StateT s m) where+ gcatch act onError = StateT $ \st -> gcatch (runStateT act st) (\e -> runStateT (onError e) st)+ gmask f = StateT $ gmask . f' where+ f' st' act' = runStateT (f act) st' where+ act st = StateT $ act' . runStateT st++instance ExceptionMonad m => ExceptionMonad (ReaderT r m) where+ gcatch act onError = ReaderT $ \v -> gcatch (runReaderT act v) (\e -> runReaderT (onError e) v)+ gmask f = ReaderT $ gmask . f' where+ f' v' act' = runReaderT (f act) v' where+ act v = ReaderT $ act' . runReaderT v++-- | Multi-session ghc monad+newtype MGhcT s m a = MGhcT { unMGhcT :: GhcT (ReaderT (Maybe FilePath) (StateT (SessionState s) m)) a }+ deriving (Functor, Applicative, Monad, MonadIO, ExceptionMonad, HasDynFlags, GhcMonad, MonadState (SessionState s), MonadReader (Maybe FilePath), MonadThrow, MonadCatch, MonadMask, MonadLog)++instance MonadTrans GhcT where+ lift = liftGhcT++instance MFunctor GhcT where+ hoist fn = GhcT . (fn .) . unGhcT++instance MonadState st m => MonadState st (GhcT m) where+ get = lift get+ put = lift . put+ state = lift . state++instance MonadReader r m => MonadReader r (GhcT m) where+ ask = lift ask+ local f act = GhcT $ local f . unGhcT act++instance MonadThrow m => MonadThrow (GhcT m) where+ throwM = lift . throwM++instance MonadCatch m => MonadCatch (GhcT m) where+ catch act onError = GhcT $ \sess -> catch (unGhcT act sess) (flip unGhcT sess . onError)++instance MonadMask m => MonadMask (GhcT m) where+ mask f = GhcT $ \s -> mask $ \g -> unGhcT (f $ q g) s where+ q g' act = GhcT $ g' . unGhcT act+ uninterruptibleMask f = GhcT $ \s -> uninterruptibleMask $ \g -> unGhcT (f $ q g) s where+ q g' act = GhcT $ g' . unGhcT act++-- | Run multi-session ghc+runMGhcT :: (MonadIO m, ExceptionMonad m, Ord s) => Maybe FilePath -> MGhcT s m a -> m a+runMGhcT lib act = do+ ref <- liftIO $ newIORef (panic "empty session")+ let+ session = Session ref+ flip evalStateT def $ flip runReaderT lib $ flip unGhcT session $ unMGhcT $ act `gfinally` cleanup+ where+ cleanup :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m ()+ cleanup = do+ void saveSession+ sessions <- gets (M.elems . view sessionMap)+ liftIO $ mapM_ cleanupSession sessions+ modify (set sessionMap M.empty)++-- | Lift `Ghc` monad onto `MGhc`+liftGhc :: MonadIO m => Ghc a -> MGhcT s m a+liftGhc (Ghc act) = MGhcT $ GhcT $ liftIO . act++-- | Does session exist+hasSession :: (MonadIO m, Ord s) => s -> MGhcT s m Bool+hasSession key = do+ msess <- gets (preview (sessionMap . ix key))+ return $ isJust msess++-- | Find session+findSession :: (MonadIO m, Ord s) => s -> MGhcT s m (Maybe s)+findSession key = do+ mkeys <- gets (M.keys . view sessionMap)+ return $ find (== key) mkeys++-- | Find session by+findSessionBy :: MonadIO m => (s -> Bool) -> MGhcT s m [s]+findSessionBy p = do+ mkeys <- gets (M.keys . view sessionMap)+ return $ filter p mkeys++-- | Save current session+saveSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m (Maybe s)+saveSession = do+ key <- gets (view sessionActive)+ case key of+ Just key' -> do+ sess <- getSession+ modify (set (sessionMap . at key') (Just sess))+ Nothing -> return ()+ return key++-- | Initialize new session+initSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m ()+initSession = do+ lib <- ask+ initGhcMonad lib+ void saveSession++activateSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m (Maybe HscEnv)+activateSession key = do+ void saveSession+ modify (set sessionActive $ Just key)+ gets (view (sessionMap . at key))++-- | Create new named session, deleting existing session+newSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m ()+newSession key = do+ msess <- activateSession key+ maybe (return ()) (liftIO . cleanupSession) msess+ initSession++-- | Switch to session, creating if not exist, returns True if session was created+switchSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m Bool+switchSession key = do+ msess <- activateSession key+ case msess of+ Nothing -> initSession >> return True+ Just sess -> setSession sess >> return False++-- | Switch to session, creating if not exist and initializing with passed function+switchSession_ :: (MonadIO m, ExceptionMonad m, Ord s) => s -> Maybe (MGhcT s m ()) -> MGhcT s m ()+switchSession_ key f = do+ new <- switchSession key+ when new $ fromMaybe (return ()) f++-- | Delete existing session+deleteSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m ()+deleteSession key = do+ cur <- saveSession+ when (cur == Just key) $+ modify (set sessionActive Nothing)+ msess <- gets (view (sessionMap . at key))+ modify (set (sessionMap . at key) Nothing)+ case msess of+ Nothing -> return ()+ Just sess -> liftIO $ cleanupSession sess++-- | Save and restore session+restoreSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => MGhcT s m a -> MGhcT s m a+restoreSession act = bracket saveSession (maybe (return ()) (void . switchSession)) $ const act++-- | Run action using session, restoring session back+usingSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => s -> MGhcT s m a -> MGhcT s m a+usingSession key act = restoreSession $ do+ void $ switchSession key+ act++-- | Cleanup session+cleanupSession :: HscEnv -> IO ()+cleanupSession env = do+ cleanTempFiles df+ cleanTempDirs df+ where+ df = hsc_dflags env
src/HsDev/Tools/Ghc/Session.hs view
@@ -1,49 +1,49 @@-{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Tools.Ghc.Session ( - ghcSession, ghciSession, haddockSession, targetSession, interpretModule, - - module HsDev.Tools.Ghc.Worker - ) where - -import Control.Lens -import Data.Text (unpack) -import System.FilePath - -import Control.Concurrent.Worker -import HsDev.Symbols.Types -import HsDev.Sandbox (getModuleOpts) -import HsDev.Tools.Ghc.Worker - -import qualified GHC - --- | Get ghc session -ghcSession :: [String] -> GhcM () -ghcSession = workerSession . SessionGhc - --- | Get ghci session -ghciSession :: GhcM () -ghciSession = workerSession SessionGhci - --- | Get haddock session with flags -haddockSession :: [String] -> GhcM () -haddockSession opts = ghcSession ("-haddock" : opts) - --- | Session for module -targetSession :: [String] -> Module -> GhcM () -targetSession opts m = do - opts' <- getModuleOpts opts m - ghcSession ("-Wall" : opts') - --- | Interpret file -interpretModule :: Module -> Maybe String -> GhcM () -interpretModule m mcts = do - targetSession [] m - let - f = preview (moduleLocation . moduleFile) m - case f of - Nothing -> return () - Just f' -> withCurrentDirectory (takeDirectory f') $ do - t <- makeTarget (takeFileName f') mcts - loadTargets [t] - GHC.setContext [GHC.IIModule $ GHC.mkModuleName $ unpack $ view moduleName m] +{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Tools.Ghc.Session (+ ghcSession, ghciSession, haddockSession, targetSession, interpretModule,++ module HsDev.Tools.Ghc.Worker+ ) where++import Control.Lens+import Data.Text (unpack)+import System.FilePath++import Control.Concurrent.Worker+import HsDev.Symbols.Types+import HsDev.Sandbox (getModuleOpts)+import HsDev.Tools.Ghc.Worker++import qualified GHC++-- | Get ghc session+ghcSession :: [String] -> GhcM ()+ghcSession = workerSession . SessionGhc++-- | Get ghci session+ghciSession :: GhcM ()+ghciSession = workerSession SessionGhci++-- | Get haddock session with flags+haddockSession :: [String] -> GhcM ()+haddockSession opts = ghcSession ("-haddock" : opts)++-- | Session for module+targetSession :: [String] -> Module -> GhcM ()+targetSession opts m = do+ opts' <- getModuleOpts opts m+ ghcSession ("-Wall" : opts')++-- | Interpret file+interpretModule :: Module -> Maybe String -> GhcM ()+interpretModule m mcts = do+ targetSession [] m+ let+ f = preview (moduleLocation . moduleFile) m+ case f of+ Nothing -> return ()+ Just f' -> withCurrentDirectory (takeDirectory f') $ do+ t <- makeTarget (takeFileName f') mcts+ loadTargets [t]+ GHC.setContext [GHC.IIModule $ GHC.mkModuleName $ unpack $ view moduleName m]
src/HsDev/Tools/Ghc/Types.hs view
@@ -1,145 +1,143 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, RankNTypes, TemplateHaskell, OverloadedStrings #-} - -module HsDev.Tools.Ghc.Types ( - TypedExpr(..), typedExpr, typedType, - moduleTypes, fileTypes, - setModuleTypes, inferTypes - ) where - -import Control.DeepSeq -import Control.Lens (over, view, set, each, preview, makeLenses, _Just) -import Control.Monad -import Control.Monad.IO.Class -import Data.Aeson -import Data.Generics -import Data.List (find) -import Data.Maybe -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)) -import GhcPlugins (mkFunTys) -import CoreUtils -import Desugar (deSugarExpr) -import TcHsSyn (hsPatType) -import Outputable -import PprTyThing -import qualified Pretty - -import System.Directory.Paths (canonicalize) -import HsDev.Error -import HsDev.Symbols -import HsDev.Tools.Ghc.Worker as Ghc -import HsDev.Tools.Ghc.Compat -import HsDev.Tools.Types -import HsDev.Util - -class HasType a where - getType :: GhcMonad m => TypecheckedModule -> a -> m (Maybe (SrcSpan, Type)) - -instance HasType (LHsExpr Id) where - getType _ e = do - env <- getSession - mbe <- liftIO $ liftM snd $ deSugarExpr env e - return $ do - ex <- mbe - return (getLoc e, exprType ex) - -instance HasType (LHsBind Id) where - getType _ (L spn FunBind { fun_matches = m}) = return $ Just (spn, typ) where - typ = mkFunTys (mg_arg_tys m) (mg_res_ty m) - getType _ _ = return Nothing - -instance HasType (LPat Id) where - getType _ (L spn pat) = return $ Just (spn, hsPatType pat) - -locatedTypes :: Typeable a => TypecheckedSource -> [Located a] -locatedTypes = types' p where - types' :: Typeable r => (r -> Bool) -> GenericQ [r] - types' p' = everythingStaged TypeChecker (++) [] ([] `mkQ` (\x -> [x | p' x])) - p (L spn _) = isGoodSrcSpan spn - -moduleTypes :: GhcMonad m => FilePath -> m [(SrcSpan, Type)] -moduleTypes fpath = do - fpath' <- liftIO $ canonicalize fpath - mg <- getModuleGraph - [m] <- liftIO $ flip filterM mg $ \m -> do - mfile <- traverse (liftIO . canonicalize) $ ml_hs_file (ms_location m) - return (Just fpath' == mfile) - p <- parseModule m - tm <- typecheckModule p - let - ts = tm_typechecked_source tm - liftM (catMaybes . concat) $ sequence [ - mapM (getType tm) (locatedTypes ts :: [LHsBind Id]), - mapM (getType tm) (locatedTypes ts :: [LHsExpr Id]), - mapM (getType tm) (locatedTypes ts :: [LPat Id])] - -data TypedExpr = TypedExpr { - _typedExpr :: String, - _typedType :: String } - deriving (Eq, Ord, Read, Show) - -makeLenses ''TypedExpr - -instance NFData TypedExpr where - rnf (TypedExpr e t) = rnf e `seq` rnf t - -instance ToJSON TypedExpr where - toJSON (TypedExpr e t) = object [ - "expr" .= e, - "type" .= t] - -instance FromJSON TypedExpr where - parseJSON = withObject "typed-expr" $ \v -> TypedExpr <$> - v .:: "expr" <*> - v .:: "type" - --- | Get all types in module -fileTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note TypedExpr] -fileTypes opts m msrc = scope "types" $ case view moduleLocation m of - FileModule file proj -> do - file' <- liftIO $ canonicalize file - cts <- maybe (liftIO $ readFileUtf8 file') return msrc - let - dir = fromMaybe - (sourceModuleRoot (view moduleName m) file') $ - preview (_Just . projectPath) proj - dirExist <- liftIO $ doesDirectoryExist dir - withFlags $ (if dirExist then Ghc.withCurrentDirectory dir else id) $ do - addCmdOpts opts - target <- makeTarget (makeRelative dir file') msrc - loadTargets [target] - ts <- moduleTypes file' - df <- getSessionDynFlags - return $ map (setExpr cts . recalcTabs cts 8 . uncurry (toNote df)) ts - _ -> hsdevError $ ModuleNotSource (view moduleLocation m) - where - toNote :: DynFlags -> SrcSpan -> Type -> Note String - toNote df spn tp = Note { - _noteSource = noLocation, - _noteRegion = spanRegion spn, - _noteLevel = Nothing, - _note = showType df tp } - setExpr :: String -> Note String -> Note TypedExpr - setExpr cts n = over note (TypedExpr (regionStr (view noteRegion n) cts)) n - showType :: DynFlags -> Type -> String - showType df = renderStyle Pretty.OneLineMode 80 . withPprStyleDoc df unqualStyle . pprTypeForUser - unqualStyle :: PprStyle - unqualStyle = mkUserStyle neverQualify AllTheWay - --- | Set types to module -setModuleTypes :: [Note TypedExpr] -> Module -> Module -setModuleTypes ts = over (moduleDeclarations . each) setType where - setType :: Declaration -> Declaration - setType d = fromMaybe d $ do - pos <- view declarationPosition d - tnote <- find ((== pos) . view (noteRegion . regionFrom)) ts - return $ set (declaration . functionType) (Just $ fromString $ view (note . typedType) tnote) d - --- | Infer types in module -inferTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m Module -inferTypes opts m msrc = scope "infer" $ liftM (`setModuleTypes` m) $ fileTypes opts m msrc +{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, RankNTypes, TemplateHaskell, OverloadedStrings #-}++module HsDev.Tools.Ghc.Types (+ TypedExpr(..), typedExpr, typedType,+ moduleTypes, fileTypes,+ setModuleTypes, inferTypes+ ) where++import Control.DeepSeq+import Control.Lens (over, view, set, each, preview, makeLenses, _Just)+import Control.Monad+import Control.Monad.IO.Class+import Data.Aeson+import Data.Generics+import Data.List (find)+import Data.Maybe+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))+import GhcPlugins (mkFunTys)+import CoreUtils+import Desugar (deSugarExpr)+import TcHsSyn (hsPatType)+import Outputable+import PprTyThing+import qualified Pretty++import System.Directory.Paths (canonicalize)+import HsDev.Error+import HsDev.Symbols+import HsDev.Tools.Ghc.Worker as Ghc+import HsDev.Tools.Ghc.Compat+import HsDev.Tools.Types+import HsDev.Util++class HasType a where+ getType :: GhcMonad m => TypecheckedModule -> a -> m (Maybe (SrcSpan, Type))++instance HasType (LHsExpr Id) where+ getType _ e = do+ env <- getSession+ mbe <- liftIO $ liftM snd $ deSugarExpr env e+ return $ do+ ex <- mbe+ return (getLoc e, exprType ex)++instance HasType (LHsBind Id) where+ getType _ (L spn FunBind { fun_matches = m}) = return $ Just (spn, typ) where+ typ = mkFunTys (mg_arg_tys m) (mg_res_ty m)+ getType _ _ = return Nothing++instance HasType (LPat Id) where+ getType _ (L spn pat) = return $ Just (spn, hsPatType pat)++locatedTypes :: Typeable a => TypecheckedSource -> [Located a]+locatedTypes = types' p where+ types' :: Typeable r => (r -> Bool) -> GenericQ [r]+ types' p' = everythingStaged TypeChecker (++) [] ([] `mkQ` (\x -> [x | p' x]))+ p (L spn _) = isGoodSrcSpan spn++moduleTypes :: GhcMonad m => FilePath -> m [(SrcSpan, Type)]+moduleTypes fpath = do+ fpath' <- liftIO $ canonicalize fpath+ mg <- getModuleGraph+ [m] <- liftIO $ flip filterM mg $ \m -> do+ mfile <- traverse (liftIO . canonicalize) $ ml_hs_file (ms_location m)+ return (Just fpath' == mfile)+ p <- parseModule m+ tm <- typecheckModule p+ let+ ts = tm_typechecked_source tm+ liftM (catMaybes . concat) $ sequence [+ mapM (getType tm) (locatedTypes ts :: [LHsBind Id]),+ mapM (getType tm) (locatedTypes ts :: [LHsExpr Id]),+ mapM (getType tm) (locatedTypes ts :: [LPat Id])]++data TypedExpr = TypedExpr {+ _typedExpr :: String,+ _typedType :: String }+ deriving (Eq, Ord, Read, Show)++makeLenses ''TypedExpr++instance NFData TypedExpr where+ rnf (TypedExpr e t) = rnf e `seq` rnf t++instance ToJSON TypedExpr where+ toJSON (TypedExpr e t) = object [+ "expr" .= e,+ "type" .= t]++instance FromJSON TypedExpr where+ parseJSON = withObject "typed-expr" $ \v -> TypedExpr <$>+ v .:: "expr" <*>+ v .:: "type"++-- | Get all types in module+fileTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note TypedExpr]+fileTypes opts m msrc = scope "types" $ case view moduleLocation m of+ FileModule file proj -> do+ file' <- liftIO $ canonicalize file+ cts <- maybe (liftIO $ readFileUtf8 file') return msrc+ let+ dir = fromMaybe+ (sourceModuleRoot (view moduleName m) file') $+ preview (_Just . projectPath) proj+ dirExist <- liftIO $ doesDirectoryExist dir+ withFlags $ (if dirExist then Ghc.withCurrentDirectory dir else id) $ do+ addCmdOpts opts+ target <- makeTarget (makeRelative dir file') msrc+ loadTargets [target]+ ts <- moduleTypes file'+ df <- getSessionDynFlags+ return $ map (setExpr cts . recalcTabs cts 8 . uncurry (toNote df)) ts+ _ -> hsdevError $ ModuleNotSource (view moduleLocation m)+ where+ toNote :: DynFlags -> SrcSpan -> Type -> Note String+ toNote df spn tp = Note {+ _noteSource = noLocation,+ _noteRegion = spanRegion spn,+ _noteLevel = Nothing,+ _note = showType df tp }+ setExpr :: String -> Note String -> Note TypedExpr+ setExpr cts n = over note (TypedExpr (regionStr (view noteRegion n) cts)) n+ showType :: DynFlags -> Type -> String+ showType df = renderStyle Pretty.OneLineMode 80 . withPprStyleDoc df (unqualStyle df) . pprTypeForUser++-- | Set types to module+setModuleTypes :: [Note TypedExpr] -> Module -> Module+setModuleTypes ts = over (moduleDeclarations . each) setType where+ setType :: Declaration -> Declaration+ setType d = fromMaybe d $ do+ pos <- view declarationPosition d+ tnote <- find ((== pos) . view (noteRegion . regionFrom)) ts+ return $ set (declaration . functionType) (Just $ fromString $ view (note . typedType) tnote) d++-- | Infer types in module+inferTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m Module+inferTypes opts m msrc = scope "infer" $ liftM (`setModuleTypes` m) $ fileTypes opts m msrc
src/HsDev/Tools/Ghc/Worker.hs view
@@ -1,248 +1,249 @@-{-# LANGUAGE PatternGuards, OverloadedStrings, FlexibleContexts #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Tools.Ghc.Worker ( - -- * Workers - SessionTarget(..), - GhcM, GhcWorker, MGhcT(..), runGhcM, - ghcWorker, - workerSession, - - -- * Initializers and actions - ghcRun, - withFlags, modifyFlags, addCmdOpts, setCmdOpts, - importModules, preludeModules, - evaluate, - clearTargets, makeTarget, loadTargets, - -- * Utils - listPackages, spanRegion, - withCurrentDirectory, - logToChan, logToNull, - - Ghc, - LogT(..), - - module HsDev.Tools.Ghc.MGhc, - module Control.Concurrent.Worker - ) where - -import Control.Monad -import Control.Monad.Except -import Control.Monad.Reader -import Control.Monad.Catch -import Data.Dynamic -import Data.List (intercalate) -import Data.Maybe -import Data.Time.Clock (getCurrentTime) -import Data.Version (showVersion) -import System.Directory (getCurrentDirectory, setCurrentDirectory) -import qualified System.Log.Simple as Log -import System.Log.Simple.Monad (MonadLog(..), LogT(..), withLog) -import Text.Read (readMaybe) -import Text.Format hiding (withFlags) - -import Exception (ExceptionMonad(..)) -import GHC hiding (Warning, Module, moduleName, pkgDatabase) -import GHC.Paths -import Outputable -import FastString (unpackFS) -import Packages -import StringBuffer - -import Control.Concurrent.FiniteChan -import Control.Concurrent.Worker -import System.Directory.Paths -import HsDev.Symbols.Location (Position(..), Region(..), region, ModulePackage, ModuleLocation(..)) -import HsDev.Tools.Types -import HsDev.Tools.Ghc.Compat -import HsDev.Tools.Ghc.MGhc - -data SessionTarget = - SessionGhci | - SessionGhc [String] - -instance Show SessionTarget where - show SessionGhci = "ghci" - show (SessionGhc opts) = "ghc " ++ intercalate ", " opts - -instance Formattable SessionTarget - -instance Eq SessionTarget where - SessionGhci == SessionGhci = True - SessionGhc lopts == SessionGhc ropts = lopts == ropts - _ == _ = False - -instance Ord SessionTarget where - compare l r = compare (isGhci l) (isGhci r) where - isGhci SessionGhci = True - isGhci _ = False - -type GhcM a = MGhcT SessionTarget (LogT IO) a - -type GhcWorker = Worker (MGhcT SessionTarget (LogT IO)) - -instance (Monad m, GhcMonad m) => GhcMonad (ReaderT r m) where - getSession = lift getSession - setSession = lift . setSession - -instance ExceptionMonad m => ExceptionMonad (LogT m) where - gcatch act onError = LogT $ gcatch (runLogT act) (runLogT . onError) - gmask f = LogT $ gmask f' where - f' g' = runLogT $ f (LogT . g' . runLogT) - -instance MonadThrow Ghc where - throwM = liftIO . throwM - -runGhcM :: MonadLog m => Maybe FilePath -> GhcM a -> m a -runGhcM dir act = do - l <- Log.askLog - liftIO $ withLog l $ runMGhcT dir act - --- | Multi-session ghc worker -ghcWorker :: MonadLog m => m GhcWorker -ghcWorker = do - l <- Log.askLog - liftIO $ startWorker (withLog l . runGhcM (Just libdir)) id (Log.scope "ghc") - --- | Create session with options -workerSession :: SessionTarget -> GhcM () -workerSession SessionGhci = do - Log.sendLog Log.Trace $ "session: {}" ~~ SessionGhci - switchSession_ SessionGhci $ Just $ ghcRun [] (importModules preludeModules) -workerSession s@(SessionGhc opts) = do - ms <- findSessionBy isGhcSession - forM_ ms $ \s'@(SessionGhc opts') -> when (opts /= opts') $ do - Log.sendLog Log.Trace $ "killing session: {}" ~~ s' - deleteSession s' - Log.sendLog Log.Trace $ "session: {}" ~~ s - switchSession_ s $ Just $ ghcRun opts (return ()) - where - isGhcSession (SessionGhc _) = True - isGhcSession _ = False - --- | Run ghc -ghcRun :: GhcMonad m => [String] -> m a -> m a -ghcRun opts f = do - fs <- getSessionDynFlags - cleanupHandler fs $ do - (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) - let fs'' = fs' { - ghcMode = CompManager, - ghcLink = LinkInMemory, - hscTarget = HscInterpreted } - -- ghcLink = NoLink, - -- hscTarget = HscNothing } - void $ setSessionDynFlags fs'' - modifyFlags $ setLogAction logToNull - f - --- | Alter @DynFlags@ temporary -withFlags :: GhcMonad m => m a -> m a -withFlags = gbracket getSessionDynFlags (\fs -> setSessionDynFlags fs >> return ()) . const - --- | Update @DynFlags@ -modifyFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m () -modifyFlags f = do - fs <- getSessionDynFlags - let - fs' = f fs - _ <- setSessionDynFlags fs' - _ <- liftIO $ initPackages fs' - return () - --- | Add options without reinit session -addCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m () -addCmdOpts opts = do - Log.sendLog Log.Trace $ "setting ghc options: {}" ~~ unwords opts - fs <- getSessionDynFlags - (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) - let fs'' = fs' { - ghcMode = CompManager, - ghcLink = LinkInMemory, - hscTarget = HscInterpreted } - -- ghcLink = NoLink, - -- hscTarget = HscNothing } - void $ setSessionDynFlags fs'' - --- | Set options after session reinit -setCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m () -setCmdOpts opts = do - Log.sendLog Log.Trace $ "restarting ghc session with: {}" ~~ unwords opts - initGhcMonad (Just libdir) - addCmdOpts opts - modifyFlags $ setLogAction logToNull - --- | Import some modules -importModules :: GhcMonad m => [String] -> m () -importModules mods = mapM parseImportDecl ["import " ++ m | m <- mods] >>= setContext . map IIDecl - --- | Default interpreter modules -preludeModules :: [String] -preludeModules = ["Prelude", "Data.List", "Control.Monad", "HsDev.Tools.Ghc.Prelude"] - --- | Evaluate expression -evaluate :: GhcMonad m => String -> m String -evaluate expr = liftM fromDynamic (dynCompileExpr $ "show ({})" ~~ expr) >>= - maybe (fail "evaluate fail") return - --- | Clear loaded targets -clearTargets :: GhcMonad m => m () -clearTargets = loadTargets [] - --- | Make target with its source code optional -makeTarget :: GhcMonad m => String -> Maybe String -> m Target -makeTarget name Nothing = guessTarget name Nothing -makeTarget name (Just cts) = do - t <- guessTarget name Nothing - tm <- liftIO getCurrentTime - return t { targetContents = Just (stringToStringBuffer cts, tm) } - --- | Load all targets -loadTargets :: GhcMonad m => [Target] -> m () -loadTargets ts = setTargets ts >> load LoadAllTargets >> return () - --- | Get list of installed packages -listPackages :: GhcMonad m => m [ModulePackage] -listPackages = liftM (mapMaybe readPackage . fromMaybe [] . pkgDatabase) getSessionDynFlags - -readPackage :: PackageConfig -> Maybe ModulePackage -readPackage pc = readMaybe $ packageNameString pc ++ "-" ++ showVersion (packageVersion pc) - --- | Get region of @SrcSpan@ -spanRegion :: SrcSpan -> Region -spanRegion (RealSrcSpan s) = Position (srcSpanStartLine s) (srcSpanStartCol s) `region` Position (srcSpanEndLine s) (srcSpanEndCol s) -spanRegion _ = Position 0 0 `region` Position 0 0 - --- | Set current directory and restore it after action -withCurrentDirectory :: GhcMonad m => FilePath -> m a -> m a -withCurrentDirectory dir act = gbracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $ - const (liftIO (setCurrentDirectory dir) >> act) - --- | Log ghc warnings and errors as to chan --- You may have to apply recalcTabs on result notes -logToChan :: Chan (Note OutputMessage) -> LogAction -logToChan ch fs sev src msg - | Just sev' <- checkSev sev = do - src' <- canonicalize srcMod - putChan ch $ Note { - _noteSource = src', - _noteRegion = spanRegion src, - _noteLevel = Just sev', - _note = OutputMessage { - _message = showSDoc fs msg, - _messageSuggestion = Nothing } } - | otherwise = return () - where - checkSev SevWarning = Just Warning - checkSev SevError = Just Error - checkSev SevFatal = Just Error - checkSev _ = Nothing - srcMod = case src of - RealSrcSpan s' -> FileModule (unpackFS $ srcSpanFile s') Nothing - _ -> ModuleSource Nothing - --- | Don't log ghc warnings and errors -logToNull :: LogAction -logToNull _ _ _ _ = return () - --- TODO: Load target by @ModuleLocation@, which may cause updating @DynFlags@ +{-# LANGUAGE PatternGuards, OverloadedStrings, FlexibleContexts #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Tools.Ghc.Worker (+ -- * Workers+ SessionTarget(..),+ GhcM, GhcWorker, MGhcT(..), runGhcM,+ ghcWorker,+ workerSession,++ -- * Initializers and actions+ ghcRun,+ withFlags, modifyFlags, addCmdOpts, setCmdOpts,+ importModules, preludeModules,+ evaluate,+ clearTargets, makeTarget, loadTargets,+ -- * Utils+ listPackages, spanRegion,+ withCurrentDirectory,+ logToChan, logToNull,++ Ghc,+ LogT(..),++ module HsDev.Tools.Ghc.MGhc,+ module Control.Concurrent.Worker+ ) where++import Control.Monad+import Control.Monad.Except+import Control.Monad.Reader+import Control.Monad.Catch+import Data.Dynamic+import Data.List (intercalate)+import Data.Maybe+import Data.Time.Clock (getCurrentTime)+import Data.Version (showVersion)+import System.Directory (getCurrentDirectory, setCurrentDirectory)+import qualified System.Log.Simple as Log+import System.Log.Simple.Monad (MonadLog(..), LogT(..), withLog)+import Text.Read (readMaybe)+import Text.Format hiding (withFlags)++import Exception (ExceptionMonad(..))+import GHC hiding (Warning, Module, moduleName, pkgDatabase)+import GHC.Paths+import Outputable+import FastString (unpackFS)+import Packages+import StringBuffer++import Control.Concurrent.FiniteChan+import Control.Concurrent.Worker+import System.Directory.Paths+import HsDev.Symbols.Location (Position(..), Region(..), region, ModulePackage, ModuleLocation(..))+import HsDev.Tools.Types+import HsDev.Tools.Ghc.Compat hiding (setLogAction)+import qualified HsDev.Tools.Ghc.Compat as C (setLogAction)+import HsDev.Tools.Ghc.MGhc++data SessionTarget =+ SessionGhci |+ SessionGhc [String]++instance Show SessionTarget where+ show SessionGhci = "ghci"+ show (SessionGhc opts) = "ghc " ++ intercalate ", " opts++instance Formattable SessionTarget++instance Eq SessionTarget where+ SessionGhci == SessionGhci = True+ SessionGhc lopts == SessionGhc ropts = lopts == ropts+ _ == _ = False++instance Ord SessionTarget where+ compare l r = compare (isGhci l) (isGhci r) where+ isGhci SessionGhci = True+ isGhci _ = False++type GhcM a = MGhcT SessionTarget (LogT IO) a++type GhcWorker = Worker (MGhcT SessionTarget (LogT IO))++instance (Monad m, GhcMonad m) => GhcMonad (ReaderT r m) where+ getSession = lift getSession+ setSession = lift . setSession++instance ExceptionMonad m => ExceptionMonad (LogT m) where+ gcatch act onError = LogT $ gcatch (runLogT act) (runLogT . onError)+ gmask f = LogT $ gmask f' where+ f' g' = runLogT $ f (LogT . g' . runLogT)++instance MonadThrow Ghc where+ throwM = liftIO . throwM++runGhcM :: MonadLog m => Maybe FilePath -> GhcM a -> m a+runGhcM dir act = do+ l <- Log.askLog+ liftIO $ withLog l $ runMGhcT dir act++-- | Multi-session ghc worker+ghcWorker :: MonadLog m => m GhcWorker+ghcWorker = do+ l <- Log.askLog+ liftIO $ startWorker (withLog l . runGhcM (Just libdir)) id (Log.scope "ghc")++-- | Create session with options+workerSession :: SessionTarget -> GhcM ()+workerSession SessionGhci = do+ Log.sendLog Log.Trace $ "session: {}" ~~ SessionGhci+ switchSession_ SessionGhci $ Just $ ghcRun [] (importModules preludeModules)+workerSession s@(SessionGhc opts) = do+ ms <- findSessionBy isGhcSession+ forM_ ms $ \s'@(SessionGhc opts') -> when (opts /= opts') $ do+ Log.sendLog Log.Trace $ "killing session: {}" ~~ s'+ deleteSession s'+ Log.sendLog Log.Trace $ "session: {}" ~~ s+ switchSession_ s $ Just $ ghcRun opts (return ())+ where+ isGhcSession (SessionGhc _) = True+ isGhcSession _ = False++-- | Run ghc+ghcRun :: GhcMonad m => [String] -> m a -> m a+ghcRun opts f = do+ fs <- getSessionDynFlags+ cleanupHandler fs $ do+ (fs', _, _) <- parseDynamicFlags fs (map noLoc opts)+ let fs'' = fs' {+ ghcMode = CompManager,+ ghcLink = LinkInMemory,+ hscTarget = HscInterpreted }+ -- ghcLink = NoLink,+ -- hscTarget = HscNothing }+ void $ setSessionDynFlags fs''+ modifyFlags $ C.setLogAction logToNull+ f++-- | Alter @DynFlags@ temporary+withFlags :: GhcMonad m => m a -> m a+withFlags = gbracket getSessionDynFlags (\fs -> setSessionDynFlags fs >> return ()) . const++-- | Update @DynFlags@+modifyFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m ()+modifyFlags f = do+ fs <- getSessionDynFlags+ let+ fs' = f fs+ _ <- setSessionDynFlags fs'+ _ <- liftIO $ initPackages fs'+ return ()++-- | Add options without reinit session+addCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()+addCmdOpts opts = do+ Log.sendLog Log.Trace $ "setting ghc options: {}" ~~ unwords opts+ fs <- getSessionDynFlags+ (fs', _, _) <- parseDynamicFlags fs (map noLoc opts)+ let fs'' = fs' {+ ghcMode = CompManager,+ ghcLink = LinkInMemory,+ hscTarget = HscInterpreted }+ -- ghcLink = NoLink,+ -- hscTarget = HscNothing }+ void $ setSessionDynFlags fs''++-- | Set options after session reinit+setCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()+setCmdOpts opts = do+ Log.sendLog Log.Trace $ "restarting ghc session with: {}" ~~ unwords opts+ initGhcMonad (Just libdir)+ addCmdOpts opts+ modifyFlags $ C.setLogAction logToNull++-- | Import some modules+importModules :: GhcMonad m => [String] -> m ()+importModules mods = mapM parseImportDecl ["import " ++ m | m <- mods] >>= setContext . map IIDecl++-- | Default interpreter modules+preludeModules :: [String]+preludeModules = ["Prelude", "Data.List", "Control.Monad", "HsDev.Tools.Ghc.Prelude"]++-- | Evaluate expression+evaluate :: GhcMonad m => String -> m String+evaluate expr = liftM fromDynamic (dynCompileExpr $ "show ({})" ~~ expr) >>=+ maybe (fail "evaluate fail") return++-- | Clear loaded targets+clearTargets :: GhcMonad m => m ()+clearTargets = loadTargets []++-- | Make target with its source code optional+makeTarget :: GhcMonad m => String -> Maybe String -> m Target+makeTarget name Nothing = guessTarget name Nothing+makeTarget name (Just cts) = do+ t <- guessTarget name Nothing+ tm <- liftIO getCurrentTime+ return t { targetContents = Just (stringToStringBuffer cts, tm) }++-- | Load all targets+loadTargets :: GhcMonad m => [Target] -> m ()+loadTargets ts = setTargets ts >> load LoadAllTargets >> return ()++-- | Get list of installed packages+listPackages :: GhcMonad m => m [ModulePackage]+listPackages = liftM (mapMaybe readPackage . fromMaybe [] . pkgDatabase) getSessionDynFlags++readPackage :: PackageConfig -> Maybe ModulePackage+readPackage pc = readMaybe $ packageNameString pc ++ "-" ++ showVersion (packageVersion pc)++-- | Get region of @SrcSpan@+spanRegion :: SrcSpan -> Region+spanRegion (RealSrcSpan s) = Position (srcSpanStartLine s) (srcSpanStartCol s) `region` Position (srcSpanEndLine s) (srcSpanEndCol s)+spanRegion _ = Position 0 0 `region` Position 0 0++-- | Set current directory and restore it after action+withCurrentDirectory :: GhcMonad m => FilePath -> m a -> m a+withCurrentDirectory dir act = gbracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $+ const (liftIO (setCurrentDirectory dir) >> act)++-- | Log ghc warnings and errors as to chan+-- You may have to apply recalcTabs on result notes+logToChan :: Chan (Note OutputMessage) -> LogAction+logToChan ch fs sev src msg+ | Just sev' <- checkSev sev = do+ src' <- canonicalize srcMod+ putChan ch $ Note {+ _noteSource = src',+ _noteRegion = spanRegion src,+ _noteLevel = Just sev',+ _note = OutputMessage {+ _message = showSDoc fs msg,+ _messageSuggestion = Nothing } }+ | otherwise = return ()+ where+ checkSev SevWarning = Just Warning+ checkSev SevError = Just Error+ checkSev SevFatal = Just Error+ checkSev _ = Nothing+ srcMod = case src of+ RealSrcSpan s' -> FileModule (unpackFS $ srcSpanFile s') Nothing+ _ -> ModuleSource Nothing++-- | Don't log ghc warnings and errors+logToNull :: LogAction+logToNull _ _ _ _ = return ()++-- TODO: Load target by @ModuleLocation@, which may cause updating @DynFlags@
src/HsDev/Tools/HDocs.hs view
@@ -1,60 +1,60 @@-module HsDev.Tools.HDocs ( - hdocsy, hdocs, hdocsCabal, - setDocs, - loadDocs, - - hdocsProcess, - - module Control.Monad.Except - ) where - -import Control.DeepSeq -import Control.Lens (set, view, over) -import Control.Monad () -import Control.Monad.Except - -import Data.Aeson (decode) -import qualified Data.ByteString.Lazy.Char8 as L (pack) -import Data.Map (Map) -import qualified Data.Map as M -import Data.String (fromString) - -import qualified HDocs.Module as HDocs -import qualified HDocs.Haddock as HDocs (readSource, readSources_) - -import HsDev.Tools.Base -import HsDev.Symbols - --- | Get docs for modules -hdocsy :: [ModuleLocation] -> [String] -> IO [Map String String] -hdocsy mlocs opts = runExceptT (docs' mlocs) >>= return . either (const $ replicate (length mlocs) M.empty) (map $ force . HDocs.formatDocs) where - docs' :: [ModuleLocation] -> ExceptT String IO [HDocs.ModuleDocMap] - docs' ms = liftM (map snd) $ HDocs.readSources_ opts $ map (view moduleFile) ms - --- | Get docs for module -hdocs :: ModuleLocation -> [String] -> IO (Map String String) -hdocs mloc opts = runExceptT (docs' mloc) >>= return . either (const M.empty) (force . HDocs.formatDocs) where - docs' :: ModuleLocation -> ExceptT String IO HDocs.ModuleDocMap - docs' (FileModule fpath _) = liftM snd $ HDocs.readSource opts fpath - docs' (InstalledModule _ _ mname) = HDocs.moduleDocs opts mname - docs' _ = throwError $ "Can't get docs for: " ++ show mloc - --- | Get all docs -hdocsCabal :: PackageDbStack -> [String] -> ExceptT String IO (Map String (Map String String)) -hdocsCabal pdbs opts = liftM (M.map $ force . HDocs.formatDocs) $ HDocs.installedDocs (packageDbStackOpts pdbs ++ opts) - --- | Set docs for module -setDocs :: Map String String -> Module -> Module -setDocs d = over moduleDeclarations (map setDoc) where - setDoc decl' = set declarationDocs (M.lookup (view declarationName decl') d') decl' - d' = M.mapKeys fromString . M.map fromString $ d - --- | Load docs for module -loadDocs :: [String] -> Module -> IO Module -loadDocs opts m = do - d <- hdocs (view moduleLocation m) opts - return $ setDocs d m - -hdocsProcess :: String -> [String] -> IO (Maybe (Map String String)) -hdocsProcess mname opts = liftM (decode . L.pack . last . lines) $ runTool_ "hdocs" opts' where - opts' = mname : concat [["-g", opt] | opt <- opts] +module HsDev.Tools.HDocs (+ hdocsy, hdocs, hdocsCabal,+ setDocs,+ loadDocs,++ hdocsProcess,++ module Control.Monad.Except+ ) where++import Control.DeepSeq+import Control.Lens (set, view, over)+import Control.Monad ()+import Control.Monad.Except++import Data.Aeson (decode)+import qualified Data.ByteString.Lazy.Char8 as L (pack)+import Data.Map (Map)+import qualified Data.Map as M+import Data.String (fromString)++import qualified HDocs.Module as HDocs+import qualified HDocs.Haddock as HDocs (readSource, readSources_)++import HsDev.Tools.Base+import HsDev.Symbols++-- | Get docs for modules+hdocsy :: [ModuleLocation] -> [String] -> IO [Map String String]+hdocsy mlocs opts = runExceptT (docs' mlocs) >>= return . either (const $ replicate (length mlocs) M.empty) (map $ force . HDocs.formatDocs) where+ docs' :: [ModuleLocation] -> ExceptT String IO [HDocs.ModuleDocMap]+ docs' ms = liftM (map snd) $ HDocs.readSources_ opts $ map (view moduleFile) ms++-- | Get docs for module+hdocs :: ModuleLocation -> [String] -> IO (Map String String)+hdocs mloc opts = runExceptT (docs' mloc) >>= return . either (const M.empty) (force . HDocs.formatDocs) where+ docs' :: ModuleLocation -> ExceptT String IO HDocs.ModuleDocMap+ docs' (FileModule fpath _) = liftM snd $ HDocs.readSource opts fpath+ docs' (InstalledModule _ _ mname) = HDocs.moduleDocs opts mname+ docs' _ = throwError $ "Can't get docs for: " ++ show mloc++-- | Get all docs+hdocsCabal :: PackageDbStack -> [String] -> ExceptT String IO (Map String (Map String String))+hdocsCabal pdbs opts = liftM (M.map $ force . HDocs.formatDocs) $ HDocs.installedDocs (packageDbStackOpts pdbs ++ opts)++-- | Set docs for module+setDocs :: Map String String -> Module -> Module+setDocs d = over moduleDeclarations (map setDoc) where+ setDoc decl' = set declarationDocs (M.lookup (view declarationName decl') d') decl'+ d' = M.mapKeys fromString . M.map fromString $ d++-- | Load docs for module+loadDocs :: [String] -> Module -> IO Module+loadDocs opts m = do+ d <- hdocs (view moduleLocation m) opts+ return $ setDocs d m++hdocsProcess :: String -> [String] -> IO (Maybe (Map String String))+hdocsProcess mname opts = liftM (decode . L.pack . last . lines) $ runTool_ "hdocs" opts' where+ opts' = mname : concat [["-g", opt] | opt <- opts]
src/HsDev/Tools/HLint.hs view
@@ -1,97 +1,97 @@-module HsDev.Tools.HLint ( - hlint, hlintFile, hlintSource, - - module Control.Monad.Except - ) where - -import Control.Arrow -import Control.Lens (over, view, _Just) -import Control.Monad.Except -import Data.Char -import Data.List -import Data.Maybe (mapMaybe) -import Data.Ord -import Language.Haskell.HLint3 (autoSettings, parseModuleEx, applyHints, Idea(..), parseErrorMessage, ParseFlags(..), CppFlags(..)) -import Language.Haskell.Exts.SrcLoc -import qualified Language.Haskell.HLint3 as HL (Severity(..)) - -import System.Directory.Paths (canonicalize) -import HsDev.Symbols.Location -import HsDev.Tools.Base -import HsDev.Util (readFileUtf8, split) - -hlint :: FilePath -> Maybe String -> ExceptT String IO [Note OutputMessage] -hlint file msrc = do - file' <- liftIO $ canonicalize file - cts <- maybe (liftIO $ readFileUtf8 file') return msrc - (flags, classify, hint) <- liftIO autoSettings - p <- liftIO $ parseModuleEx (flags { cppFlags = CppSimple }) file' (Just cts) - m <- either (throwError . parseErrorMessage) return p - return $ map (recalcTabs cts 8 . indentIdea cts . fromIdea) $ applyHints classify hint [m] - -hlintFile :: FilePath -> ExceptT String IO [Note OutputMessage] -hlintFile f = hlint f Nothing - -hlintSource :: FilePath -> String -> ExceptT String IO [Note OutputMessage] -hlintSource f = hlint f . Just - -fromIdea :: Idea -> Note OutputMessage -fromIdea idea = Note { - _noteSource = FileModule (srcSpanFilename src) Nothing, - _noteRegion = Region (Position (srcSpanStartLine src) (srcSpanStartColumn src)) (Position (srcSpanEndLine src) (srcSpanEndColumn src)), - _noteLevel = Just $ case ideaSeverity idea of - HL.Warning -> Warning - HL.Error -> Error - _ -> Hint, - _note = OutputMessage { - _message = ideaHint idea, - _messageSuggestion = ideaTo idea } } - where - src = ideaSpan idea - -indentIdea :: String -> Note OutputMessage -> Note OutputMessage -indentIdea cts idea = case analyzeIndent cts of - Nothing -> idea - Just i -> over (note . messageSuggestion . _Just) (indent' i) idea - where - indent' i' = intercalate "\n" . indentTail . map (uncurry (++) . first (concat . (`replicate` i') . (`div` 2) . length) . span isSpace) . split (== '\n') - indentTail [] = [] - indentTail (h : hs) = h : map (firstIndent ++) hs - firstIndent = takeWhile isSpace firstLine - firstLine = regionStr (Position firstLineNum 1 `region` Position (succ firstLineNum) 1) cts - firstLineNum = view (noteRegion . regionFrom . positionLine) idea - --- | Indent in source -data Indent = Spaces Int | Tabs deriving (Eq, Ord) - -instance Show Indent where - show (Spaces n) = replicate n ' ' - show Tabs = "\t" - --- | Analyze source indentation to convert suggestion to same indentation --- Returns one indent -analyzeIndent :: String -> Maybe String -analyzeIndent = - fmap show . selectIndent . map fst . dropUnusual . - sortBy (comparing $ negate . snd) . - map (head &&& length) . - group . sort . - mapMaybe (guessIndent . takeWhile isSpace) . lines - where - selectIndent :: [Indent] -> Maybe Indent - selectIndent [] = Nothing - selectIndent (Tabs : _) = Just Tabs - selectIndent indents = Just $ Spaces $ foldr1 gcd $ mapMaybe spaces indents where - spaces :: Indent -> Maybe Int - spaces Tabs = Nothing - spaces (Spaces n) = Just n - dropUnusual :: [(Indent, Int)] -> [(Indent, Int)] - dropUnusual [] = [] - dropUnusual is@((_, freq):_) = takeWhile ((> freq `div` 5) . snd) is - --- | Guess indent of one line -guessIndent :: String -> Maybe Indent -guessIndent s - | all (== ' ') s = Just $ Spaces $ length s - | all (== '\t') s = Just Tabs - | otherwise = Nothing +module HsDev.Tools.HLint (+ hlint, hlintFile, hlintSource,++ module Control.Monad.Except+ ) where++import Control.Arrow+import Control.Lens (over, view, _Just)+import Control.Monad.Except+import Data.Char+import Data.List+import Data.Maybe (mapMaybe)+import Data.Ord+import Language.Haskell.HLint3 (autoSettings, parseModuleEx, applyHints, Idea(..), parseErrorMessage, ParseFlags(..), CppFlags(..))+import Language.Haskell.Exts.SrcLoc+import qualified Language.Haskell.HLint3 as HL (Severity(..))++import System.Directory.Paths (canonicalize)+import HsDev.Symbols.Location+import HsDev.Tools.Base+import HsDev.Util (readFileUtf8, split)++hlint :: FilePath -> Maybe String -> ExceptT String IO [Note OutputMessage]+hlint file msrc = do+ file' <- liftIO $ canonicalize file+ cts <- maybe (liftIO $ readFileUtf8 file') return msrc+ (flags, classify, hint) <- liftIO autoSettings+ p <- liftIO $ parseModuleEx (flags { cppFlags = CppSimple }) file' (Just cts)+ m <- either (throwError . parseErrorMessage) return p+ return $ map (recalcTabs cts 8 . indentIdea cts . fromIdea) $ applyHints classify hint [m]++hlintFile :: FilePath -> ExceptT String IO [Note OutputMessage]+hlintFile f = hlint f Nothing++hlintSource :: FilePath -> String -> ExceptT String IO [Note OutputMessage]+hlintSource f = hlint f . Just++fromIdea :: Idea -> Note OutputMessage+fromIdea idea = Note {+ _noteSource = FileModule (srcSpanFilename src) Nothing,+ _noteRegion = Region (Position (srcSpanStartLine src) (srcSpanStartColumn src)) (Position (srcSpanEndLine src) (srcSpanEndColumn src)),+ _noteLevel = Just $ case ideaSeverity idea of+ HL.Warning -> Warning+ HL.Error -> Error+ _ -> Hint,+ _note = OutputMessage {+ _message = ideaHint idea,+ _messageSuggestion = ideaTo idea } }+ where+ src = ideaSpan idea++indentIdea :: String -> Note OutputMessage -> Note OutputMessage+indentIdea cts idea = case analyzeIndent cts of+ Nothing -> idea+ Just i -> over (note . messageSuggestion . _Just) (indent' i) idea+ where+ indent' i' = intercalate "\n" . indentTail . map (uncurry (++) . first (concat . (`replicate` i') . (`div` 2) . length) . span isSpace) . split (== '\n')+ indentTail [] = []+ indentTail (h : hs) = h : map (firstIndent ++) hs+ firstIndent = takeWhile isSpace firstLine+ firstLine = regionStr (Position firstLineNum 1 `region` Position (succ firstLineNum) 1) cts+ firstLineNum = view (noteRegion . regionFrom . positionLine) idea++-- | Indent in source+data Indent = Spaces Int | Tabs deriving (Eq, Ord)++instance Show Indent where+ show (Spaces n) = replicate n ' '+ show Tabs = "\t"++-- | Analyze source indentation to convert suggestion to same indentation+-- Returns one indent+analyzeIndent :: String -> Maybe String+analyzeIndent =+ fmap show . selectIndent . map fst . dropUnusual .+ sortBy (comparing $ negate . snd) .+ map (head &&& length) .+ group . sort .+ mapMaybe (guessIndent . takeWhile isSpace) . lines+ where+ selectIndent :: [Indent] -> Maybe Indent+ selectIndent [] = Nothing+ selectIndent (Tabs : _) = Just Tabs+ selectIndent indents = Just $ Spaces $ foldr1 gcd $ mapMaybe spaces indents where+ spaces :: Indent -> Maybe Int+ spaces Tabs = Nothing+ spaces (Spaces n) = Just n+ dropUnusual :: [(Indent, Int)] -> [(Indent, Int)]+ dropUnusual [] = []+ dropUnusual is@((_, freq):_) = takeWhile ((> freq `div` 5) . snd) is++-- | Guess indent of one line+guessIndent :: String -> Maybe Indent+guessIndent s+ | all (== ' ') s = Just $ Spaces $ length s+ | all (== '\t') s = Just Tabs+ | otherwise = Nothing
src/HsDev/Tools/Hayoo.hs view
@@ -1,135 +1,135 @@-{-# LANGUAGE OverloadedStrings #-} - -module HsDev.Tools.Hayoo ( - -- * Types - HayooResult(..), HayooSymbol(..), - hayooAsDeclaration, - -- * Search help online - hayoo, - -- * Utils - untagDescription, - - -- * Reexportss - module Control.Monad.Except - ) where - -import Control.Arrow -import Control.Applicative -import Control.Monad.Except - -import Data.Aeson -import qualified Data.ByteString.Lazy.Char8 as L -import Data.Either -import Network.HTTP -import Data.String (fromString) - -import HsDev.Symbols -import HsDev.Tools.Base (replaceRx) -import HsDev.Util - --- | Hayoo response -data HayooResult = HayooResult { - resultMax :: Int, - resultOffset :: Int, - resultCount :: Int, - resultResult :: [HayooSymbol] } - deriving (Eq, Ord, Read, Show) - --- | Hayoo symbol -data HayooSymbol = HayooSymbol { - resultUri :: String, - tag :: String, - hayooPackage :: String, - hayooName :: String, - hayooSource :: String, - hayooDescription :: String, - hayooSignature :: String, - hayooModules :: [String], - hayooScore :: Double, - hayooType :: String } - deriving (Eq, Ord, Read, Show) - -newtype HayooValue = HayooValue { hayooValue :: Either Value HayooSymbol } - -instance FromJSON HayooResult where - parseJSON = withObject "hayoo response" $ \v -> HayooResult <$> - (v .:: "max") <*> - (v .:: "offset") <*> - (v .:: "count") <*> - ((rights . map hayooValue) <$> (v .:: "result")) - -instance Symbol HayooSymbol where - symbolName = fromString . hayooName - symbolQualifiedName f = fromString $ case hayooModules f of - [] -> hayooName f - (m:_) -> m ++ "." ++ hayooName f - symbolDocs = Just . fromString . hayooDescription - symbolLocation r = Location (ModuleSource $ Just $ resultUri r) Nothing - -instance Documented HayooSymbol where - brief f - | hayooType f == "function" = hayooName f ++ " :: " ++ hayooSignature f - | otherwise = hayooType f ++ " " ++ hayooName f - detailed f = unlines $ defaultDetailed f ++ online where - online = [ - "", "Hayoo online documentation", "", - "Package: " ++ hayooPackage f, - "Hackage URL: " ++ resultUri f] - -instance FromJSON HayooSymbol where - parseJSON = withObject "symbol" $ \v -> HayooSymbol <$> - (v .:: "resultUri") <*> - (v .:: "tag") <*> - (v .:: "resultPackage") <*> - (v .:: "resultName") <*> - (v .:: "resultSource") <*> - (v .:: "resultDescription") <*> - (v .:: "resultSignature") <*> - (v .:: "resultModules") <*> - (v .:: "resultScore") <*> - (v .:: "resultType") - -instance FromJSON HayooValue where - parseJSON v = HayooValue <$> ((Right <$> parseJSON v) <|> pure (Left v)) - --- | 'HayooFunction' as 'Declaration' -hayooAsDeclaration :: HayooSymbol -> Maybe ModuleDeclaration -hayooAsDeclaration f - | hayooType f `elem` ["function", "type", "newtype", "data", "class"] = Just ModuleDeclaration { - _declarationModuleId = ModuleId { - _moduleIdName = fromString $ head $ hayooModules f, - _moduleIdLocation = ModuleSource (Just $ resultUri f) }, - _moduleDeclaration = Declaration { - _declarationName = fromString $ hayooName f, - _declarationDefined = Nothing, - _declarationImported = Nothing, - _declarationDocs = Just (fromString $ addOnline $ untagDescription $ hayooDescription f), - _declarationPosition = Nothing, - _declaration = declInfo } } - | otherwise = Nothing - where - -- Add other info - addOnline d = unlines [ - d, "", - "Hayoo online documentation", - "", - "Package: " ++ hayooPackage f, - "Hackage URL: " ++ resultUri f] - - declInfo - | hayooType f == "function" = Function (Just $ fromString $ hayooSignature f) [] Nothing - | hayooType f `elem` ["type", "newtype", "data", "class"] = declarationTypeCtor (hayooType f) $ TypeInfo Nothing [] Nothing [] - | otherwise = error "Impossible" - --- | Search hayoo -hayoo :: String -> Maybe Int -> ExceptT String IO HayooResult -hayoo q page = do - resp <- ExceptT $ (show +++ rspBody) <$> simpleHTTP (getRequest $ maybe id addPage page $ "http://hayoo.fh-wedel.de/json/?query=" ++ urlEncode q) - ExceptT $ return $ eitherDecode $ L.pack resp - where - addPage :: Int -> String -> String - addPage p s = s ++ "&page=" ++ show p - --- | Remove tags in description -untagDescription :: String -> String -untagDescription = replaceRx "</?\\w+[^>]*>" "" +{-# LANGUAGE OverloadedStrings #-}++module HsDev.Tools.Hayoo (+ -- * Types+ HayooResult(..), HayooSymbol(..),+ hayooAsDeclaration,+ -- * Search help online+ hayoo,+ -- * Utils+ untagDescription,++ -- * Reexportss+ module Control.Monad.Except+ ) where++import Control.Arrow+import Control.Applicative+import Control.Monad.Except++import Data.Aeson+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Either+import Network.HTTP+import Data.String (fromString)++import HsDev.Symbols+import HsDev.Tools.Base (replaceRx)+import HsDev.Util++-- | Hayoo response+data HayooResult = HayooResult {+ resultMax :: Int,+ resultOffset :: Int,+ resultCount :: Int,+ resultResult :: [HayooSymbol] }+ deriving (Eq, Ord, Read, Show)++-- | Hayoo symbol+data HayooSymbol = HayooSymbol {+ resultUri :: String,+ tag :: String,+ hayooPackage :: String,+ hayooName :: String,+ hayooSource :: String,+ hayooDescription :: String,+ hayooSignature :: String,+ hayooModules :: [String],+ hayooScore :: Double,+ hayooType :: String }+ deriving (Eq, Ord, Read, Show)++newtype HayooValue = HayooValue { hayooValue :: Either Value HayooSymbol }++instance FromJSON HayooResult where+ parseJSON = withObject "hayoo response" $ \v -> HayooResult <$>+ (v .:: "max") <*>+ (v .:: "offset") <*>+ (v .:: "count") <*>+ ((rights . map hayooValue) <$> (v .:: "result"))++instance Symbol HayooSymbol where+ symbolName = fromString . hayooName+ symbolQualifiedName f = fromString $ case hayooModules f of+ [] -> hayooName f+ (m:_) -> m ++ "." ++ hayooName f+ symbolDocs = Just . fromString . hayooDescription+ symbolLocation r = Location (ModuleSource $ Just $ resultUri r) Nothing++instance Documented HayooSymbol where+ brief f+ | hayooType f == "function" = hayooName f ++ " :: " ++ hayooSignature f+ | otherwise = hayooType f ++ " " ++ hayooName f+ detailed f = unlines $ defaultDetailed f ++ online where+ online = [+ "", "Hayoo online documentation", "",+ "Package: " ++ hayooPackage f,+ "Hackage URL: " ++ resultUri f]++instance FromJSON HayooSymbol where+ parseJSON = withObject "symbol" $ \v -> HayooSymbol <$>+ (v .:: "resultUri") <*>+ (v .:: "tag") <*>+ (v .:: "resultPackage") <*>+ (v .:: "resultName") <*>+ (v .:: "resultSource") <*>+ (v .:: "resultDescription") <*>+ (v .:: "resultSignature") <*>+ (v .:: "resultModules") <*>+ (v .:: "resultScore") <*>+ (v .:: "resultType")++instance FromJSON HayooValue where+ parseJSON v = HayooValue <$> ((Right <$> parseJSON v) <|> pure (Left v))++-- | 'HayooFunction' as 'Declaration'+hayooAsDeclaration :: HayooSymbol -> Maybe ModuleDeclaration+hayooAsDeclaration f+ | hayooType f `elem` ["function", "type", "newtype", "data", "class"] = Just ModuleDeclaration {+ _declarationModuleId = ModuleId {+ _moduleIdName = fromString $ head $ hayooModules f,+ _moduleIdLocation = ModuleSource (Just $ resultUri f) },+ _moduleDeclaration = Declaration {+ _declarationName = fromString $ hayooName f,+ _declarationDefined = Nothing,+ _declarationImported = Nothing,+ _declarationDocs = Just (fromString $ addOnline $ untagDescription $ hayooDescription f),+ _declarationPosition = Nothing,+ _declaration = declInfo } }+ | otherwise = Nothing+ where+ -- Add other info+ addOnline d = unlines [+ d, "",+ "Hayoo online documentation",+ "",+ "Package: " ++ hayooPackage f,+ "Hackage URL: " ++ resultUri f]++ declInfo+ | hayooType f == "function" = Function (Just $ fromString $ hayooSignature f) [] Nothing+ | hayooType f `elem` ["type", "newtype", "data", "class"] = declarationTypeCtor (hayooType f) $ TypeInfo Nothing [] Nothing []+ | otherwise = error "Impossible"++-- | Search hayoo+hayoo :: String -> Maybe Int -> ExceptT String IO HayooResult+hayoo q page = do+ resp <- ExceptT $ (show +++ rspBody) <$> simpleHTTP (getRequest $ maybe id addPage page $ "http://hayoo.fh-wedel.de/json/?query=" ++ urlEncode q)+ ExceptT $ return $ eitherDecode $ L.pack resp+ where+ addPage :: Int -> String -> String+ addPage p s = s ++ "&page=" ++ show p++-- | Remove tags in description+untagDescription :: String -> String+untagDescription = replaceRx "</?\\w+[^>]*>" ""
src/HsDev/Tools/Types.hs view
@@ -1,99 +1,99 @@-{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} - -module HsDev.Tools.Types ( - Severity(..), - Note(..), noteSource, noteRegion, noteLevel, note, - OutputMessage(..), message, messageSuggestion, outputMessage - ) where - -import Control.DeepSeq (NFData(..)) -import Control.Lens (makeLenses) -import Control.Monad -import Data.Aeson hiding (Error) - -import System.Directory.Paths -import HsDev.Symbols.Location -import HsDev.Util ((.::), (.::?)) - --- | Note severity -data Severity = Error | Warning | Hint deriving (Enum, Bounded, Eq, Ord, Read, Show) - -instance NFData Severity where - rnf Error = () - rnf Warning = () - rnf Hint = () - -instance ToJSON Severity where - toJSON Error = toJSON ("error" :: String) - toJSON Warning = toJSON ("warning" :: String) - toJSON Hint = toJSON ("hint" :: String) - -instance FromJSON Severity where - parseJSON v = do - s <- parseJSON v - msum [ - guard (s == ("error" :: String)) >> return Error, - guard (s == ("warning" :: String)) >> return Warning, - guard (s == ("hint" :: String)) >> return Hint, - fail $ "Unknown severity: " ++ s] - --- | Note over some region -data Note a = Note { - _noteSource :: ModuleLocation, - _noteRegion :: Region, - _noteLevel :: Maybe Severity, - _note :: a } - deriving (Eq, Show) - -makeLenses ''Note - -instance Functor Note where - fmap f (Note s r l n) = Note s r l (f n) - -instance NFData a => NFData (Note a) where - rnf (Note s r l n) = rnf s `seq` rnf r `seq` rnf l `seq` rnf n - -instance ToJSON a => ToJSON (Note a) where - toJSON (Note s r l n) = object [ - "source" .= s, - "region" .= r, - "level" .= l, - "note" .= n] - -instance FromJSON a => FromJSON (Note a) where - parseJSON = withObject "note" $ \v -> Note <$> - v .:: "source" <*> - v .:: "region" <*> - v .::? "level" <*> - v .:: "note" - -instance RecalcTabs (Note a) where - recalcTabs cts n' (Note s r l n) = Note s (recalcTabs cts n' r) l n - calcTabs cts n' (Note s r l n) = Note s (calcTabs cts n' r) l n - -instance Paths (Note a) where - paths f (Note s r l n) = Note <$> paths f s <*> pure r <*> pure l <*> pure n - --- | Output message from some tool (ghc, ghc-mod, hlint) with optional suggestion -data OutputMessage = OutputMessage { - _message :: String, - _messageSuggestion :: Maybe String } - deriving (Eq, Ord, Read, Show) - -instance NFData OutputMessage where - rnf (OutputMessage m s) = rnf m `seq` rnf s - -instance ToJSON OutputMessage where - toJSON (OutputMessage m s) = object [ - "message" .= m, - "suggestion" .= s] - -instance FromJSON OutputMessage where - parseJSON = withObject "output-message" $ \v -> OutputMessage <$> - v .:: "message" <*> - v .:: "suggestion" - -outputMessage :: String -> OutputMessage -outputMessage msg = OutputMessage msg Nothing - -makeLenses ''OutputMessage +{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}++module HsDev.Tools.Types (+ Severity(..),+ Note(..), noteSource, noteRegion, noteLevel, note,+ OutputMessage(..), message, messageSuggestion, outputMessage+ ) where++import Control.DeepSeq (NFData(..))+import Control.Lens (makeLenses)+import Control.Monad+import Data.Aeson hiding (Error)++import System.Directory.Paths+import HsDev.Symbols.Location+import HsDev.Util ((.::), (.::?))++-- | Note severity+data Severity = Error | Warning | Hint deriving (Enum, Bounded, Eq, Ord, Read, Show)++instance NFData Severity where+ rnf Error = ()+ rnf Warning = ()+ rnf Hint = ()++instance ToJSON Severity where+ toJSON Error = toJSON ("error" :: String)+ toJSON Warning = toJSON ("warning" :: String)+ toJSON Hint = toJSON ("hint" :: String)++instance FromJSON Severity where+ parseJSON v = do+ s <- parseJSON v+ msum [+ guard (s == ("error" :: String)) >> return Error,+ guard (s == ("warning" :: String)) >> return Warning,+ guard (s == ("hint" :: String)) >> return Hint,+ fail $ "Unknown severity: " ++ s]++-- | Note over some region+data Note a = Note {+ _noteSource :: ModuleLocation,+ _noteRegion :: Region,+ _noteLevel :: Maybe Severity,+ _note :: a }+ deriving (Eq, Show)++makeLenses ''Note++instance Functor Note where+ fmap f (Note s r l n) = Note s r l (f n)++instance NFData a => NFData (Note a) where+ rnf (Note s r l n) = rnf s `seq` rnf r `seq` rnf l `seq` rnf n++instance ToJSON a => ToJSON (Note a) where+ toJSON (Note s r l n) = object [+ "source" .= s,+ "region" .= r,+ "level" .= l,+ "note" .= n]++instance FromJSON a => FromJSON (Note a) where+ parseJSON = withObject "note" $ \v -> Note <$>+ v .:: "source" <*>+ v .:: "region" <*>+ v .::? "level" <*>+ v .:: "note"++instance RecalcTabs (Note a) where+ recalcTabs cts n' (Note s r l n) = Note s (recalcTabs cts n' r) l n+ calcTabs cts n' (Note s r l n) = Note s (calcTabs cts n' r) l n++instance Paths (Note a) where+ paths f (Note s r l n) = Note <$> paths f s <*> pure r <*> pure l <*> pure n++-- | Output message from some tool (ghc, ghc-mod, hlint) with optional suggestion+data OutputMessage = OutputMessage {+ _message :: String,+ _messageSuggestion :: Maybe String }+ deriving (Eq, Ord, Read, Show)++instance NFData OutputMessage where+ rnf (OutputMessage m s) = rnf m `seq` rnf s++instance ToJSON OutputMessage where+ toJSON (OutputMessage m s) = object [+ "message" .= m,+ "suggestion" .= s]++instance FromJSON OutputMessage where+ parseJSON = withObject "output-message" $ \v -> OutputMessage <$>+ v .:: "message" <*>+ v .:: "suggestion"++outputMessage :: String -> OutputMessage+outputMessage msg = OutputMessage msg Nothing++makeLenses ''OutputMessage
src/HsDev/Types.hs view
@@ -1,112 +1,112 @@-{-# 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 | - PackageNotFound 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 (PackageNotFound 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 (PackageNotFound p) = format "package '{}' 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 Formattable 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 (PackageNotFound p) = jsonErr "package not found" ["package" .= 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" - "package not found" -> PackageNotFound <$> v .: "package" - "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 +{-# 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 |+ PackageNotFound 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 (PackageNotFound 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 (PackageNotFound p) = format "package '{}' 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 Formattable 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 (PackageNotFound p) = jsonErr "package not found" ["package" .= 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"+ "package not found" -> PackageNotFound <$> v .: "package"+ "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
src/HsDev/Util.hs view
@@ -1,338 +1,340 @@-{-# LANGUAGE FlexibleContexts, OverloadedStrings, TemplateHaskell, CPP #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module HsDev.Util ( - withCurrentDirectory, - directoryContents, - traverseDirectory, searchPath, - isParent, - haskellSource, - cabalFile, - -- * String utils - tab, tabs, trim, split, - -- * Other utils - ordNub, uniqueBy, mapBy, - -- * Helper - (.::), (.::?), (.::?!), objectUnion, jsonUnion, noNulls, - -- * Exceptions - liftException, liftE, liftEIO, tries, triesMap, liftExceptionM, liftIOErrors, - eitherT, - liftThrow, - -- * UTF-8 - fromUtf8, toUtf8, readFileUtf8, writeFileUtf8, - -- * IO - hGetLineBS, logException, logIO, ignoreIO, logAsync, - -- * Async - liftAsync, - -- * Command line - FromCmd(..), - cmdJson, withCmd, guardCmd, - withHelp, cmd, parseArgs, - -- * Version stuff - version, cutVersion, sameVersion, strVersion, - - -- * Reexportss - module Control.Monad.Except, - MonadIO(..) - ) where - -import Control.Applicative -import Control.Arrow (second, left, (&&&)) -import Control.Exception -import Control.Concurrent.Async -import Control.Monad -import Control.Monad.Except -import qualified Control.Monad.Catch as C -import Data.Aeson hiding (Result(..), Error) -import qualified Data.Aeson.Types as A -import Data.Char (isSpace) -import Data.List (isPrefixOf, unfoldr, intercalate) -import qualified Data.Map as M -import Data.Maybe (catMaybes, fromMaybe) -import Data.Monoid ((<>)) -import qualified Data.Set as Set -import qualified Data.HashMap.Strict as HM (HashMap, toList, union) -import qualified Data.ByteString.Char8 as B -import Data.ByteString.Lazy (ByteString) -import qualified Data.ByteString.Lazy.Char8 as L -import Data.Text (Text) -import qualified Data.Text.Lazy as T -import qualified Data.Text.Lazy.Encoding as T -import Options.Applicative -import qualified System.Directory as Dir -import System.FilePath -import System.IO -import Text.Read (readMaybe) - -#if !MIN_VERSION_directory(1,2,6) -#if mingw32_HOST_OS -import qualified System.Win32 as Win32 -import Data.Bits ((.&.)) -#else -import qualified System.Posix as Posix -#endif -#endif - -import HsDev.Version - --- | Run action with current directory set -withCurrentDirectory :: (MonadIO m, C.MonadMask m) => FilePath -> m a -> m a -withCurrentDirectory cur act = C.bracket (liftIO Dir.getCurrentDirectory) (liftIO . Dir.setCurrentDirectory) $ - const (liftIO (Dir.setCurrentDirectory cur) >> act) - --- | Is directory symbolic link -dirIsSymLink :: FilePath -> IO Bool -#if MIN_VERSION_directory(1,2,6) -dirIsSymLink = Dir.isSymbolicLink -#else -dirIsSymLink path = do -#if mingw32_HOST_OS - isReparsePoint <$> Win32.getFileAttributes path - where - fILE_ATTRIBUTE_REPARSE_POINT = 0x400 - isReparsePoint attr = attr .&. fILE_ATTRIBUTE_REPARSE_POINT /= 0 -#else - Posix.isSymbolicLink <$> Posix.getSymbolicLinkStatus path -#endif -#endif - --- | Get directory contents safely: no fail, ignoring symbolic links, also prepends paths with dir name -directoryContents :: FilePath -> IO [FilePath] -directoryContents p = handle ignore $ do - b <- Dir.doesDirectoryExist p - isLink <- dirIsSymLink p - if b && (not isLink) - then liftM (map (p </>) . filter (`notElem` [".", ".."])) (Dir.getDirectoryContents p) - else return [] - where - ignore :: SomeException -> IO [FilePath] - ignore _ = return [] - --- | Collect all file names in directory recursively -traverseDirectory :: FilePath -> IO [FilePath] -traverseDirectory path = handle onError $ do - cts <- directoryContents path - liftM concat $ forM cts $ \c -> do - isDir <- Dir.doesDirectoryExist c - if isDir - then (c :) <$> traverseDirectory c - else return [c] - where - onError :: IOException -> IO [FilePath] - onError _ = return [] - --- | Search something up -searchPath :: (MonadIO m, MonadPlus m) => FilePath -> (FilePath -> m a) -> m a -searchPath path f = do - path' <- liftIO $ Dir.canonicalizePath path - isDir <- liftIO $ Dir.doesDirectoryExist path' - search' (if isDir then path' else takeDirectory path') - where - search' dir - | isDrive dir = f dir - | otherwise = f dir `mplus` search' (takeDirectory dir) - --- | Is one path parent of another -isParent :: FilePath -> FilePath -> Bool -isParent dir file = norm dir `isPrefixOf` norm file where - norm = dropDot . splitDirectories . normalise - dropDot ("." : chs) = chs - dropDot chs = chs - --- | Is haskell source? -haskellSource :: FilePath -> Bool -haskellSource f = takeExtension f `elem` [".hs", ".lhs"] - --- | Is cabal file? -cabalFile :: FilePath -> Bool -cabalFile f = takeExtension f == ".cabal" - --- | Add N tabs to line -tab :: Int -> String -> String -tab n s = replicate n '\t' ++ s - --- | Add N tabs to multiline -tabs :: Int -> String -> String -tabs n = unlines . map (tab n) . lines - --- | Trim string -trim :: String -> String -trim = p . p where - p = reverse . dropWhile isSpace - --- | Split list -split :: (a -> Bool) -> [a] -> [[a]] -split p = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break p) - --- | nub is quadratic, https://github.com/nh2/haskell-ordnub/#ordnub -ordNub :: Ord a => [a] -> [a] -ordNub = go Set.empty where - go _ [] = [] - go s (x:xs) - | x `Set.member` s = go s xs - | otherwise = x : go (Set.insert x s) xs - -uniqueBy :: Ord b => (a -> b) -> [a] -> [a] -uniqueBy f = M.elems . mapBy f - -mapBy :: Ord b => (a -> b) -> [a] -> M.Map b a -mapBy f = M.fromList . map (f &&& id) - --- | Workaround, sometimes we get HM.lookup "foo" v == Nothing, but lookup "foo" (HM.toList v) == Just smth -(.::) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser a -v .:: name = maybe (fail $ "key " ++ show name ++ " not present") parseJSON $ lookup name $ HM.toList v - --- | Returns @Nothing@ when key doesn't exist or value is @Null@ -(.::?) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser (Maybe a) -v .::? name = fmap join $ traverse parseJSON $ lookup name $ HM.toList v - --- | Same as @.::?@ for list, returns empty list for non-existant key or @Null@ value -(.::?!) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser [a] -v .::?! name = fromMaybe [] <$> (v .::? name) - --- | Union two JSON objects -objectUnion :: Value -> Value -> Value -objectUnion (Object l) (Object r) = Object $ HM.union l r -objectUnion (Object l) _ = Object l -objectUnion _ (Object r) = Object r -objectUnion _ _ = Null - --- | Union two JSON objects -jsonUnion :: (ToJSON a, ToJSON b) => a -> b -> Value -jsonUnion x y = objectUnion (toJSON x) (toJSON y) - --- | No Nulls in JSON object -noNulls :: [A.Pair] -> [A.Pair] -noNulls = filter (not . isNull . snd) where - isNull Null = True - isNull v = v == A.emptyArray || v == A.emptyObject - --- | Lift IO exception to ExceptT -liftException :: C.MonadCatch m => m a -> ExceptT String m a -liftException = ExceptT . liftM (left $ \(SomeException e) -> displayException e) . C.try - --- | Same as @liftException@ -liftE :: C.MonadCatch m => m a -> ExceptT String m a -liftE = liftException - --- | @liftE@ for IO -liftEIO :: (C.MonadCatch m, MonadIO m) => IO a -> ExceptT String m a -liftEIO = liftE . liftIO - --- | Run actions ignoring errors -tries :: MonadPlus m => [m a] -> m [a] -tries acts = liftM catMaybes $ sequence [liftM Just act `mplus` return Nothing | act <- acts] - -triesMap :: MonadPlus m => (a -> m b) -> [a] -> m [b] -triesMap f = tries . map f - --- | Lift IO exception to MonadError -liftExceptionM :: (C.MonadCatch m, MonadError String m) => m a -> m a -liftExceptionM act = C.catch act onError where - onError = throwError . (\(SomeException e) -> displayException e) - --- | Lift IO exceptions to ExceptT -liftIOErrors :: C.MonadCatch m => ExceptT String m a -> ExceptT String m a -liftIOErrors act = liftException (runExceptT act) >>= either throwError return - -eitherT :: MonadError String m => Either String a -> m a -eitherT = either throwError return - --- | Throw error as exception -liftThrow :: (Show e, MonadError e m, C.MonadCatch m) => m a -> m a -liftThrow act = catchError act (C.throwM . userError . show) - -fromUtf8 :: ByteString -> String -fromUtf8 = T.unpack . T.decodeUtf8 - -toUtf8 :: String -> ByteString -toUtf8 = T.encodeUtf8 . T.pack - --- | Read file in UTF8 -readFileUtf8 :: FilePath -> IO String -readFileUtf8 f = withFile f ReadMode $ \h -> do - hSetEncoding h utf8 - cts <- hGetContents h - length cts `seq` return cts - -writeFileUtf8 :: FilePath -> String -> IO () -writeFileUtf8 f cts = withFile f WriteMode $ \h -> do - hSetEncoding h utf8 - hPutStr h cts - -hGetLineBS :: Handle -> IO ByteString -hGetLineBS = fmap L.fromStrict . B.hGetLine - -logException :: String -> (String -> IO ()) -> IO () -> IO () -logException pre out = handle onErr where - onErr :: SomeException -> IO () - onErr e = out $ pre ++ displayException e - -logIO :: C.MonadCatch m => String -> (String -> m ()) -> m () -> m () -logIO pre out = flip C.catch (onIO out) where - onIO :: (String -> a) -> IOException -> a - onIO out' e = out' $ pre ++ displayException e - -logAsync :: (MonadIO m, C.MonadCatch m) => (String -> m ()) -> m () -> m () -logAsync out = flip C.catch (onAsync out) where - onAsync :: (MonadIO m, C.MonadThrow m) => (String -> m ()) -> AsyncException -> m () - onAsync out' e = out' (displayException e) >> C.throwM e - -ignoreIO :: C.MonadCatch m => m () -> m () -ignoreIO = C.handle ignore' where - ignore' :: Monad m => IOException -> m () - ignore' _ = return () - -liftAsync :: (C.MonadThrow m, C.MonadCatch m, MonadIO m) => IO (Async a) -> ExceptT String m a -liftAsync = liftExceptionM . ExceptT . liftIO . liftM (left displayException) . join . liftM waitCatch - -class FromCmd a where - cmdP :: Parser a - -cmdJson :: String -> [A.Pair] -> Value -cmdJson nm ps = object $ ("command" .= nm) : ps - -withCmd :: String -> (Object -> A.Parser a) -> Value -> A.Parser a -withCmd nm fn = withObject ("command " ++ nm) $ \v -> guardCmd nm v *> fn v - -guardCmd :: String -> Object -> A.Parser () -guardCmd nm obj = do - cmdName <- obj .:: "command" - guard (nm == cmdName) - --- | Add help command to parser -withHelp :: Parser a -> Parser a -withHelp = (helper' <*>) where - helper' = abortOption ShowHelpText $ long "help" <> short '?' <> help "show help" <> hidden - --- | Subcommand -cmd :: String -> String -> Parser a -> Mod CommandFields a -cmd n d p = command n (info (withHelp p) (progDesc d)) - --- | Parse arguments or return help -parseArgs :: String -> ParserInfo a -> [String] -> Either String a -parseArgs nm p = handle' . execParserPure (prefs mempty) (p { infoParser = withHelp (infoParser p) }) where - handle' :: ParserResult a -> Either String a - handle' (Success r) = Right r - handle' (Failure f) = Left $ fst $ renderFailure f nm - handle' _ = Left "error: completion invoked result" - --- instance Log.MonadLog m => Log.MonadLog (ExceptT e m) where --- askLog = lift Log.askLog - --- | Get hsdev version as list of integers -version :: Maybe [Int] -version = mapM readMaybe $ split (== '.') $cabalVersion - --- | Cut version to contain only significant numbers (currently 3) -cutVersion :: Maybe [Int] -> Maybe [Int] -cutVersion = fmap (take 3) - --- | Check if version is the same -sameVersion :: Maybe [Int] -> Maybe [Int] -> Bool -sameVersion l r = fromMaybe False $ liftA2 (==) l r - --- | Version to string -strVersion :: Maybe [Int] -> String -strVersion Nothing = "unknown" -strVersion (Just vers) = intercalate "." $ map show vers +{-# LANGUAGE FlexibleContexts, OverloadedStrings, TemplateHaskell, CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module HsDev.Util (+ withCurrentDirectory,+ directoryContents,+ traverseDirectory, searchPath,+ isParent,+ haskellSource,+ cabalFile,+ -- * String utils+ tab, tabs, trim, split,+ -- * Other utils+ ordNub, uniqueBy, mapBy,+ -- * Helper+ (.::), (.::?), (.::?!), objectUnion, jsonUnion, noNulls,+ -- * Exceptions+ liftException, liftE, liftEIO, tries, triesMap, liftExceptionM, liftIOErrors,+ eitherT,+ liftThrow,+ -- * UTF-8+ fromUtf8, toUtf8, readFileUtf8, writeFileUtf8,+ -- * IO+ hGetLineBS, logException, logIO, ignoreIO, logAsync,+ -- * Async+ liftAsync,+ -- * Command line+ FromCmd(..),+ cmdJson, withCmd, guardCmd,+ withHelp, cmd, parseArgs,+ -- * Version stuff+ version, cutVersion, sameVersion, strVersion,++ -- * Reexportss+ module Control.Monad.Except,+ MonadIO(..)+ ) where++import Control.Applicative+import Control.Arrow (second, left, (&&&))+import Control.Exception+import Control.Concurrent.Async+import Control.Monad+import Control.Monad.Except+import qualified Control.Monad.Catch as C+import Data.Aeson hiding (Result(..), Error)+import qualified Data.Aeson.Types as A+import Data.Char (isSpace)+import Data.List (isPrefixOf, unfoldr, intercalate)+import qualified Data.Map as M+import Data.Maybe (catMaybes, fromMaybe)+import Data.Monoid ((<>))+import qualified Data.Set as Set+import qualified Data.HashMap.Strict as HM (HashMap, toList, union)+import qualified Data.ByteString.Char8 as B+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Text (Text)+import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.Encoding as T+import Options.Applicative+import qualified System.Directory as Dir+import System.FilePath+import System.IO+import Text.Read (readMaybe)++#if !MIN_VERSION_directory(1,2,6)+#if mingw32_HOST_OS+import qualified System.Win32 as Win32+import Data.Bits ((.&.))+#else+import qualified System.Posix as Posix+#endif+#endif++import HsDev.Version++-- | Run action with current directory set+withCurrentDirectory :: (MonadIO m, C.MonadMask m) => FilePath -> m a -> m a+withCurrentDirectory cur act = C.bracket (liftIO Dir.getCurrentDirectory) (liftIO . Dir.setCurrentDirectory) $+ const (liftIO (Dir.setCurrentDirectory cur) >> act)++-- | Is directory symbolic link+dirIsSymLink :: FilePath -> IO Bool+#if MIN_VERSION_directory(1,3,0)+dirIsSymLink = Dir.pathIsSymbolicLink+#elif MIN_VERSION_directory(1,2,6)+dirIsSymLink = Dir.isSymbolicLink+#else+dirIsSymLink path = do+#if mingw32_HOST_OS+ isReparsePoint <$> Win32.getFileAttributes path+ where+ fILE_ATTRIBUTE_REPARSE_POINT = 0x400+ isReparsePoint attr = attr .&. fILE_ATTRIBUTE_REPARSE_POINT /= 0+#else+ Posix.isSymbolicLink <$> Posix.getSymbolicLinkStatus path+#endif+#endif++-- | Get directory contents safely: no fail, ignoring symbolic links, also prepends paths with dir name+directoryContents :: FilePath -> IO [FilePath]+directoryContents p = handle ignore $ do+ b <- Dir.doesDirectoryExist p+ isLink <- dirIsSymLink p+ if b && (not isLink)+ then liftM (map (p </>) . filter (`notElem` [".", ".."])) (Dir.getDirectoryContents p)+ else return []+ where+ ignore :: SomeException -> IO [FilePath]+ ignore _ = return []++-- | Collect all file names in directory recursively+traverseDirectory :: FilePath -> IO [FilePath]+traverseDirectory path = handle onError $ do+ cts <- directoryContents path+ liftM concat $ forM cts $ \c -> do+ isDir <- Dir.doesDirectoryExist c+ if isDir+ then (c :) <$> traverseDirectory c+ else return [c]+ where+ onError :: IOException -> IO [FilePath]+ onError _ = return []++-- | Search something up+searchPath :: (MonadIO m, MonadPlus m) => FilePath -> (FilePath -> m a) -> m a+searchPath path f = do+ path' <- liftIO $ Dir.canonicalizePath path+ isDir <- liftIO $ Dir.doesDirectoryExist path'+ search' (if isDir then path' else takeDirectory path')+ where+ search' dir+ | isDrive dir = f dir+ | otherwise = f dir `mplus` search' (takeDirectory dir)++-- | Is one path parent of another+isParent :: FilePath -> FilePath -> Bool+isParent dir file = norm dir `isPrefixOf` norm file where+ norm = dropDot . splitDirectories . normalise+ dropDot ("." : chs) = chs+ dropDot chs = chs++-- | Is haskell source?+haskellSource :: FilePath -> Bool+haskellSource f = takeExtension f `elem` [".hs", ".lhs"]++-- | Is cabal file?+cabalFile :: FilePath -> Bool+cabalFile f = takeExtension f == ".cabal"++-- | Add N tabs to line+tab :: Int -> String -> String+tab n s = replicate n '\t' ++ s++-- | Add N tabs to multiline+tabs :: Int -> String -> String+tabs n = unlines . map (tab n) . lines++-- | Trim string+trim :: String -> String+trim = p . p where+ p = reverse . dropWhile isSpace++-- | Split list+split :: (a -> Bool) -> [a] -> [[a]]+split p = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break p)++-- | nub is quadratic, https://github.com/nh2/haskell-ordnub/#ordnub+ordNub :: Ord a => [a] -> [a]+ordNub = go Set.empty where+ go _ [] = []+ go s (x:xs)+ | x `Set.member` s = go s xs+ | otherwise = x : go (Set.insert x s) xs++uniqueBy :: Ord b => (a -> b) -> [a] -> [a]+uniqueBy f = M.elems . mapBy f++mapBy :: Ord b => (a -> b) -> [a] -> M.Map b a+mapBy f = M.fromList . map (f &&& id)++-- | Workaround, sometimes we get HM.lookup "foo" v == Nothing, but lookup "foo" (HM.toList v) == Just smth+(.::) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser a+v .:: name = maybe (fail $ "key " ++ show name ++ " not present") parseJSON $ lookup name $ HM.toList v++-- | Returns @Nothing@ when key doesn't exist or value is @Null@+(.::?) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser (Maybe a)+v .::? name = fmap join $ traverse parseJSON $ lookup name $ HM.toList v++-- | Same as @.::?@ for list, returns empty list for non-existant key or @Null@ value+(.::?!) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser [a]+v .::?! name = fromMaybe [] <$> (v .::? name)++-- | Union two JSON objects+objectUnion :: Value -> Value -> Value+objectUnion (Object l) (Object r) = Object $ HM.union l r+objectUnion (Object l) _ = Object l+objectUnion _ (Object r) = Object r+objectUnion _ _ = Null++-- | Union two JSON objects+jsonUnion :: (ToJSON a, ToJSON b) => a -> b -> Value+jsonUnion x y = objectUnion (toJSON x) (toJSON y)++-- | No Nulls in JSON object+noNulls :: [A.Pair] -> [A.Pair]+noNulls = filter (not . isNull . snd) where+ isNull Null = True+ isNull v = v == A.emptyArray || v == A.emptyObject++-- | Lift IO exception to ExceptT+liftException :: C.MonadCatch m => m a -> ExceptT String m a+liftException = ExceptT . liftM (left $ \(SomeException e) -> displayException e) . C.try++-- | Same as @liftException@+liftE :: C.MonadCatch m => m a -> ExceptT String m a+liftE = liftException++-- | @liftE@ for IO+liftEIO :: (C.MonadCatch m, MonadIO m) => IO a -> ExceptT String m a+liftEIO = liftE . liftIO++-- | Run actions ignoring errors+tries :: MonadPlus m => [m a] -> m [a]+tries acts = liftM catMaybes $ sequence [liftM Just act `mplus` return Nothing | act <- acts]++triesMap :: MonadPlus m => (a -> m b) -> [a] -> m [b]+triesMap f = tries . map f++-- | Lift IO exception to MonadError+liftExceptionM :: (C.MonadCatch m, MonadError String m) => m a -> m a+liftExceptionM act = C.catch act onError where+ onError = throwError . (\(SomeException e) -> displayException e)++-- | Lift IO exceptions to ExceptT+liftIOErrors :: C.MonadCatch m => ExceptT String m a -> ExceptT String m a+liftIOErrors act = liftException (runExceptT act) >>= either throwError return++eitherT :: MonadError String m => Either String a -> m a+eitherT = either throwError return++-- | Throw error as exception+liftThrow :: (Show e, MonadError e m, C.MonadCatch m) => m a -> m a+liftThrow act = catchError act (C.throwM . userError . show)++fromUtf8 :: ByteString -> String+fromUtf8 = T.unpack . T.decodeUtf8++toUtf8 :: String -> ByteString+toUtf8 = T.encodeUtf8 . T.pack++-- | Read file in UTF8+readFileUtf8 :: FilePath -> IO String+readFileUtf8 f = withFile f ReadMode $ \h -> do+ hSetEncoding h utf8+ cts <- hGetContents h+ length cts `seq` return cts++writeFileUtf8 :: FilePath -> String -> IO ()+writeFileUtf8 f cts = withFile f WriteMode $ \h -> do+ hSetEncoding h utf8+ hPutStr h cts++hGetLineBS :: Handle -> IO ByteString+hGetLineBS = fmap L.fromStrict . B.hGetLine++logException :: String -> (String -> IO ()) -> IO () -> IO ()+logException pre out = handle onErr where+ onErr :: SomeException -> IO ()+ onErr e = out $ pre ++ displayException e++logIO :: C.MonadCatch m => String -> (String -> m ()) -> m () -> m ()+logIO pre out = flip C.catch (onIO out) where+ onIO :: (String -> a) -> IOException -> a+ onIO out' e = out' $ pre ++ displayException e++logAsync :: (MonadIO m, C.MonadCatch m) => (String -> m ()) -> m () -> m ()+logAsync out = flip C.catch (onAsync out) where+ onAsync :: (MonadIO m, C.MonadThrow m) => (String -> m ()) -> AsyncException -> m ()+ onAsync out' e = out' (displayException e) >> C.throwM e++ignoreIO :: C.MonadCatch m => m () -> m ()+ignoreIO = C.handle ignore' where+ ignore' :: Monad m => IOException -> m ()+ ignore' _ = return ()++liftAsync :: (C.MonadThrow m, C.MonadCatch m, MonadIO m) => IO (Async a) -> ExceptT String m a+liftAsync = liftExceptionM . ExceptT . liftIO . liftM (left displayException) . join . liftM waitCatch++class FromCmd a where+ cmdP :: Parser a++cmdJson :: String -> [A.Pair] -> Value+cmdJson nm ps = object $ ("command" .= nm) : ps++withCmd :: String -> (Object -> A.Parser a) -> Value -> A.Parser a+withCmd nm fn = withObject ("command " ++ nm) $ \v -> guardCmd nm v *> fn v++guardCmd :: String -> Object -> A.Parser ()+guardCmd nm obj = do+ cmdName <- obj .:: "command"+ guard (nm == cmdName)++-- | Add help command to parser+withHelp :: Parser a -> Parser a+withHelp = (helper' <*>) where+ helper' = abortOption ShowHelpText $ long "help" <> short '?' <> help "show help" <> hidden++-- | Subcommand+cmd :: String -> String -> Parser a -> Mod CommandFields a+cmd n d p = command n (info (withHelp p) (progDesc d))++-- | Parse arguments or return help+parseArgs :: String -> ParserInfo a -> [String] -> Either String a+parseArgs nm p = handle' . execParserPure (prefs mempty) (p { infoParser = withHelp (infoParser p) }) where+ handle' :: ParserResult a -> Either String a+ handle' (Success r) = Right r+ handle' (Failure f) = Left $ fst $ renderFailure f nm+ handle' _ = Left "error: completion invoked result"++-- instance Log.MonadLog m => Log.MonadLog (ExceptT e m) where+-- askLog = lift Log.askLog++-- | Get hsdev version as list of integers+version :: Maybe [Int]+version = mapM readMaybe $ split (== '.') $cabalVersion++-- | Cut version to contain only significant numbers (currently 3)+cutVersion :: Maybe [Int] -> Maybe [Int]+cutVersion = fmap (take 3)++-- | Check if version is the same+sameVersion :: Maybe [Int] -> Maybe [Int] -> Bool+sameVersion l r = fromMaybe False $ liftA2 (==) l r++-- | Version to string+strVersion :: Maybe [Int] -> String+strVersion Nothing = "unknown"+strVersion (Just vers) = intercalate "." $ map show vers
src/HsDev/Watcher.hs view
@@ -1,73 +1,73 @@-module HsDev.Watcher ( - watchProject, watchModule, watchPackageDb, watchPackageDbStack, - unwatchProject, unwatchModule, unwatchPackageDb, - isSource, isCabal, isConf, - - module System.Directory.Watcher, - module HsDev.Watcher.Types - ) where - -import Control.Lens (view) -import Control.Monad (void) -import System.FilePath (takeDirectory, takeExtension, (</>)) - -import System.Directory.Watcher hiding (Watcher) -import HsDev.Project -import HsDev.Symbols -import HsDev.Watcher.Types -import HsDev.Util - --- | Watch for project sources changes -watchProject :: Watcher -> Project -> [String] -> IO () -watchProject w proj opts = do - mapM_ (\dir -> watchTree w dir isSource (WatchedProject proj opts)) dirs - watchDir w projDir isCabal (WatchedProject proj opts) - where - dirs = map ((projDir </>) . view entity) $ maybe [] sourceDirs $ view projectDescription proj - projDir = view projectPath proj - --- | Watch for standalone source -watchModule :: Watcher -> ModuleLocation -> IO () -watchModule w (FileModule f Nothing) = watchDir w (takeDirectory f) isSource WatchedModule -watchModule w (FileModule _ (Just proj)) = watchProject w proj [] -watchModule _ _ = return () - --- | Watch for top of package-db stack -watchPackageDb :: Watcher -> PackageDbStack -> [String] -> IO () -watchPackageDb w pdbs opts = case topPackageDb pdbs of - GlobalDb -> return () -- TODO: Watch for global package-db - UserDb -> return () -- TODO: Watch for user package-db - (PackageDb pdb) -> watchTree w pdb isConf (WatchedPackageDb pdbs opts) - --- | Watch for package-db stack -watchPackageDbStack :: Watcher -> PackageDbStack -> [String] -> IO () -watchPackageDbStack w pdbs opts = mapM_ (\pdbs' -> watchPackageDb w pdbs' opts) $ packageDbStacks pdbs - -unwatchProject :: Watcher -> Project -> IO () -unwatchProject w proj = do - mapM_ (unwatchTree w) dirs - void $ unwatchDir w projDir - where - dirs = map ((projDir </>) . view entity) $ maybe [] sourceDirs $ view projectDescription proj - projDir = view projectPath proj - -unwatchModule :: Watcher -> ModuleLocation -> IO () -unwatchModule w (FileModule f Nothing) = void $ unwatchDir w (takeDirectory f) -unwatchModule _ (FileModule _ (Just _)) = return () -unwatchModule _ (InstalledModule _ _ _) = return () -unwatchModule _ _ = return () - --- | Unwatch package-db -unwatchPackageDb :: Watcher -> PackageDb -> IO () -unwatchPackageDb _ GlobalDb = return () -- TODO: Unwatch global package-db -unwatchPackageDb _ UserDb = return () -- TODO: Unwatch user package-db -unwatchPackageDb w (PackageDb pdb) = void $ unwatchTree w pdb - -isSource :: Event -> Bool -isSource = haskellSource . view eventPath - -isCabal :: Event -> Bool -isCabal = cabalFile . view eventPath - -isConf :: Event -> Bool -isConf (Event _ f _) = takeExtension f == ".conf" +module HsDev.Watcher (+ watchProject, watchModule, watchPackageDb, watchPackageDbStack,+ unwatchProject, unwatchModule, unwatchPackageDb,+ isSource, isCabal, isConf,++ module System.Directory.Watcher,+ module HsDev.Watcher.Types+ ) where++import Control.Lens (view)+import Control.Monad (void)+import System.FilePath (takeDirectory, takeExtension, (</>))++import System.Directory.Watcher hiding (Watcher)+import HsDev.Project+import HsDev.Symbols+import HsDev.Watcher.Types+import HsDev.Util++-- | Watch for project sources changes+watchProject :: Watcher -> Project -> [String] -> IO ()+watchProject w proj opts = do+ mapM_ (\dir -> watchTree w dir isSource (WatchedProject proj opts)) dirs+ watchDir w projDir isCabal (WatchedProject proj opts)+ where+ dirs = map ((projDir </>) . view entity) $ maybe [] sourceDirs $ view projectDescription proj+ projDir = view projectPath proj++-- | Watch for standalone source+watchModule :: Watcher -> ModuleLocation -> IO ()+watchModule w (FileModule f Nothing) = watchDir w (takeDirectory f) isSource WatchedModule+watchModule w (FileModule _ (Just proj)) = watchProject w proj []+watchModule _ _ = return ()++-- | Watch for top of package-db stack+watchPackageDb :: Watcher -> PackageDbStack -> [String] -> IO ()+watchPackageDb w pdbs opts = case topPackageDb pdbs of+ GlobalDb -> return () -- TODO: Watch for global package-db+ UserDb -> return () -- TODO: Watch for user package-db+ (PackageDb pdb) -> watchTree w pdb isConf (WatchedPackageDb pdbs opts)++-- | Watch for package-db stack+watchPackageDbStack :: Watcher -> PackageDbStack -> [String] -> IO ()+watchPackageDbStack w pdbs opts = mapM_ (\pdbs' -> watchPackageDb w pdbs' opts) $ packageDbStacks pdbs++unwatchProject :: Watcher -> Project -> IO ()+unwatchProject w proj = do+ mapM_ (unwatchTree w) dirs+ void $ unwatchDir w projDir+ where+ dirs = map ((projDir </>) . view entity) $ maybe [] sourceDirs $ view projectDescription proj+ projDir = view projectPath proj++unwatchModule :: Watcher -> ModuleLocation -> IO ()+unwatchModule w (FileModule f Nothing) = void $ unwatchDir w (takeDirectory f)+unwatchModule _ (FileModule _ (Just _)) = return ()+unwatchModule _ (InstalledModule _ _ _) = return ()+unwatchModule _ _ = return ()++-- | Unwatch package-db+unwatchPackageDb :: Watcher -> PackageDb -> IO ()+unwatchPackageDb _ GlobalDb = return () -- TODO: Unwatch global package-db+unwatchPackageDb _ UserDb = return () -- TODO: Unwatch user package-db+unwatchPackageDb w (PackageDb pdb) = void $ unwatchTree w pdb++isSource :: Event -> Bool+isSource = haskellSource . view eventPath++isCabal :: Event -> Bool+isCabal = cabalFile . view eventPath++isConf :: Event -> Bool+isConf (Event _ f _) = takeExtension f == ".conf"
src/System/Directory/Paths.hs view
@@ -1,31 +1,31 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} - -module System.Directory.Paths ( - Paths(..), - canonicalize, - absolutise, - relativise - ) where - -import Control.Lens -import System.Directory -import System.FilePath - --- | Something with paths inside -class Paths a where - paths :: Traversal' a FilePath - -instance Paths FilePath where - paths = id - --- | Canonicalize all paths -canonicalize :: Paths a => a -> IO a -canonicalize = paths canonicalizePath - --- | Absolutise paths -absolutise :: Paths a => FilePath -> a -> a -absolutise parent = over paths (parent </>) - --- | Relativise paths -relativise :: Paths a => FilePath -> a -> a -relativise parent = over paths (makeRelative parent) +{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}++module System.Directory.Paths (+ Paths(..),+ canonicalize,+ absolutise,+ relativise+ ) where++import Control.Lens+import System.Directory+import System.FilePath++-- | Something with paths inside+class Paths a where+ paths :: Traversal' a FilePath++instance Paths FilePath where+ paths = id++-- | Canonicalize all paths+canonicalize :: Paths a => a -> IO a+canonicalize = paths canonicalizePath++-- | Absolutise paths+absolutise :: Paths a => FilePath -> a -> a+absolutise parent = over paths (parent </>)++-- | Relativise paths+relativise :: Paths a => FilePath -> a -> a+relativise parent = over paths (makeRelative parent)
src/System/Directory/Watcher.hs view
@@ -1,151 +1,151 @@-{-# LANGUAGE PatternGuards, TemplateHaskell #-} - -module System.Directory.Watcher ( - EventType(..), Event(..), eventType, eventPath, eventTime, - Watcher(..), - withWatcher, - watchDir, watchDir_, unwatchDir, isWatchingDir, - watchTree, watchTree_, unwatchTree, isWatchingTree, - -- * Working with events - readEvent, events, onEvent - ) where - -import Control.Lens (makeLenses) -import Control.Arrow -import Control.Concurrent.MVar -import Control.Concurrent.Chan -import Control.Monad -import Data.Map (Map) -import qualified Data.Map as M -import Data.Maybe (isJust) -import Data.String (fromString) -import Data.Time.Clock.POSIX -import System.FilePath (takeDirectory, isDrive) -import System.Directory -import qualified System.FSNotify as FS - --- | Event type -data EventType = Added | Modified | Removed deriving (Eq, Ord, Enum, Bounded, Read, Show) - --- | Event -data Event = Event { - _eventType :: EventType, - _eventPath :: FilePath, - _eventTime :: POSIXTime } - -makeLenses ''Event - --- | Directories watcher -data Watcher a = Watcher { - -- | Map from directory to watch stopper - watcherDirs :: MVar (Map FilePath (Bool, IO ())), - watcherMan :: FS.WatchManager, - watcherChan :: Chan (a, Event) } - --- | Create watcher -withWatcher :: (Watcher a -> IO b) -> IO b -withWatcher act = FS.withManager $ \man -> do - ch <- newChan - dirs <- newMVar M.empty - act $ Watcher dirs man ch - --- | Watch directory -watchDir :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO () -watchDir w f p v = do - e <- doesDirectoryExist f - when e $ do - f' <- canonicalizePath f - watching <- isWatchingDir w f' - unless watching $ do - stop <- FS.watchDir - (watcherMan w) - (fromString f') - (p . fromEvent) - (writeChan (watcherChan w) . (,) v . fromEvent) - modifyMVar_ (watcherDirs w) $ return . M.insert f' (False, stop) - -watchDir_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO () -watchDir_ w f p = watchDir w f p () - --- | Unwatch directory, return @False@, if not watched -unwatchDir :: Watcher a -> FilePath -> IO Bool -unwatchDir w f = do - f' <- canonicalizePath f - stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f') - maybe (return ()) snd stop - return $ isJust stop - --- | Check if we are watching dir -isWatchingDir :: Watcher a -> FilePath -> IO Bool -isWatchingDir w f = do - f' <- canonicalizePath f - dirs <- readMVar (watcherDirs w) - return $ isWatchingDir' dirs f' || isWatchingParents' dirs f' - --- | Watch directory tree -watchTree :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO () -watchTree w f p v = do - e <- doesDirectoryExist f - when e $ do - f' <- canonicalizePath f - watching <- isWatchingTree w f' - unless watching $ do - stop <- FS.watchTree - (watcherMan w) - (fromString f') - (p . fromEvent) - (writeChan (watcherChan w) . (,) v . fromEvent) - modifyMVar_ (watcherDirs w) $ return . M.insert f' (True, stop) - -watchTree_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO () -watchTree_ w f p = watchTree w f p () - --- | Unwatch directory tree -unwatchTree :: Watcher a -> FilePath -> IO Bool -unwatchTree w f = do - f' <- canonicalizePath f - stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f') - maybe (return ()) snd stop - return $ isJust stop - --- | Check if we are watching tree -isWatchingTree :: Watcher a -> FilePath -> IO Bool -isWatchingTree w f = do - f' <- canonicalizePath f - dirs <- readMVar (watcherDirs w) - return $ isWatchingTree' dirs f' || isWatchingParents' dirs f' - --- | Read next event -readEvent :: Watcher a -> IO (a, Event) -readEvent = readChan . watcherChan - --- | Get lazy list of events -events :: Watcher a -> IO [(a, Event)] -events = getChanContents . watcherChan - --- | Process all events -onEvent :: Watcher a -> (a -> Event -> IO ()) -> IO () -onEvent w act = events w >>= mapM_ (uncurry act) - -fromEvent :: FS.Event -> Event -fromEvent e = Event t (FS.eventPath e) (utcTimeToPOSIXSeconds $ FS.eventTime e) where - t = case e of - FS.Added _ _ -> Added - FS.Modified _ _ -> Modified - FS.Removed _ _ -> Removed - -isWatchingDir' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool -isWatchingDir' m dir - | Just (_, _) <- M.lookup dir m = True - | isDrive dir = False - | otherwise = isWatchingDir' m (takeDirectory dir) - -isWatchingTree' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool -isWatchingTree' m dir - | Just (True, _) <- M.lookup dir m = True - | isDrive dir = False - | otherwise = isWatchingTree' m (takeDirectory dir) - -isWatchingParents' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool -isWatchingParents' m dir = or (map (isWatchingTree' m) parents) where - parents = takeWhile (not . isDrive) $ iterate takeDirectory dir +{-# LANGUAGE PatternGuards, TemplateHaskell #-}++module System.Directory.Watcher (+ EventType(..), Event(..), eventType, eventPath, eventTime,+ Watcher(..),+ withWatcher,+ watchDir, watchDir_, unwatchDir, isWatchingDir,+ watchTree, watchTree_, unwatchTree, isWatchingTree,+ -- * Working with events+ readEvent, events, onEvent+ ) where++import Control.Lens (makeLenses)+import Control.Arrow+import Control.Concurrent.MVar+import Control.Concurrent.Chan+import Control.Monad+import Data.Map (Map)+import qualified Data.Map as M+import Data.Maybe (isJust)+import Data.String (fromString)+import Data.Time.Clock.POSIX+import System.FilePath (takeDirectory, isDrive)+import System.Directory+import qualified System.FSNotify as FS++-- | Event type+data EventType = Added | Modified | Removed deriving (Eq, Ord, Enum, Bounded, Read, Show)++-- | Event+data Event = Event {+ _eventType :: EventType,+ _eventPath :: FilePath,+ _eventTime :: POSIXTime }++makeLenses ''Event++-- | Directories watcher+data Watcher a = Watcher {+ -- | Map from directory to watch stopper+ watcherDirs :: MVar (Map FilePath (Bool, IO ())),+ watcherMan :: FS.WatchManager,+ watcherChan :: Chan (a, Event) }++-- | Create watcher+withWatcher :: (Watcher a -> IO b) -> IO b+withWatcher act = FS.withManager $ \man -> do+ ch <- newChan+ dirs <- newMVar M.empty+ act $ Watcher dirs man ch++-- | Watch directory+watchDir :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO ()+watchDir w f p v = do+ e <- doesDirectoryExist f+ when e $ do+ f' <- canonicalizePath f+ watching <- isWatchingDir w f'+ unless watching $ do+ stop <- FS.watchDir+ (watcherMan w)+ (fromString f')+ (p . fromEvent)+ (writeChan (watcherChan w) . (,) v . fromEvent)+ modifyMVar_ (watcherDirs w) $ return . M.insert f' (False, stop)++watchDir_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO ()+watchDir_ w f p = watchDir w f p ()++-- | Unwatch directory, return @False@, if not watched+unwatchDir :: Watcher a -> FilePath -> IO Bool+unwatchDir w f = do+ f' <- canonicalizePath f+ stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f')+ maybe (return ()) snd stop+ return $ isJust stop++-- | Check if we are watching dir+isWatchingDir :: Watcher a -> FilePath -> IO Bool+isWatchingDir w f = do+ f' <- canonicalizePath f+ dirs <- readMVar (watcherDirs w)+ return $ isWatchingDir' dirs f' || isWatchingParents' dirs f'++-- | Watch directory tree+watchTree :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO ()+watchTree w f p v = do+ e <- doesDirectoryExist f+ when e $ do+ f' <- canonicalizePath f+ watching <- isWatchingTree w f'+ unless watching $ do+ stop <- FS.watchTree+ (watcherMan w)+ (fromString f')+ (p . fromEvent)+ (writeChan (watcherChan w) . (,) v . fromEvent)+ modifyMVar_ (watcherDirs w) $ return . M.insert f' (True, stop)++watchTree_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO ()+watchTree_ w f p = watchTree w f p ()++-- | Unwatch directory tree+unwatchTree :: Watcher a -> FilePath -> IO Bool+unwatchTree w f = do+ f' <- canonicalizePath f+ stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f')+ maybe (return ()) snd stop+ return $ isJust stop++-- | Check if we are watching tree+isWatchingTree :: Watcher a -> FilePath -> IO Bool+isWatchingTree w f = do+ f' <- canonicalizePath f+ dirs <- readMVar (watcherDirs w)+ return $ isWatchingTree' dirs f' || isWatchingParents' dirs f'++-- | Read next event+readEvent :: Watcher a -> IO (a, Event)+readEvent = readChan . watcherChan++-- | Get lazy list of events+events :: Watcher a -> IO [(a, Event)]+events = getChanContents . watcherChan++-- | Process all events+onEvent :: Watcher a -> (a -> Event -> IO ()) -> IO ()+onEvent w act = events w >>= mapM_ (uncurry act)++fromEvent :: FS.Event -> Event+fromEvent e = Event t (FS.eventPath e) (utcTimeToPOSIXSeconds $ FS.eventTime e) where+ t = case e of+ FS.Added _ _ -> Added+ FS.Modified _ _ -> Modified+ FS.Removed _ _ -> Removed++isWatchingDir' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool+isWatchingDir' m dir+ | Just (_, _) <- M.lookup dir m = True+ | isDrive dir = False+ | otherwise = isWatchingDir' m (takeDirectory dir)++isWatchingTree' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool+isWatchingTree' m dir+ | Just (True, _) <- M.lookup dir m = True+ | isDrive dir = False+ | otherwise = isWatchingTree' m (takeDirectory dir)++isWatchingParents' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool+isWatchingParents' m dir = or (map (isWatchingTree' m) parents) where+ parents = takeWhile (not . isDrive) $ iterate takeDirectory dir
src/System/Win32/PowerShell.hs view
@@ -1,214 +1,214 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, LambdaCase #-} - -module System.Win32.PowerShell ( - -- * Run PowerShell - ps, - -- * PowerShell functions - startProcess, codepage, outNull, - -- * PowerShell monad - PS(..), seqPS, emit, emit_, (=:), invoke, - -- * Expression - Args(..), CmdLet(..), Expr(..), compile, - raw, name, lit, var, bra, cbra, (.=), flag, named, call, cmdlet, lambda, (.|), foreach, filter, - -- * Convertible - ToPS(..), - -- * Escape functions - translate, translateArg, - quote, quoteDouble, - escape - ) where - -import Prelude hiding (filter) - -import Control.Monad -import Control.Monad.Writer -import Data.Char (isAlphaNum) -import Data.List (intercalate) -import qualified Data.List as List (filter) -import Data.Map (Map) -import qualified Data.Map as M - -import HsDev.Tools.Base (ToolM, tool_) - -ps :: PS a -> ToolM String -ps script = tool_ "powershell" ["-Command", compile $ seqPS script] - --- start-process -startProcess :: String -> [String] -> CmdLet -startProcess n as = cmdlet "start-process" [name n, lit $ intercalate ", " (map translateArg as)] [named "WindowStyle" $ lit "Hidden"] - --- chcp -codepage :: Int -> CmdLet -codepage n = cmdlet "chcp" [lit n] [] - --- out-null -outNull :: CmdLet -outNull = cmdlet "out-null" [] [] - -newtype PS a = PS { unPS :: Writer [Expr] a } - deriving (Applicative, Functor, Monad, MonadWriter [Expr]) - -seqPS :: PS a -> Expr -seqPS (PS act) = case execWriter act of - [] -> error "No expressions" - es -> foldr1 Sequence es - -emit :: Expr -> PS Expr -emit e = PS $ tell [e] >> return e - -emit_ :: Expr -> PS () -emit_ = void . emit - -infixr 6 =: -(=:) :: String -> Expr -> PS Expr -n =: expr = emit_ (n .= expr) >> return (var n) - --- | Invoke cmdlet -invoke :: CmdLet -> PS () -invoke = emit_ . Invoke - --- | Positional and named args -data Args = Args [Expr] (Map String (Maybe Expr)) - -instance Monoid Args where - mempty = Args [] M.empty - mappend (Args lp ln) (Args rp rn) = Args (lp ++ rp) (M.union ln rn) - --- | Call to cmdlet -data CmdLet = CmdLet { - cmdLet :: Expr, - cmdArgs :: Args } - --- | Expression -data Expr = - Emit String | - -- ^ native expression - Literal String | - -- ^ literal - Var String | - -- ^ $name - Bracket Expr | - -- ^ (expr) - Assign [String] [Expr] | - -- ^ $x, $y, ... = expr1, expr2, ... - Invoke CmdLet | - -- ^ cmd args... named... - Lambda [String] ([Expr] -> Expr) | - -- ^ { param($...); expr } - Sequence Expr Expr | - -- ^ expr1; expr2 - Pipe Expr CmdLet - -- ^ expr | cmd - -compile :: Expr -> String -compile (Emit s) = s -compile (Literal l) = l -compile (Var v) = '$':v -compile (Bracket e) = "(" ++ compile e ++ ")" -compile (Assign vs es) = intercalate ", " (map ('$':) vs) ++ " = " ++ intercalate ", " (map (compile . cbra) es) -compile (Invoke (CmdLet e (Args p ns))) = unwords $ List.filter (not . null) [invoke', p', ns'] where - invoke' = case e of - Var n -> n - _ -> unwords ["&", compile $ cbra e] - p' = unwords $ map (compile . cbra) p - ns' = unwords $ concatMap named' $ M.toList ns where - named' :: (String, Maybe Expr) -> [String] - named' (n, Just v) = ['-':n, compile $ cbra v] - named' (n, Nothing) = ['-':n] --- { param($File); $args[0]; } -compile (Lambda ns body) = unwords ["{", param', body', "}"] where - param' = "param(" ++ intercalate "," ns ++ ");" - body' = compile $ body (map var ns) -compile (Sequence l r) = compile l ++ "; " ++ compile r -compile (Pipe e c) = compile e ++ " | " ++ compile (Invoke c) - -raw :: String -> Expr -raw = Emit - -name :: String -> Expr -name = Literal - -lit :: ToPS a => a -> Expr -lit = Literal . toPS - -var :: String -> Expr -var = Var - -bra :: Expr -> Expr -bra = Bracket - -cbra :: Expr -> Expr -cbra e@(Literal _) = e -cbra v@(Var _) = v -cbra b = bra b - -infixr 6 .= -(.=) :: String -> Expr -> Expr -v .= e = Assign [v] [e] - -flag :: String -> (String, Maybe Expr) -flag n = (n, Nothing) - -named :: String -> Expr -> (String, Maybe Expr) -named n e = (n, Just e) - -call :: Expr -> [Expr] -> [(String, Maybe Expr)] -> CmdLet -call f pos named' = CmdLet f $ Args pos (M.fromList named') - -cmdlet :: String -> [Expr] -> [(String, Maybe Expr)] -> CmdLet -cmdlet n = call (name n) - -lambda :: [String] -> ([Expr] -> Expr) -> Expr -lambda = Lambda - -infixr 6 .| -(.|) :: Expr -> CmdLet -> Expr -e .| c = Pipe e c - -foreach :: (Expr -> Expr) -> CmdLet -foreach f = cmdlet "%" [f $ var "_"] [] - -filter :: (Expr -> Expr) -> CmdLet -filter p = cmdlet "?" [p $ var "_"] [] - -class ToPS a where - toPS :: a -> String - -instance ToPS Int where - toPS = show - -instance ToPS String where - toPS = translate - -instance ToPS Bool where - toPS True = "$true" - toPS False = "$false" - -instance {-# OVERLAPPABLE #-} ToPS a => ToPS [a] where - toPS = intercalate ", " . map toPS - -translate :: String -> String -translate s - | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s - | otherwise = '"' : snd (foldr escape' (True, "\"") s) - where - escape' '"' (_, s') = (True, '\\' : '"' : s') - escape' '\\' (True, s') = (True, '\\' : '\\' : s') - escape' '\\' (False, s') = (False, '\\' : s') - escape' c (_, s') = (False, c : s') - -translateArg :: String -> String -translateArg s - | all isAlphaNum s = s - | otherwise = "'" ++ translate s ++ "'" - -quote :: String -> String -quote s = "'" ++ concatMap (\case { '\'' -> "''"; ch -> [ch] }) s ++ "'" - -quoteDouble :: String -> String -quoteDouble s = "\"" ++ concatMap (\case { '"' -> "\"\""; ch -> [ch] }) s ++ "\"" - -escape :: (String -> String) -> String -> String -escape fn s - | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s - | otherwise = fn s +{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, LambdaCase #-}++module System.Win32.PowerShell (+ -- * Run PowerShell+ ps,+ -- * PowerShell functions+ startProcess, codepage, outNull,+ -- * PowerShell monad+ PS(..), seqPS, emit, emit_, (=:), invoke,+ -- * Expression+ Args(..), CmdLet(..), Expr(..), compile,+ raw, name, lit, var, bra, cbra, (.=), flag, named, call, cmdlet, lambda, (.|), foreach, filter,+ -- * Convertible+ ToPS(..),+ -- * Escape functions+ translate, translateArg,+ quote, quoteDouble,+ escape+ ) where++import Prelude hiding (filter)++import Control.Monad+import Control.Monad.Writer+import Data.Char (isAlphaNum)+import Data.List (intercalate)+import qualified Data.List as List (filter)+import Data.Map (Map)+import qualified Data.Map as M++import HsDev.Tools.Base (ToolM, tool_)++ps :: PS a -> ToolM String+ps script = tool_ "powershell" ["-Command", compile $ seqPS script]++-- start-process+startProcess :: String -> [String] -> CmdLet+startProcess n as = cmdlet "start-process" [name n, lit $ intercalate ", " (map translateArg as)] [named "WindowStyle" $ lit "Hidden"]++-- chcp+codepage :: Int -> CmdLet+codepage n = cmdlet "chcp" [lit n] []++-- out-null+outNull :: CmdLet+outNull = cmdlet "out-null" [] []++newtype PS a = PS { unPS :: Writer [Expr] a }+ deriving (Applicative, Functor, Monad, MonadWriter [Expr])++seqPS :: PS a -> Expr+seqPS (PS act) = case execWriter act of+ [] -> error "No expressions"+ es -> foldr1 Sequence es++emit :: Expr -> PS Expr+emit e = PS $ tell [e] >> return e++emit_ :: Expr -> PS ()+emit_ = void . emit++infixr 6 =:+(=:) :: String -> Expr -> PS Expr+n =: expr = emit_ (n .= expr) >> return (var n)++-- | Invoke cmdlet+invoke :: CmdLet -> PS ()+invoke = emit_ . Invoke++-- | Positional and named args+data Args = Args [Expr] (Map String (Maybe Expr))++instance Monoid Args where+ mempty = Args [] M.empty+ mappend (Args lp ln) (Args rp rn) = Args (lp ++ rp) (M.union ln rn)++-- | Call to cmdlet+data CmdLet = CmdLet {+ cmdLet :: Expr,+ cmdArgs :: Args }++-- | Expression+data Expr =+ Emit String |+ -- ^ native expression+ Literal String |+ -- ^ literal+ Var String |+ -- ^ $name+ Bracket Expr |+ -- ^ (expr)+ Assign [String] [Expr] |+ -- ^ $x, $y, ... = expr1, expr2, ...+ Invoke CmdLet |+ -- ^ cmd args... named...+ Lambda [String] ([Expr] -> Expr) |+ -- ^ { param($...); expr }+ Sequence Expr Expr |+ -- ^ expr1; expr2+ Pipe Expr CmdLet+ -- ^ expr | cmd++compile :: Expr -> String+compile (Emit s) = s+compile (Literal l) = l+compile (Var v) = '$':v+compile (Bracket e) = "(" ++ compile e ++ ")"+compile (Assign vs es) = intercalate ", " (map ('$':) vs) ++ " = " ++ intercalate ", " (map (compile . cbra) es)+compile (Invoke (CmdLet e (Args p ns))) = unwords $ List.filter (not . null) [invoke', p', ns'] where+ invoke' = case e of+ Var n -> n+ _ -> unwords ["&", compile $ cbra e]+ p' = unwords $ map (compile . cbra) p+ ns' = unwords $ concatMap named' $ M.toList ns where+ named' :: (String, Maybe Expr) -> [String]+ named' (n, Just v) = ['-':n, compile $ cbra v]+ named' (n, Nothing) = ['-':n]+-- { param($File); $args[0]; }+compile (Lambda ns body) = unwords ["{", param', body', "}"] where+ param' = "param(" ++ intercalate "," ns ++ ");"+ body' = compile $ body (map var ns)+compile (Sequence l r) = compile l ++ "; " ++ compile r+compile (Pipe e c) = compile e ++ " | " ++ compile (Invoke c)++raw :: String -> Expr+raw = Emit++name :: String -> Expr+name = Literal++lit :: ToPS a => a -> Expr+lit = Literal . toPS++var :: String -> Expr+var = Var++bra :: Expr -> Expr+bra = Bracket++cbra :: Expr -> Expr+cbra e@(Literal _) = e+cbra v@(Var _) = v+cbra b = bra b++infixr 6 .= +(.=) :: String -> Expr -> Expr+v .= e = Assign [v] [e]++flag :: String -> (String, Maybe Expr)+flag n = (n, Nothing)++named :: String -> Expr -> (String, Maybe Expr)+named n e = (n, Just e)++call :: Expr -> [Expr] -> [(String, Maybe Expr)] -> CmdLet+call f pos named' = CmdLet f $ Args pos (M.fromList named')++cmdlet :: String -> [Expr] -> [(String, Maybe Expr)] -> CmdLet+cmdlet n = call (name n)++lambda :: [String] -> ([Expr] -> Expr) -> Expr+lambda = Lambda++infixr 6 .|+(.|) :: Expr -> CmdLet -> Expr+e .| c = Pipe e c++foreach :: (Expr -> Expr) -> CmdLet+foreach f = cmdlet "%" [f $ var "_"] []++filter :: (Expr -> Expr) -> CmdLet+filter p = cmdlet "?" [p $ var "_"] []++class ToPS a where+ toPS :: a -> String++instance ToPS Int where+ toPS = show++instance ToPS String where+ toPS = translate++instance ToPS Bool where+ toPS True = "$true"+ toPS False = "$false"++instance {-# OVERLAPPABLE #-} ToPS a => ToPS [a] where+ toPS = intercalate ", " . map toPS++translate :: String -> String+translate s+ | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s+ | otherwise = '"' : snd (foldr escape' (True, "\"") s)+ where+ escape' '"' (_, s') = (True, '\\' : '"' : s')+ escape' '\\' (True, s') = (True, '\\' : '\\' : s')+ escape' '\\' (False, s') = (False, '\\' : s')+ escape' c (_, s') = (False, c : s')++translateArg :: String -> String+translateArg s+ | all isAlphaNum s = s+ | otherwise = "'" ++ translate s ++ "'"++quote :: String -> String+quote s = "'" ++ concatMap (\case { '\'' -> "''"; ch -> [ch] }) s ++ "'"++quoteDouble :: String -> String+quoteDouble s = "\"" ++ concatMap (\case { '"' -> "\"\""; ch -> [ch] }) s ++ "\""++escape :: (String -> String) -> String -> String+escape fn s+ | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s+ | otherwise = fn s
tests/Test.hs view
@@ -1,47 +1,47 @@-{-# LANGUAGE OverloadedStrings #-} - -module Main ( - main - ) where - -import Control.Lens -import Control.Exception (displayException) -import Data.Aeson hiding (Error) -import Data.Aeson.Lens -import Data.Default -import HsDev -import Test.Hspec -import System.FilePath -import System.Directory - -call :: Server -> Command -> IO (Maybe Value) -call srv c = do - r <- inServer srv def c - case r of - Result v -> return $ Just v - Error e -> do - expectationFailure $ "command result error: " ++ displayException e - return Nothing - -exports :: Maybe Value -> [String] -exports v = v ^.. key "exports" . traverseArray . each . key "name" . each . _Just - -main :: IO () -main = hspec $ do - describe "scan project" $ do - dir <- runIO getCurrentDirectory - s <- runIO $ startServer (def { serverSilent = True }) - it "should scan project" $ do - _ <- call s $ Scan [dir </> "tests/test-package"] False [] [] [] [] False False - one <- call s $ InfoResolve (dir </> "tests/test-package/ModuleOne.hs") True - when (["test", "forkIO", "untypedFoo"] /= exports one) $ - expectationFailure "invalid exports of ModuleOne.hs" - two <- call s $ InfoResolve (dir </> "tests/test-package/ModuleTwo.hs") True - when (["untypedFoo", "twice", "overloadedStrings"] /= exports two) $ - expectationFailure "invalid exports of ModuleTwo.hs" - it "should pass extensions when checkings" $ do - checks <- call s (Check [FileSource (dir </> "tests/test-package/ModuleTwo.hs") Nothing] []) - when (("error" :: String) `elem` (checks ^.. traverseArray . key "level" . _Just)) $ - expectationFailure "there should be no errors, only warnings" - _ <- runIO $ call s Exit - return () +{-# LANGUAGE OverloadedStrings #-}++module Main (+ main+ ) where++import Control.Lens+import Control.Exception (displayException)+import Data.Aeson hiding (Error)+import Data.Aeson.Lens+import Data.Default+import HsDev+import Test.Hspec+import System.FilePath+import System.Directory++call :: Server -> Command -> IO (Maybe Value)+call srv c = do+ r <- inServer srv def c+ case r of+ Result v -> return $ Just v+ Error e -> do+ expectationFailure $ "command result error: " ++ displayException e+ return Nothing++exports :: Maybe Value -> [String]+exports v = v ^.. key "exports" . traverseArray . each . key "name" . each . _Just++main :: IO ()+main = hspec $ do+ describe "scan project" $ do+ dir <- runIO getCurrentDirectory+ s <- runIO $ startServer (def { serverSilent = True })+ it "should scan project" $ do+ _ <- call s $ Scan [dir </> "tests/test-package"] False [] [] [] [] False False+ one <- call s $ InfoResolve (dir </> "tests/test-package/ModuleOne.hs") True+ when (["test", "forkIO", "untypedFoo"] /= exports one) $+ expectationFailure "invalid exports of ModuleOne.hs"+ two <- call s $ InfoResolve (dir </> "tests/test-package/ModuleTwo.hs") True+ when (["untypedFoo", "twice", "overloadedStrings"] /= exports two) $+ expectationFailure "invalid exports of ModuleTwo.hs"+ it "should pass extensions when checkings" $ do+ checks <- call s (Check [FileSource (dir </> "tests/test-package/ModuleTwo.hs") Nothing] [])+ when (("error" :: String) `elem` (checks ^.. traverseArray . key "level" . _Just)) $+ expectationFailure "there should be no errors, only warnings"+ _ <- runIO $ call s Exit+ return ()
tests/test-package/ModuleOne.hs view
@@ -1,14 +1,14 @@-module ModuleOne ( - test, - forkIO, - untypedFoo - ) where - -import Control.Concurrent (forkIO) - --- | Some test function -test :: IO () -test = return () - --- | Some function without type -untypedFoo x y = x + y +module ModuleOne (+ test,+ forkIO,+ untypedFoo+ ) where++import Control.Concurrent (forkIO)++-- | Some test function+test :: IO ()+test = return ()++-- | Some function without type+untypedFoo x y = x + y
tools/hsautofix.hs view
@@ -1,87 +1,87 @@-{-# LANGUAGE LambdaCase #-} - -module Main ( - main - ) where - -import Control.Lens (preview) -import Control.Arrow ((***)) -import Control.Monad (liftM) -import Control.Monad.IO.Class (liftIO) -import Control.Monad.Except (throwError) -import Data.Aeson hiding (Error) -import Data.List (partition, sort) -import Data.Maybe (mapMaybe) -import System.FilePath (normalise) -import System.Directory (canonicalizePath) -import Text.Read (readMaybe) - -import System.Directory.Paths (canonicalize) -import HsDev.Symbols (moduleFile) -import HsDev.Symbols.Location (ModuleLocation(..), regionAt, Position(..)) -import HsDev.Tools.Base -import HsDev.Tools.Ghc.Check (recalcNotesTabs) -import HsDev.Tools.AutoFix -import HsDev.Util (toUtf8, liftE, readFileUtf8, writeFileUtf8, ordNub) - -import Tool - -data FixCmd = ShowCmd Bool | FixCmd [Int] Bool - -parseOutputMessages :: String -> [Note OutputMessage] -parseOutputMessages = mapMaybe parseOutputMessage . lines - -parseOutputMessage :: String -> Maybe (Note OutputMessage) -parseOutputMessage s = do - groups <- matchRx "^(.+):(\\d+):(\\d+):(\\s*(Warning|Error):)?\\s*(.*)$" s - l <- readMaybe (groups `at` 2) - c <- readMaybe (groups `at` 3) - return Note { - _noteSource = FileModule (normalise (groups `at` 1)) Nothing, - _noteRegion = regionAt (Position l c), - _noteLevel = Just $ if groups 5 == Just "Warning" then Warning else Error, - _note = outputMessage $ nullToNL (groups `at` 6) } - --- | Replace NULL with newline -nullToNL :: String -> String -nullToNL = map $ \case - '\0' -> '\n' - ch -> ch - -fixP :: Parser FixCmd -fixP = subparser $ mconcat [ - cmd "show" "show what can be auto-fixed" (ShowCmd <$> switch (long "json" <> help "output messages in JSON format")), - cmd "fix" "fix selected errors" (FixCmd <$> - many (argument auto (metavar "N" <> help "indices of corrections to apply")) <*> - switch (long "pure" <> help "don't modify files, just return updated corrections"))] - -main :: IO () -main = toolMain "hsautofix" "automatically fix some errors" fixP (printExceptT . printResult . go) where - go (ShowCmd isJson) = do - input <- liftE getContents - msgs <- if isJson - then maybe (throwError "Can't parse messages") return $ decode (toUtf8 input) - else liftIO $ recalcNotesTabs (parseOutputMessages input) - mapM (liftE . canonicalize) $ corrections msgs - go (FixCmd ns pure') = do - input <- liftE getContents - corrs <- maybe (throwError "Can't parse messages") return $ decode (toUtf8 input) - let - check i = i `elem` ns || null ns - (fixCorrs, upCorrs) = (map snd *** map snd) $ - partition (check . fst) $ zip [1..] corrs - files <- liftE $ mapM canonicalizePath $ ordNub $ sort $ mapMaybe (preview $ noteSource . moduleFile) corrs - let - doFix :: FilePath -> Maybe String -> ([Note Correction], Maybe String) - doFix file mcts = autoFix fixCorrs' (upCorrs', mcts) where - findCorrs :: FilePath -> [Note Correction] -> [Note Correction] - findCorrs f = filter ((== Just f) . preview (noteSource . moduleFile)) - fixCorrs' = findCorrs file fixCorrs - upCorrs' = findCorrs file upCorrs - runFix file - | pure' = return $ fst $ doFix file Nothing - | otherwise = do - (corrs', Just cts') <- liftM (doFix file) $ liftE $ liftM Just $ readFileUtf8 file - liftE $ writeFileUtf8 file cts' - return corrs' - liftM concat $ mapM runFix files +{-# LANGUAGE LambdaCase #-}++module Main (+ main+ ) where++import Control.Lens (preview)+import Control.Arrow ((***))+import Control.Monad (liftM)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Except (throwError)+import Data.Aeson hiding (Error)+import Data.List (partition, sort)+import Data.Maybe (mapMaybe)+import System.FilePath (normalise)+import System.Directory (canonicalizePath)+import Text.Read (readMaybe)++import System.Directory.Paths (canonicalize)+import HsDev.Symbols (moduleFile)+import HsDev.Symbols.Location (ModuleLocation(..), regionAt, Position(..))+import HsDev.Tools.Base+import HsDev.Tools.Ghc.Check (recalcNotesTabs)+import HsDev.Tools.AutoFix+import HsDev.Util (toUtf8, liftE, readFileUtf8, writeFileUtf8, ordNub)++import Tool++data FixCmd = ShowCmd Bool | FixCmd [Int] Bool++parseOutputMessages :: String -> [Note OutputMessage]+parseOutputMessages = mapMaybe parseOutputMessage . lines++parseOutputMessage :: String -> Maybe (Note OutputMessage)+parseOutputMessage s = do+ groups <- matchRx "^(.+):(\\d+):(\\d+):(\\s*(Warning|Error):)?\\s*(.*)$" s+ l <- readMaybe (groups `at` 2)+ c <- readMaybe (groups `at` 3)+ return Note {+ _noteSource = FileModule (normalise (groups `at` 1)) Nothing,+ _noteRegion = regionAt (Position l c),+ _noteLevel = Just $ if groups 5 == Just "Warning" then Warning else Error,+ _note = outputMessage $ nullToNL (groups `at` 6) }++-- | Replace NULL with newline+nullToNL :: String -> String+nullToNL = map $ \case+ '\0' -> '\n'+ ch -> ch++fixP :: Parser FixCmd+fixP = subparser $ mconcat [+ cmd "show" "show what can be auto-fixed" (ShowCmd <$> switch (long "json" <> help "output messages in JSON format")),+ cmd "fix" "fix selected errors" (FixCmd <$>+ many (argument auto (metavar "N" <> help "indices of corrections to apply")) <*>+ switch (long "pure" <> help "don't modify files, just return updated corrections"))]++main :: IO ()+main = toolMain "hsautofix" "automatically fix some errors" fixP (printExceptT . printResult . go) where+ go (ShowCmd isJson) = do+ input <- liftE getContents+ msgs <- if isJson+ then maybe (throwError "Can't parse messages") return $ decode (toUtf8 input)+ else liftIO $ recalcNotesTabs (parseOutputMessages input)+ mapM (liftE . canonicalize) $ corrections msgs+ go (FixCmd ns pure') = do+ input <- liftE getContents+ corrs <- maybe (throwError "Can't parse messages") return $ decode (toUtf8 input)+ let+ check i = i `elem` ns || null ns+ (fixCorrs, upCorrs) = (map snd *** map snd) $+ partition (check . fst) $ zip [1..] corrs+ files <- liftE $ mapM canonicalizePath $ ordNub $ sort $ mapMaybe (preview $ noteSource . moduleFile) corrs+ let+ doFix :: FilePath -> Maybe String -> ([Note Correction], Maybe String)+ doFix file mcts = autoFix fixCorrs' (upCorrs', mcts) where+ findCorrs :: FilePath -> [Note Correction] -> [Note Correction]+ findCorrs f = filter ((== Just f) . preview (noteSource . moduleFile))+ fixCorrs' = findCorrs file fixCorrs+ upCorrs' = findCorrs file upCorrs+ runFix file+ | pure' = return $ fst $ doFix file Nothing+ | otherwise = do+ (corrs', Just cts') <- liftM (doFix file) $ liftE $ liftM Just $ readFileUtf8 file+ liftE $ writeFileUtf8 file cts'+ return corrs'+ liftM concat $ mapM runFix files
tools/hscabal.hs view
@@ -1,10 +1,10 @@-module Main ( - main - ) where - -import HsDev.Tools.Cabal - -import Tool - -main :: IO () -main = toolMain "hscabal" "cabal tool" (many (strArgument (metavar "package"))) $ printExceptT . printResult . cabalList +module Main (+ main+ ) where++import HsDev.Tools.Cabal++import Tool++main :: IO ()+main = toolMain "hscabal" "cabal tool" (many (strArgument (metavar "package"))) $ printExceptT . printResult . cabalList
tools/hsclearimports.hs view
@@ -1,44 +1,44 @@-module Main ( - main - ) where - -import Control.Lens (view) -import Control.Exception (finally) -import Control.Monad.Except -import System.Directory - -import HsDev.Tools.ClearImports (clearImports) -import HsDev.Symbols (locateSourceDir) -import HsDev.Project (entity) - -import Tool - -data Opts = Opts { - optsFile :: String, - optsGHC :: [String], - optsHideImportList :: Bool, - optsMaxImportList :: Maybe Int } - -opts :: Parser Opts -opts = Opts <$> - strArgument (metavar "file" <> help "file to clear imports in") <*> - many (strOption (long "ghc" <> short 'g' <> metavar "GHC_OPT" <> help "options for GHC")) <*> - switch (long "hide-import-list" <> help "hide import list") <*> - optional (option auto (long "max-import-list" <> metavar "N" <> help "hide long import lists")) - -main :: IO () -main = toolMain "hsclearimports" "clears imports in haskell source" opts $ \opts' -> do - file <- canonicalizePath (optsFile opts') - mroot <- liftM (fmap $ view entity) $ locateSourceDir file - cur <- getCurrentDirectory - flip finally (setCurrentDirectory cur) $ do - maybe (return ()) setCurrentDirectory mroot - void $ runExceptT $ catchError - (clearImports (optsGHC opts') file >>= mapM_ (liftIO . putStrLn . format opts')) - (\e -> liftIO (putStrLn $ "Error: " ++ e)) - where - format :: Opts -> (String, String) -> String - format as (imp, lst) - | optsHideImportList as = imp - | maybe False (length lst >) (optsMaxImportList as) = imp - | otherwise = imp ++ " (" ++ lst ++ ")" +module Main (+ main+ ) where++import Control.Lens (view)+import Control.Exception (finally)+import Control.Monad.Except+import System.Directory++import HsDev.Tools.ClearImports (clearImports)+import HsDev.Symbols (locateSourceDir)+import HsDev.Project (entity)++import Tool++data Opts = Opts {+ optsFile :: String,+ optsGHC :: [String],+ optsHideImportList :: Bool,+ optsMaxImportList :: Maybe Int }++opts :: Parser Opts+opts = Opts <$>+ strArgument (metavar "file" <> help "file to clear imports in") <*>+ many (strOption (long "ghc" <> short 'g' <> metavar "GHC_OPT" <> help "options for GHC")) <*>+ switch (long "hide-import-list" <> help "hide import list") <*>+ optional (option auto (long "max-import-list" <> metavar "N" <> help "hide long import lists"))++main :: IO ()+main = toolMain "hsclearimports" "clears imports in haskell source" opts $ \opts' -> do+ file <- canonicalizePath (optsFile opts')+ mroot <- liftM (fmap $ view entity) $ locateSourceDir file+ cur <- getCurrentDirectory+ flip finally (setCurrentDirectory cur) $ do+ maybe (return ()) setCurrentDirectory mroot+ void $ runExceptT $ catchError+ (clearImports (optsGHC opts') file >>= mapM_ (liftIO . putStrLn . format opts'))+ (\e -> liftIO (putStrLn $ "Error: " ++ e))+ where+ format :: Opts -> (String, String) -> String+ format as (imp, lst)+ | optsHideImportList as = imp+ | maybe False (length lst >) (optsMaxImportList as) = imp+ | otherwise = imp ++ " (" ++ lst ++ ")"
tools/hshayoo.hs view
@@ -1,22 +1,22 @@-module Main ( - main - ) where - -import Data.Maybe - -import HsDev.Tools.Hayoo - -import Tool - -data HayooOpts = HayooOpts String Int Int - -hayooOpts :: Parser HayooOpts -hayooOpts = HayooOpts <$> - strArgument (help "hayoo query") <*> - (option auto (long "page" <> short 'p' <> help "page number (0 by default") <|> pure 0) <*> - (option auto (long "pages" <> short 'n' <> help "pages count (1 by default") <|> pure 1) - -main :: IO () -main = toolMain "hshayoo" "hayoo search" hayooOpts $ \(HayooOpts q page pages) -> printExceptT $ printResult $ - liftM concat $ forM [page .. page + pred pages] $ \i -> - liftM (mapMaybe hayooAsDeclaration . resultResult) $ hayoo q (Just i) +module Main (+ main+ ) where++import Data.Maybe++import HsDev.Tools.Hayoo++import Tool++data HayooOpts = HayooOpts String Int Int++hayooOpts :: Parser HayooOpts+hayooOpts = HayooOpts <$>+ strArgument (help "hayoo query") <*>+ (option auto (long "page" <> short 'p' <> help "page number (0 by default") <|> pure 0) <*>+ (option auto (long "pages" <> short 'n' <> help "pages count (1 by default") <|> pure 1)++main :: IO ()+main = toolMain "hshayoo" "hayoo search" hayooOpts $ \(HayooOpts q page pages) -> printExceptT $ printResult $+ liftM concat $ forM [page .. page + pred pages] $ \i ->+ liftM (mapMaybe hayooAsDeclaration . resultResult) $ hayoo q (Just i)
tools/hsinspect.hs view
@@ -1,52 +1,52 @@-{-# LANGUAGE OverloadedStrings, ViewPatterns #-} - -module Main ( - main - ) where - -import Control.Monad (liftM, (>=>)) -import Control.Monad.IO.Class -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.Symbols.Location (ModuleLocation(..)) -import HsDev.Tools.Ghc.Types (inferTypes) -import HsDev.Tools.Ghc.Worker -import HsDev.Tools.Ghc.Session (ghcSession) - -import Tool - -data Opts = Opts (Maybe String) [String] - -opts :: Parser Opts -opts = Opts <$> - optional (strArgument (metavar "what" <> help "depending of what <what> is, inspect installed module, source file (.hs), cabal file (.cabal) or contents, passes as input if no <what> specified")) <*> - many (strOption (metavar "GHC_OPT" <> long "ghc" <> short 'g' <> help "options to pass to GHC")) - -main :: IO () -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 $ 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 <- ghcWorker - let - scanAdditional = - scanModify' (\opts' _ -> liftIO . inspectDocs opts') >=> - scanModify' (\opts' _ m -> liftIO (inWorker ghc (ghcSession opts' >> inferTypes opts' m Nothing))) - toJSON <$> scanAdditional im - inspect' (Opts (Just fcabal@(takeExtension -> ".cabal")) _) = do - fcabal' <- liftIO $ canonicalizePath 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 +{-# LANGUAGE OverloadedStrings, ViewPatterns #-}++module Main (+ main+ ) where++import Control.Monad (liftM, (>=>))+import Control.Monad.IO.Class+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.Symbols.Location (ModuleLocation(..))+import HsDev.Tools.Ghc.Types (inferTypes)+import HsDev.Tools.Ghc.Worker+import HsDev.Tools.Ghc.Session (ghcSession)++import Tool++data Opts = Opts (Maybe String) [String]++opts :: Parser Opts+opts = Opts <$>+ optional (strArgument (metavar "what" <> help "depending of what <what> is, inspect installed module, source file (.hs), cabal file (.cabal) or contents, passes as input if no <what> specified")) <*>+ many (strOption (metavar "GHC_OPT" <> long "ghc" <> short 'g' <> help "options to pass to GHC"))++main :: IO ()+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 $ 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 <- ghcWorker + let+ scanAdditional =+ scanModify' (\opts' _ -> liftIO . inspectDocs opts') >=>+ scanModify' (\opts' _ m -> liftIO (inWorker ghc (ghcSession opts' >> inferTypes opts' m Nothing)))+ toJSON <$> scanAdditional im+ inspect' (Opts (Just fcabal@(takeExtension -> ".cabal")) _) = do+ fcabal' <- liftIO $ canonicalizePath 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