Skip to content

Commit 9d5877b

Browse files
committed
Add --utc flag to set explicit closing timestamp on do/end commands
1 parent 6c42d79 commit 9d5877b

4 files changed

Lines changed: 94 additions & 21 deletions

File tree

docs-source/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ This document lists all notable changes to the functionality of TaskLite.
1111

1212
#### 💻 General
1313

14+
- Add a `--utc` flag to `tl do`, `tl doonly`, `tl doall`, `tl end`, and
15+
`tl endall` to set an explicit closing timestamp instead of the
16+
current time (e.g. `tl do <id> --utc "2020-01-15 08:30:00"`).
1417
- Add task dependency support via `blocks` / `unblocks` subcommands
1518
and `blocks:<id>` / `blocked-by:<id>` body tokens for `tl add`
1619
([#14](https://github.com/ad-si/TaskLite/issues/14)).

tasklite-core/source/Cli.hs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import Options.Applicative (
9797
maybeReader,
9898
metavar,
9999
noIntersperse,
100+
option,
100101
parserFailure,
101102
progDesc,
102103
progDescDoc,
@@ -285,10 +286,10 @@ data Command
285286
| WaitFor Iso.Duration [IdText]
286287
| ReviewTasks [IdText]
287288
| ReviewTasksIn Iso.Duration [IdText]
288-
| DoTasks [IdText]
289-
| DoOneTask IdText (Maybe [Text])
290-
| EndTasks [IdText]
291-
| EndOneTask IdText (Maybe [Text])
289+
| DoTasks (Maybe DateTime) [IdText]
290+
| DoOneTask IdText (Maybe DateTime) (Maybe [Text])
291+
| EndTasks (Maybe DateTime) [IdText]
292+
| EndOneTask IdText (Maybe DateTime) (Maybe [Text])
292293
| TrashTasks [IdText]
293294
| DeleteTasks [IdText]
294295
| RepeatTasks Iso.Duration [IdText]
@@ -494,6 +495,18 @@ idsVar =
494495
metavar "TASK_ID ..." <> help "Ids of the tasks (Ulid)"
495496

496497

498+
-- | Optional `--utc` flag to set an explicit closing timestamp
499+
closedUtcOption :: Parser (Maybe DateTime)
500+
closedUtcOption =
501+
optional $
502+
option
503+
(maybeReader (parseUtc . T.pack))
504+
( long "utc"
505+
<> metavar "CLOSE_UTC"
506+
<> help "Timestamp when the task was closed (instead of now)"
507+
)
508+
509+
497510
-- | Help Sections
498511
basic_sec
499512
, shortcut_sec
@@ -589,24 +602,28 @@ commandParser conf =
589602

590603
<> command "do" (toParserInfo (DoOneTask
591604
<$> strArgument idsVar
605+
<*> closedUtcOption
592606
<*> optional (some (strArgument (metavar "CLOSING_NOTE"
593607
<> help "Final note to explain why and how it was done"))))
594608
"Mark a task as done and add optional closing note")
595609

596610
<> command "doonly" (toParserInfo
597-
(DoOneTask <$> strArgument idsVar <*> pure Nothing)
611+
(DoOneTask <$> strArgument idsVar <*> closedUtcOption <*> pure Nothing)
598612
"Mark only one task as done")
599613

600-
<> command "doall" (toParserInfo (DoTasks <$> some (strArgument idsVar))
614+
<> command "doall" (toParserInfo
615+
(DoTasks <$> closedUtcOption <*> some (strArgument idsVar))
601616
"Mark one or more tasks as done")
602617

603618
<> command "end" (toParserInfo (EndOneTask
604619
<$> strArgument idsVar
620+
<*> closedUtcOption
605621
<*> optional (some (strArgument (metavar "CLOSING_NOTE"
606622
<> help "Final note to explain why and how it was closed"))))
607623
"Mark a task as obsolete and add optional closing note")
608624

609-
<> command "endall" (toParserInfo (EndTasks <$> some (strArgument idsVar))
625+
<> command "endall" (toParserInfo
626+
(EndTasks <$> closedUtcOption <*> some (strArgument idsVar))
610627
"Mark a task as obsolete")
611628

612629
<> command "edit" (toParserInfo (EditTask <$> strArgument idVar)
@@ -1414,10 +1431,12 @@ executeCLiCommand config now connection progName args availableLinesMb = do
14141431
let days3 = Iso.DurationDate (Iso.DurDateDay (Iso.DurDay 3) Nothing)
14151432
in reviewTasksIn conf connection days3 ids
14161433
ReviewTasksIn days ids -> reviewTasksIn conf connection days ids
1417-
DoTasks ids -> doTasks conf connection Nothing ids
1418-
DoOneTask id noteWords -> doTasks conf connection noteWords [id]
1419-
EndTasks ids -> endTasks conf connection Nothing ids
1420-
EndOneTask id noteWords -> endTasks conf connection noteWords [id]
1434+
DoTasks utcMb ids -> doTasks conf connection utcMb Nothing ids
1435+
DoOneTask id utcMb noteWords ->
1436+
doTasks conf connection utcMb noteWords [id]
1437+
EndTasks utcMb ids -> endTasks conf connection utcMb Nothing ids
1438+
EndOneTask id utcMb noteWords ->
1439+
endTasks conf connection utcMb noteWords [id]
14211440
EditTask id -> editTask conf connection id
14221441
TrashTasks ids -> trashTasks conf connection ids
14231442
DeleteTasks ids -> deleteTasks conf connection ids

tasklite-core/source/Lib.hs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,26 @@ setClosedWithState connection task theTaskState = do
855855
]
856856

857857

858+
{-| Override the `closed_utc` set by the `set_closed_utc_after_update` trigger
859+
with an explicit timestamp (e.g. supplied via the `--utc` flag).
860+
Does nothing when no timestamp is given.
861+
-}
862+
overrideClosedUtc ::
863+
Config -> Connection -> Maybe DateTime -> Task -> IO ()
864+
overrideClosedUtc conf connection closedUtcMaybe task =
865+
forM_ closedUtcMaybe $ \closedUtc ->
866+
executeNamed
867+
connection
868+
[sql|
869+
UPDATE tasks
870+
SET closed_utc = :closed_utc
871+
WHERE ulid == :ulid
872+
|]
873+
[ ":closed_utc" := T.pack (timePrint conf.utcFormat closedUtc)
874+
, ":ulid" := task.ulid
875+
]
876+
877+
858878
{-| Returns the ULID + body of every still-open task that blocks the given
859879
task via a `relation = 'blocks'` row in `task_to_task`. A blocker counts
860880
as open when its `closed_utc IS NULL`. Used to gate close actions.
@@ -1214,8 +1234,14 @@ createNextRecurrence conf connection task = do
12141234
<+> dquotes (pretty newUlidText)
12151235

12161236

1217-
doTasks :: Config -> Connection -> Maybe [Text] -> [Text] -> IO (Doc AnsiStyle)
1218-
doTasks conf connection noteWordsMaybe ids = do
1237+
doTasks ::
1238+
Config ->
1239+
Connection ->
1240+
Maybe DateTime ->
1241+
Maybe [Text] ->
1242+
[Text] ->
1243+
IO (Doc AnsiStyle)
1244+
doTasks conf connection closedUtcMaybe noteWordsMaybe ids = do
12191245
docs <- forM ids $ \idSubstr -> do
12201246
execWithTask conf connection idSubstr $ \task -> do
12211247
let
@@ -1250,6 +1276,7 @@ doTasks conf connection noteWordsMaybe ids = do
12501276
<&> Just
12511277

12521278
setClosedWithState connection task $ Just Done
1279+
overrideClosedUtc conf connection closedUtcMaybe task
12531280

12541281
pure $
12551282
fromMaybe "" (noteMessageMaybe <&> (<> hardline))
@@ -1263,8 +1290,14 @@ doTasks conf connection noteWordsMaybe ids = do
12631290
pure $ vsep docs
12641291

12651292

1266-
endTasks :: Config -> Connection -> Maybe [Text] -> [Text] -> IO (Doc AnsiStyle)
1267-
endTasks conf connection noteWordsMaybe ids = do
1293+
endTasks ::
1294+
Config ->
1295+
Connection ->
1296+
Maybe DateTime ->
1297+
Maybe [Text] ->
1298+
[Text] ->
1299+
IO (Doc AnsiStyle)
1300+
endTasks conf connection closedUtcMaybe noteWordsMaybe ids = do
12681301
docs <- forM ids $ \idSubstr -> do
12691302
execWithTask conf connection idSubstr $ \task -> do
12701303
let
@@ -1299,6 +1332,7 @@ endTasks conf connection noteWordsMaybe ids = do
12991332
<&> Just
13001333

13011334
setClosedWithState connection task $ Just Obsolete
1335+
overrideClosedUtc conf connection closedUtcMaybe task
13021336

13031337
pure $
13041338
fromMaybe "" (noteMessageMaybe <&> (<> hardline))

tasklite-core/test/LibSpec.hs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ spec = do
440440
withMemoryDb conf $ \memConn -> do
441441
insertRecord "tasks" memConn exampleTask
442442
result <-
443-
doTasks confWithPostModify memConn Nothing [exampleTask.ulid]
443+
doTasks confWithPostModify memConn Nothing Nothing [exampleTask.ulid]
444444
unpack (show result) `shouldContain` "post-modify ran"
445445

446446
it "fires post-modify hook on `tag`" $ do
@@ -498,7 +498,7 @@ spec = do
498498
it "completes it" $ do
499499
withMemoryDb conf $ \memConn -> do
500500
insertRecord "tasks" memConn exampleTask
501-
doResult <- doTasks conf memConn Nothing [exampleTask.ulid]
501+
doResult <- doTasks conf memConn Nothing Nothing [exampleTask.ulid]
502502
unpack (show doResult) `shouldStartWith` "✅ Finished task"
503503
tasks :: [Task] <- query_ memConn "SELECT * FROM tasks"
504504
case tasks of
@@ -507,12 +507,29 @@ spec = do
507507
updatedTask `shouldSatisfy` (\task -> isJust task.closed_utc)
508508
_ -> P.die "Found more than one task"
509509

510+
it "completes it with an explicit closed UTC via --utc" $ do
511+
withMemoryDb conf $ \memConn -> do
512+
insertRecord "tasks" memConn exampleTask
513+
let utcTxt = "2020-01-15 08:30:00"
514+
case parseUtc utcTxt of
515+
Nothing -> P.die "Invalid UTC string"
516+
Just utcStamp -> do
517+
doResult <-
518+
doTasks conf memConn (Just utcStamp) Nothing [exampleTask.ulid]
519+
unpack (show doResult) `shouldStartWith` "✅ Finished task"
520+
tasks :: [Task] <- query_ memConn "SELECT * FROM tasks"
521+
case tasks of
522+
[updatedTask] -> do
523+
updatedTask.state `shouldBe` Just Done
524+
updatedTask.closed_utc `shouldBe` Just "2020-01-15 08:30:00"
525+
_ -> P.die "Found more than one task"
526+
510527
it "refuses to complete a task with open blockers" $ do
511528
withMemoryDb conf $ \memConn -> do
512529
insertRecord "tasks" memConn task1
513530
insertRecord "tasks" memConn task2
514531
_ <- blockTasks conf memConn task1.ulid task2.ulid
515-
result <- doTasks conf memConn Nothing [task2.ulid]
532+
result <- doTasks conf memConn Nothing Nothing [task2.ulid]
516533
unpack (show result) `shouldContain` "is blocked by"
517534
-- The blocked task must remain open.
518535
tasks :: [Task] <-
@@ -524,7 +541,7 @@ spec = do
524541
insertRecord "tasks" memConn task1
525542
insertRecord "tasks" memConn task2
526543
_ <- blockTasks conf memConn task1.ulid task2.ulid
527-
result <- endTasks conf memConn Nothing [task2.ulid]
544+
result <- endTasks conf memConn Nothing Nothing [task2.ulid]
528545
unpack (show result) `shouldContain` "is blocked by"
529546

530547
it "refuses to trash a task with open blockers" $ do
@@ -541,10 +558,10 @@ spec = do
541558
insertRecord "tasks" memConn task2
542559
_ <- blockTasks conf memConn task1.ulid task2.ulid
543560
-- Close the blocker first.
544-
blockerResult <- doTasks conf memConn Nothing [task1.ulid]
561+
blockerResult <- doTasks conf memConn Nothing Nothing [task1.ulid]
545562
unpack (show blockerResult) `shouldStartWith` "✅ Finished task"
546563
-- Now the originally-blocked task can be closed.
547-
targetResult <- doTasks conf memConn Nothing [task2.ulid]
564+
targetResult <- doTasks conf memConn Nothing Nothing [task2.ulid]
548565
unpack (show targetResult) `shouldStartWith` "✅ Finished task"
549566

550567
it "deletes it" $ do

0 commit comments

Comments
 (0)