diff --git a/hledger-lib/Hledger/Query.hs b/hledger-lib/Hledger/Query.hs index 65b7d9187..130d1eeff 100644 --- a/hledger-lib/Hledger/Query.hs +++ b/hledger-lib/Hledger/Query.hs @@ -84,7 +84,7 @@ import qualified Data.Text as T import Data.Time.Calendar (Day, fromGregorian ) import Safe (readDef, readMay, maximumByMay, maximumMay, minimumMay) import Text.Megaparsec (between, noneOf, sepBy, try, (), notFollowedBy) -import Text.Megaparsec.Char (char, string) +import Text.Megaparsec.Char (char, string, string') import Hledger.Utils hiding (words') @@ -359,18 +359,18 @@ parseBooleanQuery d t = either (Left . ("failed to parse query:" <>) . customErr Left err -> error' err keywordSpaceP :: SimpleTextParser T.Text - keywordSpaceP = choice' ["NOT ", "AND ", "OR "] + keywordSpaceP = choice' (string' <$> ["not ", "and ", "or "]) parQueryP,notQueryP :: SimpleTextParser (Query, [QueryOpt]) parQueryP = between (char '(' >> skipNonNewlineSpaces) (try $ skipNonNewlineSpaces >> char ')') spacedQueriesP <|> queryTermP - notQueryP = (maybe id (\_ (q, qopts) -> (Not q, qopts)) <$> optional (string "NOT" >> skipNonNewlineSpaces1)) <*> parQueryP + notQueryP = (maybe id (\_ (q, qopts) -> (Not q, qopts)) <$> optional (try $ string' "not" >> notFollowedBy (char ':') >> skipNonNewlineSpaces1)) <*> parQueryP andQueriesP,orQueriesP,spacedQueriesP :: SimpleTextParser (Query, [QueryOpt]) - andQueriesP = nArityOp And <$> notQueryP `sepBy` (try $ skipNonNewlineSpaces >> string "AND" >> skipNonNewlineSpaces1) - orQueriesP = nArityOp Or <$> andQueriesP `sepBy` (try $ skipNonNewlineSpaces >> string "OR" >> skipNonNewlineSpaces1) + andQueriesP = nArityOp And <$> notQueryP `sepBy` (try $ skipNonNewlineSpaces >> string' "and" >> skipNonNewlineSpaces1) + orQueriesP = nArityOp Or <$> andQueriesP `sepBy` (try $ skipNonNewlineSpaces >> string' "or" >> skipNonNewlineSpaces1) spacedQueriesP = nArityOp combineQueryList <$> orQueriesP `sepBy` skipNonNewlineSpaces1 nArityOp :: ([Query] -> Query) -> [(Query, [QueryOpt])] -> (Query, [QueryOpt]) @@ -939,6 +939,9 @@ tests_Query = testGroup "Query" [ parseBooleanQuery nulldate " acct:'a' acct:'b'" @?= Right (Or [Acct $ toRegexCI' "a", Acct $ toRegexCI' "b"], []) parseBooleanQuery nulldate "not:a" @?= Right (Not $ Acct $ toRegexCI' "a", []) parseBooleanQuery nulldate "expenses:food OR (tag:A expenses:drink)" @?= Right (Or [Acct $ toRegexCI' "expenses:food", And [Acct $ toRegexCI' "expenses:drink", Tag (toRegexCI' "A") Nothing]], []) + parseBooleanQuery nulldate "not a" @?= Right (Not $ Acct $ toRegexCI' "a", []) + parseBooleanQuery nulldate "nota" @?= Right (Acct $ toRegexCI' "nota", []) + parseBooleanQuery nulldate "not (acct:a)" @?= Right (Not $ Acct $ toRegexCI' "a", []) ,testCase "words''" $ do (words'' [] "a b") @?= ["a","b"] diff --git a/hledger/test/query-bool.test b/hledger/test/query-bool.test index 463062f81..53e1b9bec 100644 --- a/hledger/test/query-bool.test +++ b/hledger/test/query-bool.test @@ -93,3 +93,54 @@ $ hledger -f - print expr:"NOT tag:'transactiontag=B' OR desc:4" expenses:drink >= + +# 7. Boolean expression query keywords are case insensitive +$ hledger -f - print expr:"NoT tag:'transactiontag=B' OR desc:4" +2022-01-01 Transaction 1 ; transactiontag:A + assets:bank:main -1 ; A comment + expenses:food + +2022-01-01 Transaction 2 ; transactiontag:A + assets:bank:main -1 + assets:bank:secondary -1 ; atag:a + expenses:food + +2022-01-01 Transaction 4 ; transactiontag:B + assets:bank:main -1 ; A comment + expenses:food 2 + expenses:drink + +>= + +# 8. Lower case not is not confused with existing not: queries +$ hledger -f - print expr:"not tag:transactiontag=B" +2022-01-01 Transaction 1 ; transactiontag:A + assets:bank:main -1 ; A comment + expenses:food + +2022-01-01 Transaction 2 ; transactiontag:A + assets:bank:main -1 + assets:bank:secondary -1 ; atag:a + expenses:food + +>= + +# 9. Having parentheses directly follow 'not' sees 'not' as part of a query. +$ hledger -f - print expr:"not(tag:transactiontag=B)" +>2 +hledger: Error: This regular expression is malformed, please correct it: +not(tag:transactiontag=B +>=1 + +# 10. ... whereas parentheses with a space between 'not' and '(' is fine. +$ hledger -f - print expr:"not (tag:transactiontag=B)" +2022-01-01 Transaction 1 ; transactiontag:A + assets:bank:main -1 ; A comment + expenses:food + +2022-01-01 Transaction 2 ; transactiontag:A + assets:bank:main -1 + assets:bank:secondary -1 ; atag:a + expenses:food + +>=