Skip to content

Commit ba2054b

Browse files
committed
Feat: Add support for specifying lifecycle hooks in the config
1 parent 98f040d commit ba2054b

File tree

6 files changed

+395
-27
lines changed

6 files changed

+395
-27
lines changed

docs-source/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
- [Usage](./usage/readme.md)
1515
- [CLI](./usage/cli.md)
16+
- [Hooks](./usage/hooks.md)
1617
- [Desktop App](./usage/desktop_app.md)
1718
- [Web App](./usage/web_app.md)
1819
- [REST API](./usage/rest_api.md)

docs-source/usage/hooks.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Hooks
2+
3+
- `pre-launch` - Before any TaskLite code is executed
4+
- `post-launch`
5+
- `pre-add`
6+
- `post-add`
7+
- `pre-modify`
8+
- `post-modify`
9+
- `pre-exit` - Pre printing results
10+
- `post-exit` - Last thing before program termination
11+
12+
<!--
13+
- `pre-delete`
14+
- `post-delete`
15+
- `pre-review`
16+
- `post-review`
17+
-->
18+
19+
20+
<table>
21+
<thead>
22+
<tr>
23+
<th>Event</th>
24+
<th>Input</th>
25+
<th>Success<br>(exitcode == 0)</th>
26+
<th>Error<br>(exitcode != 0)</th>
27+
</tr>
28+
</thead>
29+
<tbody>
30+
<tr>
31+
<td><code>pre&#8209;launch</code></td>
32+
<td>❌</td>
33+
<td><pre>
34+
{
35+
stdout: "…"
36+
}
37+
</pre></td>
38+
<td>
39+
- Processing terminates
40+
<br>
41+
- <code>{stderr: "…"}</code>
42+
</td>
43+
</tr>
44+
<tr>
45+
<td><code>post&#8209;launch</code></td>
46+
<td>❌</td>
47+
<td><pre>
48+
{
49+
stdout: "…"
50+
}
51+
</pre></td>
52+
<td>
53+
- Processing terminates
54+
<br>
55+
- <code>{stderr: "…"}</code>
56+
</td>
57+
</tr>
58+
<tr>
59+
<td><code>pre&#8209;add</code></td>
60+
<td><pre>
61+
{
62+
command: "…",
63+
taskToAdd: {}
64+
}
65+
</pre></td>
66+
<td><pre>
67+
{
68+
taskToAdd: {},
69+
stdout: "…"
70+
}
71+
</pre></td>
72+
<td>
73+
- Processing terminates
74+
<br>
75+
- <code>{stderr: "…"}</code>
76+
</td>
77+
</tr>
78+
<tr>
79+
<td><code>post&#8209;add</code></td>
80+
<td><pre>
81+
{
82+
command: "…",
83+
taskAdded: {}
84+
}
85+
</pre></td>
86+
<td><pre>
87+
{
88+
stdout: "…"
89+
}
90+
</pre></td>
91+
<td>
92+
- Processing terminates
93+
<br>
94+
- <code>{stderr: "…"}</code>
95+
</td>
96+
</tr>
97+
<tr>
98+
<td><code>pre&#8209;modify</code></td>
99+
<td><pre>
100+
{
101+
command: "…",
102+
taskOriginal: {}
103+
}
104+
</pre></td>
105+
<td><pre>
106+
{
107+
taskModified: {},
108+
stdout: "…"
109+
}
110+
</pre></td>
111+
<td>
112+
- Processing terminates
113+
<br>
114+
- <code>{stderr: "…"}</code>
115+
</td>
116+
</tr>
117+
<tr>
118+
<td><code>post&#8209;modify</code></td>
119+
<td><pre>
120+
{
121+
command: "…",
122+
taskOriginal: {},
123+
taskModified: {}
124+
}
125+
</pre></td>
126+
<td><pre>
127+
{
128+
taskModified: {},
129+
stdout: "…"
130+
}
131+
</pre></td>
132+
<td>
133+
- Processing terminates
134+
<br>
135+
- <code>{stderr: "…"}</code>
136+
</td>
137+
</tr>
138+
<tr>
139+
<td><code>pre&#8209;exit</code></td>
140+
<td><pre>
141+
{}
142+
</pre></td>
143+
<td><pre>
144+
{
145+
stdout: "…"
146+
}
147+
</pre></td>
148+
<td>
149+
- Processing terminates
150+
<br>
151+
- <code>{stderr: "…"}</code>
152+
</td>
153+
</tr>
154+
<tr>
155+
<td><code>post&#8209;exit</code></td>
156+
<td><pre>
157+
{}
158+
</pre></td>
159+
<td><pre>
160+
{
161+
stdout: "…"
162+
}
163+
</pre></td>
164+
<td>
165+
- Processing terminates
166+
<br>
167+
- <code>{stderr: "…"}</code>
168+
</td>
169+
</tr>
170+
</tbody>
171+
</table>
172+
173+
174+
&nbsp;
175+
176+
177+
To see the JSON for a single task run:
178+
179+
```sh
180+
tl ndjson | head -n 1 | jq
181+
```

tasklite-core/app/Main.hs

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Protolude
1212

1313
import Lib
1414
import Data.Char (isSpace)
15-
import Data.FileEmbed (embedStringFile)
15+
import Data.FileEmbed (embedStringFile, makeRelativeToProject)
1616
import Data.Hourglass
1717
import Data.String (fromString)
1818
import qualified Data.Text as T
@@ -27,15 +27,19 @@ import Options.Applicative
2727
import Paths_tasklite_core ()
2828
import System.Directory
2929
( createDirectoryIfMissing
30+
, executable
3031
, getHomeDirectory
32+
, getPermissions
3133
, getXdgDirectory
34+
, listDirectory
35+
, Permissions
3236
, XdgDirectory(..)
3337
)
3438
import System.FilePath ((</>))
3539
import Time.System
3640
import Database.SQLite.Simple (close, Connection(..))
3741

38-
import Config (Config(..))
42+
import Config (Config(..), HooksConfig(..), addHookFilesToConfig)
3943
import DbSetup
4044
import ImportExport
4145
import Migrations
@@ -918,33 +922,76 @@ executeCLiCommand conf now connection cmd =
918922

919923

920924
printOutput :: [Char] -> Config -> IO ()
921-
printOutput appName configUser = do
922-
configUserNorm <-
923-
if (dataDir configUser /= "")
924-
then pure $ configUser
925-
else do
926-
xdgDataDir <- getXdgDirectory XdgData appName
927-
pure $ configUser {dataDir = xdgDataDir}
928-
929-
config <- case (T.stripPrefix "~/" $ T.pack $ dataDir configUserNorm) of
930-
Nothing ->
931-
pure $ configUser {dataDir = dataDir configUserNorm}
932-
Just rest -> do
933-
homeDir <- getHomeDirectory
934-
pure $ configUser { dataDir = homeDir </> T.unpack rest }
935-
936-
cliCommand <- execParser $ commandParserInfo config
937-
938-
connection <- setupConnection config
925+
printOutput appName config = do
926+
let dataPath = config & dataDir
927+
928+
configNormDataDir <-
929+
if null dataPath
930+
then do
931+
xdgDataDir <- getXdgDirectory XdgData appName
932+
pure $ config {dataDir = xdgDataDir}
933+
else
934+
case T.stripPrefix "~/" $ T.pack dataPath of
935+
Nothing -> pure $ config
936+
Just rest -> do
937+
homeDir <- getHomeDirectory
938+
pure $ config { dataDir = homeDir </> T.unpack rest }
939+
940+
941+
let hooksPath = configNormDataDir & hooks & directory
942+
943+
configNormHookDir <-
944+
if null hooksPath
945+
then pure $
946+
configNormDataDir
947+
{ hooks = (configNormDataDir & hooks)
948+
{ directory = dataDir configNormDataDir </> "hooks" }
949+
}
950+
else
951+
case T.stripPrefix "~/" $ T.pack hooksPath of
952+
Nothing -> pure $ configNormDataDir
953+
Just rest -> do
954+
homeDir <- getHomeDirectory
955+
pure $ configNormDataDir
956+
{ hooks = (configNormDataDir & hooks)
957+
{ directory = homeDir </> T.unpack rest }
958+
}
959+
960+
let hooksPathNorm = configNormHookDir & hooks & directory
961+
962+
createDirectoryIfMissing True hooksPathNorm
963+
964+
hookFiles <- listDirectory hooksPathNorm
965+
966+
hookFilesPerm :: [(FilePath, Permissions)] <- sequence $ hookFiles
967+
& filter (\name ->
968+
("pre-" `isPrefixOf` name) || ("post-" `isPrefixOf` name))
969+
<&> (hooksPathNorm </>)
970+
<&> \path -> do
971+
perm <- getPermissions path
972+
pure (path, perm)
973+
974+
hookFilesPermContent <- sequence $ hookFilesPerm
975+
& filter (\(_, perm) -> executable perm)
976+
<&> \(filePath, perm) -> do
977+
fileContent <- readFile filePath
978+
pure (filePath, perm, fileContent)
979+
980+
981+
let configNorm = addHookFilesToConfig configNormHookDir hookFilesPermContent
982+
983+
cliCommand <- execParser $ commandParserInfo configNorm
984+
985+
connection <- setupConnection configNorm
939986
-- TODO: Integrate into migrations
940-
tableStatus <- createTables config connection
941-
migrationsStatus <- runMigrations config connection
987+
tableStatus <- createTables configNorm connection
988+
migrationsStatus <- runMigrations configNorm connection
942989
nowElapsed <- timeCurrentP
943990

944991
let
945992
now = timeFromElapsedP nowElapsed :: DateTime
946993

947-
doc <- executeCLiCommand config now connection cliCommand
994+
doc <- executeCLiCommand configNorm now connection cliCommand
948995

949996
-- TODO: Use withConnection instead
950997
close connection
@@ -976,7 +1023,8 @@ main = do
9761023

9771024
case configResult of
9781025
Left error -> do
979-
if "not found" `T.isInfixOf` (T.pack $ prettyPrintParseException error)
1026+
if "file not found" `T.isInfixOf`
1027+
(T.pack $ prettyPrintParseException error)
9801028
then do
9811029
writeFile configPath exampleConfig
9821030
configResult2 <- decodeFileEither configPath

tasklite-core/example-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@ prioWidth: 4
2525
headCount: 20
2626
maxWidth: 120
2727
progressBarWidth: 24
28+
29+
hooks:
30+
#| Is per default the "hooks" directory in the `dataDir`
31+
# directory: /custom/path/hooks
32+
launch:
33+
pre:
34+
- interpreter: python3
35+
body: print('Python test')

tasklite-core/package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies:
3939
- optparse-applicative
4040
- parsec
4141
- portable-lines
42+
- pretty-simple
4243
- prettyprinter
4344
- prettyprinter-ansi-terminal
4445
- process

0 commit comments

Comments
 (0)