Skip to content

Commit

Permalink
Parse generated task of pre-add hook, out-source Hooks and ImportTask
Browse files Browse the repository at this point in the history
  • Loading branch information
ad-si committed Aug 25, 2024
1 parent fd033f9 commit cd53661
Show file tree
Hide file tree
Showing 10 changed files with 774 additions and 515 deletions.
31 changes: 14 additions & 17 deletions docs-source/cli/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Included fields are:
}
```

After execution, a called hook can print a JSON object to stdout.
During execution, a called hook should print a result JSON object to stdout.
All fields of the JSON are optional.

Possible values:
Expand All @@ -69,14 +69,14 @@ Possible values:
message: "", // A message to display on stdout
warning: "", // A warning to display on stderr
error: "", // An error to display on stderr
// Any other fields you want to include
// Other fields depending on hook type (check out table below)
}
```

Hooks can write to stdout at any time, but it's not recommended.
Rather write a `{ message: "…" }` object to stdout and
let TaskLite print the message with improved formatting and coloring.
Same goes for stderr.
Hooks can write to stderr at any time, but it's not recommended.
Rather write a JSON object to stdout and
let TaskLite print the message / warning / error
with improved formatting and coloring.

<small>
<table>
Expand Down Expand Up @@ -135,7 +135,13 @@ Same goes for stderr.
taskAdded: {}
}
</pre></td>
<td><pre>{ message: "…", … }</pre></td>
<td><pre>
{
taskAdded: {},
message: "…",
}
</pre></td>
<td>
<pre>{ message: "…", … }</pre>
<small>Processing terminates</small>
Expand All @@ -151,7 +157,7 @@ Same goes for stderr.
</pre></td>
<td><pre>
{
taskModified: {},
taskToModify: {},
message: "…",
}
Expand Down Expand Up @@ -191,15 +197,6 @@ Same goes for stderr.
<small>Processing terminates</small>
</td>
</tr>
<tr>
<td><code>post&#8209;exit</code></td>
<td><pre>❌</pre></td>
<td><pre>{ message: "…", … }</pre></td>
<td>
<pre>{ message: "…", … }</pre>
<small>Processing terminates</small>
</td>
</tr>
</tbody>
</table>
</small>
Expand Down
28 changes: 22 additions & 6 deletions tasklite-core/source/Cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ import Config (
HooksConfig (..),
addHookFilesToConfig,
)
import Hooks (HookResult (message), executeHooks)
import ImportExport (
backupDatabase,
dumpCsv,
Expand Down Expand Up @@ -225,9 +226,9 @@ import Utils (
IdText,
ListModifiedFlag (AllItems, ModifiedItemsOnly),
TagText,
executeHooks,
parseUtc,
ulidText2utc,
(<$$>),
)


Expand Down Expand Up @@ -1341,8 +1342,13 @@ printOutput appName argsMb config = do

let configNorm = addHookFilesToConfig configNormHookDir hookFilesPermContent

preLaunchResult <- executeHooks "" configNorm.hooks.launch.pre
putDoc preLaunchResult
preLaunchResults <- executeHooks "" configNorm.hooks.launch.pre
let preLaunchHookMsg =
preLaunchResults
<&> \case
Left error -> pretty error
Right hookResult -> pretty hookResult.message
& P.fold

connection <- setupConnection configNorm

Expand All @@ -1359,23 +1365,33 @@ printOutput appName argsMb config = do
args <- case argsMb of
Just args -> pure args
Nothing -> getArgs
postLaunchResult <-
postLaunchResults <-
executeHooks
( TL.toStrict $
TL.decodeUtf8 $
Aeson.encode $
object ["arguments" .= args]
)
configNorm.hooks.launch.post
putDoc postLaunchResult

let postLaunchHookMsg =
postLaunchResults
<&> \case
Left error -> pretty error
Right hookResult -> pretty hookResult.message
& P.fold

doc <- executeCLiCommand configNorm now connection progName args

-- TODO: Use withConnection instead
SQLite.close connection

-- TODO: Remove color when piping into other command
putDoc $ migrationsStatus <> doc <> hardline
putDoc $
preLaunchHookMsg
<$$> migrationsStatus
<> doc
<$$> postLaunchHookMsg


exampleConfig :: Text
Expand Down
174 changes: 174 additions & 0 deletions tasklite-core/source/Hooks.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
{-# 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 Data.Aeson qualified as Aeson
import Data.Text (Text)
import Data.Text qualified as T
import System.Process (readProcess)

import Config (Hook (body, filePath, interpreter))
import Control.Arrow ((>>>))
import ImportTask (ImportTask)
import Options.Applicative.Arrows (left)
import System.FilePath (takeExtension)


data HookTiming = PreEvent | PostEvent
deriving (Show)


data HookType
= LaunchHook
| AddHook
| ModifyHook
| ExitHook
deriving (Show)


data HookEvent = HookEvent HookType HookTiming
deriving (Show)


data HookResult
= BasicHookResult
{ message :: Maybe Text
, warning :: Maybe Text
, error :: Maybe Text
}
| PreAddHookResult
{ taskToAdd :: Maybe ImportTask
, message :: Maybe Text
, warning :: Maybe Text
, error :: Maybe Text
}
| PostAddHookResult
{ taskAdded :: ImportTask
, message :: Maybe Text
, warning :: Maybe Text
, error :: Maybe Text
}
| PreModifyHookResult
{ taskToModify :: ImportTask
, message :: Maybe Text
, warning :: Maybe Text
, error :: Maybe Text
}
| PostModifyHookResult
{ taskModified :: ImportTask
, message :: Maybe Text
, warning :: Maybe Text
, error :: Maybe Text
}
deriving (Show, P.Generic)


instance Aeson.FromJSON HookResult where
parseJSON = Aeson.withObject "PreAddHookResult" $ \v -> do
message <- v Aeson..:? "message"
warning <- v Aeson..:? "warning"
error <- v Aeson..:? "error"

taskToAddMb <- v Aeson..:? "taskToAdd"
taskAddedMb <- v Aeson..:? "taskAdded"
taskToModifyMb <- v Aeson..:? "taskToModify"
taskModifiedMb <- v Aeson..:? "taskModified"

case (taskToAddMb, taskAddedMb, taskToModifyMb, taskModifiedMb) of
(Just taskToAdd, _, _, _) -> do
pure $ PreAddHookResult taskToAdd message warning error
(_, Just taskAdded, _, _) -> do
pure $ PostAddHookResult taskAdded message warning error
(_, _, Just taskToModify, _) -> do
pure $ PreModifyHookResult taskToModify message warning error
(_, _, _, Just taskModified) -> do
pure $ PostModifyHookResult taskModified message warning error
(_, _, _, _) -> do
pure $ BasicHookResult message warning error


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 excuted 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
Loading

0 comments on commit cd53661

Please sign in to comment.