fix:import: save each file's latest dates, separately (#2125)

This commit is contained in:
Simon Michael 2023-12-06 21:57:06 -10:00
parent d1635a55f8
commit c6a580ff3b
2 changed files with 36 additions and 22 deletions

View File

@ -109,6 +109,7 @@ module Hledger.Read (
-- * Misc -- * Misc
journalStrictChecks, journalStrictChecks,
saveLatestDates, saveLatestDates,
saveLatestDatesForFiles,
-- * Re-exported -- * Re-exported
JournalReader.tmpostingrulep, JournalReader.tmpostingrulep,
@ -132,7 +133,7 @@ import Data.Default (def)
import Data.Foldable (asum) import Data.Foldable (asum)
import Data.List (group, sort, sortBy) import Data.List (group, sort, sortBy)
import Data.List.NonEmpty (nonEmpty) import Data.List.NonEmpty (nonEmpty)
import Data.Maybe (fromMaybe) import Data.Maybe (catMaybes, fromMaybe)
import Data.Ord (comparing) import Data.Ord (comparing)
import Data.Semigroup (sconcat) import Data.Semigroup (sconcat)
import Data.Text (Text) import Data.Text (Text)
@ -243,15 +244,17 @@ readJournal iopts@InputOpts{strict_} mpath txt = do
-- --
readJournalFile :: InputOpts -> PrefixedFilePath -> ExceptT String IO Journal readJournalFile :: InputOpts -> PrefixedFilePath -> ExceptT String IO Journal
readJournalFile iopts@InputOpts{new_, new_save_} prefixedfile = do readJournalFile iopts@InputOpts{new_, new_save_} prefixedfile = do
(j,latestdates) <- readJournalFileAndLatestDates iopts prefixedfile (j, mlatestdates) <- readJournalFileAndLatestDates iopts prefixedfile
when (new_ && new_save_) $ liftIO $ when (new_ && new_save_) $ liftIO $
saveLatestDates latestdates (snd $ splitReaderPrefix prefixedfile) case mlatestdates of
Nothing -> return ()
Just (LatestDatesForFile f ds) -> saveLatestDates ds f
return j return j
-- The implementation of readJournalFile, but with --new, -- The implementation of readJournalFile.
-- also returns the latest transaction date(s) read. -- With --new, it also returns the latest transaction date(s) read from each file.
-- Used by readJournalFiles, to save those at the end. -- readJournalFiles uses this to update .latest files only after a successful read of all.
readJournalFileAndLatestDates :: InputOpts -> PrefixedFilePath -> ExceptT String IO (Journal,LatestDates) readJournalFileAndLatestDates :: InputOpts -> PrefixedFilePath -> ExceptT String IO (Journal, Maybe LatestDatesForFile)
readJournalFileAndLatestDates iopts prefixedfile = do readJournalFileAndLatestDates iopts prefixedfile = do
let let
(mfmt, f) = splitReaderPrefix prefixedfile (mfmt, f) = splitReaderPrefix prefixedfile
@ -266,9 +269,9 @@ readJournalFileAndLatestDates iopts prefixedfile = do
then do then do
ds <- liftIO $ previousLatestDates f ds <- liftIO $ previousLatestDates f
let (newj, newds) = journalFilterSinceLatestDates ds j let (newj, newds) = journalFilterSinceLatestDates ds j
return (newj, newds) return (newj, Just $ LatestDatesForFile f newds)
else else
return (j, []) return (j, Nothing)
-- | Read a Journal from each specified file path (using @readJournalFile@) -- | Read a Journal from each specified file path (using @readJournalFile@)
-- and combine them into one; or return the first error message. -- and combine them into one; or return the first error message.
@ -285,20 +288,20 @@ readJournalFileAndLatestDates iopts prefixedfile = do
readJournalFiles :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO Journal readJournalFiles :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO Journal
readJournalFiles iopts@InputOpts{strict_,new_,new_save_} prefixedfiles = do readJournalFiles iopts@InputOpts{strict_,new_,new_save_} prefixedfiles = do
let iopts' = iopts{strict_=False, new_save_=False} let iopts' = iopts{strict_=False, new_save_=False}
(j,latestdates) <- (j, latestdatesforfiles) <-
traceOrLogAt 6 ("readJournalFiles: "++show prefixedfiles) $ traceOrLogAt 6 ("readJournalFiles: "++show prefixedfiles) $
readJournalFilesAndLatestDates iopts' prefixedfiles readJournalFilesAndLatestDates iopts' prefixedfiles
when strict_ $ liftEither $ journalStrictChecks j when strict_ $ liftEither $ journalStrictChecks j
when (new_ && new_save_) $ liftIO $ when (new_ && new_save_) $ liftIO $ saveLatestDatesForFiles latestdatesforfiles
mapM_ (saveLatestDates latestdates . snd . splitReaderPrefix) prefixedfiles
return j return j
-- The implementation of readJournalFiles, but with --new, -- The implementation of readJournalFiles, but with --new,
-- also returns the latest transaction date(s) read in each file. -- also returns the latest transaction date(s) read in each file.
-- Used by the import command, to save those at the end. -- Used by the import command, to save those at the end.
readJournalFilesAndLatestDates :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO (Journal,LatestDates) readJournalFilesAndLatestDates :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO (Journal, [LatestDatesForFile])
readJournalFilesAndLatestDates iopts = readJournalFilesAndLatestDates iopts pfs = do
fmap (maybe def sconcat . nonEmpty) . mapM (readJournalFileAndLatestDates iopts) (js, lastdates) <- unzip <$> mapM (readJournalFileAndLatestDates iopts) pfs
return (maybe def sconcat $ nonEmpty js, catMaybes lastdates)
-- | Run the extra -s/--strict checks on a journal, -- | Run the extra -s/--strict checks on a journal,
-- returning the first error message if any of them fail. -- returning the first error message if any of them fail.
@ -371,6 +374,9 @@ newJournalContent = do
-- and how many transactions there were on that date. -- and how many transactions there were on that date.
type LatestDates = [Day] type LatestDates = [Day]
-- The path of an input file, and its current "LatestDates".
data LatestDatesForFile = LatestDatesForFile FilePath LatestDates
-- | Get all instances of the latest date in an unsorted list of dates. -- | Get all instances of the latest date in an unsorted list of dates.
-- Ie, if the latest date appears once, return it in a one-element list, -- Ie, if the latest date appears once, return it in a one-element list,
-- if it appears three times (anywhere), return three of it. -- if it appears three times (anywhere), return three of it.
@ -383,6 +389,10 @@ latestDates = {-# HLINT ignore "Avoid reverse" #-}
saveLatestDates :: LatestDates -> FilePath -> IO () saveLatestDates :: LatestDates -> FilePath -> IO ()
saveLatestDates dates f = T.writeFile (latestDatesFileFor f) $ T.unlines $ map showDate dates saveLatestDates dates f = T.writeFile (latestDatesFileFor f) $ T.unlines $ map showDate dates
-- | Save each file's latest dates.
saveLatestDatesForFiles :: [LatestDatesForFile] -> IO ()
saveLatestDatesForFiles = mapM_ (\(LatestDatesForFile f ds) -> saveLatestDates ds f)
-- | What were the latest transaction dates seen the last time this -- | What were the latest transaction dates seen the last time this
-- journal file was read ? If there were multiple transactions on the -- journal file was read ? If there were multiple transactions on the
-- latest date, that number of dates is returned, otherwise just one. -- latest date, that number of dates is returned, otherwise just one.

View File

@ -54,10 +54,10 @@ importcmd opts@CliOpts{rawopts_=rawopts,inputopts_=iopts} j = do
case inputfiles of case inputfiles of
[] -> error' "please provide one or more input files as arguments" -- PARTIAL: [] -> error' "please provide one or more input files as arguments" -- PARTIAL:
fs -> do fs -> do
enewjandlatestdates <- runExceptT $ readJournalFilesAndLatestDates iopts' fs enewjandlatestdatesforfiles <- runExceptT $ readJournalFilesAndLatestDates iopts' fs
case enewjandlatestdates of case enewjandlatestdatesforfiles of
Left err -> error' err Left err -> error' err
Right (newj, latestdates) -> Right (newj, latestdatesforfiles) ->
case sortOn tdate $ jtxns newj of case sortOn tdate $ jtxns newj of
-- with --dry-run the output should be valid journal format, so messages have ; prepended -- with --dry-run the output should be valid journal format, so messages have ; prepended
[] -> do [] -> do
@ -71,23 +71,27 @@ importcmd opts@CliOpts{rawopts_=rawopts,inputopts_=iopts} j = do
newts -> do newts -> do
if dryrun if dryrun
then do then do
-- first show imported txns -- show txns to be imported
printf "; would import %d new transactions from %s:\n\n" (length newts) inputstr printf "; would import %d new transactions from %s:\n\n" (length newts) inputstr
mapM_ (T.putStr . showTransaction) newts mapM_ (T.putStr . showTransaction) newts
-- then check the whole journal with them added, if in strict mode -- then check the whole journal with them added, if in strict mode
when (strict_ iopts) $ strictChecks when (strict_ iopts) $ strictChecks
else do else do
-- first check the whole journal with them added, if in strict mode -- first check the whole journal with them added, if in strict mode
when (strict_ iopts) $ strictChecks when (strict_ iopts) $ strictChecks
-- then add (append) the transactions to the main journal file
-- then append the transactions to the main journal file.
-- XXX This writes unix line endings (\n), some at least, -- XXX This writes unix line endings (\n), some at least,
-- even if the file uses dos line endings (\r\n), which could leave -- even if the file uses dos line endings (\r\n), which could leave
-- mixed line endings in the file. See also writeFileWithBackupIfChanged. -- mixed line endings in the file. See also writeFileWithBackupIfChanged.
foldM_ (`journalAddTransaction` opts) j newts -- gets forced somehow.. (how ?) foldM_ (`journalAddTransaction` opts) j newts -- gets forced somehow.. (how ?)
printf "imported %d new transactions from %s to %s\n" (length newts) inputstr (journalFilePath j) printf "imported %d new transactions from %s to %s\n" (length newts) inputstr (journalFilePath j)
-- and finally update the .latest files
mapM_ (saveLatestDates latestdates . snd . splitReaderPrefix) fs -- and if we got this far, update each file's .latest file
saveLatestDatesForFiles latestdatesforfiles
where where
-- add the new transactions to the journal in memory and check the whole thing -- add the new transactions to the journal in memory and check the whole thing