glean-0.1.0.0: glean/glass/tools/Glean/Glass/Test/DemoClient.hs
{-
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
-}
{-# LANGUAGE ApplicativeDo #-}
module Glean.Glass.Test.DemoClient ( main ) where
import Glean.Init as Glean ( withOptions )
import Glean.Glass.Types
import Glean.Glass.GlassService.Client
import Data.Default ( Default(def) )
import Data.Maybe ( catMaybes )
import Data.Text (Text)
import Options.Applicative
import Options.Applicative.Types ( readerAsk )
import System.FilePath ( pathSeparator )
import Util.Text ( textShow, textToInt )
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import Glean.Impl.ThriftService
import Glean.Util.ThriftService
import Glean.Util.Service
import Thrift.Protocol
import Thrift.Monad hiding (runThrift)
import Thrift.Channel
import Util.EventBase ( withEventBaseDataplane )
data Options =
Options {
optCommand :: Command,
optHost :: Service
}
defService :: Service
defService = HostPort "127.0.0.1" 26073
data Command
= List RepoName Path
| Describe SymbolId
| FindRefs SymbolId
| FindLocation SymbolId
| Search RepoName Text Bool{- case-insensitve? -}
options :: ParserInfo Options
options = info (helper <*> parser) (fullDesc <>
progDesc "Glass code navigation and search tool")
where
parser :: Parser Options
parser = do
optHost <- option readService
(value defService
<> long "service"
<> metavar "HOST:PORT"
)
optCommand <- hsubparser $
command "list" (info listCommand
(progDesc $ unlines
["List symbols in file specified by REPO/PATH"
]
)
) <>
command "describe" (info describeCommand
(progDesc $ unlines
["Describe basic details of a symbol"
]
)
) <>
command "references" (info findRefsCommand
(progDesc $ unlines
["Find references to this symbol"
]
)
) <>
command "location" (info locationCommand
(progDesc $ unlines
["Find the definition location of a symbol"
]
)
) <>
command "search" (info searchCommand
(progDesc $ unlines
["Find entities matching string"
]
)
)
return $ Options {..}
cmd parser help = argument parser help
symbolHelp = metavar "SYMBOL"
searchHelp = metavar "REPO/STR"
listCommand = cmd readListRepoPath (metavar "REPO/PATH")
describeCommand = cmd (Describe <$> readSymbol) symbolHelp
findRefsCommand = cmd (FindRefs <$> readSymbol) symbolHelp
locationCommand = cmd (FindLocation <$> readSymbol) symbolHelp
searchCommand = do
~(repo,needle) <- argument readSearch searchHelp
ignoreCase <- switch
( long "case-insensitive"
<> short 'i'
)
return $ Search repo needle ignoreCase
readService :: ReadM Service
readService = do
hostPort <- Text.pack <$> readerAsk
case Text.breakOn ":" hostPort of
(_, "") -> fail "Not a valid host:port"
(host, portStr) -> case textToInt (Text.drop 1 portStr) of
Left _ -> fail ("Not a valid port: " <> show portStr)
Right port -> return (HostPort host (fromIntegral port))
readSearch :: ReadM (RepoName, Text)
readSearch = do
repoStr <- Text.pack <$> readerAsk
case Text.breakOn "/" repoStr of
(_, "") -> fail "Not a valid repo/string"
(repo, str) -> return (RepoName repo, Text.tail str)
readListRepoPath :: ReadM Command
readListRepoPath = do
path <- Text.pack <$> readerAsk
case Text.breakOn sep path of
(_, "") -> fail "Not a valid repo/path"
(repo, file) -> return $ case Text.stripPrefix sep file of
Nothing -> List (RepoName repo) (Path file)
Just file' -> List (RepoName repo) (Path file')
where
sep = Text.singleton pathSeparator
readSymbol :: ReadM SymbolId
readSymbol = do
sym <- Text.pack <$> readerAsk
if length (Text.splitOn "/" sym) < 3
then err
else return (SymbolId sym)
where
err = fail "Not a valid symbol. Symbols have the form: repo/lang/path-ish"
defDocumentSymbolsReq :: DocumentSymbolsRequest
defDocumentSymbolsReq = def {
documentSymbolsRequest_repository = RepoName "react",
documentSymbolsRequest_filepath = Path "packages/react/src/ReactHooks.js",
documentSymbolsRequest_include_refs = False -- just definitions
}
defCfg :: ThriftServiceOptions
defCfg = def { processingTimeout = Just 15000 }
type GlassM p a = forall c . ClientChannel c => ThriftM p c GlassService a
svc :: Service -> ThriftService GlassService
svc s = mkThriftService s defCfg
main :: IO ()
main = Glean.withOptions options $ \Options{..} ->
withEventBaseDataplane $ \evp -> do
res <- runThrift evp (svc optHost) $
case optCommand of
List repo path -> runListSymbols repo path
Describe sym -> runDescribe sym
FindRefs sym -> runFindRefs sym
FindLocation sym -> runLocation sym
Search repo str ignoreCase -> runSearch repo str ignoreCase
mapM_ Text.putStrLn res
runListSymbols :: Protocol p => RepoName -> Path -> GlassM p [Text]
runListSymbols repo path = do
DocumentSymbolListXResult{..} <- documentSymbolListX query def
return $ pprDefs documentSymbolListXResult_definitions
where
query = defDocumentSymbolsReq {
documentSymbolsRequest_repository = repo
, documentSymbolsRequest_filepath = path
}
runDescribe :: Protocol p => SymbolId -> GlassM p [Text]
runDescribe sym = do
SymbolDescription{..} <- describeSymbol sym def
let loc = pprLocationRange $ locationRange symbolDescription_location
name = pprQName symbolDescription_name
kind = textShow <$> symbolDescription_kind
annot = textShow <$> symbolDescription_annotations
return $ [loc,name] <> catMaybes [kind,annot]
runSearch :: Protocol p => RepoName -> Text -> Bool -> GlassM p [Text]
runSearch repoName strName ignoreCase = do
SymbolSearchResult syms _ <- searchSymbol req def
return (map textShow syms)
where
req = SymbolSearchRequest
strName
(Just repoName)
def -- language
def -- kinds
def { -- search options
symbolSearchOptions_ignoreCase = ignoreCase
}
runLocation :: Protocol p => SymbolId -> GlassM p [Text]
runLocation sym = do
range <- symbolLocation sym def
return [pprLocationRange (symbolLocation_location range)]
runFindRefs :: Protocol p => SymbolId -> GlassM p [Text]
runFindRefs sym = do
ranges <- findReferenceRanges sym def
return $
("Found " <> textShow (length ranges) <> " references") :
map pprLocationRange ranges
-- List unique definition symbols
pprDefs :: [DefinitionSymbolX] -> [Text]
pprDefs = map (unSymbolId . definitionSymbolX_sym)
pprLocationRange :: LocationRange -> Text
pprLocationRange LocationRange{..} =
Text.concat [repo, "@", path,":", pprRange range]
where
RepoName repo = locationRange_repository
Path path = locationRange_filepath
range = locationRange_range
pprRange :: Range -> Text
pprRange (Range lineBegin colBegin lineEnd colEnd) = Text.concat [
textShow lineBegin, ":", textShow colBegin, "-",
textShow lineEnd, ":", textShow colEnd
]
pprQName :: QualifiedName -> Text
pprQName QualifiedName{..} =
unName qualifiedName_container <> ":" <> unName qualifiedName_localName
locationRange :: SymbolPath -> LocationRange
locationRange SymbolPath{..} = LocationRange {
locationRange_repository = symbolPath_repository,
locationRange_filepath = symbolPath_filepath,
locationRange_range = symbolPath_range
}