Skip to content

Commit

Permalink
fix:import: save each file's latest dates, separately (#2125)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonmichael committed Dec 7, 2023
1 parent e6c4273 commit c0481e6
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 22 deletions.
40 changes: 25 additions & 15 deletions hledger-lib/Hledger/Read.hs
Expand Up @@ -109,6 +109,7 @@ module Hledger.Read (
-- * Misc
journalStrictChecks,
saveLatestDates,
saveLatestDatesForFiles,

-- * Re-exported
JournalReader.tmpostingrulep,
Expand All @@ -132,7 +133,7 @@ import Data.Default (def)
import Data.Foldable (asum)
import Data.List (group, sort, sortBy)
import Data.List.NonEmpty (nonEmpty)
import Data.Maybe (fromMaybe)
import Data.Maybe (catMaybes, fromMaybe)
import Data.Ord (comparing)
import Data.Semigroup (sconcat)
import Data.Text (Text)
Expand Down Expand Up @@ -243,15 +244,17 @@ readJournal iopts@InputOpts{strict_} mpath txt = do
--
readJournalFile :: InputOpts -> PrefixedFilePath -> ExceptT String IO Journal
readJournalFile iopts@InputOpts{new_, new_save_} prefixedfile = do
(j,latestdates) <- readJournalFileAndLatestDates iopts prefixedfile
(j, mlatestdates) <- readJournalFileAndLatestDates iopts prefixedfile
when (new_ && new_save_) $ liftIO $
saveLatestDates latestdates (snd $ splitReaderPrefix prefixedfile)
case mlatestdates of
Nothing -> return ()
Just (LatestDatesForFile f ds) -> saveLatestDates ds f
return j

-- The implementation of readJournalFile, but with --new,
-- also returns the latest transaction date(s) read.
-- Used by readJournalFiles, to save those at the end.
readJournalFileAndLatestDates :: InputOpts -> PrefixedFilePath -> ExceptT String IO (Journal,LatestDates)
-- The implementation of readJournalFile.
-- With --new, it also returns the latest transaction date(s) read from each file.
-- readJournalFiles uses this to update .latest files only after a successful read of all.
readJournalFileAndLatestDates :: InputOpts -> PrefixedFilePath -> ExceptT String IO (Journal, Maybe LatestDatesForFile)
readJournalFileAndLatestDates iopts prefixedfile = do
let
(mfmt, f) = splitReaderPrefix prefixedfile
Expand All @@ -266,9 +269,9 @@ readJournalFileAndLatestDates iopts prefixedfile = do
then do
ds <- liftIO $ previousLatestDates f
let (newj, newds) = journalFilterSinceLatestDates ds j
return (newj, newds)
return (newj, Just $ LatestDatesForFile f newds)
else
return (j, [])
return (j, Nothing)

-- | Read a Journal from each specified file path (using @readJournalFile@)
-- and combine them into one; or return the first error message.
Expand All @@ -285,20 +288,20 @@ readJournalFileAndLatestDates iopts prefixedfile = do
readJournalFiles :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO Journal
readJournalFiles iopts@InputOpts{strict_,new_,new_save_} prefixedfiles = do
let iopts' = iopts{strict_=False, new_save_=False}
(j,latestdates) <-
(j, latestdatesforfiles) <-
traceOrLogAt 6 ("readJournalFiles: "++show prefixedfiles) $
readJournalFilesAndLatestDates iopts' prefixedfiles
when strict_ $ liftEither $ journalStrictChecks j
when (new_ && new_save_) $ liftIO $
mapM_ (saveLatestDates latestdates . snd . splitReaderPrefix) prefixedfiles
when (new_ && new_save_) $ liftIO $ saveLatestDatesForFiles latestdatesforfiles
return j

-- The implementation of readJournalFiles, but with --new,
-- also returns the latest transaction date(s) read in each file.
-- Used by the import command, to save those at the end.
readJournalFilesAndLatestDates :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO (Journal,LatestDates)
readJournalFilesAndLatestDates iopts =
fmap (maybe def sconcat . nonEmpty) . mapM (readJournalFileAndLatestDates iopts)
readJournalFilesAndLatestDates :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO (Journal, [LatestDatesForFile])
readJournalFilesAndLatestDates iopts pfs = do
(js, lastdates) <- unzip <$> mapM (readJournalFileAndLatestDates iopts) pfs
return (maybe def sconcat $ nonEmpty js, catMaybes lastdates)

-- | Run the extra -s/--strict checks on a journal,
-- returning the first error message if any of them fail.
Expand Down Expand Up @@ -371,6 +374,9 @@ newJournalContent = do
-- and how many transactions there were on that date.
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.
-- Ie, if the latest date appears once, return it in a one-element list,
-- if it appears three times (anywhere), return three of it.
Expand All @@ -383,6 +389,10 @@ latestDates = {-# HLINT ignore "Avoid reverse" #-}
saveLatestDates :: LatestDates -> FilePath -> IO ()
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
-- journal file was read ? If there were multiple transactions on the
-- latest date, that number of dates is returned, otherwise just one.
Expand Down
18 changes: 11 additions & 7 deletions hledger/Hledger/Cli/Commands/Import.hs
Expand Up @@ -54,10 +54,10 @@ importcmd opts@CliOpts{rawopts_=rawopts,inputopts_=iopts} j = do
case inputfiles of
[] -> error' "please provide one or more input files as arguments" -- PARTIAL:
fs -> do
enewjandlatestdates <- runExceptT $ readJournalFilesAndLatestDates iopts' fs
case enewjandlatestdates of
enewjandlatestdatesforfiles <- runExceptT $ readJournalFilesAndLatestDates iopts' fs
case enewjandlatestdatesforfiles of
Left err -> error' err
Right (newj, latestdates) ->
Right (newj, latestdatesforfiles) ->
case sortOn tdate $ jtxns newj of
-- with --dry-run the output should be valid journal format, so messages have ; prepended
[] -> do
Expand All @@ -71,23 +71,27 @@ importcmd opts@CliOpts{rawopts_=rawopts,inputopts_=iopts} j = do
newts -> do
if dryrun
then do
-- first show imported txns
-- show txns to be imported
printf "; would import %d new transactions from %s:\n\n" (length newts) inputstr
mapM_ (T.putStr . showTransaction) newts

-- then check the whole journal with them added, if in strict mode
when (strict_ iopts) $ strictChecks

else do
-- first check the whole journal with them added, if in strict mode
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,
-- even if the file uses dos line endings (\r\n), which could leave
-- mixed line endings in the file. See also writeFileWithBackupIfChanged.
foldM_ (`journalAddTransaction` opts) j newts -- gets forced somehow.. (how ?)

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
-- add the new transactions to the journal in memory and check the whole thing
Expand Down

0 comments on commit c0481e6

Please sign in to comment.