lib: number transactions as they are read
And allow looking up transactions by their read order (index), or the previous/next transactions in the sequence.
This commit is contained in:
parent
4d97db02cb
commit
2feace32dd
@ -35,6 +35,9 @@ module Hledger.Data.Journal (
|
||||
journalDescriptions,
|
||||
journalFilePath,
|
||||
journalFilePaths,
|
||||
journalTransactionAt,
|
||||
journalNextTransaction,
|
||||
journalPrevTransaction,
|
||||
journalPostings,
|
||||
-- * Standard account types
|
||||
journalBalanceSheetAccountQuery,
|
||||
@ -134,7 +137,7 @@ nulljournal = Journal { jmodifiertxns = []
|
||||
}
|
||||
|
||||
nullctx :: JournalContext
|
||||
nullctx = Ctx { ctxYear = Nothing, ctxDefaultCommodityAndStyle = Nothing, ctxAccount = [], ctxAliases = [] }
|
||||
nullctx = Ctx{ctxYear=Nothing, ctxDefaultCommodityAndStyle=Nothing, ctxAccount=[], ctxAliases=[], ctxTransactionIndex=0}
|
||||
|
||||
journalFilePath :: Journal -> FilePath
|
||||
journalFilePath = fst . mainfile
|
||||
@ -160,6 +163,20 @@ addMarketPrice h j = j { jmarketprices = h : jmarketprices j }
|
||||
addTimeLogEntry :: TimeLogEntry -> Journal -> Journal
|
||||
addTimeLogEntry tle j = j { open_timelog_entries = tle : open_timelog_entries j }
|
||||
|
||||
-- | Get the transaction with this index (its 1-based position in the input stream), if any.
|
||||
journalTransactionAt :: Journal -> Integer -> Maybe Transaction
|
||||
journalTransactionAt Journal{jtxns=ts} i =
|
||||
-- it's probably ts !! (i+1), but we won't assume
|
||||
headMay [t | t <- ts, tindex t == i]
|
||||
|
||||
-- | Get the transaction that appeared immediately after this one in the input stream, if any.
|
||||
journalNextTransaction :: Journal -> Transaction -> Maybe Transaction
|
||||
journalNextTransaction j t = journalTransactionAt j (tindex t + 1)
|
||||
|
||||
-- | Get the transaction that appeared immediately before this one in the input stream, if any.
|
||||
journalPrevTransaction :: Journal -> Transaction -> Maybe Transaction
|
||||
journalPrevTransaction j t = journalTransactionAt j (tindex t - 1)
|
||||
|
||||
-- | Unique transaction descriptions used in this journal.
|
||||
journalDescriptions :: Journal -> [String]
|
||||
journalDescriptions = nub . sort . map tdescription . jtxns
|
||||
@ -683,6 +700,7 @@ Right samplejournal = journalBalanceTransactions $
|
||||
nulljournal
|
||||
{jtxns = [
|
||||
txnTieKnot $ Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2008/01/01",
|
||||
tdate2=Nothing,
|
||||
@ -699,6 +717,7 @@ Right samplejournal = journalBalanceTransactions $
|
||||
}
|
||||
,
|
||||
txnTieKnot $ Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2008/06/01",
|
||||
tdate2=Nothing,
|
||||
@ -715,6 +734,7 @@ Right samplejournal = journalBalanceTransactions $
|
||||
}
|
||||
,
|
||||
txnTieKnot $ Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2008/06/02",
|
||||
tdate2=Nothing,
|
||||
@ -731,6 +751,7 @@ Right samplejournal = journalBalanceTransactions $
|
||||
}
|
||||
,
|
||||
txnTieKnot $ Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2008/06/03",
|
||||
tdate2=Nothing,
|
||||
@ -747,6 +768,7 @@ Right samplejournal = journalBalanceTransactions $
|
||||
}
|
||||
,
|
||||
txnTieKnot $ Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2008/12/31",
|
||||
tdate2=Nothing,
|
||||
|
||||
@ -79,6 +79,7 @@ entryFromTimeLogInOut i o
|
||||
error' $ "clock-out time less than clock-in time in:\n" ++ showTransaction t
|
||||
where
|
||||
t = Transaction {
|
||||
tindex = 0,
|
||||
tsourcepos = tlsourcepos i,
|
||||
tdate = idate,
|
||||
tdate2 = Nothing,
|
||||
|
||||
@ -61,6 +61,7 @@ nullsourcepos = GenericSourcePos "" 1 1
|
||||
|
||||
nulltransaction :: Transaction
|
||||
nulltransaction = Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=nulldate,
|
||||
tdate2=Nothing,
|
||||
@ -419,7 +420,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
," assets:checking"
|
||||
,""
|
||||
])
|
||||
(let t = Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
(let t = Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
[posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18], ptransaction=Just t}
|
||||
,posting{paccount="assets:checking", pamount=Mixed [usd (-47.18)], ptransaction=Just t}
|
||||
] ""
|
||||
@ -433,7 +434,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
," assets:checking $-47.18"
|
||||
,""
|
||||
])
|
||||
(let t = Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
(let t = Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
[posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18], ptransaction=Just t}
|
||||
,posting{paccount="assets:checking", pamount=Mixed [usd (-47.18)], ptransaction=Just t}
|
||||
] ""
|
||||
@ -449,7 +450,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
,""
|
||||
])
|
||||
(showTransaction
|
||||
(txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
(txnTieKnot $ Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
[posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18]}
|
||||
,posting{paccount="assets:checking", pamount=Mixed [usd (-47.19)]}
|
||||
] ""))
|
||||
@ -462,7 +463,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
,""
|
||||
])
|
||||
(showTransaction
|
||||
(txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
(txnTieKnot $ Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
[posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18]}
|
||||
] ""))
|
||||
|
||||
@ -474,7 +475,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
,""
|
||||
])
|
||||
(showTransaction
|
||||
(txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
(txnTieKnot $ Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" []
|
||||
[posting{paccount="expenses:food:groceries", pamount=missingmixedamt}
|
||||
] ""))
|
||||
|
||||
@ -487,7 +488,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
,""
|
||||
])
|
||||
(showTransaction
|
||||
(txnTieKnot $ Transaction nullsourcepos (parsedate "2010/01/01") Nothing Uncleared "" "x" "" []
|
||||
(txnTieKnot $ Transaction 0 nullsourcepos (parsedate "2010/01/01") Nothing Uncleared "" "x" "" []
|
||||
[posting{paccount="a", pamount=Mixed [num 1 `at` (usd 2 `withPrecision` 0)]}
|
||||
,posting{paccount="b", pamount= missingmixedamt}
|
||||
] ""))
|
||||
@ -495,19 +496,19 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
,"balanceTransaction" ~: do
|
||||
assertBool "detect unbalanced entry, sign error"
|
||||
(isLeft $ balanceTransaction Nothing
|
||||
(Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "test" "" []
|
||||
(Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "test" "" []
|
||||
[posting{paccount="a", pamount=Mixed [usd 1]}
|
||||
,posting{paccount="b", pamount=Mixed [usd 1]}
|
||||
] ""))
|
||||
|
||||
assertBool "detect unbalanced entry, multiple missing amounts"
|
||||
(isLeft $ balanceTransaction Nothing
|
||||
(Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "test" "" []
|
||||
(Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "test" "" []
|
||||
[posting{paccount="a", pamount=missingmixedamt}
|
||||
,posting{paccount="b", pamount=missingmixedamt}
|
||||
] ""))
|
||||
|
||||
let e = balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "" "" []
|
||||
let e = balanceTransaction Nothing (Transaction 0 nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "" "" []
|
||||
[posting{paccount="a", pamount=Mixed [usd 1]}
|
||||
,posting{paccount="b", pamount=missingmixedamt}
|
||||
] "")
|
||||
@ -518,7 +519,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
Right e' -> (pamount $ last $ tpostings e')
|
||||
Left _ -> error' "should not happen")
|
||||
|
||||
let e = balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" []
|
||||
let e = balanceTransaction Nothing (Transaction 0 nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" []
|
||||
[posting{paccount="a", pamount=Mixed [usd 1.35]}
|
||||
,posting{paccount="b", pamount=Mixed [eur (-1)]}
|
||||
] "")
|
||||
@ -530,49 +531,49 @@ tests_Hledger_Data_Transaction = TestList $ concat [
|
||||
Left _ -> error' "should not happen")
|
||||
|
||||
assertBool "balanceTransaction balances based on cost if there are unit prices" (isRight $
|
||||
balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" []
|
||||
balanceTransaction Nothing (Transaction 0 nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" []
|
||||
[posting{paccount="a", pamount=Mixed [usd 1 `at` eur 2]}
|
||||
,posting{paccount="a", pamount=Mixed [usd (-2) `at` eur 1]}
|
||||
] ""))
|
||||
|
||||
assertBool "balanceTransaction balances based on cost if there are total prices" (isRight $
|
||||
balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" []
|
||||
balanceTransaction Nothing (Transaction 0 nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" []
|
||||
[posting{paccount="a", pamount=Mixed [usd 1 @@ eur 1]}
|
||||
,posting{paccount="a", pamount=Mixed [usd (-2) @@ eur 1]}
|
||||
] ""))
|
||||
|
||||
,"isTransactionBalanced" ~: do
|
||||
let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
let t = Transaction 0 nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
[posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t}
|
||||
,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t}
|
||||
] ""
|
||||
assertBool "detect balanced" (isTransactionBalanced Nothing t)
|
||||
let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
let t = Transaction 0 nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
[posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t}
|
||||
,posting{paccount="c", pamount=Mixed [usd (-1.01)], ptransaction=Just t}
|
||||
] ""
|
||||
assertBool "detect unbalanced" (not $ isTransactionBalanced Nothing t)
|
||||
let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
let t = Transaction 0 nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
[posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t}
|
||||
] ""
|
||||
assertBool "detect unbalanced, one posting" (not $ isTransactionBalanced Nothing t)
|
||||
let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
let t = Transaction 0 nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
[posting{paccount="b", pamount=Mixed [usd 0], ptransaction=Just t}
|
||||
] ""
|
||||
assertBool "one zero posting is considered balanced for now" (isTransactionBalanced Nothing t)
|
||||
let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
let t = Transaction 0 nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
[posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t}
|
||||
,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t}
|
||||
,posting{paccount="d", pamount=Mixed [usd 100], ptype=VirtualPosting, ptransaction=Just t}
|
||||
] ""
|
||||
assertBool "virtual postings don't need to balance" (isTransactionBalanced Nothing t)
|
||||
let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
let t = Transaction 0 nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
[posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t}
|
||||
,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t}
|
||||
,posting{paccount="d", pamount=Mixed [usd 100], ptype=BalancedVirtualPosting, ptransaction=Just t}
|
||||
] ""
|
||||
assertBool "balanced virtual postings need to balance among themselves" (not $ isTransactionBalanced Nothing t)
|
||||
let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
let t = Transaction 0 nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" []
|
||||
[posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t}
|
||||
,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t}
|
||||
,posting{paccount="d", pamount=Mixed [usd 100], ptype=BalancedVirtualPosting, ptransaction=Just t}
|
||||
|
||||
@ -170,6 +170,7 @@ data GenericSourcePos = GenericSourcePos FilePath Int Int
|
||||
instance NFData GenericSourcePos
|
||||
|
||||
data Transaction = Transaction {
|
||||
tindex :: Integer, -- ^ this transaction's 1-based position in the input stream, or 0 when not available
|
||||
tsourcepos :: GenericSourcePos,
|
||||
tdate :: Day,
|
||||
tdate2 :: Maybe Day,
|
||||
@ -233,6 +234,7 @@ data JournalContext = Ctx {
|
||||
-- specified with "account" directive(s). Concatenated, these
|
||||
-- are the account prefix prepended to parsed account names.
|
||||
, ctxAliases :: ![AccountAlias] -- ^ the current list of account name aliases in effect
|
||||
, ctxTransactionIndex :: !Integer -- ^ the number of transactions read so far
|
||||
} deriving (Read, Show, Eq, Data, Typeable, Generic)
|
||||
|
||||
instance NFData JournalContext
|
||||
|
||||
@ -212,6 +212,12 @@ getAccountAliases = liftM ctxAliases getState
|
||||
clearAccountAliases :: Stream [Char] m Char => ParsecT [Char] JournalContext m ()
|
||||
clearAccountAliases = modifyState (\(ctx@Ctx{..}) -> ctx{ctxAliases=[]})
|
||||
|
||||
getIndex :: Stream [Char] m Char => ParsecT s JournalContext m Integer
|
||||
getIndex = liftM ctxTransactionIndex getState
|
||||
|
||||
setIndex :: Stream [Char] m Char => Integer -> ParsecT [Char] JournalContext m ()
|
||||
setIndex i = modifyState (\ctx -> ctx{ctxTransactionIndex=i})
|
||||
|
||||
-- parsers
|
||||
|
||||
-- | Top-level journal parser. Returns a single composite, I/O performing,
|
||||
@ -437,7 +443,9 @@ transactionp = do
|
||||
comment <- try followingcommentp <|> (newline >> return "")
|
||||
let tags = tagsInComment comment
|
||||
postings <- postingsp
|
||||
return $ txnTieKnot $ Transaction sourcepos date edate status code description comment tags postings ""
|
||||
index <- getIndex
|
||||
setIndex (index+1)
|
||||
return $ txnTieKnot $ Transaction index sourcepos date edate status code description comment tags postings ""
|
||||
|
||||
descriptionp = many (noneOf ";\n")
|
||||
|
||||
|
||||
@ -368,6 +368,7 @@ Right samplejournal2 = journalBalanceTransactions $
|
||||
nulljournal
|
||||
{jtxns = [
|
||||
txnTieKnot $ Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2008/01/01",
|
||||
tdate2=Just $ parsedate "2009/01/01",
|
||||
|
||||
@ -343,6 +343,7 @@ journal7 :: Journal
|
||||
journal7 = nulljournal {jtxns =
|
||||
[
|
||||
txnTieKnot Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2007/01/01",
|
||||
tdate2=Nothing,
|
||||
@ -359,6 +360,7 @@ journal7 = nulljournal {jtxns =
|
||||
}
|
||||
,
|
||||
txnTieKnot Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2007/02/01",
|
||||
tdate2=Nothing,
|
||||
@ -375,6 +377,7 @@ journal7 = nulljournal {jtxns =
|
||||
}
|
||||
,
|
||||
txnTieKnot Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2007/01/02",
|
||||
tdate2=Nothing,
|
||||
@ -391,6 +394,7 @@ journal7 = nulljournal {jtxns =
|
||||
}
|
||||
,
|
||||
txnTieKnot Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2007/01/03",
|
||||
tdate2=Nothing,
|
||||
@ -407,6 +411,7 @@ journal7 = nulljournal {jtxns =
|
||||
}
|
||||
,
|
||||
txnTieKnot Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2007/01/03",
|
||||
tdate2=Nothing,
|
||||
@ -423,6 +428,7 @@ journal7 = nulljournal {jtxns =
|
||||
}
|
||||
,
|
||||
txnTieKnot Transaction {
|
||||
tindex=0,
|
||||
tsourcepos=nullsourcepos,
|
||||
tdate=parsedate "2007/01/03",
|
||||
tdate2=Nothing,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user