glean-0.1.0.0: glean/shell/Glean/Shell/Types.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 DeriveTraversable #-}
module Glean.Shell.Types (
Parse(..), Statement(..), JSONQuery(..), AngleQuery(..),
SchemaQuery(..),
Stats(..),
ShellState(..),
Eval(..),
ExpandResults(..),
withBackend,
getState,
getRepo,
setRepo,
) where
import Control.Concurrent
import Control.Exception
import Data.Default
import Data.Bifunctor
import Data.Functor (($>))
import qualified Control.Monad.Catch as C
import Control.Monad.IO.Class (MonadIO, liftIO)
import qualified Control.Monad.Trans.State.Strict as State
import Data.Int
import Data.List
import qualified Data.Map as Map
import Data.Maybe
import Compat.Prettyprinter as Pretty
#if !MIN_VERSION_haskeline(0,8,0)
import qualified System.Console.Haskeline as Haskeline
#endif
import System.IO
import qualified Text.JSON as JSON
import qualified Text.Parsec as P
import qualified Text.Parsec.Language as P
import qualified Text.Parsec.Token as P
import Glean
import qualified Glean.Types as Thrift
import Glean.LocalOrRemote (LocalOrRemote)
import Glean.Database.Config (ProcessedSchema(..), processSchema)
import Glean.Util.Some
data Statement pat
= Command String String
| Pattern pat
| FactRef Fid
data ExpandResults
= ExpandRecursive
| ExpandPredicates [SourceRef]
type Parser = P.Parsec String ()
class Parse a where
parse :: Parser a
instance Parse Fid where
parse = Fid . fromInteger <$> P.braces lexer (P.natural lexer)
instance Parse pat => Parse (Statement pat) where
parse = P.choice [command, P.try factref, ptrn]
where
command = split <$> (P.char ':' *> P.getInput <* P.setInput "")
ptrn = Pattern <$> parse
factref = FactRef <$> parse
split s
| (cmd,' ':arg) <- break (==' ') s = Command cmd arg
| otherwise = Command s ""
data AngleQuery = AngleQuery
{ angleQueryDeprecatedRec :: Bool
, angleQueryStored :: Bool
, angleQuery :: String
}
instance Parse AngleQuery where
parse = AngleQuery
<$> P.option False (P.char '!' $> True)
<*> P.option False (P.char '*' $> True)
<*> P.many P.anyChar
data JSONQuery = JSONQuery
{ jsonQueryPred :: String
, jsonQueryDeprecatedRec :: Bool
, jsonQueryStored :: Bool
, jsonQuery :: String
}
instance Parse JSONQuery where
parse = JSONQuery
<$> P.identifier lexer
<*> P.option False (P.char '!' $> True)
<*> P.option False (P.char '*' $> True)
<*> P.many P.anyChar
lexer :: P.TokenParser st
lexer = P.makeTokenParser P.emptyDef
{ P.identLetter = P.alphaNum P.<|> P.oneOf "_." }
data SchemaQuery = SchemaQuery
{ sqPredicate :: String
, sqRecursive :: ExpandResults
, sqStored :: Bool
, sqQuery :: String
, sqCont :: Maybe Thrift.UserQueryCont
, sqTransform :: Maybe (JSON.JSValue -> JSON.Result JSON.JSValue)
, sqSyntax :: Thrift.QuerySyntax
, sqOmitResults :: Bool
, sqContinue :: Bool
}
data Stats = NoStats | SummaryStats | FullStats
deriving Eq
data ShellState = ShellState
{ backend :: Some LocalOrRemote
, repo :: Maybe Repo
, schemas :: Maybe ProcessedSchema
, schemaInfo :: Maybe Thrift.SchemaInfo
, useSchemaId :: Thrift.SelectSchema
, limit :: Int64
, timeout :: Maybe Int64
, stats :: Stats
, lastSchemaQuery :: Maybe SchemaQuery
, updateSchema :: Maybe (Eval ())
, isTTY :: Bool
, pageWidth :: Maybe PageWidth
, expandResults :: ExpandResults
, pager :: Bool
, outputHandle :: MVar System.IO.Handle
, debug :: Thrift.QueryDebugOptions
, client_info :: Thrift.UserQueryClientInfo
, query_file :: FilePath
}
newtype Eval a = Eval
{ unEval :: State.StateT ShellState IO a
}
deriving
( Functor
, Applicative
, Monad
#if !MIN_VERSION_haskeline(0,8,0)
, Haskeline.MonadException
#endif
, MonadIO
, C.MonadCatch
, C.MonadMask
, C.MonadThrow )
withBackend :: (forall b . LocalOrRemote b => b -> Eval a) -> Eval a
withBackend f = do
state <- getState
case backend state of
Some b -> f b
getState :: Eval ShellState
getState = Eval State.get
getRepo :: Eval (Maybe Repo)
getRepo = repo <$> getState
setRepo :: Repo -> Eval ()
setRepo r = do
sel <- useSchemaId <$> getState
withBackend $ \backend -> do
info@SchemaInfo{..} <- liftIO $
getSchemaInfo backend (Just r) def { getSchemaInfo_select = sel }
let sids = map (first Thrift.SchemaId) $ Map.toList $ case sel of
Thrift.SelectSchema_current{} -> schemaInfo_schemaIds
Thrift.SelectSchema_stored{} -> schemaInfo_dbSchemaIds
Thrift.SelectSchema_schema_id id
| Map.member (unSchemaId id) schemaInfo_schemaIds ->
schemaInfo_schemaIds
| Map.member (unSchemaId id) schemaInfo_dbSchemaIds ->
schemaInfo_dbSchemaIds
| Just ids <- find (unSchemaId id `Map.member`)
schemaInfo_otherSchemaIds -> ids
_otherwise -> Map.empty
Eval $ State.modify $ \s -> s
{ repo = Just r
, schemaInfo = Just info }
proc <- either (liftIO . throwIO . ErrorCall) return $
processSchema (listToMaybe sids) schemaInfo_schema
Eval $ State.modify $ \s -> s { schemas = Just proc }