hiedb-0.4.4.0: src/HieDb/Run.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module HieDb.Run where
import Prelude hiding (mod)
import GHC
import Compat.HieTypes
import Compat.HieUtils
import qualified Data.Map as M
import qualified Data.Text.IO as T
import System.Environment
import System.Directory
import System.IO
import System.IO.Unsafe (unsafeInterleaveIO)
import System.Exit
import System.Time.Extra
import System.Console.ANSI
import System.Console.Terminal.Size
import Control.Monad
import Control.Monad.IO.Class
import Data.Maybe
import Data.Either
import Data.Foldable
import Data.IORef
import Data.List.Extra
import Numeric.Natural
import qualified Data.ByteString.Char8 as BS
import Options.Applicative
import HieDb
import HieDb.Compat
import HieDb.Dump
import Text.Printf (printf)
hiedbMain :: LibDir -> IO ()
hiedbMain libdir = do
defaultLoc <- getXdgDirectory XdgData $ "default_"++ show dB_VERSION ++".hiedb"
defdb <- fromMaybe defaultLoc <$> lookupEnv "HIEDB"
colr <- hSupportsANSIColor stdout
hSetBuffering stdout NoBuffering
hSetBuffering stderr NoBuffering
(opts, cmd) <- execParser $ progParseInfo defdb colr
runCommand libdir opts cmd
{- USAGE
Some default db location overridden by environment var HIEDB
hiedb init <foo.hiedb>
hiedb index [<dir>...] [hiedb]
hiedb name-refs <name> <module> [unitid] [hiedb]
hiedb type-refs <name> <module> [unitid] [hiedb]
hiedb query-pos <file.hie> <row> <col> [hiedb]
hiedb query-pos --hiedir=<dir> <file.hs> <row> <col> [hiedb]
hiedb cat <module> [unitid]
-}
data Options
= Options
{ database :: FilePath
, trace :: Bool
, quiet :: Bool
, colour :: Bool
, context :: Maybe Natural
, reindex :: Bool
, keepMissing :: Bool
, srcBaseDir :: Maybe FilePath
}
data Command
= Init
| Index [FilePath]
| NameRefs String (Maybe ModuleName) (Maybe Unit)
| TypeRefs String (Maybe ModuleName) (Maybe Unit)
| NameDef String (Maybe ModuleName) (Maybe Unit)
| TypeDef String (Maybe ModuleName) (Maybe Unit)
| Cat HieTarget
| Ls
| LsExports (Maybe ModuleName)
| Rm [HieTarget]
| ModuleUIDs ModuleName
| LookupHieFile ModuleName (Maybe Unit)
| RefsAtPoint HieTarget (Int,Int) (Maybe (Int,Int))
| TypesAtPoint HieTarget (Int,Int) (Maybe (Int,Int))
| DefsAtPoint HieTarget (Int,Int) (Maybe (Int,Int))
| InfoAtPoint HieTarget (Int,Int) (Maybe (Int,Int))
| RefGraph
| Dump FilePath
| Reachable [Symbol]
| Unreachable [Symbol]
| Html [Symbol]
| GCTypeNames
progParseInfo :: FilePath -> Bool -> ParserInfo (Options, Command)
progParseInfo db colr = info (progParser db colr <**> helper)
( fullDesc
<> progDesc "Query .hie files"
<> header "hiedb - a tool to query groups of .hie files" )
progParser :: FilePath -> Bool -> Parser (Options,Command)
progParser db colr = (,) <$> optParser db colr <*> cmdParser
optParser :: FilePath -> Bool -> Parser Options
optParser defdb colr
= Options
<$> strOption (long "database" <> short 'D' <> metavar "DATABASE"
<> value defdb <> showDefault <> help "References Database")
<*> switch (long "trace" <> short 'v' <> help "Print SQL queries being executed")
<*> switch (long "quiet" <> short 'q' <> help "Don't print progress messages")
<*> colourFlag
<*> optional (option auto (long "context" <> short 'C' <> help "Number of lines of context for source spans - show no context by default"))
<*> switch (long "reindex" <> short 'r' <> help "Re-index all files in database before running command, deleting those with missing '.hie' files")
<*> switch (long "keep-missing" <> help "Keep missing files when re-indexing")
<*> optional (strOption (long "src-base-dir" <> help "Provide a base directory to index src files as real files"))
where
colourFlag = flag' True (long "colour" <> long "color" <> help "Force coloured output")
<|> flag' False (long "no-colour" <> long "no-color" <> help "Force uncoloured ouput")
<|> pure colr
cmdParser :: Parser Command
cmdParser
= hsubparser
$ command "init" (info (pure Init) $ progDesc "Initialize database")
<> command "index" (info (Index <$> many (strArgument (metavar "DIRECTORY..."))) $ progDesc "Index files from directory")
<> command "name-refs" (info (NameRefs <$> strArgument (metavar "NAME")
<*> optional (mkModuleName <$> strArgument (metavar "MODULE"))
<*> maybeUnitId)
$ progDesc "Lookup references of value MODULE.NAME")
<> command "type-refs" (info (TypeRefs <$> strArgument (metavar "NAME")
<*> optional moduleNameParser
<*> maybeUnitId)
$ progDesc "Lookup references of type MODULE.NAME")
<> command "name-def" (info (NameDef <$> strArgument (metavar "NAME")
<*> optional moduleNameParser
<*> maybeUnitId)
$ progDesc "Lookup definition of value MODULE.NAME")
<> command "type-def" (info (TypeDef <$> strArgument (metavar "NAME")
<*> optional moduleNameParser
<*> maybeUnitId)
$ progDesc "Lookup definition of type MODULE.NAME")
<> command "cat" (info (Cat <$> hieTarget)
$ progDesc "Dump contents of MODULE as stored in the hiefile")
<> command "ls" (info (pure Ls)
$ progDesc "List all indexed files/modules")
<> command "ls-exports" (info (LsExports <$> optional moduleNameParser)
$ progDesc "List all exports")
<> command "rm" (info (Rm <$> many hieTarget)
$ progDesc "Remove targets from index")
<> command "module-uids" (info (ModuleUIDs <$> moduleNameParser)
$ progDesc "List all the UnitIds MODULE is indexed under in the db")
<> command "lookup-hie" (info (LookupHieFile <$> moduleNameParser <*> maybeUnitId)
$ progDesc "Lookup the location of the .hie file corresponding to MODULE")
<> command "point-refs"
(info (RefsAtPoint <$> hieTarget
<*> posParser 'S'
<*> optional (posParser 'E'))
$ progDesc "Find references for symbol at point/span")
<> command "point-types"
(info (TypesAtPoint <$> hieTarget
<*> posParser 'S'
<*> optional (posParser 'E'))
$ progDesc "List types of ast at point/span")
<> command "point-defs"
(info (DefsAtPoint <$> hieTarget
<*> posParser 'S'
<*> optional (posParser 'E'))
$ progDesc "Find definition for symbol at point/span")
<> command "point-info"
(info (InfoAtPoint <$> hieTarget
<*> posParser 'S'
<*> optional (posParser 'E'))
$ progDesc "Print name, module name, unit id for symbol at point/span")
<> command "ref-graph" (info (pure RefGraph) $ progDesc "Generate a reachability graph")
<> command "dump" (info (Dump <$> strArgument (metavar "HIE")) $ progDesc "Dump a HIE AST")
<> command "reachable" (info (Reachable <$> some symbolParser)
$ progDesc "Find all symbols reachable from the given symbols")
<> command "unreachable" (info (Unreachable <$> some symbolParser)
$ progDesc "Find all symbols unreachable from the given symbols")
<> command "html" (info (Html <$> some symbolParser)
$ progDesc "generate html files for reachability from the given symbols")
<> command "gc" (info (pure GCTypeNames) mempty)
posParser :: Char -> Parser (Int,Int)
posParser c = (,) <$> argument auto (metavar $ c:"LINE") <*> argument auto (metavar $ c:"COL")
maybeUnitId :: Parser (Maybe Unit)
maybeUnitId =
optional (stringToUnit <$> strOption (short 'u' <> long "unit-id" <> metavar "UNITID"))
symbolParser :: Parser Symbol
symbolParser = argument auto $ metavar "SYMBOL"
moduleNameParser :: Parser ModuleName
moduleNameParser = mkModuleName <$> strArgument (metavar "MODULE")
hieTarget :: Parser HieTarget
hieTarget =
(Left <$> strOption (long "hiefile" <> short 'f' <> metavar "HIEFILE"))
<|> (Right <$> ((,) <$> moduleNameParser <*> maybeUnitId))
progress :: Handle -> Int -> Int -> (FilePath -> DbMonad Bool) -> FilePath -> DbMonad Bool
progress hndl total cur act f = do
mw <- liftIO $ fmap width <$> size
let msg' = unwords ["Processing file", show (cur + 1) ++ "/" ++ show total ++ ":", f] ++ "..."
msg <- liftIO $ case mw of
Nothing -> hPutStrLn hndl "" >> pure msg'
Just w -> do
hPutStr hndl $ replicate w ' '
hPutStr hndl "\r"
pure $ take (w-8) $ msg'
liftIO $ hPutStr hndl msg
x <- act f
if x
then liftIO $ hPutStr hndl " done\r"
else liftIO $ hPutStr hndl " skipped\r"
return x
doIndex :: HieDb -> Options -> Handle -> [FilePath] -> IO ()
doIndex _ opts _ [] | reindex opts = pure ()
doIndex conn opts h files = do
nc <- newIORef =<< makeNc
let progress' = if quiet opts then (\_ _ _ k -> k) else progress
istart <- offsetTime
(length -> done, length -> skipped)<- runDbM nc $ partition id <$>
zipWithM (\f n -> progress' h (length files) n (addRefsFrom conn (srcBaseDir opts)) f) files [0..]
indexTime <- istart
start <- offsetTime
when (done /= 0) $ void $ garbageCollectTypeNames conn
gcTime <- start
unless (quiet opts) $
hPutStrLn h $ "\nCompleted! (" <> show done <> " indexed, " <> show skipped <> " skipped in " <> showDuration indexTime <> " + " <> showDuration gcTime <> " gc)"
runCommand :: LibDir -> Options -> Command -> IO ()
runCommand libdir opts cmd = withHieDbAndFlags libdir (database opts) $ \dynFlags conn -> do
when (trace opts) $
setHieTrace conn (Just $ T.hPutStrLn stderr . ("\n****TRACE: "<>))
when (reindex opts) $ do
initConn conn
files' <- map hieModuleHieFile <$> getAllIndexedMods conn
files <- fmap catMaybes $ forM files' $ \f -> do
exists <- doesFileExist f
if exists
then pure $ Just f
else do
unless (keepMissing opts) $
deleteFileFromIndex conn f
pure Nothing
let n = length files
orig = length files'
unless (quiet opts) $
hPutStrLn stderr $ "Re-indexing " ++ show n ++ " files, deleting " ++ show (n-orig) ++ " files"
doIndex conn opts stderr files
case cmd of
Init -> initConn conn
Index dirs -> do
initConn conn
files <- concat <$> mapM getHieFilesIn dirs
doIndex conn opts stderr files
TypeRefs typ mn muid -> do
let occ = mkOccName tcClsName typ
refs <- findReferences conn False occ mn muid []
reportRefs opts refs
NameRefs nm mn muid -> do
let ns = if isCons nm then dataName else varName
let occ = mkOccName ns nm
refs <- findReferences conn False occ mn muid []
reportRefs opts refs
NameDef nm mn muid -> do
let ns = if isCons nm then dataName else varName
let occ = mkOccName ns nm
(row:.inf) <- reportAmbiguousErr opts =<< findOneDef conn occ mn muid
let mdl = mkModule (modInfoUnit inf) (modInfoName inf)
reportRefSpans opts [(mdl, (defSLine row, defSCol row), (defELine row, defECol row),Just $ Left (defSrc row))]
TypeDef nm mn muid -> do
let occ = mkOccName tcClsName nm
(row:.inf) <- reportAmbiguousErr opts =<< findOneDef conn occ mn muid
let mdl = mkModule (modInfoUnit inf) (modInfoName inf)
reportRefSpans opts [(mdl, (defSLine row, defSCol row), (defELine row, defECol row),Just $ Left (defSrc row))]
Cat target -> hieFileCommand conn opts target (BS.putStrLn . hie_hs_src)
Ls -> do
mods <- getAllIndexedMods conn
forM_ mods $ \mod -> do
putStr $ hieModuleHieFile mod
putStr "\t"
putStr $ moduleNameString $ modInfoName $ hieModInfo mod
putStr "\t"
putStrLn $ unitString $ modInfoUnit $ hieModInfo mod
LsExports mn -> do
exports <- maybe (getAllIndexedExports conn) (getExportsForModule conn) mn
forM_ exports $ \ExportRow{..} -> do
putStr exportHieFile
putStr "\t"
case exportParent of
Nothing -> putStrLn $ occNameString exportName
Just p -> printf "%s(%s)\n" (occNameString p) (occNameString exportName)
Rm targets -> do
forM_ targets $ \target -> do
case target of
Left f -> do
dir <- doesDirectoryExist f
if dir
then do
fs <- getHieFilesIn f
mapM_ (deleteFileFromIndex conn) fs
else do
cf <- canonicalizePath f
deleteFileFromIndex conn cf
Right (mn,muid) -> do
uid <- reportAmbiguousErr opts =<< maybe (resolveUnitId conn mn) (return . Right) muid
mFile <- lookupHieFile conn mn uid
case mFile of
Nothing -> reportAmbiguousErr opts $ Left (NotIndexed mn $ Just uid)
Just x -> deleteFileFromIndex conn (hieModuleHieFile x)
ModuleUIDs mn ->
print =<< reportAmbiguousErr opts =<< resolveUnitId conn mn
LookupHieFile mn muid -> reportAmbiguousErr opts =<< do
euid <- maybe (resolveUnitId conn mn) (return . Right) muid
case euid of
Left err -> return $ Left err
Right uid -> do
mFile <- lookupHieFile conn mn uid
case mFile of
Nothing -> return $ Left (NotIndexed mn $ Just uid)
Just x -> Right <$> putStrLn (hieModuleHieFile x)
RefsAtPoint target sp mep -> hieFileCommand conn opts target $ \hf -> do
let names = concat $ pointCommand hf sp mep $ rights . M.keys . nodeIdentifiers . nodeInfo'
when (null names) $
reportAmbiguousErr opts (Left $ NoNameAtPoint target sp)
forM_ names $ \name -> do
unless (quiet opts) $ do
hPutStrLn stderr $ unwords ["Name", ppName opts (nameOccName name),"at",ppSpan opts sp,"is used at:"]
hPutStrLn stderr ""
case nameModule_maybe name of
Just mod -> do
reportRefs opts =<< findReferences conn False (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod) []
Nothing -> do
let refmap = generateReferencesMap (getAsts $ hie_asts hf)
refs = map (toRef . fst) $ M.findWithDefault [] (Right name) refmap
toRef spn = (hie_module hf
,(srcSpanStartLine spn , srcSpanStartCol spn)
,(srcSpanEndLine spn , srcSpanEndCol spn)
,Just $ Right (hie_hs_src hf))
reportRefSpans opts refs
TypesAtPoint target sp mep -> hieFileCommand conn opts target $ \hf -> do
let types' = concat $ pointCommand hf sp mep $ nodeType . nodeInfo'
types = map (flip recoverFullType $ hie_types hf) types'
when (null types) $
reportAmbiguousErr opts (Left $ NoNameAtPoint target sp)
forM_ types $ \typ -> do
putStrLn $ renderHieType dynFlags typ
DefsAtPoint target sp mep -> hieFileCommand conn opts target $ \hf -> do
let names = concat $ pointCommand hf sp mep $ rights . M.keys . nodeIdentifiers . nodeInfo'
when (null names) $
reportAmbiguousErr opts (Left $ NoNameAtPoint target sp)
forM_ names $ \name -> do
case nameSrcSpan name of
#if __GLASGOW_HASKELL__ >= 900
RealSrcSpan dsp _ -> do
#else
RealSrcSpan dsp -> do
#endif
unless (quiet opts) $
hPutStrLn stderr $ unwords ["Name", ppName opts (nameOccName name),"at",ppSpan opts sp,"is defined at:"]
contents <- case nameModule_maybe name of
Nothing -> pure $ Just $ Right $ hie_hs_src hf
Just mod
| mod == hie_module hf -> pure $ Just $ Right $ hie_hs_src hf
| otherwise -> unsafeInterleaveIO $ do
loc <- findOneDef conn (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod)
pure $ case loc of
Left _ -> Nothing
Right (row:._) -> Just $ Left $ defSrc row
reportRefSpans opts
[(fromMaybe (hie_module hf) (nameModule_maybe name)
,(srcSpanStartLine dsp,srcSpanStartCol dsp)
,(srcSpanEndLine dsp, srcSpanEndCol dsp)
,contents
)]
UnhelpfulSpan msg -> do
case nameModule_maybe name of
Just mod -> do
(row:.inf) <- reportAmbiguousErr opts
=<< findOneDef conn (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod)
unless (quiet opts) $
hPutStrLn stderr $ unwords ["Name", ppName opts (nameOccName name),"at",ppSpan opts sp,"is defined at:"]
reportRefSpans opts
[(mkModule (modInfoUnit inf) (modInfoName inf)
,(defSLine row,defSCol row)
,(defELine row,defECol row)
,Just $ Left $ defSrc row
)]
Nothing -> do
reportAmbiguousErr opts $ Left $ NameUnhelpfulSpan name (unpackFS $ unhelpfulSpanFS msg)
InfoAtPoint target sp mep -> hieFileCommand conn opts target $ \hf -> do
mapM_ (uncurry $ printInfo dynFlags) $ pointCommand hf sp mep $ \ast ->
(hieTypeToIface . flip recoverFullType (hie_types hf) <$> nodeInfo' ast, nodeSpan ast)
RefGraph -> declRefs conn
Dump path -> do
nc <- newIORef =<< makeNc
runDbM nc $ dump dynFlags path
Reachable s -> getReachable conn s >>= mapM_ print
Unreachable s -> getUnreachable conn s >>= mapM_ print
Html s -> do
nc <- newIORef =<< makeNc
runDbM nc $ html conn s
GCTypeNames -> do
start <- offsetTime
n <- garbageCollectTypeNames conn
end <- start
unless (quiet opts) $
hPutStrLn stderr $ "GCed " ++ show n ++ " types in " <> showDuration end
printInfo :: DynFlags -> NodeInfo IfaceType -> RealSrcSpan -> IO ()
printInfo dynFlags x sp = do
putStrLn $ "Span: " ++ showSDoc dynFlags (ppr sp)
putStrLn $ "Constructors: " ++ showSDoc dynFlags (ppr $ nodeAnnotations x)
putStrLn "Identifiers:"
let idents = M.toList $ nodeIdentifiers x
forM_ idents $ \(ident,inf) -> do
case ident of
Left mdl -> putStrLn $ "Module: " ++ moduleNameString mdl
Right nm -> do
case nameModule_maybe nm of
Nothing -> pure ()
Just m -> do
putStr "Symbol:"
print $ Symbol (nameOccName nm) m
putStrLn $ showSDoc dynFlags $
hang (ppr nm <+> text "defined at" <+> ppr (nameSrcSpan nm)) 4 (ppr inf)
putStrLn "Types:"
let types = nodeType x
forM_ types $ \typ -> do
putStrLn $ showSDoc dynFlags (ppr typ)
putStrLn ""
hieFileCommand :: HieDb -> Options -> HieTarget -> (HieFile -> IO a) -> IO a
hieFileCommand conn opts target f = join $ reportAmbiguousErr opts =<< withTarget conn target f
reportAmbiguousErr :: Options -> Either HieDbErr a -> IO a
reportAmbiguousErr _ (Right x) = return x
reportAmbiguousErr o (Left e) = do
hPutStrLn stderr $ showHieDbErr o e
exitFailure
showHieDbErr :: Options -> HieDbErr -> String
showHieDbErr opts e = case e of
NoNameAtPoint t spn -> unwords ["No symbols found at",ppSpan opts spn,"in",either id (\(mn,muid) -> ppMod opts mn ++ maybe "" (\uid -> "("++ppUnit opts uid++")") muid) t]
NotIndexed mn muid -> unwords ["Module", ppMod opts mn ++ maybe "" (\uid -> "("++ppUnit opts uid++")") muid, "not indexed."]
AmbiguousUnitId xs -> unlines $ "Unit could be any of:" : map ((" - "<>) . unitString . modInfoUnit) (toList xs)
<> ["Use --unit-id to disambiguate"]
NameNotFound occ mn muid -> unwords
["Couldn't find name:", ppName opts occ, maybe "" (("from module " ++) . moduleNameString) mn ++ maybe "" (\uid ->"("++ppUnit opts uid++")") muid]
NameUnhelpfulSpan nm msg -> unwords
["Got no helpful spans for:", occNameString (nameOccName nm), "\nMsg:", msg]
reportRefSpans :: Options -> [(Module,(Int,Int),(Int,Int),Maybe (Either FilePath BS.ByteString))] -> IO ()
reportRefSpans opts xs = do
nc <- newIORef =<< makeNc
runDbM nc $ forM_ xs $ \(mn,(sl,sc),(el,ec),hie_f) -> do
liftIO $ do
when (colour opts) $
setSGR [SetUnderlining SingleUnderline]
putStr $ ppMod opts $ moduleName mn
when (colour opts) $
setSGR [SetUnderlining SingleUnderline]
putStr ":"
when (colour opts) $
setSGR [SetUnderlining SingleUnderline]
putStrLn $ colouredPP Magenta id opts $ concat
[ show sl
, ':':show sc
, '-':show el
, ':':show ec
]
when (colour opts) $
setSGR []
case context opts of
Nothing -> pure ()
Just (fromIntegral -> n) -> do
msrc <- forM hie_f $ \case
Left loc -> withHieFile loc $ pure . hie_hs_src
Right src -> pure src
liftIO $ case msrc of
Nothing -> putStrLn "<source unavailable>"
Just src -> do
let ls = BS.lines src
(beforeLines',duringLines') = splitAt (sl-1) ls
(duringLines,afterLines') = splitAt (el-sl+1) duringLines'
beforeLines = takeEnd n beforeLines'
afterLines = take n afterLines'
(beforeChars,during') = BS.splitAt (sc-1) $ BS.concat $ intersperse "\n" $ duringLines
(during,afterChars) = BS.splitAt (BS.length during' - (BS.length (last duringLines) - ec) - 1) during'
before = BS.unlines beforeLines <> beforeChars
after = afterChars <> "\n" <> BS.unlines afterLines
BS.putStr before
when (colour opts) $
setSGR [SetColor Foreground Vivid Red, SetConsoleIntensity BoldIntensity]
BS.putStr during
when (colour opts) $
setSGR []
BS.putStrLn after
reportRefs :: Options -> [Res RefRow] -> IO ()
reportRefs opts xs = reportRefSpans opts
[ (mdl,(refSLine x, refSCol x),(refELine x, refECol x),Just $ Left $ refSrc x)
| (x:.inf) <- xs
, let mdl = mkModule (modInfoUnit inf) (modInfoName inf)
]
colouredPP :: Color -> (a -> String) -> Options -> a -> String
colouredPP c pp opts x = pre <> pp x <> post
where
(pre,post)
| colour opts = (setSGRCode [SetColor Foreground Vivid c], setSGRCode [])
| otherwise = ("","")
ppName :: Options -> OccName -> String
ppName = colouredPP Red occNameString
ppMod :: Options -> ModuleName -> String
ppMod = colouredPP Green moduleNameString
ppUnit :: Options -> Unit -> String
ppUnit = colouredPP Yellow show
ppSpan :: Options -> (Int,Int) -> String
ppSpan = colouredPP Magenta show