{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use maybe" #-}
module Hooks where
import Protolude (
Applicative (pure),
IO,
Maybe (..),
Show,
otherwise,
($),
(&),
(<&>),
)
import Protolude qualified as P
import Control.Arrow ((>>>))
import Data.Aeson qualified as Aeson
import Data.Text (Text)
import Data.Text qualified as T
import Options.Applicative.Arrows (left)
import Prettyprinter (Doc, annotate, pretty)
import Prettyprinter.Render.Terminal (AnsiStyle, Color (Red, Yellow))
import System.FilePath (takeExtension)
import System.Process (readProcess)
import Config (Config, Hook (body, filePath, interpreter))
import ImportTask (ImportTask)
import Utils (colr, (<!!>))
data HookTiming = PreEvent | PostEvent
deriving (Show)
data HookType
= LaunchHook
| AddHook
| ModifyHook
| ExitHook
deriving (Show)
data HookEvent = HookEvent HookType HookTiming
deriving (Show)
-- | Output of a hook that must be parsed by TaskLite
data HookResult
= BasicHookResult
{ message :: Maybe Text
, warning :: Maybe Text
, error :: Maybe Text
}
| TaskHookResult
{ task :: Maybe ImportTask
, message :: Maybe Text
, warning :: Maybe Text
, error :: Maybe Text
}
deriving (Show, P.Generic)
instance Aeson.FromJSON HookResult where
parseJSON = Aeson.withObject "HookResult" $ \v -> do
taskMb <- v Aeson..:? "task"
messageMb <- v Aeson..:? "message"
warningMb <- v Aeson..:? "warning"
errorMb <- v Aeson..:? "error"
case taskMb of
Just task -> pure $ TaskHookResult task messageMb warningMb errorMb
Nothing -> pure $ BasicHookResult messageMb warningMb errorMb
data ExecMode = ExecFile | ExecStdin
type String = [P.Char]
executeHooks :: Text -> [Hook] -> IO [P.Either Text HookResult]
executeHooks stdinText hooks = do
let
stdinStr = T.unpack stdinText
getInterpreter :: String -> (String, [String], ExecMode)
getInterpreter s =
if
| s `P.elem` ["javascript", "js", "node", "node.js"] ->
("node", ["-e"], ExecStdin)
| s `P.elem` ["lua"] ->
("lua", ["-e"], ExecStdin)
| s `P.elem` ["python", "python3", "py"] ->
("python3", ["-c"], ExecStdin)
| s `P.elem` ["ruby", "rb"] ->
("ruby", ["-e"], ExecStdin)
| s `P.elem` ["v", "vsh"] ->
-- `crun` keeps the binary after execution
("v", ["-raw-vsh-tmp-prefix", "_v_executable_"], ExecFile)
| otherwise ->
("", [""], ExecFile)
hookToResult <-
P.sequence $
hooks <&> \hook -> do
case hook.filePath of
Just fPath -> do
case fPath & takeExtension & P.drop 1 of
"" ->
-- Is executed with shell
readProcess fPath [] stdinStr
ext -> do
let (interpreter, cliFlags, execMode) = getInterpreter ext
case execMode of
ExecStdin -> do
fileContent <- P.readFile fPath
readProcess
interpreter
(P.concat [cliFlags, [T.unpack fileContent]])
stdinStr
ExecFile -> do
readProcess
interpreter
(P.concat [cliFlags, [fPath]])
stdinStr
---
Nothing -> do
let
(interpreter, cliFlags, _) =
getInterpreter (T.unpack hook.interpreter)
readProcess
interpreter
(P.concat [cliFlags, [T.unpack hook.body]])
stdinStr
let parsedHookResults :: [P.Either Text HookResult] =
hookToResult
& P.filter (T.pack >>> T.strip >>> T.null >>> P.not)
<&> ( ( \hookOutput -> do
Aeson.eitherDecodeStrictText (T.pack hookOutput)
)
>>> left T.pack
)
pure parsedHookResults
formatHookResult :: Config -> HookResult -> Doc AnsiStyle
formatHookResult conf hookResult =
""
<!!> pretty hookResult.message
<!!> annotate (colr conf Yellow) (pretty hookResult.warning)
<!!> annotate (colr conf Red) (pretty hookResult.error)
<!!> ""