Skip to content

Commit 09df546

Browse files
committed
Add new subcommand enter to open the default editor with an empty task
1 parent 283471a commit 09df546

File tree

4 files changed

+184
-114
lines changed

4 files changed

+184
-114
lines changed

tasklite-core/source/Cli.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ import ImportExport (
157157
dumpNdjson,
158158
dumpSql,
159159
editTask,
160+
enterTask,
160161
importDir,
161162
importEml,
162163
importFile,
@@ -258,7 +259,8 @@ data Command
258259
| AddSell [Text]
259260
| AddPay [Text]
260261
| AddShip [Text]
261-
| LogTask [Text] --
262+
| LogTask [Text]
263+
| EnterTask --
262264
{- Modify -}
263265
| ReadyOn DateTime [IdText]
264266
| WaitTasks [IdText]
@@ -486,6 +488,9 @@ commandParser conf =
486488
(metavar "BODY" <> help "Body of the task")))
487489
"Log an already completed task")
488490

491+
<> command "enter" (toParserInfo (pure EnterTask)
492+
"Open your default editor with an empty task template")
493+
489494
<> command "readyon" (toParserInfo (ReadyOn
490495
<$> argument (maybeReader (parseUtc . T.pack))
491496
(metavar "READY_UTC" <> help "Timestamp when task is ready")
@@ -1251,6 +1256,7 @@ executeCLiCommand conf now connection progName args availableLinesMb = do
12511256
AddPay bodyWords -> addTaskC $ ["Pay"] <> bodyWords <> ["+pay"]
12521257
AddShip bodyWords -> addTaskC $ ["Ship"] <> bodyWords <> ["+ship"]
12531258
LogTask bodyWords -> logTask conf connection bodyWords
1259+
EnterTask -> enterTask conf connection
12541260
ReadyOn datetime ids -> setReadyUtc conf connection datetime ids
12551261
WaitTasks ids -> waitTasks conf connection ids
12561262
WaitFor duration ids -> waitFor conf connection duration ids

tasklite-core/source/ImportExport.hs

Lines changed: 137 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ import Data.Text qualified as T
6969
import Data.Text.Lazy qualified as TL
7070
import Data.Text.Lazy.Encoding qualified as TL
7171
import Data.ULID (ulidFromInteger)
72-
import Data.ULID.TimeStamp (getULIDTimeStamp)
72+
import Data.ULID.TimeStamp (ULIDTimeStamp, getULIDTimeStamp)
7373
import Data.Vector qualified as V
7474
import Data.Yaml (
7575
ParseException (InvalidYaml),
@@ -89,6 +89,7 @@ import ImportTask (
8989
setMissingFields,
9090
)
9191
import Lib (
92+
addEmptyTask,
9293
execWithConn,
9394
execWithTask,
9495
insertNotes,
@@ -116,9 +117,10 @@ import Prettyprinter.Render.Terminal (
116117
)
117118
import System.Directory (createDirectoryIfMissing, listDirectory, removeFile)
118119
import System.FilePath (isExtensionOf, takeExtension, (</>))
120+
import System.Hourglass (timeCurrentP)
119121
import System.Posix.User (getEffectiveUserName)
120122
import System.Process (readProcess)
121-
import Task (Task (..), setMetadataField, taskToEditableMarkdown)
123+
import Task (Task (..), emptyTask, setMetadataField, taskToEditableMarkdown)
122124
import Text.Editor (runUserEditorDWIM, yamlTemplate)
123125
import Text.Parsec.Rfc2822 qualified as Email
124126
import Text.ParserCombinators.Parsec as Parsec (parse)
@@ -128,6 +130,7 @@ import Utils (
128130
IdText,
129131
countCharTL,
130132
emptyUlid,
133+
formatElapsedP,
131134
setDateTime,
132135
ulidTextToDateTime,
133136
zeroUlidTxt,
@@ -631,6 +634,132 @@ editUntilValidMarkdown editMode conn initialMarkdown wipMarkdown = do
631634
pure $ Right (newTask, BSL.toStrict yamlContent)
632635

633636

637+
insertTaskFromEdit ::
638+
Config ->
639+
Connection ->
640+
ImportTask ->
641+
P.ByteString ->
642+
P.Text ->
643+
IO (Doc AnsiStyle)
644+
insertTaskFromEdit conf conn importTaskRec newContent modified_utc = do
645+
-- Insert empty task if the edited task was newly created
646+
ulid <-
647+
if T.null importTaskRec.task.ulid
648+
then addEmptyTask conf conn <&> Task.ulid
649+
else pure importTaskRec.task.ulid
650+
651+
effectiveUserName <- getEffectiveUserName
652+
now <- getULIDTimeStamp <&> (show @ULIDTimeStamp >>> T.toLower)
653+
let
654+
parseMetadata :: Value -> Parser Bool
655+
parseMetadata val = case val of
656+
Object obj -> do
657+
let mdataMaybe = KeyMap.lookup "metadata" obj
658+
pure $ case mdataMaybe of
659+
Just (Object _) -> True
660+
_ -> False
661+
_ -> pure False
662+
663+
hasMetadata =
664+
parseMaybe parseMetadata
665+
=<< rightToMaybe (Yaml.decodeEither' newContent)
666+
667+
taskFixed =
668+
importTaskRec.task
669+
{ Task.ulid = ulid
670+
, Task.user =
671+
if importTaskRec.task.user == ""
672+
then T.pack effectiveUserName
673+
else importTaskRec.task.user
674+
, Task.metadata =
675+
if hasMetadata == Just True
676+
then importTaskRec.task.metadata
677+
else Nothing
678+
, -- Set to previous value to force SQL trigger to update it
679+
Task.modified_utc = modified_utc
680+
}
681+
notesCorrectUtc =
682+
importTaskRec.notes
683+
<&> ( \note ->
684+
note
685+
{ Note.ulid =
686+
if zeroUlidTxt `T.isPrefixOf` note.ulid
687+
then note.ulid & T.replace zeroUlidTxt now
688+
else note.ulid
689+
}
690+
)
691+
692+
updateTask conn taskFixed
693+
694+
nowDateTime <- dateCurrent
695+
696+
let taskFixedUtc =
697+
if P.isNothing taskFixed.closed_utc
698+
then taskFixed
699+
else
700+
taskFixed
701+
{ Task.modified_utc =
702+
nowDateTime
703+
& timePrint (toFormat importUtcFormat)
704+
& T.pack
705+
}
706+
707+
-- TODO: Remove after it was added to `createSetClosedUtcTrigger`
708+
-- Update again with the same `state` field to avoid firing
709+
-- SQL trigger which would overwrite the `closed_utc` field.
710+
P.when (P.isJust taskFixed.closed_utc) $ do
711+
updateTask conn taskFixedUtc
712+
713+
tagWarnings <- insertTags conn Nothing taskFixedUtc importTaskRec.tags
714+
noteWarnings <- insertNotes conn Nothing taskFixedUtc notesCorrectUtc
715+
716+
args <- P.getArgs
717+
postModifyResults <-
718+
executeHooks
719+
( TL.toStrict $
720+
TL.decodeUtf8 $
721+
Aeson.encode $
722+
object
723+
[ "arguments" .= args
724+
, "taskModified" .= taskFixedUtc
725+
-- TODO: Add tags and notes to task
726+
]
727+
)
728+
conf.hooks.modify.post
729+
730+
let postModifyHookMsg =
731+
( postModifyResults
732+
<&> \case
733+
Left error -> "ERROR:" <+> pretty error
734+
Right hookResult -> pretty hookResult.message
735+
& P.fold
736+
)
737+
<> hardline
738+
739+
pure $
740+
tagWarnings
741+
<$$> noteWarnings
742+
<$$> "✏️ Edited task"
743+
<+> dquotes (pretty taskFixed.body)
744+
<+> "with ulid"
745+
<+> dquotes (pretty taskFixed.ulid)
746+
<!!> postModifyHookMsg
747+
748+
749+
enterTask :: Config -> Connection -> IO (Doc AnsiStyle)
750+
enterTask conf conn = do
751+
taskMarkdown <- taskToEditableMarkdown conn emptyTask
752+
taskMarkdownTupleRes <-
753+
editUntilValidMarkdown OpenEditorRequireEdit conn taskMarkdown taskMarkdown
754+
case taskMarkdownTupleRes of
755+
Left error -> case error of
756+
InvalidYaml (Just (YamlException "")) -> pure P.mempty
757+
_ -> pure $ pretty $ Yaml.prettyPrintParseException error
758+
Right (importTaskRec, newContent) -> do
759+
modified_utc <- formatElapsedP conf timeCurrentP
760+
insertTaskFromEdit conf conn importTaskRec newContent modified_utc
761+
762+
634763
editTaskByTask :: Config -> EditMode -> Connection -> Task -> IO (Doc AnsiStyle)
635764
editTaskByTask conf editMode conn taskToEdit = do
636765
taskMarkdown <- taskToEditableMarkdown conn taskToEdit
@@ -641,101 +770,12 @@ editTaskByTask conf editMode conn taskToEdit = do
641770
InvalidYaml (Just (YamlException "")) -> pure P.mempty
642771
_ -> pure $ pretty $ Yaml.prettyPrintParseException error
643772
Right (importTaskRec, newContent) -> do
644-
effectiveUserName <- getEffectiveUserName
645-
now <- getULIDTimeStamp <&> (show >>> T.toLower)
646-
let
647-
parseMetadata :: Value -> Parser Bool
648-
parseMetadata val = case val of
649-
Object obj -> do
650-
let mdataMaybe = KeyMap.lookup "metadata" obj
651-
pure $ case mdataMaybe of
652-
Just (Object _) -> True
653-
_ -> False
654-
_ -> pure False
655-
656-
hasMetadata =
657-
parseMaybe parseMetadata
658-
=<< rightToMaybe (Yaml.decodeEither' newContent)
659-
660-
taskFixed =
661-
importTaskRec.task
662-
{ Task.user =
663-
if importTaskRec.task.user == ""
664-
then T.pack effectiveUserName
665-
else importTaskRec.task.user
666-
, Task.metadata =
667-
if hasMetadata == Just True
668-
then importTaskRec.task.metadata
669-
else Nothing
670-
, -- Set to previous value to force SQL trigger to update it
671-
Task.modified_utc = taskToEdit.modified_utc
672-
}
673-
notesCorrectUtc =
674-
importTaskRec.notes
675-
<&> ( \note ->
676-
note
677-
{ Note.ulid =
678-
if zeroUlidTxt `T.isPrefixOf` note.ulid
679-
then note.ulid & T.replace zeroUlidTxt now
680-
else note.ulid
681-
}
682-
)
683-
684-
updateTask conn taskFixed
685-
686-
nowDateTime <- dateCurrent
687-
688-
let taskFixedUtc =
689-
if P.isNothing taskFixed.closed_utc
690-
then taskFixed
691-
else
692-
taskFixed
693-
{ Task.modified_utc =
694-
nowDateTime
695-
& timePrint (toFormat importUtcFormat)
696-
& T.pack
697-
}
698-
699-
-- TODO: Remove after it was added to `createSetClosedUtcTrigger`
700-
-- Update again with the same `state` field to avoid firing
701-
-- SQL trigger which would overwrite the `closed_utc` field.
702-
P.when (P.isJust taskFixed.closed_utc) $ do
703-
updateTask conn taskFixedUtc
704-
705-
tagWarnings <- insertTags conn Nothing taskFixedUtc importTaskRec.tags
706-
noteWarnings <- insertNotes conn Nothing taskFixedUtc notesCorrectUtc
707-
708-
args <- P.getArgs
709-
postModifyResults <-
710-
executeHooks
711-
( TL.toStrict $
712-
TL.decodeUtf8 $
713-
Aeson.encode $
714-
object
715-
[ "arguments" .= args
716-
, "taskModified" .= taskFixedUtc
717-
-- TODO: Add tags and notes to task
718-
]
719-
)
720-
conf.hooks.modify.post
721-
722-
let postModifyHookMsg =
723-
( postModifyResults
724-
<&> \case
725-
Left error -> "ERROR:" <+> pretty error
726-
Right hookResult -> pretty hookResult.message
727-
& P.fold
728-
)
729-
<> hardline
730-
731-
pure $
732-
tagWarnings
733-
<$$> noteWarnings
734-
<$$> "✏️ Edited task"
735-
<+> dquotes (pretty taskFixed.body)
736-
<+> "with ulid"
737-
<+> dquotes (pretty taskFixed.ulid)
738-
<!!> postModifyHookMsg
773+
insertTaskFromEdit
774+
conf
775+
conn
776+
importTaskRec
777+
newContent
778+
taskToEdit.modified_utc
739779

740780

741781
-- TODO: Eliminate code duplications with `addTask`

tasklite-core/source/Lib.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,24 @@ getTriple conf = do
490490
pure (ulid, modified_utc, effectiveUserName)
491491

492492

493+
addEmptyTask :: Config -> Connection -> IO Task
494+
addEmptyTask conf conn = do
495+
(ulid, modified_utc, effectiveUserName) <- getTriple conf
496+
let task =
497+
emptyTask
498+
{ Task.ulid = T.toLower $ show ulid
499+
, Task.body = ""
500+
, Task.state = Just Done
501+
, Task.due_utc = Nothing
502+
, Task.closed_utc = Just modified_utc
503+
, Task.user = T.pack effectiveUserName
504+
, Task.modified_utc = modified_utc
505+
}
506+
507+
insertRecord "tasks" conn task
508+
pure task
509+
510+
493511
-- TODO: Eliminate code duplications with `editTask`
494512
addTask :: Config -> Connection -> [Text] -> IO (Doc AnsiStyle)
495513
addTask conf connection bodyWords = do

tasklite-core/source/Task.hs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -414,24 +414,30 @@ setMetadataField fieldNameText value task =
414414
taskToEditableMarkdown :: Connection -> Task -> P.IO P.ByteString
415415
taskToEditableMarkdown conn task = do
416416
(tags :: [[P.Text]]) <-
417-
query
418-
conn
419-
[sql|
420-
SELECT tag
421-
FROM task_to_tag
422-
WHERE task_ulid == ?
423-
|]
424-
(Only $ ulid task)
417+
if T.null task.ulid
418+
then pure []
419+
else
420+
query
421+
conn
422+
[sql|
423+
SELECT tag
424+
FROM task_to_tag
425+
WHERE task_ulid == ?
426+
|]
427+
(Only task.ulid)
425428

426429
(notes :: [[P.Text]]) <-
427-
query
428-
conn
429-
[sql|
430-
SELECT note
431-
FROM task_to_note
432-
WHERE task_ulid == ?
433-
|]
434-
(Only $ ulid task)
430+
if T.null task.ulid
431+
then pure []
432+
else
433+
query
434+
conn
435+
[sql|
436+
SELECT note
437+
FROM task_to_note
438+
WHERE task_ulid == ?
439+
|]
440+
(Only task.ulid)
435441

436442
let
437443
indentNoteContent noteContent =

0 commit comments

Comments
 (0)