print: --new shows only transactions added since last time
First cut, error messages could be refined etc.
This commit is contained in:
parent
e3c4a76119
commit
669fa706c0
@ -42,18 +42,20 @@ import qualified Control.Exception as C
|
|||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
|
import Data.Ord
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import Data.Time (Day)
|
||||||
import Safe
|
import Safe
|
||||||
import System.Directory (doesFileExist, getHomeDirectory)
|
import System.Directory (doesFileExist, getHomeDirectory)
|
||||||
import System.Environment (getEnv)
|
import System.Environment (getEnv)
|
||||||
import System.Exit (exitFailure)
|
import System.Exit (exitFailure)
|
||||||
import System.FilePath ((</>), takeExtension)
|
import System.FilePath
|
||||||
import System.IO (stderr)
|
import System.IO
|
||||||
import Test.HUnit
|
import Test.HUnit
|
||||||
import Text.Printf
|
import Text.Printf
|
||||||
|
|
||||||
import Hledger.Data.Dates (getCurrentDay)
|
import Hledger.Data.Dates (getCurrentDay, parsedate, showDate)
|
||||||
import Hledger.Data.Types
|
import Hledger.Data.Types
|
||||||
import Hledger.Read.Common
|
import Hledger.Read.Common
|
||||||
import qualified Hledger.Read.JournalReader as JournalReader
|
import qualified Hledger.Read.JournalReader as JournalReader
|
||||||
@ -259,7 +261,7 @@ tryReaders readers mrulesfile assrt path t = firstSuccessOrFirstError [] readers
|
|||||||
path' = fromMaybe "(string)" path
|
path' = fromMaybe "(string)" path
|
||||||
|
|
||||||
|
|
||||||
--- New versions of readJournal* with easier arguments
|
--- New versions of readJournal* with easier arguments, and --new/last-seen handling.
|
||||||
|
|
||||||
readJournalFilesWithOpts :: InputOpts -> [FilePath] -> IO (Either String Journal)
|
readJournalFilesWithOpts :: InputOpts -> [FilePath] -> IO (Either String Journal)
|
||||||
readJournalFilesWithOpts iopts =
|
readJournalFilesWithOpts iopts =
|
||||||
@ -275,7 +277,67 @@ readJournalFileWithOpts iopts prefixedfile = do
|
|||||||
(mfmt, f) = splitReaderPrefix prefixedfile
|
(mfmt, f) = splitReaderPrefix prefixedfile
|
||||||
iopts' = iopts{mformat_=firstJust [mfmt, mformat_ iopts]}
|
iopts' = iopts{mformat_=firstJust [mfmt, mformat_ iopts]}
|
||||||
requireJournalFileExists f
|
requireJournalFileExists f
|
||||||
readFileOrStdinAnyLineEnding f >>= readJournalWithOpts iopts' (Just f)
|
t <- readFileOrStdinAnyLineEnding f
|
||||||
|
ej <- readJournalWithOpts iopts' (Just f) t
|
||||||
|
case ej of
|
||||||
|
Left e -> return $ Left e
|
||||||
|
Right j | new_ iopts -> do
|
||||||
|
lastdates <- lastSeen f
|
||||||
|
let (newj, newlastdates) = journalFilterSinceLastDates lastdates j
|
||||||
|
when (not $ null newlastdates) $ saveLastSeen newlastdates f
|
||||||
|
return $ Right newj
|
||||||
|
Right j -> return $ Right j
|
||||||
|
|
||||||
|
-- | Given zero or more date values (all the same, representing the
|
||||||
|
-- latest previously seen transaction date, and how many transactions
|
||||||
|
-- were seen on that date), remove transactions with earlier dates
|
||||||
|
-- from the journal, and the same number of transactions on the
|
||||||
|
-- latest date, if any, leaving only transactions that we can assume
|
||||||
|
-- are newer. Also returns the new last dates of the new journal.
|
||||||
|
journalFilterSinceLastDates :: [Day] -> Journal -> (Journal, [Day])
|
||||||
|
journalFilterSinceLastDates [] j = (j, latestDates $ map tdate $ jtxns j)
|
||||||
|
journalFilterSinceLastDates ds@(d:_) j = (j', ds')
|
||||||
|
where
|
||||||
|
samedateorlaterts = filter ((>= d).tdate) $ jtxns j
|
||||||
|
(samedatets, laterts) = span ((== d).tdate) $ sortBy (comparing tdate) samedateorlaterts
|
||||||
|
newsamedatets = drop (length ds) samedatets
|
||||||
|
j' = j{jtxns=newsamedatets++laterts}
|
||||||
|
ds' = latestDates $ map tdate $ samedatets++laterts
|
||||||
|
|
||||||
|
-- | 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.
|
||||||
|
latestDates :: [Day] -> [Day]
|
||||||
|
latestDates = headDef [] . take 1 . group . reverse . sort
|
||||||
|
|
||||||
|
-- | Where to save last-seen transactions info for the given file path
|
||||||
|
-- (.FILE.seen).
|
||||||
|
seenFileFor :: FilePath -> FilePath
|
||||||
|
seenFileFor f = dir </> fname' <.> "seen"
|
||||||
|
where
|
||||||
|
(dir, fname) = splitFileName f
|
||||||
|
fname' | "." `isPrefixOf` fname = fname
|
||||||
|
| otherwise = '.':fname
|
||||||
|
|
||||||
|
-- | 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.
|
||||||
|
-- Or none if no transactions were seen.
|
||||||
|
lastSeen :: FilePath -> IO [Day]
|
||||||
|
lastSeen f = do
|
||||||
|
let seenfile = seenFileFor f
|
||||||
|
exists <- doesFileExist seenfile
|
||||||
|
if exists
|
||||||
|
then map (parsedate . strip) . lines . strip . T.unpack <$> readFileStrictly seenfile
|
||||||
|
else return []
|
||||||
|
|
||||||
|
readFileStrictly :: FilePath -> IO Text
|
||||||
|
readFileStrictly f = readFile' f >>= \t -> C.evaluate (T.length t) >> return t
|
||||||
|
|
||||||
|
-- | Remember that these transaction dates were the latest seen when
|
||||||
|
-- reading this journal file.
|
||||||
|
saveLastSeen :: [Day] -> FilePath -> IO ()
|
||||||
|
saveLastSeen dates f = writeFile (seenFileFor f) $ unlines $ map showDate dates
|
||||||
|
|
||||||
readJournalWithOpts :: InputOpts -> Maybe FilePath -> Text -> IO (Either String Journal)
|
readJournalWithOpts :: InputOpts -> Maybe FilePath -> Text -> IO (Either String Journal)
|
||||||
readJournalWithOpts iopts mfile txt =
|
readJournalWithOpts iopts mfile txt =
|
||||||
|
|||||||
@ -55,13 +55,14 @@ data InputOpts = InputOpts {
|
|||||||
,aliases_ :: [String] -- ^ account name aliases to apply
|
,aliases_ :: [String] -- ^ account name aliases to apply
|
||||||
,anon_ :: Bool -- ^ do light anonymisation/obfuscation of the data
|
,anon_ :: Bool -- ^ do light anonymisation/obfuscation of the data
|
||||||
,ignore_assertions_ :: Bool -- ^ don't check balance assertions
|
,ignore_assertions_ :: Bool -- ^ don't check balance assertions
|
||||||
|
,new_ :: Bool -- ^ read only new transactions since this file was last read
|
||||||
,pivot_ :: String -- ^ use the given field's value as the account name
|
,pivot_ :: String -- ^ use the given field's value as the account name
|
||||||
} deriving (Show, Data) --, Typeable)
|
} deriving (Show, Data) --, Typeable)
|
||||||
|
|
||||||
instance Default InputOpts where def = definputopts
|
instance Default InputOpts where def = definputopts
|
||||||
|
|
||||||
definputopts :: InputOpts
|
definputopts :: InputOpts
|
||||||
definputopts = InputOpts def def def def def def
|
definputopts = InputOpts def def def def def def def
|
||||||
|
|
||||||
rawOptsToInputOpts :: RawOpts -> InputOpts
|
rawOptsToInputOpts :: RawOpts -> InputOpts
|
||||||
rawOptsToInputOpts rawopts = InputOpts{
|
rawOptsToInputOpts rawopts = InputOpts{
|
||||||
@ -71,6 +72,7 @@ rawOptsToInputOpts rawopts = InputOpts{
|
|||||||
,aliases_ = map (T.unpack . stripquotes . T.pack) $ listofstringopt "alias" rawopts
|
,aliases_ = map (T.unpack . stripquotes . T.pack) $ listofstringopt "alias" rawopts
|
||||||
,anon_ = boolopt "anon" rawopts
|
,anon_ = boolopt "anon" rawopts
|
||||||
,ignore_assertions_ = boolopt "ignore-assertions" rawopts
|
,ignore_assertions_ = boolopt "ignore-assertions" rawopts
|
||||||
|
,new_ = boolopt "new" rawopts
|
||||||
,pivot_ = stringopt "pivot" rawopts
|
,pivot_ = stringopt "pivot" rawopts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,13 +31,13 @@ printmode = (defCommandMode $ ["print"] ++ aliases) {
|
|||||||
modeHelp = "show transaction journal entries, sorted by date. With --date2, sort by secondary date instead." `withAliases` aliases
|
modeHelp = "show transaction journal entries, sorted by date. With --date2, sort by secondary date instead." `withAliases` aliases
|
||||||
,modeGroupFlags = Group {
|
,modeGroupFlags = Group {
|
||||||
groupUnnamed = [
|
groupUnnamed = [
|
||||||
let matcharg = "STR"
|
let arg = "STR" in
|
||||||
in
|
flagReq ["match","m"] (\s opts -> Right $ setopt "match" s opts) arg
|
||||||
flagReq ["match","m"] (\s opts -> Right $ setopt "match" s opts) matcharg
|
("show the transaction whose description is most similar to "++arg++", and is most recent")
|
||||||
("show the transaction whose description is most similar to "++matcharg
|
,flagNone ["explicit","x"] (setboolopt "explicit")
|
||||||
++ ", and is most recent"),
|
|
||||||
flagNone ["explicit","x"] (setboolopt "explicit")
|
|
||||||
"show all amounts explicitly"
|
"show all amounts explicitly"
|
||||||
|
,flagNone ["new"] (setboolopt "new")
|
||||||
|
"show only more recent transactions added to each file since last run"
|
||||||
]
|
]
|
||||||
++ outputflags
|
++ outputflags
|
||||||
,groupHidden = []
|
,groupHidden = []
|
||||||
|
|||||||
@ -464,12 +464,15 @@ Print all market prices from the journal.
|
|||||||
## print
|
## print
|
||||||
Show transactions from the journal. Aliases: p, txns.
|
Show transactions from the journal. Aliases: p, txns.
|
||||||
|
|
||||||
`-x --explicit`
|
|
||||||
: show all amounts explicitly
|
|
||||||
|
|
||||||
`-m STR --match=STR `
|
`-m STR --match=STR `
|
||||||
: show the transaction whose description is most similar to STR, and is most recent
|
: show the transaction whose description is most similar to STR, and is most recent
|
||||||
|
|
||||||
|
` --new`
|
||||||
|
: show only more recent transactions added to each file since last run
|
||||||
|
|
||||||
|
`-x --explicit`
|
||||||
|
: show all amounts explicitly
|
||||||
|
|
||||||
`-O FMT --output-format=FMT `
|
`-O FMT --output-format=FMT `
|
||||||
: select the output format. Supported formats:
|
: select the output format. Supported formats:
|
||||||
txt, csv.
|
txt, csv.
|
||||||
@ -501,22 +504,36 @@ $ hledger print
|
|||||||
assets:bank:checking $-1
|
assets:bank:checking $-1
|
||||||
```
|
```
|
||||||
|
|
||||||
The print command displays full journal entries (transactions) from the journal file, tidily formatted.
|
The print command displays full journal entries (transactions) from the journal file in date order, tidily formatted.
|
||||||
|
print's output is always a valid [hledger journal](/journal.html).
|
||||||
|
It preserves all transaction information, but it does not preserve directives or inter-transaction comments
|
||||||
|
|
||||||
As of hledger 1.2, print's output is always a valid [hledger journal](/journal.html).
|
Normally, the journal entry's explicit or implicit amount style is preserved.
|
||||||
However it may not preserve all original content, eg it does not print directives or inter-transaction comments.
|
Ie when an amount is omitted in the journal, it will be omitted in the output.
|
||||||
|
You can use the `-x`/`--explicit` flag to make all amounts explicit, which can be
|
||||||
Normally, transactions' implicit/explicit amount style is preserved:
|
|
||||||
when an amount is omitted in the journal, it will be omitted in the output.
|
|
||||||
You can use the `-x/--explicit` flag to make all amounts explicit, which can be
|
|
||||||
useful for troubleshooting or for making your journal more readable and
|
useful for troubleshooting or for making your journal more readable and
|
||||||
robust against data entry errors.
|
robust against data entry errors.
|
||||||
Note, in this mode postings with a multi-commodity amount
|
Note, `-x` will cause postings with a multi-commodity amount
|
||||||
(possible with an implicit amount in a multi-commodity transaction)
|
(these can arise when a multi-commodity transaction has an implicit amount)
|
||||||
will be split into multiple single-commodity postings, for valid journal output.
|
will be split into multiple single-commodity postings, for valid journal output.
|
||||||
|
|
||||||
With -B/--cost, amounts with [transaction prices](/journal.html#transaction-prices)
|
With `-B`/`--cost`, amounts with [transaction prices](/journal.html#transaction-prices)
|
||||||
are converted to cost (using the transaction price).
|
are converted to cost using that price.
|
||||||
|
|
||||||
|
With `-m`/`--match` and a STR argument, print will show at most one transaction: the one
|
||||||
|
one whose description is most similar to STR, and is most recent. STR should contain at
|
||||||
|
least two characters. If there is no similar-enough match, no transaction will be shown.
|
||||||
|
|
||||||
|
With `--new`, for each FILE being read, hledger reads (and writes) a special .FILE.seen file in the same directory,
|
||||||
|
containing the latest transaction date(s) that were seen last time FILE was read.
|
||||||
|
When this file is found, only transactions with newer dates (and new transactions on the latest date) are printed.
|
||||||
|
This is useful for ignoring already-seen entries in import data, such as downloaded CSV files.
|
||||||
|
Eg:
|
||||||
|
```console
|
||||||
|
$ hledger -f bank1.csv print --new
|
||||||
|
# shows transactions added since last print --new on this file
|
||||||
|
```
|
||||||
|
It assumes that only same-or-newer-dated transactions are added to FILE, and that the order of same-date transactions remains stable.
|
||||||
|
|
||||||
The print command also supports
|
The print command also supports
|
||||||
[output destination](#output-destination)
|
[output destination](#output-destination)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user