Skip to content

Commit

Permalink
Merge pull request #914 from simonmichael/rule-tags
Browse files Browse the repository at this point in the history
tags for matching rule-generated txns and postings
  • Loading branch information
simonmichael committed Jul 17, 2019
1 parent a5cae2a commit 3579914
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 114 deletions.
14 changes: 11 additions & 3 deletions hledger-lib/Hledger/Data/PeriodicTransaction.hs
Expand Up @@ -21,7 +21,7 @@ import Text.Printf
import Hledger.Data.Types
import Hledger.Data.Dates
import Hledger.Data.Amount
import Hledger.Data.Posting (post)
import Hledger.Data.Posting (post, commentAddTagNextLine)
import Hledger.Data.Transaction
import Hledger.Utils.UTF8IOCompat (error')
-- import Hledger.Utils.Debug
Expand Down Expand Up @@ -69,6 +69,10 @@ instance Show PeriodicTransaction where
-- | Generate transactions from 'PeriodicTransaction' within a 'DateSpan'
--
-- Note that new transactions require 'txnTieKnot' post-processing.
-- The new transactions will have three tags added:
-- - a recur:PERIODICEXPR tag whose value is the generating periodic expression
-- - a generated-transaction: tag
-- - a hidden _generated-transaction: tag which does not appear in the comment.
--
-- >>> _ptgen "monthly from 2017/1 to 2017/4"
-- 2017/01/01
Expand Down Expand Up @@ -204,10 +208,14 @@ runPeriodicTransaction PeriodicTransaction{..} requestedspan =
tstatus = ptstatus
,tcode = ptcode
,tdescription = ptdescription
,tcomment = (if T.null ptcomment then "\n" else ptcomment) <> "recur: " <> ptperiodexpr
,ttags = ("recur", ptperiodexpr) : pttags
,tcomment = ptcomment
`commentAddTagNextLine` ("generated-transaction",period)
,ttags = ("_generated-transaction",period) :
("generated-transaction" ,period) :
pttags
,tpostings = ptpostings
}
period = "~ " <> ptperiodexpr

-- | Check that this date span begins at a boundary of this interval,
-- or return an explanatory error message including the provided period expression
Expand Down
36 changes: 36 additions & 0 deletions hledger-lib/Hledger/Data/Posting.hs
Expand Up @@ -54,6 +54,10 @@ module Hledger.Data.Posting (
concatAccountNames,
accountNameApplyAliases,
accountNameApplyAliasesMemo,
-- * comment/tag operations
commentJoin,
commentAddTag,
commentAddTagNextLine,
-- * arithmetic
sumPostings,
-- * rendering
Expand Down Expand Up @@ -356,6 +360,29 @@ postingValueAtDate prices styles mc d p = postingTransformAmount (mixedAmountVal
postingTransformAmount :: (MixedAmount -> MixedAmount) -> Posting -> Posting
postingTransformAmount f p@Posting{pamount=a} = p{pamount=f a}

-- | Join two parts of a comment, eg a tag and another tag, or a tag
-- and a non-tag, on a single line. Interpolates a comma and space
-- unless one of the parts is empty.
commentJoin :: Text -> Text -> Text
commentJoin c1 c2
| T.null c1 = c2
| T.null c2 = c1
| otherwise = c1 <> ", " <> c2

-- | Add a tag to a comment, comma-separated from any prior content.
commentAddTag :: Text -> Tag -> Text
commentAddTag c (t,v)
| T.null c' = tag
| otherwise = c' `commentJoin` tag
where
c' = textchomp c
tag = t <> ":" <> v

-- | Add a tag on its own line to a comment, preserving any prior content.
commentAddTagNextLine :: Text -> Tag -> Text
commentAddTagNextLine cmt (t,v) =
cmt <> if "\n" `T.isSuffixOf` cmt then "" else "\n" <> t <> ":" <> v


-- tests

Expand Down Expand Up @@ -387,5 +414,14 @@ tests_Posting = tests "Posting" [
,concatAccountNames ["a","(b)","[c:d]"] `is` "(a:b:c:d)"
]

,tests "commentAddTag" [
commentAddTag "" ("a","") `is` "a:"
,commentAddTag "[1/2]" ("a","") `is` "[1/2], a:"
]

,tests "commentAddTagNextLine" [
commentAddTagNextLine "" ("a","") `is` "\na:"
,commentAddTagNextLine "[1/2]" ("a","") `is` "[1/2]\na:"
]
]

41 changes: 30 additions & 11 deletions hledger-lib/Hledger/Data/TransactionModifier.hs
Expand Up @@ -12,6 +12,7 @@ module Hledger.Data.TransactionModifier (
)
where

import Control.Applicative ((<|>))
import Data.Maybe
#if !(MIN_VERSION_base(4,11,0))
import Data.Monoid ((<>))
Expand All @@ -23,6 +24,7 @@ import Hledger.Data.Dates
import Hledger.Data.Amount
import Hledger.Data.Transaction
import Hledger.Query
import Hledger.Data.Posting (commentJoin, commentAddTag)
import Hledger.Utils.UTF8IOCompat (error')
import Hledger.Utils.Debug

Expand All @@ -36,7 +38,15 @@ import Hledger.Utils.Debug
modifyTransactions :: [TransactionModifier] -> [Transaction] -> [Transaction]
modifyTransactions tmods = map applymods
where
applymods = foldr (flip (.) . transactionModifierToFunction) id tmods
applymods t = taggedt'
where
t' = foldr (flip (.) . transactionModifierToFunction) id tmods t
taggedt'
-- PERF: compares txns to see if any modifier had an effect, inefficient ?
| t' /= t = t'{tcomment = tcomment t' `commentAddTag` ("modified","")
,ttags = ("modified","") : ttags t'
}
| otherwise = t'

-- | Converts a 'TransactionModifier' to a 'Transaction'-transforming function,
-- which applies the modification(s) specified by the TransactionModifier.
Expand All @@ -61,10 +71,10 @@ modifyTransactions tmods = map applymods
--
transactionModifierToFunction :: TransactionModifier -> (Transaction -> Transaction)
transactionModifierToFunction mt =
\t@(tpostings -> ps) -> txnTieKnot t{ tpostings=generatePostings ps } -- TODO add modifier txn comment/tags ?
\t@(tpostings -> ps) -> txnTieKnot t{ tpostings=generatePostings ps }
where
q = simplifyQuery $ tmParseQuery mt (error' "a transaction modifier's query cannot depend on current date")
mods = map tmPostingRuleToFunction $ tmpostingrules mt
mods = map (tmPostingRuleToFunction (tmquerytxt mt)) $ tmpostingrules mt
generatePostings ps = [p' | p <- ps
, p' <- if q `matchesPosting` p then p:[ m p | m <- mods] else [p]]

Expand All @@ -86,14 +96,23 @@ tmParseQuery mt = fst . flip parseQuery (tmquerytxt mt)
-- which will be used to make a new posting based on the old one (an "automated posting").
-- The new posting's amount can optionally be the old posting's amount multiplied by a constant.
-- If the old posting had a total-priced amount, the new posting's multiplied amount will be unit-priced.
tmPostingRuleToFunction :: TMPostingRule -> (Posting -> Posting)
tmPostingRuleToFunction pr =
-- The new posting will have two tags added: a normal generated-posting: tag which also appears in the comment,
-- and a hidden _generated-posting: tag which does not.
-- The TransactionModifier's query text is also provided, and saved
-- as the tags' value.
tmPostingRuleToFunction :: T.Text -> TMPostingRule -> (Posting -> Posting)
tmPostingRuleToFunction querytxt pr =
\p -> renderPostingCommentDates $ pr
{ pdate = pdate p
, pdate2 = pdate2 p
, pamount = amount' p
{ pdate = pdate pr <|> pdate p
, pdate2 = pdate2 pr <|> pdate2 p
, pamount = amount' p
, pcomment = pcomment pr `commentAddTag` ("generated-posting",qry)
, ptags = ("generated-posting", qry) :
("_generated-posting",qry) :
ptags pr
}
where
qry = "= " <> querytxt
amount' = case postingRuleMultiplier pr of
Nothing -> const $ pamount pr
Just n -> \p ->
Expand Down Expand Up @@ -123,7 +142,7 @@ postingRuleMultiplier p =
renderPostingCommentDates :: Posting -> Posting
renderPostingCommentDates p = p { pcomment = comment' }
where
datesComment = T.concat $ catMaybes [T.pack . showDate <$> pdate p, ("=" <>) . T.pack . showDate <$> pdate2 p]
dates = T.concat $ catMaybes [T.pack . showDate <$> pdate p, ("=" <>) . T.pack . showDate <$> pdate2 p]
comment'
| T.null datesComment = pcomment p
| otherwise = T.intercalate "\n" $ filter (not . T.null) [T.strip $ pcomment p, "[" <> datesComment <> "]"]
| T.null dates = pcomment p
| otherwise = ("[" <> dates <> "]") `commentJoin` pcomment p
8 changes: 4 additions & 4 deletions hledger-lib/Hledger/Utils/Text.hs
Expand Up @@ -30,7 +30,7 @@ module Hledger.Utils.Text
textstrip,
textlstrip,
textrstrip,
-- chomp,
textchomp,
-- elideLeft,
textElideRight,
-- formatString,
Expand Down Expand Up @@ -90,9 +90,9 @@ textlstrip = T.dropWhile (`elem` (" \t" :: String)) :: Text -> Text -- XXX isSpa
textrstrip = T.reverse . textlstrip . T.reverse
textrstrip :: Text -> Text

-- -- | Remove trailing newlines/carriage returns.
-- chomp :: String -> String
-- chomp = reverse . dropWhile (`elem` "\r\n") . reverse
-- | Remove trailing newlines/carriage returns (and other whitespace).
textchomp :: Text -> Text
textchomp = T.stripEnd

-- stripbrackets :: String -> String
-- stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse :: String -> String
Expand Down
38 changes: 34 additions & 4 deletions hledger-lib/hledger_journal.m4.md
Expand Up @@ -1201,8 +1201,16 @@ can not accidentally alter their meaning, as in this example:
With the `--forecast` flag, each periodic transaction rule generates
future transactions recurring at the specified interval.
These are not saved in the journal, but appear in all reports.
They will look like normal transactions, but with an extra
[tag](manual.html#tags-1) named `recur`, whose value is the generating period expression.
They will look like normal transactions, but with an extra [tag](manual.html#tags-1):

- `generated-transaction:~ PERIODICEXPR` - shows that this was generated by a periodic transaction rule, and the period

There is also a hidden tag, with an underscore prefix, which does not appear in hledger's output:

- `_generated-transaction:~ PERIODICEXPR`

This can be used to match transactions generated "just now",
rather than generated in the past and saved to the journal.

Forecast transactions start on the first occurrence, and end on the last occurrence,
of their interval within the forecast period. The forecast period:
Expand Down Expand Up @@ -1251,11 +1259,11 @@ and

## Auto postings / transaction modifiers

Transaction modifier rules describe changes to be applied automatically to certain matched transactions.
Transaction modifier rules, AKA auto posting rules, describe changes to be applied automatically to certain matched transactions.
Currently just one kind of change is possible - adding extra postings, which we call "automated postings" or just "auto postings".
These rules become active when you use the `--auto` flag.

A transaction modifier, AKA auto posting rule, looks much like a normal transaction
A transaction modifier rule looks much like a normal transaction
except the first line is an equals sign followed by a [query](manual.html#queries) that matches certain postings
(mnemonic: `=` suggests matching).
And each "posting" is actually a posting-generating rule:
Expand Down Expand Up @@ -1309,6 +1317,12 @@ $ hledger print --auto
assets:checking $20
```

### Auto postings and dates

A [posting date](#posting-dates) (or secondary date) in the matched posting,
or (taking precedence) a posting date in the auto posting rule itself,
will also be used in the generated posting.

### Auto postings and transaction balancing / inferred amounts / balance assertions

Currently, transaction modifiers are applied / auto postings are added:
Expand All @@ -1321,6 +1335,22 @@ after auto postings are added. This changed in hledger 1.12+; see
[#893](https://github.com/simonmichael/hledger/issues/893) for
background.

### Auto posting tags

Postings added by transaction modifiers will have some extra [tags](#tags-1):

- `generated-posting:= QUERY` - shows this was generated by an auto posting rule, and the query
- `_generated-posting:= QUERY` - a hidden tag, which does not appear in hledger's output.
This can be used to match postings generated "just now",
rather than generated in the past and saved to the journal.

Also, any transaction that has been changed by transaction modifier rules will have these tags added:

- `modified:` - this transaction was modified
- `_modified:` - a hidden tag not appearing in the comment; this transaction was modified "just now".



# EDITOR SUPPORT

Helper modes exist for popular text editors, which make working with
Expand Down
8 changes: 4 additions & 4 deletions tests/forecast.test
Expand Up @@ -51,7 +51,7 @@ hledger print -b 2016-11 -e 2017-02 -f - --forecast
assets:cash

2017/01/01 * marked cleared, and with a description
; recur: monthly from 2016/1
; generated-transaction:~ monthly from 2016/1
income $-1000
expenses:food $20
expenses:leisure $15
Expand Down Expand Up @@ -118,7 +118,7 @@ Y 2000

>>>
2000/02/01 forecast
; recur: 2/1
; generated-transaction:~ 2/1

>>>2
>>>=0
Expand All @@ -135,7 +135,7 @@ Y 2000

>>>
2000/01/15 forecast
; recur: 15
; generated-transaction:~ 15

>>>2
>>>=0
Expand All @@ -152,7 +152,7 @@ Y 2000

>>>
2000/02/01 forecast
; recur: next month
; generated-transaction:~ next month

>>>2
>>>=0

0 comments on commit 3579914

Please sign in to comment.