Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: Fix filtering by payee and note (#598) #608

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 5 additions & 3 deletions hledger-lib/Hledger/Data/Posting.hs
Expand Up @@ -187,16 +187,18 @@ transactionPayee :: Transaction -> Text
transactionPayee = fst . payeeAndNoteFromDescription . tdescription

transactionNote :: Transaction -> Text
transactionNote = fst . payeeAndNoteFromDescription . tdescription
transactionNote = snd . payeeAndNoteFromDescription . tdescription

-- | Parse a transaction's description into payee and note (aka narration) fields,
-- assuming a convention of separating these with | (like Beancount).
-- Ie, everything up to the first | is the payee, everything after it is the note.
-- When there's no |, payee == note == description.
payeeAndNoteFromDescription :: Text -> (Text,Text)
payeeAndNoteFromDescription t = (textstrip p, textstrip $ T.tail n)
payeeAndNoteFromDescription t
| T.null n = (t, t)
| otherwise = (textstrip p, textstrip $ T.drop 1 n)
where
(p,n) = T.breakOn "|" t
(p, n) = T.span (/= '|') t

-- | Tags for this posting including implicit and any inherited from its parent transaction.
postingAllImplicitTags :: Posting -> [Tag]
Expand Down
25 changes: 18 additions & 7 deletions hledger-lib/Hledger/Query.hs
Expand Up @@ -225,6 +225,8 @@ prefixes = map (<>":") [
,"amt"
,"code"
,"desc"
,"payee"
,"note"
,"acct"
,"date"
,"date2"
Expand Down Expand Up @@ -260,6 +262,8 @@ parseQueryTerm d (T.stripPrefix "not:" -> Just s) =
Right _ -> Left Any -- not:somequeryoption will be ignored
parseQueryTerm _ (T.stripPrefix "code:" -> Just s) = Left $ Code $ T.unpack s
parseQueryTerm _ (T.stripPrefix "desc:" -> Just s) = Left $ Desc $ T.unpack s
parseQueryTerm _ (T.stripPrefix "payee:" -> Just s) = Left $ Tag "payee" $ Just $ T.unpack s
parseQueryTerm _ (T.stripPrefix "note:" -> Just s) = Left $ Tag "note" $ Just $ T.unpack s
parseQueryTerm _ (T.stripPrefix "acct:" -> Just s) = Left $ Acct $ T.unpack s
parseQueryTerm d (T.stripPrefix "date2:" -> Just s) =
case parsePeriodExpr d s of Left e -> error' $ "\"date2:"++T.unpack s++"\" gave a "++showDateParseError e
Expand Down Expand Up @@ -294,6 +298,8 @@ tests_parseQueryTerm = [
"status:!" `gives` (Left $ StatusQ Pending)
"status:0" `gives` (Left $ StatusQ Unmarked)
"status:" `gives` (Left $ StatusQ Unmarked)
"payee:x" `gives` (Left $ Tag "payee" (Just "x"))
"note:x" `gives` (Left $ Tag "note" (Just "x"))
"real:1" `gives` (Left $ Real True)
"date:2008" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2008/01/01") (Just $ parsedate "2009/01/01"))
"date:from 2012/5/17" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2012/05/17") Nothing)
Expand Down Expand Up @@ -684,8 +690,10 @@ matchesPosting q@(Amt _ _) Posting{pamount=amt} = q `matchesMixedAmount` amt
-- matchesPosting (Empty True) Posting{pamount=a} = isZeroMixedAmount a
matchesPosting (Empty _) _ = True
matchesPosting (Sym r) Posting{pamount=Mixed as} = any (regexMatchesCI $ "^" ++ r ++ "$") $ map (T.unpack . acommodity) as
matchesPosting (Tag n v) p = not $ null $ matchedTags n v $ postingAllTags p
-- matchesPosting _ _ = False
matchesPosting (Tag n v) p = case (n, v) of
("payee", Just v) -> maybe False (regexMatchesCI v . T.unpack . transactionPayee) $ ptransaction p
("note", Just v) -> maybe False (regexMatchesCI v . T.unpack . transactionNote) $ ptransaction p
(n, v) -> matchesTags n v $ postingAllTags p

tests_matchesPosting = [
"matchesPosting" ~: do
Expand Down Expand Up @@ -737,9 +745,10 @@ matchesTransaction q@(Amt _ _) t = any (q `matchesPosting`) $ tpostings t
matchesTransaction (Empty _) _ = True
matchesTransaction (Depth d) t = any (Depth d `matchesPosting`) $ tpostings t
matchesTransaction q@(Sym _) t = any (q `matchesPosting`) $ tpostings t
matchesTransaction (Tag n v) t = not $ null $ matchedTags n v $ transactionAllTags t

-- matchesTransaction _ _ = False
matchesTransaction (Tag n v) t = case (n, v) of
("payee", Just v) -> regexMatchesCI v . T.unpack . transactionPayee $ t
("note", Just v) -> regexMatchesCI v . T.unpack . transactionNote $ t
(n, v) -> matchesTags n v $ transactionAllTags t

tests_matchesTransaction = [
"matchesTransaction" ~: do
Expand All @@ -749,14 +758,16 @@ tests_matchesTransaction = [
assertBool "" $ (Desc "x x") `matchesTransaction` nulltransaction{tdescription="x x"}
-- see posting for more tag tests
assertBool "" $ (Tag "foo" (Just "a")) `matchesTransaction` nulltransaction{ttags=[("foo","bar")]}
assertBool "" $ (Tag "payee" (Just "payee")) `matchesTransaction` nulltransaction{tdescription="payee|note"}
assertBool "" $ (Tag "note" (Just "note")) `matchesTransaction` nulltransaction{tdescription="payee|note"}
-- a tag match on a transaction also matches posting tags
assertBool "" $ (Tag "postingtag" Nothing) `matchesTransaction` nulltransaction{tpostings=[nullposting{ptags=[("postingtag","")]}]}
]

-- | Filter a list of tags by matching against their names and
-- optionally also their values.
matchedTags :: Regexp -> Maybe Regexp -> [Tag] -> [Tag]
matchedTags namepat valuepat tags = filter (match namepat valuepat) tags
matchesTags :: Regexp -> Maybe Regexp -> [Tag] -> Bool
matchesTags namepat valuepat = not . null . filter (match namepat valuepat)
where
match npat Nothing (n,_) = regexMatchesCI npat (T.unpack n) -- XXX
match npat (Just vpat) (n,v) = regexMatchesCI npat (T.unpack n) && regexMatchesCI vpat (T.unpack v)
Expand Down