@@ -8,21 +8,24 @@ import Protolude as P hiding (state)
88
99import 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 }
6063
6164
65+ 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
96103 let
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)
101108
102109 o_tags <- o .:? " tags"
@@ -111,7 +118,7 @@ instance FromJSON ImportTask where
111118 let
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)
116123
117124 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)
131138
132139 ready' <- o .:? " ready"
@@ -135,7 +142,7 @@ instance FromJSON ImportTask where
135142 let
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)
140147
141148 review' <- o .:? " review"
@@ -145,7 +152,7 @@ instance FromJSON ImportTask where
145152 let
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)
150157
151158 waiting' <- o .:? " waiting"
@@ -154,7 +161,7 @@ instance FromJSON ImportTask where
154161 let
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)
159166
160167 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)
172179
173180 group_ulid' <- o .:? " group_ulid"
174181 group_id' <- o .:? " group_id"
175182 let
176183 maybeGroupUlid = group_ulid' <|> group_id'
177184 group_ulid = fmap
178- (T. pack . (timePrint ISO8601_DateAndTime ))
185+ (T. pack . (timePrint importUtcFormat ))
179186 (parseUtc =<< maybeGroupUlid)
180187
181188 repetition_duration' <- o .:? " repetition_duration"
182189 repeat_duration' <- o .:? " repeat_duration"
183190 let
184191 maybeRepetition = repetition_duration' <|> repeat_duration'
185192 repetition_duration = fmap
186- (T. pack . (timePrint ISO8601_DateAndTime ))
193+ (T. pack . (timePrint importUtcFormat ))
187194 (parseUtc =<< maybeRepetition)
188195
189196 recurrence_duration' <- o .:? " recurrence_duration"
190197 recur_duration' <- o .:? " recur_duration"
191198 let
192199 maybeRecurrence = recurrence_duration' <|> recur_duration'
193200 recurrence_duration = fmap
194- (T. pack . (timePrint ISO8601_DateAndTime ))
201+ (T. pack . (timePrint importUtcFormat ))
195202 (parseUtc =<< maybeRecurrence)
196203
197- o_notes <- optional ( o .: " notes" ) :: Parser (Maybe [Note ])
204+ o_notes <- o .:? " notes" :: Parser (Maybe [Note ])
198205 annotations <- o .:? " annotations" :: Parser (Maybe [Annotation ])
199206 let
200207 notes = case (o_notes, annotations) of
@@ -205,8 +212,9 @@ instance FromJSON ImportTask where
205212 o_user <- o .:? " user"
206213 let user = fromMaybe " " o_user
207214
215+ o_metadata <- o .:? " metadata"
208216 let
209- metadata = Just $ Object o
217+ metadata = o_metadata <|> ( Just $ Object o)
210218 tempTask = Task {ulid = " " , .. }
211219
212220 o_ulid <- o .:? " ulid"
@@ -227,30 +235,34 @@ instance FromJSON ImportTask where
227235 pure $ ImportTask finalTask notes tags
228236
229237
238+ 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
234259
235260 let
236- importResult = Aeson. eitherDecode content :: Either [Char ] ImportTask
261+ decodeResult = Aeson. eitherDecode content :: Either [Char ] ImportTask
237262
238- case importResult of
263+ case decodeResult of
239264 Left 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
254266
255267
256268-- 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
0 commit comments