Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 9cb8fa3

Browse files
committed
Add support for external CLI commands
1 parent 19cade7 commit 9cb8fa3

File tree

4 files changed

+81
-16
lines changed

4 files changed

+81
-16
lines changed

docs-source/usage/cli.md

+21
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,24 @@ and
248248
```sh
249249
tl metadata set kanban-state sprint 01e0k6a1p00002zgzc0845vayw
250250
```
251+
252+
253+
## External Commands
254+
255+
Like Git, TaskLite also supports external commands.
256+
This allows you to easily extend TaskLite's functionality with your own scripts.
257+
258+
For this to work, simply add an executable script (`chmod +x`)
259+
with the prefix `tasklite-` to your `$PATH`
260+
261+
For example, to add a `grin` command which simply prints a smiley:
262+
263+
```sh
264+
$ cat /usr/local/bin/tasklite-grin
265+
#! /usr/bin/env bash
266+
267+
echo '😁' "$@"
268+
269+
$ tasklite grin Hi
270+
😁 Hi
271+
```

tasklite-core/app/Main.hs

+56-16
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ import Protolude (
5050
(<&>),
5151
(||),
5252
)
53+
import Protolude qualified as P
5354

55+
import Control.Monad.Catch (catchAll)
5456
import Data.Aeson as Aeson (KeyValue ((.=)), encode, object)
5557
import Data.FileEmbed (embedStringFile, makeRelativeToProject)
5658
import Data.Hourglass (
@@ -66,9 +68,6 @@ import Data.Text.Lazy.Encoding qualified as TL
6668
import Data.Time.ISO8601.Duration qualified as Iso
6769
import Data.Version (showVersion)
6870
import Data.Yaml (decodeFileEither, prettyPrintParseException)
69-
70-
-- Special module provided by Cabal
71-
7271
import Database.SQLite.Simple (Connection (..))
7372
import Database.SQLite.Simple qualified as SQLite
7473
import GHC.IO.Encoding (setLocaleEncoding, utf8)
@@ -154,6 +153,7 @@ import Options.Applicative (
154153
fullDesc,
155154
headerDoc,
156155
help,
156+
helpHeader,
157157
helper,
158158
idm,
159159
info,
@@ -169,6 +169,7 @@ import Options.Applicative (
169169
subparser,
170170
switch,
171171
)
172+
import Options.Applicative.Help.Chunk (Chunk (Chunk), (<<+>>))
172173
import Options.Applicative.Help.Core (parserHelp)
173174
import Paths_tasklite_core (version)
174175
import Prettyprinter (
@@ -185,10 +186,11 @@ import Prettyprinter (
185186
)
186187
import Prettyprinter.Render.Terminal (
187188
AnsiStyle,
188-
Color (Black, Blue, Cyan, Yellow),
189+
Color (Black, Blue, Cyan, Red, Yellow),
189190
bold,
190191
color,
191192
colorDull,
193+
hPutDoc,
192194
putDoc,
193195
)
194196
import System.Directory (
@@ -202,6 +204,7 @@ import System.Directory (
202204
listDirectory,
203205
)
204206
import System.FilePath ((</>))
207+
import System.Process (readProcess)
205208
import Time.System (timeCurrentP)
206209

207210
import Config (
@@ -342,13 +345,15 @@ data Command
342345
| Help
343346
| PrintConfig
344347
| UlidToUtc Text
348+
| ExternalCommand Text (Maybe [Text])
345349
deriving (Show, Eq)
346350

347351

348352
data CliArgs = CliArgs
349353
{ cliCommand :: Command
350354
, runHelpCommand :: Bool
351355
}
356+
deriving (Show, Eq)
352357

353358

354359
nameToAliasList :: [(Text, Text)]
@@ -944,26 +949,27 @@ commandParser conf =
944949
-- <> command "utc-quarter" -- … last day of the quarter
945950
-- <> command "utc-year" -- … last day of the year
946951
)
952+
953+
-- Catch-all parser for any external "tasklite-???" command
954+
-- Do not show in help
955+
<|> ExternalCommand
956+
<$> strArgument P.mempty
957+
<*> optional (some (strArgument P.mempty))
947958
)
948959

949960
{- FOURMOLU_ENABLE -}
950961

951962

952-
runHelpSwitch :: Parser Bool
953-
runHelpSwitch =
954-
switch
955-
( long "help"
956-
<> short 'h'
957-
<> help "Display current help page"
958-
<> internal
959-
)
960-
961-
962963
cliArgsParser :: Config -> Parser CliArgs
963964
cliArgsParser conf =
964965
CliArgs
965966
<$> commandParser conf
966-
<*> runHelpSwitch
967+
<*> switch
968+
( long "help"
969+
<> short 'h'
970+
<> help "Display current help page"
971+
<> internal
972+
)
967973

968974

969975
parserInfo :: Config -> ParserInfo CliArgs
@@ -1101,7 +1107,7 @@ executeCLiCommand conf now connection = do
11011107

11021108
if runHelpCommand cliArgs
11031109
then pure $ extendHelp $ parserHelp defaultPrefs $ cliArgsParser conf
1104-
else case cliCommand cliArgs of
1110+
else case cliArgs.cliCommand of
11051111
ListAll -> listAll conf now connection
11061112
ListHead -> headTasks conf now connection
11071113
ListNew -> newTasks conf now connection
@@ -1191,6 +1197,40 @@ executeCLiCommand conf now connection = do
11911197
PrintConfig -> pure $ pretty conf
11921198
Alias alias _ -> pure $ aliasWarning alias
11931199
UlidToUtc ulid -> pure $ prettyUlid ulid
1200+
ExternalCommand cmd argsMb -> do
1201+
let
1202+
args =
1203+
argsMb & P.fromMaybe []
1204+
1205+
runCmd = do
1206+
output <-
1207+
readProcess
1208+
("tasklite-" <> T.unpack cmd)
1209+
(args <&> T.unpack)
1210+
""
1211+
pure $ pretty output
1212+
1213+
handleException exception = do
1214+
hPutDoc P.stderr $
1215+
if not $ exception & show & T.isInfixOf "does not exist"
1216+
then pretty (show exception :: Text)
1217+
else do
1218+
let
1219+
theHelp = parserHelp defaultPrefs $ cliArgsParser conf
1220+
newHeader =
1221+
Chunk
1222+
( Just $
1223+
annotate (color Red) $
1224+
"ERROR: Command \""
1225+
<> pretty cmd
1226+
<> "\" does not exist"
1227+
)
1228+
<<+>> helpHeader theHelp
1229+
extendHelp theHelp{helpHeader = newHeader}
1230+
1231+
P.exitFailure
1232+
1233+
catchAll runCmd handleException
11941234

11951235

11961236
printOutput :: [Char] -> Config -> IO ()

tasklite-core/package.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ executables:
9191
dependencies:
9292
- aeson
9393
- directory
94+
- exceptions
9495
- file-embed
9596
- filepath
9697
- githash
@@ -99,6 +100,7 @@ executables:
99100
- optparse-applicative
100101
- prettyprinter
101102
- prettyprinter-ansi-terminal
103+
- process
102104
- sqlite-simple
103105
- tasklite-core
104106
- text

tasklite-core/tasklite-core.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ executable tasklite
120120
aeson
121121
, base >=4.7 && <5
122122
, directory
123+
, exceptions
123124
, file-embed
124125
, filepath
125126
, githash
@@ -128,6 +129,7 @@ executable tasklite
128129
, optparse-applicative
129130
, prettyprinter
130131
, prettyprinter-ansi-terminal
132+
, process
131133
, protolude
132134
, sqlite-simple
133135
, tasklite-core

0 commit comments

Comments
 (0)