GitHub

@@ -8,21 +8,24 @@ import Protolude as P hiding (state)

8899

import Data.Aeson as Aeson

1010

import Data.Aeson.Types

11-

import qualified Data.ByteString.Lazy as BL

11+

import qualified Data.ByteString.Lazy as BSL

1212

import qualified Data.Csv as Csv

13+

import qualified Data.HashMap.Strict as HM

14+

import Data.Hourglass

1315

import qualified Data.Text as T

1416

import qualified Data.Text.Lazy.Encoding as TL

1517

import Data.Text.Prettyprint.Doc hiding ((<>))

1618

import Data.Text.Prettyprint.Doc.Render.Terminal

17-

import Data.Hourglass

1819

import Data.ULID

20+

import Data.Yaml as Yaml

1921

import Database.Beam

2022

import Database.SQLite.Simple as Sql

2123

import Lib

2224

import System.Directory

2325

import System.FilePath ((</>))

2426

import System.Process

2527

import System.Posix.User (getEffectiveUserName)

28+

import System.ReadEditor (readEditorWith)

2629

import Time.System

2730

import Utils

2831

import 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+6269

data 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)

101108102109

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)

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

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)

140147141148

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)

150157151158

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)

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"

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)

180187181188

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)

188195189196

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)

196203197-

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

207214215+

o_metadata <- o .:? "metadata"

208216

let

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

227235

pure $ 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+230255

importTask :: Config -> IO (Doc AnsiStyle)

231256

importTask conf = do

232257

connection <- setupConnection conf

233-

content <- BL.getContents

258+

content <- BSL.getContents

234259235260

let

236-

importResult = Aeson.eitherDecode content :: Either [Char] ImportTask

261+

decodeResult = Aeson.eitherDecode content :: Either [Char] ImportTask

237262238-

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

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

Read the original on github.com ↗