@@ -8,21 +8,24 @@ import Protolude as P hiding (state)
8899import Data.Aeson as Aeson
1010import Data.Aeson.Types
11-import qualified Data.ByteString.Lazy as BL
11+import qualified Data.ByteString.Lazy as BSL
1212import qualified Data.Csv as Csv
13+import qualified Data.HashMap.Strict as HM
14+import Data.Hourglass
1315import qualified Data.Text as T
1416import qualified Data.Text.Lazy.Encoding as TL
1517import Data.Text.Prettyprint.Doc hiding ((<>))
1618import Data.Text.Prettyprint.Doc.Render.Terminal
17-import Data.Hourglass
1819import Data.ULID
20+import Data.Yaml as Yaml
1921import Database.Beam
2022import Database.SQLite.Simple as Sql
2123import Lib
2224import System.Directory
2325import System.FilePath ((</>))
2426import System.Process
2527import System.Posix.User (getEffectiveUserName)
28+import System.ReadEditor (readEditorWith)
2629import Time.System
2730import Utils
2831import Task
@@ -59,6 +62,10 @@ annotationToNote annot@Annotation {entry = entry, description = description} =
5962 }
6063616465+importUtcFormat :: TimeFormatString
66+importUtcFormat = (toFormat ("YYYY-MM-DD H:MI:S" :: [Char]))
67+68+6269data ImportTask = ImportTask
6370{ task :: Task
6471 , notes :: [Note]
@@ -96,7 +103,7 @@ instance FromJSON ImportTask where
96103let
97104 maybeModified = modified <|> modified_at <|> o_modified_utc
98105<|> modification_date <|> updated_at
99- modified_utc = T.pack $ timePrint ISO8601_DateAndTime $
106+ modified_utc = T.pack $ timePrint importUtcFormat $
100107 fromMaybe createdUtc (parseUtc =<< maybeModified)
101108102109 o_tags <- o .:? "tags"
@@ -111,7 +118,7 @@ instance FromJSON ImportTask where
111118let
112119 maybeDue = due <|> o_due_utc <|> due_on
113120 due_utc = fmap
114- (T.pack . (timePrint ISO8601_DateAndTime))
121+ (T.pack . (timePrint importUtcFormat))
115122 (parseUtc =<< maybeDue)
116123117124 awake' <- o .:? "awake"
@@ -126,7 +133,7 @@ instance FromJSON ImportTask where
126133<|> sleep' <|> sleep_utc' <|> sleep_until'
127134<|> wait' <|> wait_until'
128135 awake_utc = fmap
129- (T.pack . (timePrint ISO8601_DateAndTime))
136+ (T.pack . (timePrint importUtcFormat))
130137 (parseUtc =<< maybeAwake)
131138132139 ready' <- o .:? "ready"
@@ -135,7 +142,7 @@ instance FromJSON ImportTask where
135142let
136143 maybeReady = ready' <|> ready_since' <|> ready_utc'
137144 ready_utc = fmap
138- (T.pack . (timePrint ISO8601_DateAndTime))
145+ (T.pack . (timePrint importUtcFormat))
139146 (parseUtc =<< maybeReady)
140147141148 review' <- o .:? "review"
@@ -145,7 +152,7 @@ instance FromJSON ImportTask where
145152let
146153 maybeReview = review' <|> review_at' <|> review_since' <|> review_utc'
147154 review_utc = fmap
148- (T.pack . (timePrint ISO8601_DateAndTime))
155+ (T.pack . (timePrint importUtcFormat))
149156 (parseUtc =<< maybeReview)
150157151158 waiting' <- o .:? "waiting"
@@ -154,7 +161,7 @@ instance FromJSON ImportTask where
154161let
155162 maybewaiting = waiting' <|> waiting_since' <|> waiting_utc'
156163 waiting_utc = fmap
157- (T.pack . (timePrint ISO8601_DateAndTime))
164+ (T.pack . (timePrint importUtcFormat))
158165 (parseUtc =<< maybewaiting)
159166160167 closed <- o .:? "closed"
@@ -167,34 +174,34 @@ instance FromJSON ImportTask where
167174 maybeClosed = closed <|> o_closed_utc <|> closed_on
168175<|> end <|> o_end_utc <|> end_on
169176 closed_utc = fmap
170- (T.pack . (timePrint ISO8601_DateAndTime))
177+ (T.pack . (timePrint importUtcFormat))
171178 (parseUtc =<< maybeClosed)
172179173180 group_ulid' <- o .:? "group_ulid"
174181 group_id' <- o .:? "group_id"
175182let
176183 maybeGroupUlid = group_ulid' <|> group_id'
177184 group_ulid = fmap
178- (T.pack . (timePrint ISO8601_DateAndTime))
185+ (T.pack . (timePrint importUtcFormat))
179186 (parseUtc =<< maybeGroupUlid)
180187181188 repetition_duration' <- o .:? "repetition_duration"
182189 repeat_duration' <- o .:? "repeat_duration"
183190let
184191 maybeRepetition = repetition_duration' <|> repeat_duration'
185192 repetition_duration = fmap
186- (T.pack . (timePrint ISO8601_DateAndTime))
193+ (T.pack . (timePrint importUtcFormat))
187194 (parseUtc =<< maybeRepetition)
188195189196 recurrence_duration' <- o .:? "recurrence_duration"
190197 recur_duration' <- o .:? "recur_duration"
191198let
192199 maybeRecurrence = recurrence_duration' <|> recur_duration'
193200 recurrence_duration = fmap
194- (T.pack . (timePrint ISO8601_DateAndTime))
201+ (T.pack . (timePrint importUtcFormat))
195202 (parseUtc =<< maybeRecurrence)
196203197- o_notes <- optional (o .: "notes") :: Parser (Maybe [Note])
204+ o_notes <- o .:? "notes" :: Parser (Maybe [Note])
198205 annotations <- o .:? "annotations" :: Parser (Maybe [Annotation])
199206let
200207 notes = case (o_notes, annotations) of
@@ -205,8 +212,9 @@ instance FromJSON ImportTask where
205212 o_user <- o .:? "user"
206213let user = fromMaybe "" o_user
207214215+ o_metadata <- o .:? "metadata"
208216let
209- metadata = Just $ Object o
217+ metadata = o_metadata <|> (Just $ Object o)
210218 tempTask = Task {ulid = "", ..}
211219212220 o_ulid <- o .:? "ulid"
@@ -227,30 +235,34 @@ instance FromJSON ImportTask where
227235pure $ ImportTask finalTask notes tags
228236229237238+insertImportTask :: Connection -> ImportTask -> IO (Doc ann)
239+insertImportTask connection importTaskRecord = do
240+ effectiveUserName <- getEffectiveUserName
241+let
242+ taskParsed = task importTaskRecord
243+ theTask = if Task.user taskParsed == ""
244+then taskParsed { Task.user = T.pack effectiveUserName }
245+else taskParsed
246+ insertTask connection theTask
247+ insertTags connection (primaryKey theTask) (tags importTaskRecord)
248+ insertNotes connection (primaryKey theTask) (notes importTaskRecord)
249+pure $
250+"📥 Imported task" <+> dquotes (pretty $ Task.body theTask)
251+<+> "with ulid" <+> dquotes (pretty $ Task.ulid theTask)
252+<+> hardline
253+254+230255importTask :: Config -> IO (Doc AnsiStyle)
231256importTask conf = do
232257 connection <- setupConnection conf
233- content <- BL.getContents
258+ content <- BSL.getContents
234259235260let
236-importResult = Aeson.eitherDecode content :: Either [Char] ImportTask
261+decodeResult = Aeson.eitherDecode content :: Either [Char] ImportTask
237262238-case importResult of
263+case decodeResult of
239264Left error -> die $ (T.pack error) <> " in task \n" <> show content
240-Right importTaskRecord -> do
241- effectiveUserName <- getEffectiveUserName
242-let
243- taskParsed = task importTaskRecord
244- theTask = if Task.user taskParsed == ""
245-then taskParsed { Task.user = T.pack effectiveUserName }
246-else taskParsed
247- insertTags connection (primaryKey theTask) (tags importTaskRecord)
248- insertNotes connection (primaryKey theTask) (notes importTaskRecord)
249- insertTask connection theTask
250-pure $
251-"📥 Imported task" <+> dquotes (pretty $ Task.body theTask)
252-<+> "with ulid" <+> dquotes (pretty $ Task.ulid theTask)
253-<+> hardline
265+Right importTaskRecord -> insertImportTask connection importTaskRecord
254266255267256268-- TODO: Use Task instead of FullTask to fix broken notes export
@@ -304,3 +316,57 @@ backupDatabase conf = do
304316<> pretty (
305317"✅ Backed up database \"" <> (dbName conf)
306318<> "\" to \"" <> backupFilePath <> "\"")
319+320+321+editTask :: Config -> Connection -> IdText -> IO (Doc AnsiStyle)
322+editTask conf connection idSubstr = do
323+ execWithTask conf connection idSubstr $ \taskToEdit -> do
324+let taskYaml = (T.unpack . decodeUtf8 . Yaml.encode) taskToEdit
325+326+ newContent <- readEditorWith taskYaml
327+328+let
329+ newContentBS = encodeUtf8 $ T.pack newContent
330+331+parseMetadata :: Value -> Parser Bool
332+ parseMetadata val = case val of
333+Object obj -> do
334+let mdataMaybe = HM.lookup "metadata" obj
335+336+ hasMdata <- pure $ case mdataMaybe of
337+Just (Object _) -> True
338+ _ -> False
339+340+pure hasMdata
341+ _ -> pure False
342+343+ hasMetadata = parseMaybe parseMetadata
344+=<< (rightToMaybe $ Yaml.decodeEither' newContentBS :: Maybe Value)
345+346+decodeResult :: Either ParseException ImportTask
347+ decodeResult = Yaml.decodeEither' newContentBS
348+349+case decodeResult of
350+Left error -> die $ (show error) <> " in task \n" <> show newContent
351+Right importTaskRecord -> do
352+ effectiveUserName <- getEffectiveUserName
353+let
354+ taskParsed = task importTaskRecord
355+ taskFixed = taskParsed
356+ { Task.user =
357+if Task.user taskParsed == ""
358+then T.pack effectiveUserName
359+else Task.user taskParsed
360+ , Task.metadata =
361+if hasMetadata == Just True
362+then Task.metadata taskParsed
363+else Nothing
364+ }
365+366+ replaceTask connection taskFixed
367+ insertTags connection (primaryKey taskFixed) (tags importTaskRecord)
368+ insertNotes connection (primaryKey taskFixed) (notes importTaskRecord)
369+pure $
370+"✏️ Edited task" <+> dquotes (pretty $ Task.body taskFixed)
371+<+> "with ulid" <+> dquotes (pretty $ Task.ulid taskFixed)
372+<+> hardline