Apply ModifierTransaction's from journal during hledger-rewrite. (#477)
* rewrite: rewrite every posting Also start using ModifierTransaction * rewrite: use journal parser for postings * rewrite: use ModifierTransactions from Journal See simonmichael/hledger#99
This commit is contained in:
parent
fabd6b450a
commit
8954944ee6
@ -5,6 +5,7 @@
|
||||
--package megaparsec
|
||||
--package text
|
||||
-}
|
||||
{-# LANGUAGE OverloadedStrings, LambdaCase #-}
|
||||
{-
|
||||
|
||||
hledger-rewrite [PATTERNS] --add-posting "ACCT AMTEXPR" ...
|
||||
@ -27,11 +28,11 @@ Related: https://github.com/simonmichael/hledger/issues/99
|
||||
TODO:
|
||||
- should allow regex matching and interpolating matched name in replacement
|
||||
- should apply all matching rules to a transaction, not just one
|
||||
- should apply the rule for each matched posting within a transaction, if there's more than one
|
||||
- should be possible to use this on unbalanced entries, eg while editing one
|
||||
|
||||
-}
|
||||
|
||||
import Data.Monoid
|
||||
import qualified Data.Text as T
|
||||
-- hledger lib, cli and cmdargs utils
|
||||
import Hledger.Cli
|
||||
@ -57,55 +58,53 @@ cmdmode = (defCommandMode ["hledger-rewrite"]) {
|
||||
}
|
||||
}
|
||||
|
||||
type PostingExpr = (AccountName, AmountExpr)
|
||||
postingp' :: T.Text -> IO Posting
|
||||
postingp' t = runErroringJournalParser (postingp Nothing <* eof) t' >>= \case
|
||||
Left err -> fail err
|
||||
Right p -> return p
|
||||
where t' = " " <> t <> "\n" -- inject space and newline for proper parsing
|
||||
|
||||
data AmountExpr = AmountLiteral String | AmountMultiplier Quantity deriving (Show)
|
||||
modifierTransactionFromOpts :: RawOpts -> IO ModifierTransaction
|
||||
modifierTransactionFromOpts opts = do
|
||||
postings <- mapM (postingp' . stripquotes . T.pack) $ listofstringopt "add-posting" opts
|
||||
return
|
||||
ModifierTransaction { mtvalueexpr = T.empty, mtpostings = postings }
|
||||
|
||||
addPostingExprsFromOpts :: RawOpts -> [PostingExpr]
|
||||
addPostingExprsFromOpts = map (either parseerror id . runParser (postingexprp <* eof) "") . map (stripquotes . T.pack) . listofstringopt "add-posting"
|
||||
post' :: AccountName -> Amount -> Posting
|
||||
post' acct amt = (accountNameWithoutPostingType acct `post` amt) { ptype = accountNamePostingType acct }
|
||||
|
||||
postingexprp = do
|
||||
a <- accountnamep
|
||||
spacenonewline >> some spacenonewline
|
||||
aex <- amountexprp
|
||||
many spacenonewline
|
||||
return (a,aex)
|
||||
-- mtvaluequery :: ModifierTransaction -> Day -> Query
|
||||
mtvaluequery mod = fst . flip parseQuery (mtvalueexpr mod)
|
||||
|
||||
amountexprp =
|
||||
choice [
|
||||
AmountMultiplier <$> (do char '*'
|
||||
many spacenonewline
|
||||
(q,_,_,_) <- numberp
|
||||
return q)
|
||||
,AmountLiteral <$> many anyChar
|
||||
]
|
||||
postingScale :: Posting -> Maybe Quantity
|
||||
postingScale p =
|
||||
case amounts $ pamount p of
|
||||
[a] | acommodity a == "*" -> Just $ aquantity a
|
||||
_ -> Nothing
|
||||
|
||||
amountExprRenderer :: Query -> AmountExpr -> (Transaction -> MixedAmount)
|
||||
amountExprRenderer q aex =
|
||||
case aex of
|
||||
AmountLiteral s -> const (mamountp' s)
|
||||
AmountMultiplier n -> (`divideMixedAmount` (1 / n)) . (`firstAmountMatching` q)
|
||||
where
|
||||
firstAmountMatching :: Transaction -> Query -> MixedAmount
|
||||
firstAmountMatching t q = pamount $ head $ filter (q `matchesPosting`) $ tpostings t
|
||||
runModifierPosting :: Posting -> (Posting -> Posting)
|
||||
runModifierPosting p' =
|
||||
case postingScale p' of
|
||||
Nothing -> \p -> p' { ptransaction = ptransaction p }
|
||||
Just n -> \p -> p' { pamount = pamount p `divideMixedAmount` (1/n), ptransaction = ptransaction p }
|
||||
|
||||
rewriteTransaction :: Transaction -> [(AccountName, Transaction -> MixedAmount)] -> Transaction
|
||||
rewriteTransaction t addps = t{tpostings=tpostings t ++ map (uncurry (generatePosting t)) addps}
|
||||
where
|
||||
generatePosting :: Transaction -> AccountName -> (Transaction -> MixedAmount) -> Posting
|
||||
generatePosting t acct amtfn = nullposting{paccount = accountNameWithoutPostingType acct
|
||||
,ptype = accountNamePostingType acct
|
||||
,pamount = amtfn t
|
||||
,ptransaction = Just t
|
||||
}
|
||||
runModifierTransaction :: Query -> ModifierTransaction -> (Transaction -> Transaction)
|
||||
runModifierTransaction q mod = modifier where
|
||||
q' = simplifyQuery $ And [q, mtvaluequery mod (error "query cannot depend on current time")]
|
||||
mods = map runModifierPosting $ mtpostings mod
|
||||
generatePostings ps = [mod p | p <- ps, q' `matchesPosting` p, mod <- mods]
|
||||
modifier t@Transaction{ tpostings = ps } = t { tpostings = ps ++ generatePostings ps }
|
||||
|
||||
main = do
|
||||
opts@CliOpts{rawopts_=rawopts,reportopts_=ropts} <- getCliOpts cmdmode
|
||||
d <- getCurrentDay
|
||||
let q = queryFromOpts d ropts
|
||||
addps = [(a, amountExprRenderer q aex) | (a, aex) <- addPostingExprsFromOpts rawopts]
|
||||
mod <- modifierTransactionFromOpts rawopts
|
||||
withJournalDo opts $ \opts j@Journal{jtxns=ts} -> do
|
||||
-- create re-writer
|
||||
let mods = jmodifiertxns j ++ [mod]
|
||||
modifier = foldr (.) id $ map (runModifierTransaction q) mods
|
||||
-- rewrite matched transactions
|
||||
let j' = j{jtxns=map (\t -> if q `matchesTransaction` t then rewriteTransaction t addps else t) ts}
|
||||
let j' = j{jtxns=map modifier ts}
|
||||
-- run the print command, showing all transactions
|
||||
print' opts{reportopts_=ropts{query_=""}} j'
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
# Tests for rewrite addon
|
||||
|
||||
# Add proportional income tax (from documentation)
|
||||
runghc ../../bin/hledger-rewrite.hs -f- ^income --add-posting '(liabilities:tax) *.33'
|
||||
runghc ../../bin/hledger-rewrite.hs -f- ^income --add-posting '(liabilities:tax) *.33 ; income tax'
|
||||
<<<
|
||||
2016/1/1 paycheck
|
||||
income:remuneration $-100
|
||||
income:donations $-15
|
||||
assets:bank
|
||||
|
||||
2016/1/1 withdraw
|
||||
@ -13,8 +14,10 @@ runghc ../../bin/hledger-rewrite.hs -f- ^income --add-posting '(liabilities:tax)
|
||||
>>>
|
||||
2016/01/01 paycheck
|
||||
income:remuneration $-100
|
||||
income:donations $-15
|
||||
assets:bank
|
||||
(liabilities:tax) $-33
|
||||
(liabilities:tax) $-33 ; income tax
|
||||
(liabilities:tax) $-5 ; income tax
|
||||
|
||||
2016/01/01 withdraw
|
||||
assets:cash $20
|
||||
@ -76,3 +79,69 @@ runghc ../../bin/hledger-rewrite.hs -f- assets:bank and 'amt:<0' --add-posting '
|
||||
|
||||
>>>2
|
||||
>>>=0
|
||||
|
||||
# Rewrite rule within journal
|
||||
runghc ../../bin/hledger-rewrite.hs -f- date:2017/1 --add-posting 'Here comes Santa $0'
|
||||
<<<
|
||||
= ^expenses:housing
|
||||
(budget:housing) *-1
|
||||
= ^expenses:grocery or ^expenses:food
|
||||
(budget:food) *-1
|
||||
|
||||
2016/12/31
|
||||
expenses:housing $600
|
||||
assets:cash
|
||||
|
||||
2017/1/1
|
||||
expenses:food $20
|
||||
expenses:leisure $15
|
||||
expenses:grocery $30
|
||||
assets:cash
|
||||
|
||||
2017/1/2
|
||||
assets:cash $200.00
|
||||
assets:bank
|
||||
|
||||
2017/2/1
|
||||
assets:cash $100.00
|
||||
assets:bank
|
||||
|
||||
= ^expenses not:housing not:grocery not:food
|
||||
(budget:misc) *-1
|
||||
|
||||
= ^assets:bank$ date:2017/1 amt:<0
|
||||
assets:bank *0.008
|
||||
expenses:fee *-0.008 ; cash withdraw fee
|
||||
>>>
|
||||
2016/12/31
|
||||
expenses:housing $600.00
|
||||
assets:cash
|
||||
|
||||
2017/01/01
|
||||
expenses:food $20.00
|
||||
expenses:leisure $15.00
|
||||
expenses:grocery $30.00
|
||||
assets:cash
|
||||
Here comes Santa 0
|
||||
Here comes Santa 0
|
||||
Here comes Santa 0
|
||||
Here comes Santa 0
|
||||
(budget:misc) $-15.00
|
||||
(budget:food) $-20.00
|
||||
(budget:food) $-30.00
|
||||
|
||||
2017/01/02
|
||||
assets:cash $200.00
|
||||
assets:bank
|
||||
Here comes Santa 0
|
||||
Here comes Santa 0
|
||||
assets:bank $-1.60
|
||||
expenses:fee $1.60 ; cash withdraw fee
|
||||
(budget:misc) $-1.60
|
||||
|
||||
2017/02/01
|
||||
assets:cash $100.00
|
||||
assets:bank
|
||||
|
||||
>>>2
|
||||
>>>=0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user