{-# LANGUAGE OverloadedStrings #-}
-- | Maintain the index from Qualification to the full module from the package
-- db that this Qualification probably intends.
module Index (Index, Package, empty, load, makeIndex, parseSections) where
import Prelude hiding (mod)
import Control.Monad
import qualified Data.Either as Either
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Text as T
import qualified Data.Maybe as Maybe
import Data.Text (Text)
import qualified System.IO as IO
import qualified Types
import qualified Util
-- | Map from tails of the each module in the package db to its module name.
-- So @List@ and @Data.List@ will map to @Data.List@. Modules from a set
-- of core packages, like base and containers, will take priority, so even if
-- there's a package with @Some.Obscure.List@, @List@ will still map to
-- @Data.List@.
type Index = Map.Map Types.Qualification [(Package, Types.ModuleName)]
-- | Package name without the version.
type Package = String
empty :: Index
empty = Map.empty
load :: IO Index
load = build -- TODO load cache?
build :: IO Index
build = do
(_, out, err) <- Util.readProcessWithExitCode "ghc-pkg"
["field", "*", "name,exposed,exposed-modules"]
unless (T.null err) $
IO.hPutStrLn IO.stderr $ "stderr from ghc-pkg: " ++ T.unpack err
let (errors, index) = parseDump out
unless (null errors) $
IO.hPutStrLn IO.stderr $ "errors parsing ghc-pkg output: "
++ List.intercalate ", " errors
return index
makeIndex :: [(Text, [Types.ModuleName])] -- ^ [(package, modules)]
-> Index
makeIndex packages = Map.fromListWith (++)
[ (qual, [(T.unpack package, mod)])
| (package, modules) <- packages
, mod <- modules
, qual <- moduleQualifications mod
]
parseDump :: Text -> ([String], Index)
parseDump text = (errors, makeIndex packages)
where
(errors, packages) = Either.partitionEithers $
extractSections (parseSections text)
extractSections :: [(Text, [Text])]
-> [Either String (Text, [Types.ModuleName])]
extractSections = Maybe.mapMaybe extract . Util.splitWith ((=="name") . fst)
where
extract [ ("name", [name])
, ("exposed", [exposed])
, ("exposed-modules", modules)
]
| exposed /= "True" = Nothing
| otherwise = Just $
Right (name, map (Types.ModuleName . T.unpack) modules)
-- It may be missing exposed-modules, but that means I don't need it.
extract _ = Nothing
-- | Take a module name to all its possible qualifications, i.e. its list
-- of suffixes.
moduleQualifications :: Types.ModuleName -> [Types.Qualification]
moduleQualifications = map (Types.Qualification . Util.join ".")
. filter (not . null) . List.tails . Util.split "." . Types.moduleName
parseSections :: Text -> [(Text, [Text])] -- ^ [(section_name, words)]
parseSections = List.unfoldr parseSection . stripComments . T.lines
stripComments :: [Text] -> [Text]
stripComments = filter (not . T.null) . map (T.stripEnd . fst . T.breakOn "--")
parseSection :: [Text] -> Maybe ((Text, [Text]), [Text])
parseSection [] = Nothing
parseSection (x:xs) =
Just ((tag, concatMap T.words (T.drop 1 rest : pre)), post)
where
(tag, rest) = T.break (==':') x
(pre, post) = span (" " `T.isPrefixOf`) xs