{-|

A general query system for matching things (accounts, postings,
transactions..)  by various criteria, and a SimpleTextParser for query expressions.

-}

{-# LANGUAGE CPP                #-}
{-# LANGUAGE FlexibleContexts   #-}
{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE ViewPatterns       #-}

module Hledger.Query (
  -- * Query and QueryOpt
  Query(..),
  QueryOpt(..),
  OrdPlus(..),
  payeeTag,
  noteTag,
  generatedTransactionTag,
  -- * parsing
  parseQuery,
  parseQueryList,
  simplifyQuery,
  filterQuery,
  -- * accessors
  queryIsNull,
  queryIsAcct,
  queryIsAmt,
  queryIsDepth,
  queryIsDate,
  queryIsDate2,
  queryIsDateOrDate2,
  queryIsStartDateOnly,
  queryIsSym,
  queryIsReal,
  queryIsStatus,
  queryStartDate,
  queryEndDate,
  queryDateSpan,
  queryDateSpan',
  queryDepth,
  inAccount,
  inAccountQuery,
  -- * matching
  matchesTransaction,
  matchesPosting,
  matchesAccount,
  matchesMixedAmount,
  matchesAmount,
  matchesCommodity,
  matchesTags,
  matchesPriceDirective,
  words'',
  prefixes,
  -- * tests
  tests_Query
)
where

import Control.Applicative ((<|>), many, optional)
import Data.Default (Default(..))
import Data.Either (partitionEithers)
import Data.List (partition)
import Data.Maybe (fromMaybe, isJust, mapMaybe)
#if !(MIN_VERSION_base(4,11,0))
import Data.Monoid ((<>))
#endif
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)
import Text.Megaparsec.Char (char, string)

import Hledger.Utils hiding (words')
import Hledger.Data.Types
import Hledger.Data.AccountName
import Hledger.Data.Amount (nullamt, usd)
import Hledger.Data.Dates
import Hledger.Data.Posting
import Hledger.Data.Transaction


-- | A query is a composition of search criteria, which can be used to
-- match postings, transactions, accounts and more.
data Query = Any              -- ^ always match
           | None             -- ^ never match
           | Not Query        -- ^ negate this match
           | Or [Query]       -- ^ match if any of these match
           | And [Query]      -- ^ match if all of these match
           | Code Regexp      -- ^ match if code matches this regexp
           | Desc Regexp      -- ^ match if description matches this regexp
           | Acct Regexp      -- ^ match postings whose account matches this regexp
           | Date DateSpan    -- ^ match if primary date in this date span
           | Date2 DateSpan   -- ^ match if secondary date in this date span
           | StatusQ Status  -- ^ match txns/postings with this status
           | Real Bool        -- ^ match if "realness" (involves a real non-virtual account ?) has this value
           | Amt OrdPlus Quantity  -- ^ match if the amount's numeric quantity is less than/greater than/equal to/unsignedly equal to some value
           | Sym Regexp       -- ^ match if the entire commodity symbol is matched by this regexp
           | Depth Int        -- ^ match if account depth is less than or equal to this value.
                              --   Depth is sometimes used like a query (for filtering report data)
                              --   and sometimes like a query option (for controlling display)
           | Tag Regexp (Maybe Regexp)  -- ^ match if a tag's name, and optionally its value, is matched by these respective regexps
                                        -- matching the regexp if provided, exists
    deriving (Query -> Query -> Bool
(Query -> Query -> Bool) -> (Query -> Query -> Bool) -> Eq Query
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Query -> Query -> Bool
$c/= :: Query -> Query -> Bool
== :: Query -> Query -> Bool
$c== :: Query -> Query -> Bool
Eq,Int -> Query -> ShowS
[Query] -> ShowS
Query -> String
(Int -> Query -> ShowS)
-> (Query -> String) -> ([Query] -> ShowS) -> Show Query
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Query] -> ShowS
$cshowList :: [Query] -> ShowS
show :: Query -> String
$cshow :: Query -> String
showsPrec :: Int -> Query -> ShowS
$cshowsPrec :: Int -> Query -> ShowS
Show)

instance Default Query where def :: Query
def = Query
Any

-- | Construct a payee tag
payeeTag :: Maybe String -> Either RegexError Query
payeeTag :: Maybe String -> Either String Query
payeeTag = (Maybe Regexp -> Query)
-> Either String (Maybe Regexp) -> Either String Query
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegexCI' String
"payee")) (Either String (Maybe Regexp) -> Either String Query)
-> (Maybe String -> Either String (Maybe Regexp))
-> Maybe String
-> Either String Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Either String (Maybe Regexp)
-> (String -> Either String (Maybe Regexp))
-> Maybe String
-> Either String (Maybe Regexp)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Maybe Regexp -> Either String (Maybe Regexp)
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe Regexp
forall a. Maybe a
Nothing) ((Regexp -> Maybe Regexp)
-> Either String Regexp -> Either String (Maybe Regexp)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Either String Regexp -> Either String (Maybe Regexp))
-> (String -> Either String Regexp)
-> String
-> Either String (Maybe Regexp)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Either String Regexp
toRegexCI)

-- | Construct a note tag
noteTag :: Maybe String -> Either RegexError Query
noteTag :: Maybe String -> Either String Query
noteTag = (Maybe Regexp -> Query)
-> Either String (Maybe Regexp) -> Either String Query
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegexCI' String
"note")) (Either String (Maybe Regexp) -> Either String Query)
-> (Maybe String -> Either String (Maybe Regexp))
-> Maybe String
-> Either String Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Either String (Maybe Regexp)
-> (String -> Either String (Maybe Regexp))
-> Maybe String
-> Either String (Maybe Regexp)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Maybe Regexp -> Either String (Maybe Regexp)
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe Regexp
forall a. Maybe a
Nothing) ((Regexp -> Maybe Regexp)
-> Either String Regexp -> Either String (Maybe Regexp)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Either String Regexp -> Either String (Maybe Regexp))
-> (String -> Either String Regexp)
-> String
-> Either String (Maybe Regexp)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Either String Regexp
toRegexCI)

-- | Construct a generated-transaction tag
generatedTransactionTag :: Query
generatedTransactionTag :: Query
generatedTransactionTag = Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegexCI' String
"generated-transaction") Maybe Regexp
forall a. Maybe a
Nothing

-- | A more expressive Ord, used for amt: queries. The Abs* variants
-- compare with the absolute value of a number, ignoring sign.
data OrdPlus = Lt | LtEq | Gt | GtEq | Eq | AbsLt | AbsLtEq | AbsGt | AbsGtEq | AbsEq
 deriving (Int -> OrdPlus -> ShowS
[OrdPlus] -> ShowS
OrdPlus -> String
(Int -> OrdPlus -> ShowS)
-> (OrdPlus -> String) -> ([OrdPlus] -> ShowS) -> Show OrdPlus
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [OrdPlus] -> ShowS
$cshowList :: [OrdPlus] -> ShowS
show :: OrdPlus -> String
$cshow :: OrdPlus -> String
showsPrec :: Int -> OrdPlus -> ShowS
$cshowsPrec :: Int -> OrdPlus -> ShowS
Show,OrdPlus -> OrdPlus -> Bool
(OrdPlus -> OrdPlus -> Bool)
-> (OrdPlus -> OrdPlus -> Bool) -> Eq OrdPlus
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: OrdPlus -> OrdPlus -> Bool
$c/= :: OrdPlus -> OrdPlus -> Bool
== :: OrdPlus -> OrdPlus -> Bool
$c== :: OrdPlus -> OrdPlus -> Bool
Eq)

-- | A query option changes a query's/report's behaviour and output in some way.
data QueryOpt = QueryOptInAcctOnly AccountName  -- ^ show an account register focussed on this account
              | QueryOptInAcct AccountName      -- ^ as above but include sub-accounts in the account register
           -- | QueryOptCostBasis      -- ^ show amounts converted to cost where possible
           -- | QueryOptDate2  -- ^ show secondary dates instead of primary dates
    deriving (Int -> QueryOpt -> ShowS
[QueryOpt] -> ShowS
QueryOpt -> String
(Int -> QueryOpt -> ShowS)
-> (QueryOpt -> String) -> ([QueryOpt] -> ShowS) -> Show QueryOpt
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [QueryOpt] -> ShowS
$cshowList :: [QueryOpt] -> ShowS
show :: QueryOpt -> String
$cshow :: QueryOpt -> String
showsPrec :: Int -> QueryOpt -> ShowS
$cshowsPrec :: Int -> QueryOpt -> ShowS
Show, QueryOpt -> QueryOpt -> Bool
(QueryOpt -> QueryOpt -> Bool)
-> (QueryOpt -> QueryOpt -> Bool) -> Eq QueryOpt
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: QueryOpt -> QueryOpt -> Bool
$c/= :: QueryOpt -> QueryOpt -> Bool
== :: QueryOpt -> QueryOpt -> Bool
$c== :: QueryOpt -> QueryOpt -> Bool
Eq)

-- parsing

-- -- | A query restricting the account(s) to be shown in the sidebar, if any.
-- -- Just looks at the first query option.
-- showAccountMatcher :: [QueryOpt] -> Maybe Query
-- showAccountMatcher (QueryOptInAcctSubsOnly a:_) = Just $ Acct True $ accountNameToAccountRegex a
-- showAccountMatcher _ = Nothing


-- | A version of parseQueryList which acts on a single Text of
-- space-separated terms.
--
-- The usual shell quoting rules are assumed. When a pattern contains
-- whitespace, it (or the whole term including prefix) should be enclosed
-- in single or double quotes.
--
-- >>> parseQuery nulldate "expenses:dining out"
-- Right (Or [Acct (RegexpCI "expenses:dining"),Acct (RegexpCI "out")],[])
--
-- >>> parseQuery nulldate "\"expenses:dining out\""
-- Right (Acct (RegexpCI "expenses:dining out"),[])
parseQuery :: Day -> T.Text -> Either String (Query,[QueryOpt])
parseQuery :: Day -> Text -> Either String (Query, [QueryOpt])
parseQuery Day
d = Day -> [Text] -> Either String (Query, [QueryOpt])
parseQueryList Day
d ([Text] -> Either String (Query, [QueryOpt]))
-> (Text -> [Text]) -> Text -> Either String (Query, [QueryOpt])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> Text -> [Text]
words'' [Text]
prefixes

-- | Convert a list of query expression containing to a query and zero
-- or more query options; or return an error message if query parsing fails.
--
-- A query term is either:
--
-- 1. a search pattern, which matches on one or more fields, eg:
--
--      acct:REGEXP     - match the account name with a regular expression
--      desc:REGEXP     - match the transaction description
--      date:PERIODEXP  - match the date with a period expression
--
--    The prefix indicates the field to match, or if there is no prefix
--    account name is assumed.
--
-- 2. a query option, which modifies the reporting behaviour in some
--    way. There is currently one of these, which may appear only once:
--
--      inacct:FULLACCTNAME
--
-- Period expressions may contain relative dates, so a reference date is
-- required to fully parse these.
--
-- Multiple terms are combined as follows:
-- 1. multiple account patterns are OR'd together
-- 2. multiple description patterns are OR'd together
-- 3. multiple status patterns are OR'd together
-- 4. then all terms are AND'd together
parseQueryList :: Day -> [T.Text] -> Either String (Query, [QueryOpt])
parseQueryList :: Day -> [Text] -> Either String (Query, [QueryOpt])
parseQueryList Day
d [Text]
termstrs = do
  [Either Query QueryOpt]
eterms <- [Either String (Either Query QueryOpt)]
-> Either String [Either Query QueryOpt]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence ([Either String (Either Query QueryOpt)]
 -> Either String [Either Query QueryOpt])
-> [Either String (Either Query QueryOpt)]
-> Either String [Either Query QueryOpt]
forall a b. (a -> b) -> a -> b
$ (Text -> Either String (Either Query QueryOpt))
-> [Text] -> [Either String (Either Query QueryOpt)]
forall a b. (a -> b) -> [a] -> [b]
map (Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
d) [Text]
termstrs
  let ([Query]
pats, [QueryOpt]
opts) = [Either Query QueryOpt] -> ([Query], [QueryOpt])
forall a b. [Either a b] -> ([a], [b])
partitionEithers [Either Query QueryOpt]
eterms
      ([Query]
descpats, [Query]
pats') = (Query -> Bool) -> [Query] -> ([Query], [Query])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Query -> Bool
queryIsDesc [Query]
pats
      ([Query]
acctpats, [Query]
pats'') = (Query -> Bool) -> [Query] -> ([Query], [Query])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Query -> Bool
queryIsAcct [Query]
pats'
      ([Query]
statuspats, [Query]
otherpats) = (Query -> Bool) -> [Query] -> ([Query], [Query])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Query -> Bool
queryIsStatus [Query]
pats''
      q :: Query
q = Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And ([Query] -> Query) -> [Query] -> Query
forall a b. (a -> b) -> a -> b
$ [[Query] -> Query
Or [Query]
acctpats, [Query] -> Query
Or [Query]
descpats, [Query] -> Query
Or [Query]
statuspats] [Query] -> [Query] -> [Query]
forall a. [a] -> [a] -> [a]
++ [Query]
otherpats
  (Query, [QueryOpt]) -> Either String (Query, [QueryOpt])
forall a b. b -> Either a b
Right (Query
q, [QueryOpt]
opts)

-- XXX
-- | Quote-and-prefix-aware version of words - don't split on spaces which
-- are inside quotes, including quotes which may have one of the specified
-- prefixes in front, and maybe an additional not: prefix in front of that.
words'' :: [T.Text] -> T.Text -> [T.Text]
words'' :: [Text] -> Text -> [Text]
words'' [Text]
prefixes = Either (ParseErrorBundle Text CustomErr) [Text] -> [Text]
forall t e a.
(Show t, Show (Token t), Show e) =>
Either (ParseErrorBundle t e) a -> a
fromparse (Either (ParseErrorBundle Text CustomErr) [Text] -> [Text])
-> (Text -> Either (ParseErrorBundle Text CustomErr) [Text])
-> Text
-> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Parsec CustomErr Text [Text]
-> Text -> Either (ParseErrorBundle Text CustomErr) [Text]
forall e a.
Parsec e Text a -> Text -> Either (ParseErrorBundle Text e) a
parsewith Parsec CustomErr Text [Text]
maybeprefixedquotedphrases -- XXX
    where
      maybeprefixedquotedphrases :: SimpleTextParser [T.Text]
      maybeprefixedquotedphrases :: Parsec CustomErr Text [Text]
maybeprefixedquotedphrases = [TextParser Identity Text] -> TextParser Identity Text
forall (m :: * -> *) a. [TextParser m a] -> TextParser m a
choice' [TextParser Identity Text
prefixedQuotedPattern, TextParser Identity Text
singleQuotedPattern, TextParser Identity Text
doubleQuotedPattern, TextParser Identity Text
pattern] TextParser Identity Text
-> ParsecT CustomErr Text Identity ()
-> Parsec CustomErr Text [Text]
forall (m :: * -> *) a sep. MonadPlus m => m a -> m sep -> m [a]
`sepBy` ParsecT CustomErr Text Identity ()
forall s (m :: * -> *).
(Stream s, Token s ~ Char) =>
ParsecT CustomErr s m ()
skipNonNewlineSpaces1
      prefixedQuotedPattern :: SimpleTextParser T.Text
      prefixedQuotedPattern :: TextParser Identity Text
prefixedQuotedPattern = do
        Text
not' <- Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
"" (Maybe Text -> Text)
-> ParsecT CustomErr Text Identity (Maybe Text)
-> TextParser Identity Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` (TextParser Identity Text
-> ParsecT CustomErr Text Identity (Maybe Text)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional (TextParser Identity Text
 -> ParsecT CustomErr Text Identity (Maybe Text))
-> TextParser Identity Text
-> ParsecT CustomErr Text Identity (Maybe Text)
forall a b. (a -> b) -> a -> b
$ Tokens Text -> ParsecT CustomErr Text Identity (Tokens Text)
forall e s (m :: * -> *).
MonadParsec e s m =>
Tokens s -> m (Tokens s)
string Tokens Text
"not:")
        let allowednexts :: [Text]
allowednexts | Text -> Bool
T.null Text
not' = [Text]
prefixes
                         | Bool
otherwise   = [Text]
prefixes [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ [Text
""]
        Text
next <- [TextParser Identity Text] -> TextParser Identity Text
forall (m :: * -> *) a. [TextParser m a] -> TextParser m a
choice' ([TextParser Identity Text] -> TextParser Identity Text)
-> [TextParser Identity Text] -> TextParser Identity Text
forall a b. (a -> b) -> a -> b
$ (Text -> TextParser Identity Text)
-> [Text] -> [TextParser Identity Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> TextParser Identity Text
forall e s (m :: * -> *).
MonadParsec e s m =>
Tokens s -> m (Tokens s)
string [Text]
allowednexts
        let prefix :: T.Text
            prefix :: Text
prefix = Text
not' Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
next
        Text
p <- TextParser Identity Text
singleQuotedPattern TextParser Identity Text
-> TextParser Identity Text -> TextParser Identity Text
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> TextParser Identity Text
doubleQuotedPattern
        Text -> TextParser Identity Text
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> TextParser Identity Text)
-> Text -> TextParser Identity Text
forall a b. (a -> b) -> a -> b
$ Text
prefix Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
stripquotes Text
p
      singleQuotedPattern :: SimpleTextParser T.Text
      singleQuotedPattern :: TextParser Identity Text
singleQuotedPattern = ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity String
-> ParsecT CustomErr Text Identity String
forall (m :: * -> *) open close a.
Applicative m =>
m open -> m close -> m a -> m a
between (Token Text -> ParsecT CustomErr Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'\'') (Token Text -> ParsecT CustomErr Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'\'') (ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity String
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many (ParsecT CustomErr Text Identity Char
 -> ParsecT CustomErr Text Identity String)
-> ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity String
forall a b. (a -> b) -> a -> b
$ [Token Text] -> ParsecT CustomErr Text Identity (Token Text)
forall (f :: * -> *) e s (m :: * -> *).
(Foldable f, MonadParsec e s m) =>
f (Token s) -> m (Token s)
noneOf (String
"'" :: [Char])) ParsecT CustomErr Text Identity String
-> (String -> TextParser Identity Text) -> TextParser Identity Text
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> TextParser Identity Text
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> TextParser Identity Text)
-> (String -> Text) -> String -> TextParser Identity Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
stripquotes (Text -> Text) -> (String -> Text) -> String -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack
      doubleQuotedPattern :: SimpleTextParser T.Text
      doubleQuotedPattern :: TextParser Identity Text
doubleQuotedPattern = ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity String
-> ParsecT CustomErr Text Identity String
forall (m :: * -> *) open close a.
Applicative m =>
m open -> m close -> m a -> m a
between (Token Text -> ParsecT CustomErr Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'"') (Token Text -> ParsecT CustomErr Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'"') (ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity String
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many (ParsecT CustomErr Text Identity Char
 -> ParsecT CustomErr Text Identity String)
-> ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity String
forall a b. (a -> b) -> a -> b
$ [Token Text] -> ParsecT CustomErr Text Identity (Token Text)
forall (f :: * -> *) e s (m :: * -> *).
(Foldable f, MonadParsec e s m) =>
f (Token s) -> m (Token s)
noneOf (String
"\"" :: [Char])) ParsecT CustomErr Text Identity String
-> (String -> TextParser Identity Text) -> TextParser Identity Text
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> TextParser Identity Text
forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> TextParser Identity Text)
-> (String -> Text) -> String -> TextParser Identity Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
stripquotes (Text -> Text) -> (String -> Text) -> String -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack
      pattern :: SimpleTextParser T.Text
      pattern :: TextParser Identity Text
pattern = (String -> Text)
-> ParsecT CustomErr Text Identity String
-> TextParser Identity Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> Text
T.pack (ParsecT CustomErr Text Identity String
 -> TextParser Identity Text)
-> ParsecT CustomErr Text Identity String
-> TextParser Identity Text
forall a b. (a -> b) -> a -> b
$ ParsecT CustomErr Text Identity Char
-> ParsecT CustomErr Text Identity String
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many ([Token Text] -> ParsecT CustomErr Text Identity (Token Text)
forall (f :: * -> *) e s (m :: * -> *).
(Foldable f, MonadParsec e s m) =>
f (Token s) -> m (Token s)
noneOf (String
" \n\r" :: [Char]))

-- XXX
-- keep synced with patterns below, excluding "not"
prefixes :: [T.Text]
prefixes :: [Text]
prefixes = (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>Text
":") [
     Text
"inacctonly"
    ,Text
"inacct"
    ,Text
"amt"
    ,Text
"code"
    ,Text
"desc"
    ,Text
"payee"
    ,Text
"note"
    ,Text
"acct"
    ,Text
"date"
    ,Text
"date2"
    ,Text
"status"
    ,Text
"cur"
    ,Text
"real"
    ,Text
"empty"
    ,Text
"depth"
    ,Text
"tag"
    ]

defaultprefix :: T.Text
defaultprefix :: Text
defaultprefix = Text
"acct"

-- -- | Parse the query string as a boolean tree of match patterns.
-- parseQueryTerm :: String -> Query
-- parseQueryTerm s = either (const (Any)) id $ runParser query () "" $ lexmatcher s

-- lexmatcher :: String -> [String]
-- lexmatcher s = words' s

-- query :: GenParser String () Query
-- query = undefined

-- | Parse a single query term as either a query or a query option,
-- or return an error message if parsing fails.
parseQueryTerm :: Day -> T.Text -> Either String (Either Query QueryOpt)
parseQueryTerm :: Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"inacctonly:" -> Just Text
s) = Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ QueryOpt -> Either Query QueryOpt
forall a b. b -> Either a b
Right (QueryOpt -> Either Query QueryOpt)
-> QueryOpt -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Text -> QueryOpt
QueryOptInAcctOnly Text
s
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"inacct:" -> Just Text
s) = Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ QueryOpt -> Either Query QueryOpt
forall a b. b -> Either a b
Right (QueryOpt -> Either Query QueryOpt)
-> QueryOpt -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Text -> QueryOpt
QueryOptInAcct Text
s
parseQueryTerm Day
d (Text -> Text -> Maybe Text
T.stripPrefix Text
"not:" -> Just Text
s) =
  case Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
d Text
s of
    Right (Left Query
m)  -> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Query -> Query
Not Query
m
    Right (Right QueryOpt
_) -> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left Query
Any -- not:somequeryoption will be ignored
    Left String
err        -> String -> Either String (Either Query QueryOpt)
forall a b. a -> Either a b
Left String
err
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"code:" -> Just Text
s) = Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> (Regexp -> Query) -> Regexp -> Either Query QueryOpt
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> Query
Code (Regexp -> Either Query QueryOpt)
-> Either String Regexp -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Either String Regexp
toRegexCI (Text -> String
T.unpack Text
s)
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"desc:" -> Just Text
s) = Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> (Regexp -> Query) -> Regexp -> Either Query QueryOpt
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> Query
Desc (Regexp -> Either Query QueryOpt)
-> Either String Regexp -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Either String Regexp
toRegexCI (Text -> String
T.unpack Text
s)
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"payee:" -> Just Text
s) = Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> Either String Query -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe String -> Either String Query
payeeTag (String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
s)
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"note:" -> Just Text
s) = Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> Either String Query -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe String -> Either String Query
noteTag (String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
s)
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"acct:" -> Just Text
s) = Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> (Regexp -> Query) -> Regexp -> Either Query QueryOpt
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> Query
Acct (Regexp -> Either Query QueryOpt)
-> Either String Regexp -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Either String Regexp
toRegexCI (Text -> String
T.unpack Text
s)
parseQueryTerm Day
d (Text -> Text -> Maybe Text
T.stripPrefix Text
"date2:" -> Just Text
s) =
        case Day
-> Text
-> Either (ParseErrorBundle Text CustomErr) (Interval, DateSpan)
parsePeriodExpr Day
d Text
s of Left ParseErrorBundle Text CustomErr
e         -> String -> Either String (Either Query QueryOpt)
forall a b. a -> Either a b
Left (String -> Either String (Either Query QueryOpt))
-> String -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ String
"\"date2:"String -> ShowS
forall a. [a] -> [a] -> [a]
++Text -> String
T.unpack Text
sString -> ShowS
forall a. [a] -> [a] -> [a]
++String
"\" gave a "String -> ShowS
forall a. [a] -> [a] -> [a]
++ParseErrorBundle Text CustomErr -> String
forall t e.
(Show t, Show (Token t), Show e) =>
ParseErrorBundle t e -> String
showDateParseError ParseErrorBundle Text CustomErr
e
                                    Right (Interval
_,DateSpan
span) -> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
Date2 DateSpan
span
parseQueryTerm Day
d (Text -> Text -> Maybe Text
T.stripPrefix Text
"date:" -> Just Text
s) =
        case Day
-> Text
-> Either (ParseErrorBundle Text CustomErr) (Interval, DateSpan)
parsePeriodExpr Day
d Text
s of Left ParseErrorBundle Text CustomErr
e         -> String -> Either String (Either Query QueryOpt)
forall a b. a -> Either a b
Left (String -> Either String (Either Query QueryOpt))
-> String -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ String
"\"date:"String -> ShowS
forall a. [a] -> [a] -> [a]
++Text -> String
T.unpack Text
sString -> ShowS
forall a. [a] -> [a] -> [a]
++String
"\" gave a "String -> ShowS
forall a. [a] -> [a] -> [a]
++ParseErrorBundle Text CustomErr -> String
forall t e.
(Show t, Show (Token t), Show e) =>
ParseErrorBundle t e -> String
showDateParseError ParseErrorBundle Text CustomErr
e
                                    Right (Interval
_,DateSpan
span) -> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
Date DateSpan
span
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"status:" -> Just Text
s) =
        case Text -> Either String Status
parseStatus Text
s of Left String
e   -> String -> Either String (Either Query QueryOpt)
forall a b. a -> Either a b
Left (String -> Either String (Either Query QueryOpt))
-> String -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ String
"\"status:"String -> ShowS
forall a. [a] -> [a] -> [a]
++Text -> String
T.unpack Text
sString -> ShowS
forall a. [a] -> [a] -> [a]
++String
"\" gave a parse error: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
e
                              Right Status
st -> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
st
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"real:" -> Just Text
s) = Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Bool -> Query
Real (Bool -> Query) -> Bool -> Query
forall a b. (a -> b) -> a -> b
$ Text -> Bool
parseBool Text
s Bool -> Bool -> Bool
|| Text -> Bool
T.null Text
s
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"amt:" -> Just Text
s) = Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ OrdPlus -> Quantity -> Query
Amt OrdPlus
ord Quantity
q where (OrdPlus
ord, Quantity
q) = (String -> (OrdPlus, Quantity))
-> ((OrdPlus, Quantity) -> (OrdPlus, Quantity))
-> Either String (OrdPlus, Quantity)
-> (OrdPlus, Quantity)
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> (OrdPlus, Quantity)
forall a. HasCallStack => String -> a
error (OrdPlus, Quantity) -> (OrdPlus, Quantity)
forall a. a -> a
id (Either String (OrdPlus, Quantity) -> (OrdPlus, Quantity))
-> Either String (OrdPlus, Quantity) -> (OrdPlus, Quantity)
forall a b. (a -> b) -> a -> b
$ Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
s  -- PARTIAL:
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"depth:" -> Just Text
s)
  | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0    = Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Int -> Query
Depth Int
n
  | Bool
otherwise = String -> Either String (Either Query QueryOpt)
forall a b. a -> Either a b
Left String
"depth: should have a positive number"
  where n :: Int
n = Int -> String -> Int
forall a. Read a => a -> String -> a
readDef Int
0 (Text -> String
T.unpack Text
s)

parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"cur:" -> Just Text
s) = Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> (Regexp -> Query) -> Regexp -> Either Query QueryOpt
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> Query
Sym (Regexp -> Either Query QueryOpt)
-> Either String Regexp -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Either String Regexp
toRegexCI (Char
'^' Char -> ShowS
forall a. a -> [a] -> [a]
: Text -> String
T.unpack Text
s String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"$") -- support cur: as an alias
parseQueryTerm Day
_ (Text -> Text -> Maybe Text
T.stripPrefix Text
"tag:" -> Just Text
s) = Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> Either String Query -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Either String Query
parseTag Text
s
parseQueryTerm Day
_ Text
"" = Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Either Query QueryOpt -> Either String (Either Query QueryOpt))
-> Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Query
Any
parseQueryTerm Day
d Text
s = Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
d (Text -> Either String (Either Query QueryOpt))
-> Text -> Either String (Either Query QueryOpt)
forall a b. (a -> b) -> a -> b
$ Text
defaultprefixText -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>Text
":"Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>Text
s

-- | Parse the argument of an amt query term ([OP][SIGN]NUM), to an
-- OrdPlus and a Quantity, or if parsing fails, an error message. OP
-- can be <=, <, >=, >, or = . NUM can be a simple integer or decimal.
-- If a decimal, the decimal mark must be period, and it must have
-- digits preceding it. Digit group marks are not allowed.
parseAmountQueryTerm :: T.Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm :: Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
amtarg =
  case Text
amtarg of
    -- number has a + sign, do a signed comparison
    (Text -> Text -> Maybe Quantity
parse Text
"<=+" -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
LtEq    ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"<+"  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Lt      ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
">=+" -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
GtEq    ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
">+"  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Gt      ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"=+"  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Eq      ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"+"   -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Eq      ,Quantity
q)
    -- number has a - sign, do a signed comparison
    (Text -> Text -> Maybe Quantity
parse Text
"<-"  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Lt      ,-Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"<=-" -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
LtEq    ,-Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
">-"  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Gt      ,-Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
">=-" -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
GtEq    ,-Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"=-"  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Eq      ,-Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"-"   -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Eq      ,-Quantity
q)
    -- number is unsigned and zero, do a signed comparison (more useful)
    (Text -> Text -> Maybe Quantity
parse Text
"<="  -> Just Quantity
0) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
LtEq    ,Quantity
0)
    (Text -> Text -> Maybe Quantity
parse Text
"<"   -> Just Quantity
0) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Lt      ,Quantity
0)
    (Text -> Text -> Maybe Quantity
parse Text
">="  -> Just Quantity
0) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
GtEq    ,Quantity
0)
    (Text -> Text -> Maybe Quantity
parse Text
">"   -> Just Quantity
0) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Gt      ,Quantity
0)
    -- number is unsigned and non-zero, do an absolute magnitude comparison
    (Text -> Text -> Maybe Quantity
parse Text
"<="  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsLtEq ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"<"   -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsLt   ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
">="  -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsGtEq ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
">"   -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsGt   ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
"="   -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsEq   ,Quantity
q)
    (Text -> Text -> Maybe Quantity
parse Text
""    -> Just Quantity
q) -> (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsEq   ,Quantity
q)
    Text
_ -> String -> Either String (OrdPlus, Quantity)
forall a b. a -> Either a b
Left (String -> Either String (OrdPlus, Quantity))
-> String -> Either String (OrdPlus, Quantity)
forall a b. (a -> b) -> a -> b
$
         String
"could not parse as a comparison operator followed by an optionally-signed number: "
         String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack Text
amtarg
  where
    -- Strip outer whitespace from the text, require and remove the
    -- specified prefix, remove all whitespace from the remainder, and
    -- read it as a simple integer or decimal if possible.
    parse :: T.Text -> T.Text -> Maybe Quantity
    parse :: Text -> Text -> Maybe Quantity
parse Text
p Text
s = (Text -> Text -> Maybe Text
T.stripPrefix Text
p (Text -> Maybe Text) -> (Text -> Text) -> Text -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
T.strip) Text
s Maybe Text -> (Text -> Maybe Quantity) -> Maybe Quantity
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> Maybe Quantity
forall a. Read a => String -> Maybe a
readMay (String -> Maybe Quantity)
-> (Text -> String) -> Text -> Maybe Quantity
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not(Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.(Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
==Char
' ')) ShowS -> (Text -> String) -> Text -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack

parseTag :: T.Text -> Either RegexError Query
parseTag :: Text -> Either String Query
parseTag Text
s = do
    Regexp
tag <- String -> Either String Regexp
toRegexCI (String -> Either String Regexp)
-> (Text -> String) -> Text -> Either String Regexp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> Either String Regexp) -> Text -> Either String Regexp
forall a b. (a -> b) -> a -> b
$ if Text -> Bool
T.null Text
v then Text
s else Text
n
    Maybe Regexp
body <- if Text -> Bool
T.null Text
v then Maybe Regexp -> Either String (Maybe Regexp)
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe Regexp
forall a. Maybe a
Nothing else Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp)
-> Either String Regexp -> Either String (Maybe Regexp)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Either String Regexp
toRegexCI (ShowS
forall a. [a] -> [a]
tail ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
v)
    Query -> Either String Query
forall (m :: * -> *) a. Monad m => a -> m a
return (Query -> Either String Query) -> Query -> Either String Query
forall a b. (a -> b) -> a -> b
$ Regexp -> Maybe Regexp -> Query
Tag Regexp
tag Maybe Regexp
body
  where (Text
n,Text
v) = (Char -> Bool) -> Text -> (Text, Text)
T.break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
==Char
'=') Text
s

-- | Parse the value part of a "status:" query, or return an error.
parseStatus :: T.Text -> Either String Status
parseStatus :: Text -> Either String Status
parseStatus Text
s | Text
s Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text
"*",Text
"1"] = Status -> Either String Status
forall a b. b -> Either a b
Right Status
Cleared
              | Text
s Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text
"!"]     = Status -> Either String Status
forall a b. b -> Either a b
Right Status
Pending
              | Text
s Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text
"",Text
"0"]  = Status -> Either String Status
forall a b. b -> Either a b
Right Status
Unmarked
              | Bool
otherwise          = String -> Either String Status
forall a b. a -> Either a b
Left (String -> Either String Status) -> String -> Either String Status
forall a b. (a -> b) -> a -> b
$ String
"could not parse "String -> ShowS
forall a. [a] -> [a] -> [a]
++Text -> String
forall a. Show a => a -> String
show Text
sString -> ShowS
forall a. [a] -> [a] -> [a]
++String
" as a status (should be *, ! or empty)"

-- | Parse the boolean value part of a "status:" query. "1" means true,
-- anything else will be parsed as false without error.
parseBool :: T.Text -> Bool
parseBool :: Text -> Bool
parseBool Text
s = Text
s Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
truestrings

truestrings :: [T.Text]
truestrings :: [Text]
truestrings = [Text
"1"]

simplifyQuery :: Query -> Query
simplifyQuery :: Query -> Query
simplifyQuery Query
q =
  let q' :: Query
q' = Query -> Query
simplify Query
q
  in if Query
q' Query -> Query -> Bool
forall a. Eq a => a -> a -> Bool
== Query
q then Query
q else Query -> Query
simplifyQuery Query
q'
  where
    simplify :: Query -> Query
simplify (And []) = Query
Any
    simplify (And [Query
q]) = Query -> Query
simplify Query
q
    simplify (And [Query]
qs) | [Query] -> Bool
forall a. Eq a => [a] -> Bool
same [Query]
qs = Query -> Query
simplify (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
forall a. [a] -> a
head [Query]
qs
                      | (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> Query -> Bool
forall a. Eq a => a -> a -> Bool
==Query
None) [Query]
qs = Query
None
                      | (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Query -> Bool
queryIsDate [Query]
qs = DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> DateSpan
spansIntersect ([DateSpan] -> DateSpan) -> [DateSpan] -> DateSpan
forall a b. (a -> b) -> a -> b
$ (Query -> Maybe DateSpan) -> [Query] -> [DateSpan]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Query -> Maybe DateSpan
queryTermDateSpan [Query]
qs
                      | Bool
otherwise = [Query] -> Query
And ([Query] -> Query) -> [Query] -> Query
forall a b. (a -> b) -> a -> b
$ [[Query]] -> [Query]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[Query]] -> [Query]) -> [[Query]] -> [Query]
forall a b. (a -> b) -> a -> b
$ [(Query -> Query) -> [Query] -> [Query]
forall a b. (a -> b) -> [a] -> [b]
map Query -> Query
simplify [Query]
dateqs, (Query -> Query) -> [Query] -> [Query]
forall a b. (a -> b) -> [a] -> [b]
map Query -> Query
simplify [Query]
otherqs]
                      where ([Query]
dateqs, [Query]
otherqs) = (Query -> Bool) -> [Query] -> ([Query], [Query])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Query -> Bool
queryIsDate ([Query] -> ([Query], [Query])) -> [Query] -> ([Query], [Query])
forall a b. (a -> b) -> a -> b
$ (Query -> Bool) -> [Query] -> [Query]
forall a. (a -> Bool) -> [a] -> [a]
filter (Query -> Query -> Bool
forall a. Eq a => a -> a -> Bool
/=Query
Any) [Query]
qs
    simplify (Or []) = Query
Any
    simplify (Or [Query
q]) = Query -> Query
simplifyQuery Query
q
    simplify (Or [Query]
qs) | [Query] -> Bool
forall a. Eq a => [a] -> Bool
same [Query]
qs = Query -> Query
simplify (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
forall a. [a] -> a
head [Query]
qs
                     | (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> Query -> Bool
forall a. Eq a => a -> a -> Bool
==Query
Any) [Query]
qs = Query
Any
                     -- all queryIsDate qs = Date $ spansUnion $ mapMaybe queryTermDateSpan qs  ?
                     | Bool
otherwise = [Query] -> Query
Or ([Query] -> Query) -> [Query] -> Query
forall a b. (a -> b) -> a -> b
$ (Query -> Query) -> [Query] -> [Query]
forall a b. (a -> b) -> [a] -> [b]
map Query -> Query
simplify ([Query] -> [Query]) -> [Query] -> [Query]
forall a b. (a -> b) -> a -> b
$ (Query -> Bool) -> [Query] -> [Query]
forall a. (a -> Bool) -> [a] -> [a]
filter (Query -> Query -> Bool
forall a. Eq a => a -> a -> Bool
/=Query
None) [Query]
qs
    simplify (Date (DateSpan Maybe Day
Nothing Maybe Day
Nothing)) = Query
Any
    simplify (Date2 (DateSpan Maybe Day
Nothing Maybe Day
Nothing)) = Query
Any
    simplify Query
q = Query
q

same :: [a] -> Bool
same [] = Bool
True
same (a
a:[a]
as) = (a -> Bool) -> [a] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (a
aa -> a -> Bool
forall a. Eq a => a -> a -> Bool
==) [a]
as

-- | Remove query terms (or whole sub-expressions) not matching the given
-- predicate from this query.  XXX Semantics not completely clear.
filterQuery :: (Query -> Bool) -> Query -> Query
filterQuery :: (Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
p = Query -> Query
simplifyQuery (Query -> Query) -> (Query -> Query) -> Query -> Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Query -> Bool) -> Query -> Query
filterQuery' Query -> Bool
p

filterQuery' :: (Query -> Bool) -> Query -> Query
filterQuery' :: (Query -> Bool) -> Query -> Query
filterQuery' Query -> Bool
p (And [Query]
qs) = [Query] -> Query
And ([Query] -> Query) -> [Query] -> Query
forall a b. (a -> b) -> a -> b
$ (Query -> Query) -> [Query] -> [Query]
forall a b. (a -> b) -> [a] -> [b]
map ((Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
p) [Query]
qs
filterQuery' Query -> Bool
p (Or [Query]
qs) = [Query] -> Query
Or ([Query] -> Query) -> [Query] -> Query
forall a b. (a -> b) -> a -> b
$ (Query -> Query) -> [Query] -> [Query]
forall a b. (a -> b) -> [a] -> [b]
map ((Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
p) [Query]
qs
-- filterQuery' p (Not q) = Not $ filterQuery p q
filterQuery' Query -> Bool
p Query
q = if Query -> Bool
p Query
q then Query
q else Query
Any

-- * accessors

-- | Does this query match everything ?
queryIsNull :: Query -> Bool
queryIsNull :: Query -> Bool
queryIsNull Query
Any = Bool
True
queryIsNull (And []) = Bool
True
queryIsNull (Not (Or [])) = Bool
True
queryIsNull Query
_ = Bool
False

queryIsDepth :: Query -> Bool
queryIsDepth :: Query -> Bool
queryIsDepth (Depth Int
_) = Bool
True
queryIsDepth Query
_ = Bool
False

queryIsDate :: Query -> Bool
queryIsDate :: Query -> Bool
queryIsDate (Date DateSpan
_) = Bool
True
queryIsDate Query
_ = Bool
False

queryIsDate2 :: Query -> Bool
queryIsDate2 :: Query -> Bool
queryIsDate2 (Date2 DateSpan
_) = Bool
True
queryIsDate2 Query
_ = Bool
False

queryIsDateOrDate2 :: Query -> Bool
queryIsDateOrDate2 :: Query -> Bool
queryIsDateOrDate2 (Date DateSpan
_) = Bool
True
queryIsDateOrDate2 (Date2 DateSpan
_) = Bool
True
queryIsDateOrDate2 Query
_ = Bool
False

queryIsDesc :: Query -> Bool
queryIsDesc :: Query -> Bool
queryIsDesc (Desc Regexp
_) = Bool
True
queryIsDesc Query
_ = Bool
False

queryIsAcct :: Query -> Bool
queryIsAcct :: Query -> Bool
queryIsAcct (Acct Regexp
_) = Bool
True
queryIsAcct Query
_ = Bool
False

queryIsAmt :: Query -> Bool
queryIsAmt :: Query -> Bool
queryIsAmt (Amt OrdPlus
_ Quantity
_) = Bool
True
queryIsAmt Query
_         = Bool
False

queryIsSym :: Query -> Bool
queryIsSym :: Query -> Bool
queryIsSym (Sym Regexp
_) = Bool
True
queryIsSym Query
_ = Bool
False

queryIsReal :: Query -> Bool
queryIsReal :: Query -> Bool
queryIsReal (Real Bool
_) = Bool
True
queryIsReal Query
_ = Bool
False

queryIsStatus :: Query -> Bool
queryIsStatus :: Query -> Bool
queryIsStatus (StatusQ Status
_) = Bool
True
queryIsStatus Query
_ = Bool
False

-- | Does this query specify a start date and nothing else (that would
-- filter postings prior to the date) ?
-- When the flag is true, look for a starting secondary date instead.
queryIsStartDateOnly :: Bool -> Query -> Bool
queryIsStartDateOnly :: Bool -> Query -> Bool
queryIsStartDateOnly Bool
_ Query
Any = Bool
False
queryIsStartDateOnly Bool
_ Query
None = Bool
False
queryIsStartDateOnly Bool
secondary (Or [Query]
ms) = [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
and ([Bool] -> Bool) -> [Bool] -> Bool
forall a b. (a -> b) -> a -> b
$ (Query -> Bool) -> [Query] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> Bool
queryIsStartDateOnly Bool
secondary) [Query]
ms
queryIsStartDateOnly Bool
secondary (And [Query]
ms) = [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
and ([Bool] -> Bool) -> [Bool] -> Bool
forall a b. (a -> b) -> a -> b
$ (Query -> Bool) -> [Query] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> Bool
queryIsStartDateOnly Bool
secondary) [Query]
ms
queryIsStartDateOnly Bool
False (Date (DateSpan (Just Day
_) Maybe Day
_)) = Bool
True
queryIsStartDateOnly Bool
True (Date2 (DateSpan (Just Day
_) Maybe Day
_)) = Bool
True
queryIsStartDateOnly Bool
_ Query
_ = Bool
False

-- | What start date (or secondary date) does this query specify, if any ?
-- For OR expressions, use the earliest of the dates. NOT is ignored.
queryStartDate :: Bool -> Query -> Maybe Day
queryStartDate :: Bool -> Query -> Maybe Day
queryStartDate Bool
secondary (Or [Query]
ms) = [Maybe Day] -> Maybe Day
earliestMaybeDate ([Maybe Day] -> Maybe Day) -> [Maybe Day] -> Maybe Day
forall a b. (a -> b) -> a -> b
$ (Query -> Maybe Day) -> [Query] -> [Maybe Day]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> Maybe Day
queryStartDate Bool
secondary) [Query]
ms
queryStartDate Bool
secondary (And [Query]
ms) = [Maybe Day] -> Maybe Day
latestMaybeDate ([Maybe Day] -> Maybe Day) -> [Maybe Day] -> Maybe Day
forall a b. (a -> b) -> a -> b
$ (Query -> Maybe Day) -> [Query] -> [Maybe Day]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> Maybe Day
queryStartDate Bool
secondary) [Query]
ms
queryStartDate Bool
False (Date (DateSpan (Just Day
d) Maybe Day
_)) = Day -> Maybe Day
forall a. a -> Maybe a
Just Day
d
queryStartDate Bool
True (Date2 (DateSpan (Just Day
d) Maybe Day
_)) = Day -> Maybe Day
forall a. a -> Maybe a
Just Day
d
queryStartDate Bool
_ Query
_ = Maybe Day
forall a. Maybe a
Nothing

-- | What end date (or secondary date) does this query specify, if any ?
-- For OR expressions, use the latest of the dates. NOT is ignored.
queryEndDate :: Bool -> Query -> Maybe Day
queryEndDate :: Bool -> Query -> Maybe Day
queryEndDate Bool
secondary (Or [Query]
ms) = [Maybe Day] -> Maybe Day
latestMaybeDate' ([Maybe Day] -> Maybe Day) -> [Maybe Day] -> Maybe Day
forall a b. (a -> b) -> a -> b
$ (Query -> Maybe Day) -> [Query] -> [Maybe Day]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> Maybe Day
queryEndDate Bool
secondary) [Query]
ms
queryEndDate Bool
secondary (And [Query]
ms) = [Maybe Day] -> Maybe Day
earliestMaybeDate' ([Maybe Day] -> Maybe Day) -> [Maybe Day] -> Maybe Day
forall a b. (a -> b) -> a -> b
$ (Query -> Maybe Day) -> [Query] -> [Maybe Day]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> Maybe Day
queryEndDate Bool
secondary) [Query]
ms
queryEndDate Bool
False (Date (DateSpan Maybe Day
_ (Just Day
d))) = Day -> Maybe Day
forall a. a -> Maybe a
Just Day
d
queryEndDate Bool
True (Date2 (DateSpan Maybe Day
_ (Just Day
d))) = Day -> Maybe Day
forall a. a -> Maybe a
Just Day
d
queryEndDate Bool
_ Query
_ = Maybe Day
forall a. Maybe a
Nothing

queryTermDateSpan :: Query -> Maybe DateSpan
queryTermDateSpan (Date DateSpan
span) = DateSpan -> Maybe DateSpan
forall a. a -> Maybe a
Just DateSpan
span
queryTermDateSpan Query
_ = Maybe DateSpan
forall a. Maybe a
Nothing

-- | What date span (or with a true argument, what secondary date span) does this query specify ?
-- OR clauses specifying multiple spans return their union (the span enclosing all of them).
-- AND clauses specifying multiple spans return their intersection.
-- NOT clauses are ignored.
queryDateSpan :: Bool -> Query -> DateSpan
queryDateSpan :: Bool -> Query -> DateSpan
queryDateSpan Bool
secondary (Or [Query]
qs)  = [DateSpan] -> DateSpan
spansUnion     ([DateSpan] -> DateSpan) -> [DateSpan] -> DateSpan
forall a b. (a -> b) -> a -> b
$ (Query -> DateSpan) -> [Query] -> [DateSpan]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> DateSpan
queryDateSpan Bool
secondary) [Query]
qs
queryDateSpan Bool
secondary (And [Query]
qs) = [DateSpan] -> DateSpan
spansIntersect ([DateSpan] -> DateSpan) -> [DateSpan] -> DateSpan
forall a b. (a -> b) -> a -> b
$ (Query -> DateSpan) -> [Query] -> [DateSpan]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> Query -> DateSpan
queryDateSpan Bool
secondary) [Query]
qs
queryDateSpan Bool
False (Date DateSpan
span)  = DateSpan
span
queryDateSpan Bool
True (Date2 DateSpan
span)  = DateSpan
span
queryDateSpan Bool
_ Query
_                = DateSpan
nulldatespan

-- | What date span does this query specify, treating primary and secondary dates as equivalent ?
-- OR clauses specifying multiple spans return their union (the span enclosing all of them).
-- AND clauses specifying multiple spans return their intersection.
-- NOT clauses are ignored.
queryDateSpan' :: Query -> DateSpan
queryDateSpan' :: Query -> DateSpan
queryDateSpan' (Or [Query]
qs)      = [DateSpan] -> DateSpan
spansUnion     ([DateSpan] -> DateSpan) -> [DateSpan] -> DateSpan
forall a b. (a -> b) -> a -> b
$ (Query -> DateSpan) -> [Query] -> [DateSpan]
forall a b. (a -> b) -> [a] -> [b]
map Query -> DateSpan
queryDateSpan' [Query]
qs
queryDateSpan' (And [Query]
qs)     = [DateSpan] -> DateSpan
spansIntersect ([DateSpan] -> DateSpan) -> [DateSpan] -> DateSpan
forall a b. (a -> b) -> a -> b
$ (Query -> DateSpan) -> [Query] -> [DateSpan]
forall a b. (a -> b) -> [a] -> [b]
map Query -> DateSpan
queryDateSpan' [Query]
qs
queryDateSpan' (Date DateSpan
span)  = DateSpan
span
queryDateSpan' (Date2 DateSpan
span) = DateSpan
span
queryDateSpan' Query
_            = DateSpan
nulldatespan

-- | What is the earliest of these dates, where Nothing is earliest ?
earliestMaybeDate :: [Maybe Day] -> Maybe Day
earliestMaybeDate :: [Maybe Day] -> Maybe Day
earliestMaybeDate = Maybe Day -> Maybe (Maybe Day) -> Maybe Day
forall a. a -> Maybe a -> a
fromMaybe Maybe Day
forall a. Maybe a
Nothing (Maybe (Maybe Day) -> Maybe Day)
-> ([Maybe Day] -> Maybe (Maybe Day)) -> [Maybe Day] -> Maybe Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Maybe Day] -> Maybe (Maybe Day)
forall a. Ord a => [a] -> Maybe a
minimumMay

-- | What is the latest of these dates, where Nothing is earliest ?
latestMaybeDate :: [Maybe Day] -> Maybe Day
latestMaybeDate :: [Maybe Day] -> Maybe Day
latestMaybeDate = Maybe Day -> Maybe (Maybe Day) -> Maybe Day
forall a. a -> Maybe a -> a
fromMaybe Maybe Day
forall a. Maybe a
Nothing (Maybe (Maybe Day) -> Maybe Day)
-> ([Maybe Day] -> Maybe (Maybe Day)) -> [Maybe Day] -> Maybe Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Maybe Day] -> Maybe (Maybe Day)
forall a. Ord a => [a] -> Maybe a
maximumMay

-- | What is the earliest of these dates, where Nothing is the latest ?
earliestMaybeDate' :: [Maybe Day] -> Maybe Day
earliestMaybeDate' :: [Maybe Day] -> Maybe Day
earliestMaybeDate' = Maybe Day -> Maybe (Maybe Day) -> Maybe Day
forall a. a -> Maybe a -> a
fromMaybe Maybe Day
forall a. Maybe a
Nothing (Maybe (Maybe Day) -> Maybe Day)
-> ([Maybe Day] -> Maybe (Maybe Day)) -> [Maybe Day] -> Maybe Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Maybe Day] -> Maybe (Maybe Day)
forall a. Ord a => [a] -> Maybe a
minimumMay ([Maybe Day] -> Maybe (Maybe Day))
-> ([Maybe Day] -> [Maybe Day]) -> [Maybe Day] -> Maybe (Maybe Day)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe Day -> Bool) -> [Maybe Day] -> [Maybe Day]
forall a. (a -> Bool) -> [a] -> [a]
filter Maybe Day -> Bool
forall a. Maybe a -> Bool
isJust

-- | What is the latest of these dates, where Nothing is the latest ?
latestMaybeDate' :: [Maybe Day] -> Maybe Day
latestMaybeDate' :: [Maybe Day] -> Maybe Day
latestMaybeDate' = Maybe Day -> Maybe (Maybe Day) -> Maybe Day
forall a. a -> Maybe a -> a
fromMaybe Maybe Day
forall a. Maybe a
Nothing (Maybe (Maybe Day) -> Maybe Day)
-> ([Maybe Day] -> Maybe (Maybe Day)) -> [Maybe Day] -> Maybe Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe Day -> Maybe Day -> Ordering)
-> [Maybe Day] -> Maybe (Maybe Day)
forall a. (a -> a -> Ordering) -> [a] -> Maybe a
maximumByMay Maybe Day -> Maybe Day -> Ordering
forall a. Ord a => Maybe a -> Maybe a -> Ordering
compareNothingMax
  where
    compareNothingMax :: Maybe a -> Maybe a -> Ordering
compareNothingMax Maybe a
Nothing  Maybe a
Nothing  = Ordering
EQ
    compareNothingMax (Just a
_) Maybe a
Nothing  = Ordering
LT
    compareNothingMax Maybe a
Nothing  (Just a
_) = Ordering
GT
    compareNothingMax (Just a
a) (Just a
b) = a -> a -> Ordering
forall a. Ord a => a -> a -> Ordering
compare a
a a
b

-- | The depth limit this query specifies, if it has one
queryDepth :: Query -> Maybe Int
queryDepth :: Query -> Maybe Int
queryDepth = [Int] -> Maybe Int
forall a. Ord a => [a] -> Maybe a
minimumMay ([Int] -> Maybe Int) -> (Query -> [Int]) -> Query -> Maybe Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query -> [Int]
queryDepth'
  where
    queryDepth' :: Query -> [Int]
queryDepth' (Depth Int
d) = [Int
d]
    queryDepth' (Or [Query]
qs)   = (Query -> [Int]) -> [Query] -> [Int]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Query -> [Int]
queryDepth' [Query]
qs
    queryDepth' (And [Query]
qs)  = (Query -> [Int]) -> [Query] -> [Int]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Query -> [Int]
queryDepth' [Query]
qs
    queryDepth' Query
_         = []

-- | The account we are currently focussed on, if any, and whether subaccounts are included.
-- Just looks at the first query option.
inAccount :: [QueryOpt] -> Maybe (AccountName,Bool)
inAccount :: [QueryOpt] -> Maybe (Text, Bool)
inAccount [] = Maybe (Text, Bool)
forall a. Maybe a
Nothing
inAccount (QueryOptInAcctOnly Text
a:[QueryOpt]
_) = (Text, Bool) -> Maybe (Text, Bool)
forall a. a -> Maybe a
Just (Text
a,Bool
False)
inAccount (QueryOptInAcct Text
a:[QueryOpt]
_) = (Text, Bool) -> Maybe (Text, Bool)
forall a. a -> Maybe a
Just (Text
a,Bool
True)

-- | A query for the account(s) we are currently focussed on, if any.
-- Just looks at the first query option.
inAccountQuery :: [QueryOpt] -> Maybe Query
inAccountQuery :: [QueryOpt] -> Maybe Query
inAccountQuery [] = Maybe Query
forall a. Maybe a
Nothing
inAccountQuery (QueryOptInAcctOnly Text
a : [QueryOpt]
_) = Query -> Maybe Query
forall a. a -> Maybe a
Just (Query -> Maybe Query)
-> (Regexp -> Query) -> Regexp -> Maybe Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> Query
Acct (Regexp -> Maybe Query) -> Regexp -> Maybe Query
forall a b. (a -> b) -> a -> b
$ Text -> Regexp
accountNameToAccountOnlyRegex Text
a
inAccountQuery (QueryOptInAcct Text
a     : [QueryOpt]
_) = Query -> Maybe Query
forall a. a -> Maybe a
Just (Query -> Maybe Query)
-> (Regexp -> Query) -> Regexp -> Maybe Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> Query
Acct (Regexp -> Maybe Query) -> Regexp -> Maybe Query
forall a b. (a -> b) -> a -> b
$ Text -> Regexp
accountNameToAccountRegex Text
a

-- -- | Convert a query to its inverse.
-- negateQuery :: Query -> Query
-- negateQuery =  Not

-- matching

-- | Does the match expression match this account ?
-- A matching in: clause is also considered a match.
-- When matching by account name pattern, if there's a regular
-- expression error, this function calls error.
matchesAccount :: Query -> AccountName -> Bool
matchesAccount :: Query -> Text -> Bool
matchesAccount (Query
None) Text
_ = Bool
False
matchesAccount (Not Query
m) Text
a = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Query -> Text -> Bool
matchesAccount Query
m Text
a
matchesAccount (Or [Query]
ms) Text
a = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> Text -> Bool
`matchesAccount` Text
a) [Query]
ms
matchesAccount (And [Query]
ms) Text
a = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Query -> Text -> Bool
`matchesAccount` Text
a) [Query]
ms
matchesAccount (Acct Regexp
r) Text
a = Regexp -> String -> Bool
regexMatch Regexp
r (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
a -- XXX pack
matchesAccount (Depth Int
d) Text
a = Text -> Int
accountNameLevel Text
a Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
d
matchesAccount (Tag Regexp
_ Maybe Regexp
_) Text
_ = Bool
False
matchesAccount Query
_ Text
_ = Bool
True

matchesMixedAmount :: Query -> MixedAmount -> Bool
matchesMixedAmount :: Query -> MixedAmount -> Bool
matchesMixedAmount Query
q (Mixed []) = Query
q Query -> Amount -> Bool
`matchesAmount` Amount
nullamt
matchesMixedAmount Query
q (Mixed [Amount]
as) = (Amount -> Bool) -> [Amount] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query
q Query -> Amount -> Bool
`matchesAmount`) [Amount]
as

matchesCommodity :: Query -> CommoditySymbol -> Bool
matchesCommodity :: Query -> Text -> Bool
matchesCommodity (Sym Regexp
r) = Regexp -> String -> Bool
regexMatch Regexp
r (String -> Bool) -> (Text -> String) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack
matchesCommodity Query
_ = Bool -> Text -> Bool
forall a b. a -> b -> a
const Bool
True

-- | Does the match expression match this (simple) amount ?
matchesAmount :: Query -> Amount -> Bool
matchesAmount :: Query -> Amount -> Bool
matchesAmount (Not Query
q) Amount
a = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Query
q Query -> Amount -> Bool
`matchesAmount` Amount
a
matchesAmount (Query
Any) Amount
_ = Bool
True
matchesAmount (Query
None) Amount
_ = Bool
False
matchesAmount (Or [Query]
qs) Amount
a = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> Amount -> Bool
`matchesAmount` Amount
a) [Query]
qs
matchesAmount (And [Query]
qs) Amount
a = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Query -> Amount -> Bool
`matchesAmount` Amount
a) [Query]
qs
matchesAmount (Amt OrdPlus
ord Quantity
n) Amount
a = OrdPlus -> Quantity -> Amount -> Bool
compareAmount OrdPlus
ord Quantity
n Amount
a
matchesAmount (Sym Regexp
r) Amount
a = Query -> Text -> Bool
matchesCommodity (Regexp -> Query
Sym Regexp
r) (Amount -> Text
acommodity Amount
a)
matchesAmount Query
_ Amount
_ = Bool
True

-- | Is this simple (single-amount) mixed amount's quantity less than, greater than, equal to, or unsignedly equal to this number ?
-- For multi-amount (multiple commodities, or just unsimplified) mixed amounts this is always true.

-- | Is this amount's quantity less than, greater than, equal to, or unsignedly equal to this number ?
compareAmount :: OrdPlus -> Quantity -> Amount -> Bool
compareAmount :: OrdPlus -> Quantity -> Amount -> Bool
compareAmount OrdPlus
ord Quantity
q Amount{aquantity :: Amount -> Quantity
aquantity=Quantity
aq} = case OrdPlus
ord of OrdPlus
Lt      -> Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
<  Quantity
q
                                                       OrdPlus
LtEq    -> Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
<= Quantity
q
                                                       OrdPlus
Gt      -> Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
>  Quantity
q
                                                       OrdPlus
GtEq    -> Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
>= Quantity
q
                                                       OrdPlus
Eq      -> Quantity
aq Quantity -> Quantity -> Bool
forall a. Eq a => a -> a -> Bool
== Quantity
q
                                                       OrdPlus
AbsLt   -> Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
<  Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
q
                                                       OrdPlus
AbsLtEq -> Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
<= Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
q
                                                       OrdPlus
AbsGt   -> Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
>  Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
q
                                                       OrdPlus
AbsGtEq -> Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
aq Quantity -> Quantity -> Bool
forall a. Ord a => a -> a -> Bool
>= Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
q
                                                       OrdPlus
AbsEq   -> Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
aq Quantity -> Quantity -> Bool
forall a. Eq a => a -> a -> Bool
== Quantity -> Quantity
forall a. Num a => a -> a
abs Quantity
q

-- | Does the match expression match this posting ?
--
-- Note that for account match we try both original and effective account
matchesPosting :: Query -> Posting -> Bool
matchesPosting :: Query -> Posting -> Bool
matchesPosting (Not Query
q) Posting
p = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Query
q Query -> Posting -> Bool
`matchesPosting` Posting
p
matchesPosting (Query
Any) Posting
_ = Bool
True
matchesPosting (Query
None) Posting
_ = Bool
False
matchesPosting (Or [Query]
qs) Posting
p = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> Posting -> Bool
`matchesPosting` Posting
p) [Query]
qs
matchesPosting (And [Query]
qs) Posting
p = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Query -> Posting -> Bool
`matchesPosting` Posting
p) [Query]
qs
matchesPosting (Code Regexp
r) Posting
p = Regexp -> String -> Bool
regexMatch Regexp
r (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ String -> (Transaction -> String) -> Maybe Transaction -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" (Text -> String
T.unpack (Text -> String) -> (Transaction -> Text) -> Transaction -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
tcode) (Maybe Transaction -> String) -> Maybe Transaction -> String
forall a b. (a -> b) -> a -> b
$ Posting -> Maybe Transaction
ptransaction Posting
p
matchesPosting (Desc Regexp
r) Posting
p = Regexp -> String -> Bool
regexMatch Regexp
r (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ String -> (Transaction -> String) -> Maybe Transaction -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" (Text -> String
T.unpack (Text -> String) -> (Transaction -> Text) -> Transaction -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
tdescription) (Maybe Transaction -> String) -> Maybe Transaction -> String
forall a b. (a -> b) -> a -> b
$ Posting -> Maybe Transaction
ptransaction Posting
p
matchesPosting (Acct Regexp
r) Posting
p = Posting -> Bool
matches Posting
p Bool -> Bool -> Bool
|| Posting -> Bool
matches (Posting -> Posting
originalPosting Posting
p)
  where matches :: Posting -> Bool
matches Posting
p = Regexp -> String -> Bool
regexMatch Regexp
r (String -> Bool) -> (Text -> String) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> Bool) -> Text -> Bool
forall a b. (a -> b) -> a -> b
$ Posting -> Text
paccount Posting
p -- XXX pack
matchesPosting (Date DateSpan
span) Posting
p = DateSpan
span DateSpan -> Day -> Bool
`spanContainsDate` Posting -> Day
postingDate Posting
p
matchesPosting (Date2 DateSpan
span) Posting
p = DateSpan
span DateSpan -> Day -> Bool
`spanContainsDate` Posting -> Day
postingDate2 Posting
p
matchesPosting (StatusQ Status
s) Posting
p = Posting -> Status
postingStatus Posting
p Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
s
matchesPosting (Real Bool
v) Posting
p = Bool
v Bool -> Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Posting -> Bool
isReal Posting
p
matchesPosting q :: Query
q@(Depth Int
_) Posting{paccount :: Posting -> Text
paccount=Text
a} = Query
q Query -> Text -> Bool
`matchesAccount` Text
a
matchesPosting q :: Query
q@(Amt OrdPlus
_ Quantity
_) Posting{pamount :: Posting -> MixedAmount
pamount=MixedAmount
amt} = Query
q Query -> MixedAmount -> Bool
`matchesMixedAmount` MixedAmount
amt
matchesPosting (Sym Regexp
r) Posting{pamount :: Posting -> MixedAmount
pamount=Mixed [Amount]
as} = (Text -> Bool) -> [Text] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> Text -> Bool
matchesCommodity (Regexp -> Query
Sym Regexp
r)) ([Text] -> Bool) -> [Text] -> Bool
forall a b. (a -> b) -> a -> b
$ (Amount -> Text) -> [Amount] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Amount -> Text
acommodity [Amount]
as
matchesPosting (Tag Regexp
n Maybe Regexp
v) Posting
p = case (Regexp -> String
reString Regexp
n, Maybe Regexp
v) of
  (String
"payee", Just Regexp
v) -> Bool -> (Transaction -> Bool) -> Maybe Transaction -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (Regexp -> String -> Bool
regexMatch Regexp
v (String -> Bool) -> (Transaction -> String) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> (Transaction -> Text) -> Transaction -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
transactionPayee) (Maybe Transaction -> Bool) -> Maybe Transaction -> Bool
forall a b. (a -> b) -> a -> b
$ Posting -> Maybe Transaction
ptransaction Posting
p
  (String
"note", Just Regexp
v) -> Bool -> (Transaction -> Bool) -> Maybe Transaction -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (Regexp -> String -> Bool
regexMatch Regexp
v (String -> Bool) -> (Transaction -> String) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> (Transaction -> Text) -> Transaction -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
transactionNote) (Maybe Transaction -> Bool) -> Maybe Transaction -> Bool
forall a b. (a -> b) -> a -> b
$ Posting -> Maybe Transaction
ptransaction Posting
p
  (String
_, Maybe Regexp
v) -> Regexp -> Maybe Regexp -> [(Text, Text)] -> Bool
matchesTags Regexp
n Maybe Regexp
v ([(Text, Text)] -> Bool) -> [(Text, Text)] -> Bool
forall a b. (a -> b) -> a -> b
$ Posting -> [(Text, Text)]
postingAllTags Posting
p

-- | Does the match expression match this transaction ?
matchesTransaction :: Query -> Transaction -> Bool
matchesTransaction :: Query -> Transaction -> Bool
matchesTransaction (Not Query
q) Transaction
t = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Query
q Query -> Transaction -> Bool
`matchesTransaction` Transaction
t
matchesTransaction (Query
Any) Transaction
_ = Bool
True
matchesTransaction (Query
None) Transaction
_ = Bool
False
matchesTransaction (Or [Query]
qs) Transaction
t = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> Transaction -> Bool
`matchesTransaction` Transaction
t) [Query]
qs
matchesTransaction (And [Query]
qs) Transaction
t = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Query -> Transaction -> Bool
`matchesTransaction` Transaction
t) [Query]
qs
matchesTransaction (Code Regexp
r) Transaction
t = Regexp -> String -> Bool
regexMatch Regexp
r (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
tcode Transaction
t
matchesTransaction (Desc Regexp
r) Transaction
t = Regexp -> String -> Bool
regexMatch Regexp
r (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
tdescription Transaction
t
matchesTransaction q :: Query
q@(Acct Regexp
_) Transaction
t = (Posting -> Bool) -> [Posting] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query
q Query -> Posting -> Bool
`matchesPosting`) ([Posting] -> Bool) -> [Posting] -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
matchesTransaction (Date DateSpan
span) Transaction
t = DateSpan -> Day -> Bool
spanContainsDate DateSpan
span (Day -> Bool) -> Day -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction -> Day
tdate Transaction
t
matchesTransaction (Date2 DateSpan
span) Transaction
t = DateSpan -> Day -> Bool
spanContainsDate DateSpan
span (Day -> Bool) -> Day -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction -> Day
transactionDate2 Transaction
t
matchesTransaction (StatusQ Status
s) Transaction
t = Transaction -> Status
tstatus Transaction
t Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
s
matchesTransaction (Real Bool
v) Transaction
t = Bool
v Bool -> Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Transaction -> Bool
hasRealPostings Transaction
t
matchesTransaction q :: Query
q@(Amt OrdPlus
_ Quantity
_) Transaction
t = (Posting -> Bool) -> [Posting] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query
q Query -> Posting -> Bool
`matchesPosting`) ([Posting] -> Bool) -> [Posting] -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
matchesTransaction (Depth Int
d) Transaction
t = (Posting -> Bool) -> [Posting] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Int -> Query
Depth Int
d Query -> Posting -> Bool
`matchesPosting`) ([Posting] -> Bool) -> [Posting] -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
matchesTransaction q :: Query
q@(Sym Regexp
_) Transaction
t = (Posting -> Bool) -> [Posting] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query
q Query -> Posting -> Bool
`matchesPosting`) ([Posting] -> Bool) -> [Posting] -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
matchesTransaction (Tag Regexp
n Maybe Regexp
v) Transaction
t = case (Regexp -> String
reString Regexp
n, Maybe Regexp
v) of
  (String
"payee", Just Regexp
v) -> Regexp -> String -> Bool
regexMatch Regexp
v (String -> Bool) -> (Transaction -> String) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> (Transaction -> Text) -> Transaction -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
transactionPayee (Transaction -> Bool) -> Transaction -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction
t
  (String
"note", Just Regexp
v) -> Regexp -> String -> Bool
regexMatch Regexp
v (String -> Bool) -> (Transaction -> String) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> (Transaction -> Text) -> Transaction -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
transactionNote (Transaction -> Bool) -> Transaction -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction
t
  (String
_, Maybe Regexp
v) -> Regexp -> Maybe Regexp -> [(Text, Text)] -> Bool
matchesTags Regexp
n Maybe Regexp
v ([(Text, Text)] -> Bool) -> [(Text, Text)] -> Bool
forall a b. (a -> b) -> a -> b
$ Transaction -> [(Text, Text)]
transactionAllTags Transaction
t

-- | Does the query match the name and optionally the value of any of these tags ?
matchesTags :: Regexp -> Maybe Regexp -> [Tag] -> Bool
matchesTags :: Regexp -> Maybe Regexp -> [(Text, Text)] -> Bool
matchesTags Regexp
namepat Maybe Regexp
valuepat = Bool -> Bool
not (Bool -> Bool)
-> ([(Text, Text)] -> Bool) -> [(Text, Text)] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(Text, Text)] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([(Text, Text)] -> Bool)
-> ([(Text, Text)] -> [(Text, Text)]) -> [(Text, Text)] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, Text) -> Bool) -> [(Text, Text)] -> [(Text, Text)]
forall a. (a -> Bool) -> [a] -> [a]
filter (Regexp -> Maybe Regexp -> (Text, Text) -> Bool
matches Regexp
namepat Maybe Regexp
valuepat)
  where
    matches :: Regexp -> Maybe Regexp -> (Text, Text) -> Bool
matches Regexp
npat Maybe Regexp
vpat (Text
n,Text
v) = Regexp -> String -> Bool
regexMatch Regexp
npat (Text -> String
T.unpack Text
n) Bool -> Bool -> Bool
&& (String -> Bool)
-> (Regexp -> String -> Bool) -> Maybe Regexp -> String -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Bool -> String -> Bool
forall a b. a -> b -> a
const Bool
True) Regexp -> String -> Bool
regexMatch Maybe Regexp
vpat (Text -> String
T.unpack Text
v)

-- | Does the query match this market price ?
matchesPriceDirective :: Query -> PriceDirective -> Bool
matchesPriceDirective :: Query -> PriceDirective -> Bool
matchesPriceDirective (Query
None) PriceDirective
_      = Bool
False
matchesPriceDirective (Not Query
q) PriceDirective
p     = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Query -> PriceDirective -> Bool
matchesPriceDirective Query
q PriceDirective
p
matchesPriceDirective (Or [Query]
qs) PriceDirective
p     = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Query -> PriceDirective -> Bool
`matchesPriceDirective` PriceDirective
p) [Query]
qs
matchesPriceDirective (And [Query]
qs) PriceDirective
p    = (Query -> Bool) -> [Query] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Query -> PriceDirective -> Bool
`matchesPriceDirective` PriceDirective
p) [Query]
qs
matchesPriceDirective q :: Query
q@(Amt OrdPlus
_ Quantity
_) PriceDirective
p = Query -> Amount -> Bool
matchesAmount Query
q (PriceDirective -> Amount
pdamount PriceDirective
p)
matchesPriceDirective q :: Query
q@(Sym Regexp
_) PriceDirective
p   = Query -> Text -> Bool
matchesCommodity Query
q (PriceDirective -> Text
pdcommodity PriceDirective
p)
matchesPriceDirective (Date DateSpan
span) PriceDirective
p = DateSpan -> Day -> Bool
spanContainsDate DateSpan
span (PriceDirective -> Day
pddate PriceDirective
p)
matchesPriceDirective Query
_ PriceDirective
_           = Bool
True


-- tests

tests_Query :: TestTree
tests_Query = String -> [TestTree] -> TestTree
tests String
"Query" [
   String -> Assertion -> TestTree
test String
"simplifyQuery" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
Or [Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"a"])      Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"a")
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
Or [Query
Any,Query
None])      Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query
Any)
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [Query
Any,Query
None])     Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query
None)
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [Query
Any,Query
Any])      Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query
Any)
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"b",Query
Any]) Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"b")
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [Query
Any,[Query] -> Query
And [DateSpan -> Query
Date (Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
forall a. Maybe a
Nothing)]]) Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query
Any)
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [DateSpan -> Query
Date (Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2013 Int
01 Int
01)), DateSpan -> Query
Date (Maybe Day -> Maybe Day -> DateSpan
DateSpan (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2012 Int
01 Int
01) Maybe Day
forall a. Maybe a
Nothing)])
       Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (DateSpan -> Query
Date (Maybe Day -> Maybe Day -> DateSpan
DateSpan (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2012 Int
01 Int
01) (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2013 Int
01 Int
01)))
     (Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [[Query] -> Query
Or [],[Query] -> Query
Or [Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"b b"]]) Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"b b")

  ,String -> Assertion -> TestTree
test String
"parseQuery" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     (Day -> Text -> Either String (Query, [QueryOpt])
parseQuery Day
nulldate Text
"acct:'expenses:autres d\233penses' desc:b") Either String (Query, [QueryOpt])
-> Either String (Query, [QueryOpt]) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query, [QueryOpt]) -> Either String (Query, [QueryOpt])
forall a b. b -> Either a b
Right ([Query] -> Query
And [Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"expenses:autres d\233penses", Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"b"], [])
     Day -> Text -> Either String (Query, [QueryOpt])
parseQuery Day
nulldate Text
"inacct:a desc:\"b b\""                       Either String (Query, [QueryOpt])
-> Either String (Query, [QueryOpt]) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query, [QueryOpt]) -> Either String (Query, [QueryOpt])
forall a b. b -> Either a b
Right (Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"b b", [Text -> QueryOpt
QueryOptInAcct Text
"a"])
     Day -> Text -> Either String (Query, [QueryOpt])
parseQuery Day
nulldate Text
"inacct:a inacct:b"                           Either String (Query, [QueryOpt])
-> Either String (Query, [QueryOpt]) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query, [QueryOpt]) -> Either String (Query, [QueryOpt])
forall a b. b -> Either a b
Right (Query
Any, [Text -> QueryOpt
QueryOptInAcct Text
"a", Text -> QueryOpt
QueryOptInAcct Text
"b"])
     Day -> Text -> Either String (Query, [QueryOpt])
parseQuery Day
nulldate Text
"desc:'x x'"                                  Either String (Query, [QueryOpt])
-> Either String (Query, [QueryOpt]) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query, [QueryOpt]) -> Either String (Query, [QueryOpt])
forall a b. b -> Either a b
Right (Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"x x", [])
     Day -> Text -> Either String (Query, [QueryOpt])
parseQuery Day
nulldate Text
"'a a' 'b"                                    Either String (Query, [QueryOpt])
-> Either String (Query, [QueryOpt]) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query, [QueryOpt]) -> Either String (Query, [QueryOpt])
forall a b. b -> Either a b
Right ([Query] -> Query
Or [Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"a a",Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"'b"], [])
     Day -> Text -> Either String (Query, [QueryOpt])
parseQuery Day
nulldate Text
"\""                                          Either String (Query, [QueryOpt])
-> Either String (Query, [QueryOpt]) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (Query, [QueryOpt]) -> Either String (Query, [QueryOpt])
forall a b. b -> Either a b
Right (Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"\"", [])

  ,String -> Assertion -> TestTree
test String
"words''" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
      ([Text] -> Text -> [Text]
words'' [] Text
"a b")                   [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"a",Text
"b"]
      ([Text] -> Text -> [Text]
words'' [] Text
"'a b'")                 [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"a b"]
      ([Text] -> Text -> [Text]
words'' [] Text
"not:a b")               [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"not:a",Text
"b"]
      ([Text] -> Text -> [Text]
words'' [] Text
"not:'a b'")             [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"not:a b"]
      ([Text] -> Text -> [Text]
words'' [] Text
"'not:a b'")             [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"not:a b"]
      ([Text] -> Text -> [Text]
words'' [Text
"desc:"] Text
"not:desc:'a b'") [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"not:desc:a b"]
      ([Text] -> Text -> [Text]
words'' [Text]
prefixes Text
"\"acct:expenses:autres d\233penses\"") [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"acct:expenses:autres d\233penses"]
      ([Text] -> Text -> [Text]
words'' [Text]
prefixes Text
"\"")              [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
"\""]

  ,String -> Assertion -> TestTree
test String
"filterQuery" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     (Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
queryIsDepth Query
Any       Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Query
Any
     (Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
queryIsDepth (Int -> Query
Depth Int
1) Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Int -> Query
Depth Int
1
     (Query -> Bool) -> Query -> Query
filterQuery (Bool -> Bool
not(Bool -> Bool) -> (Query -> Bool) -> Query -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Query -> Bool
queryIsDepth) ([Query] -> Query
And [[Query] -> Query
And [Status -> Query
StatusQ Status
Cleared,Int -> Query
Depth Int
1]]) Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Status -> Query
StatusQ Status
Cleared
     (Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
queryIsDepth ([Query] -> Query
And [DateSpan -> Query
Date DateSpan
nulldatespan, Query -> Query
Not ([Query] -> Query
Or [Query
Any, Int -> Query
Depth Int
1])]) Query -> Query -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Query
Any   -- XXX unclear

  ,String -> Assertion -> TestTree
test String
"parseQueryTerm" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"a"                                Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"a")
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"acct:expenses:autres d\233penses" Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"expenses:autres d\233penses")
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"not:desc:a b"                     Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Query -> Query
Not (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"a b")
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"status:1"                         Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
Cleared)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"status:*"                         Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
Cleared)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"status:!"                         Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
Pending)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"status:0"                         Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
Unmarked)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"status:"                          Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
Unmarked)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"payee:x"                          Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> Either String Query -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe String -> Either String Query
payeeTag (String -> Maybe String
forall a. a -> Maybe a
Just String
"x")
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"note:x"                           Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt)
-> Either String Query -> Either String (Either Query QueryOpt)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe String -> Either String Query
noteTag (String -> Maybe String
forall a. a -> Maybe a
Just String
"x")
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"real:1"                           Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Bool -> Query
Real Bool
True)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"date:2008"                        Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
01 Int
01) (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01))
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"date:from 2012/5/17"              Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2012 Int
05 Int
17) Maybe Day
forall a. Maybe a
Nothing)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"date:20180101-201804"             Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2018 Int
01 Int
01) (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2018 Int
04 Int
01))
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"inacct:a"                         Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (QueryOpt -> Either Query QueryOpt
forall a b. b -> Either a b
Right (QueryOpt -> Either Query QueryOpt)
-> QueryOpt -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Text -> QueryOpt
QueryOptInAcct Text
"a")
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"tag:a"                            Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegexCI' String
"a") Maybe Regexp
forall a. Maybe a
Nothing)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"tag:a=some value"                 Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegexCI' String
"a") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegexCI' String
"some value"))
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"amt:<0"                           Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ OrdPlus -> Quantity -> Query
Amt OrdPlus
Lt Quantity
0)
     Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm Day
nulldate Text
"amt:>10000.10"                    Either String (Either Query QueryOpt)
-> Either String (Either Query QueryOpt) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Either Query QueryOpt -> Either String (Either Query QueryOpt)
forall a b. b -> Either a b
Right (Query -> Either Query QueryOpt
forall a b. a -> Either a b
Left (Query -> Either Query QueryOpt) -> Query -> Either Query QueryOpt
forall a b. (a -> b) -> a -> b
$ OrdPlus -> Quantity -> Query
Amt OrdPlus
AbsGt Quantity
10000.1)

  ,String -> Assertion -> TestTree
test String
"parseAmountQueryTerm" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
"<0"        Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Lt,Quantity
0) -- special case for convenience, since AbsLt 0 would be always false
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
">0"        Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Gt,Quantity
0) -- special case for convenience and consistency with above
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
" > - 0 "   Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Gt,Quantity
0) -- accept whitespace around the argument parts
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
">10000.10" Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsGt,Quantity
10000.1)
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
"=0.23"     Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsEq,Quantity
0.23)
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
"0.23"      Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
AbsEq,Quantity
0.23)
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
"<=+0.23"   Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
LtEq,Quantity
0.23)
     Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
"-0.23"     Either String (OrdPlus, Quantity)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= (OrdPlus, Quantity) -> Either String (OrdPlus, Quantity)
forall a b. b -> Either a b
Right (OrdPlus
Eq,(-Quantity
0.23))
     Either String (OrdPlus, Quantity) -> Assertion
forall b a. (HasCallStack, Eq b, Show b) => Either a b -> Assertion
assertLeft (Either String (OrdPlus, Quantity) -> Assertion)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a b. (a -> b) -> a -> b
$ Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
"-0,23"
     Either String (OrdPlus, Quantity) -> Assertion
forall b a. (HasCallStack, Eq b, Show b) => Either a b -> Assertion
assertLeft (Either String (OrdPlus, Quantity) -> Assertion)
-> Either String (OrdPlus, Quantity) -> Assertion
forall a b. (a -> b) -> a -> b
$ Text -> Either String (OrdPlus, Quantity)
parseAmountQueryTerm Text
"=.23"

  ,String -> Assertion -> TestTree
test String
"queryStartDate" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     let small :: Maybe Day
small = Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2000 Int
01 Int
01
         big :: Maybe Day
big   = Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2000 Int
01 Int
02
     Bool -> Query -> Maybe Day
queryStartDate Bool
False ([Query] -> Query
And [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
small Maybe Day
forall a. Maybe a
Nothing, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
big Maybe Day
forall a. Maybe a
Nothing])     Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
big
     Bool -> Query -> Maybe Day
queryStartDate Bool
False ([Query] -> Query
And [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
small Maybe Day
forall a. Maybe a
Nothing, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
forall a. Maybe a
Nothing]) Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
small
     Bool -> Query -> Maybe Day
queryStartDate Bool
False ([Query] -> Query
Or  [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
small Maybe Day
forall a. Maybe a
Nothing, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
big Maybe Day
forall a. Maybe a
Nothing])     Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
small
     Bool -> Query -> Maybe Day
queryStartDate Bool
False ([Query] -> Query
Or  [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
small Maybe Day
forall a. Maybe a
Nothing, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
forall a. Maybe a
Nothing]) Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
forall a. Maybe a
Nothing

  ,String -> Assertion -> TestTree
test String
"queryEndDate" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     let small :: Maybe Day
small = Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2000 Int
01 Int
01
         big :: Maybe Day
big   = Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2000 Int
01 Int
02
     Bool -> Query -> Maybe Day
queryEndDate Bool
False ([Query] -> Query
And [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
small, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
big])     Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
small
     Bool -> Query -> Maybe Day
queryEndDate Bool
False ([Query] -> Query
And [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
small, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
forall a. Maybe a
Nothing]) Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
small
     Bool -> Query -> Maybe Day
queryEndDate Bool
False ([Query] -> Query
Or  [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
small, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
big])     Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
big
     Bool -> Query -> Maybe Day
queryEndDate Bool
False ([Query] -> Query
Or  [DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
small, DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
forall a. Maybe a
Nothing]) Maybe Day -> Maybe Day -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Maybe Day
forall a. Maybe a
Nothing

  ,String -> Assertion -> TestTree
test String
"matchesAccount" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"b:c") Query -> Text -> Bool
`matchesAccount` Text
"a:bb:c:d"
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"^a:b") Query -> Text -> Bool
`matchesAccount` Text
"c:a:b"
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Int -> Query
Depth Int
2 Query -> Text -> Bool
`matchesAccount` Text
"a"
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Int -> Query
Depth Int
2 Query -> Text -> Bool
`matchesAccount` Text
"a:b"
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Int -> Query
Depth Int
2 Query -> Text -> Bool
`matchesAccount` Text
"a:b:c"
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
Date DateSpan
nulldatespan Query -> Text -> Bool
`matchesAccount` Text
"a"
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
Date2 DateSpan
nulldatespan Query -> Text -> Bool
`matchesAccount` Text
"a"
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"a") Maybe Regexp
forall a. Maybe a
Nothing Query -> Text -> Bool
`matchesAccount` Text
"a"

  ,String -> [TestTree] -> TestTree
tests String
"matchesPosting" [
     String -> Assertion -> TestTree
test String
"positive match on cleared posting status"  (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Status -> Query
StatusQ Status
Cleared)  Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pstatus :: Status
pstatus=Status
Cleared}
    ,String -> Assertion -> TestTree
test String
"negative match on cleared posting status"  (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Query -> Query
Not (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
Cleared)  Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pstatus :: Status
pstatus=Status
Cleared}
    ,String -> Assertion -> TestTree
test String
"positive match on unmarked posting status" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Status -> Query
StatusQ Status
Unmarked) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pstatus :: Status
pstatus=Status
Unmarked}
    ,String -> Assertion -> TestTree
test String
"negative match on unmarked posting status" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Query -> Query
Not (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ Status -> Query
StatusQ Status
Unmarked) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pstatus :: Status
pstatus=Status
Unmarked}
    ,String -> Assertion -> TestTree
test String
"positive match on true posting status acquired from transaction" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Status -> Query
StatusQ Status
Cleared) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pstatus :: Status
pstatus=Status
Unmarked,ptransaction :: Maybe Transaction
ptransaction=Transaction -> Maybe Transaction
forall a. a -> Maybe a
Just Transaction
nulltransaction{tstatus :: Status
tstatus=Status
Cleared}}
    ,String -> Assertion -> TestTree
test String
"real:1 on real posting" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Bool -> Query
Real Bool
True) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptype :: PostingType
ptype=PostingType
RegularPosting}
    ,String -> Assertion -> TestTree
test String
"real:1 on virtual posting fails" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool -> Query
Real Bool
True) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptype :: PostingType
ptype=PostingType
VirtualPosting}
    ,String -> Assertion -> TestTree
test String
"real:1 on balanced virtual posting fails" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool -> Query
Real Bool
True) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptype :: PostingType
ptype=PostingType
BalancedVirtualPosting}
    ,String -> Assertion -> TestTree
test String
"acct:" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"'b") Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{paccount :: Text
paccount=Text
"'b"}
    ,String -> Assertion -> TestTree
test String
"tag:" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"a") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"r$")) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"foo") Maybe Regexp
forall a. Maybe a
Nothing) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptags :: [(Text, Text)]
ptags=[(Text
"foo",Text
"")]}
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"foo") Maybe Regexp
forall a. Maybe a
Nothing) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptags :: [(Text, Text)]
ptags=[(Text
"foo",Text
"baz")]}
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"foo") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"a")) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptags :: [(Text, Text)]
ptags=[(Text
"foo",Text
"bar")]}
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"foo") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"a$")) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptags :: [(Text, Text)]
ptags=[(Text
"foo",Text
"bar")]}
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
" foo ") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"a")) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptags :: [(Text, Text)]
ptags=[(Text
"foo",Text
"bar")]}
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"foo foo") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
" ar ba ")) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptags :: [(Text, Text)]
ptags=[(Text
"foo foo",Text
"bar bar")]}
    ,String -> Assertion -> TestTree
test String
"a tag match on a posting also sees inherited tags" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"txntag") Maybe Regexp
forall a. Maybe a
Nothing) Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{ptransaction :: Maybe Transaction
ptransaction=Transaction -> Maybe Transaction
forall a. a -> Maybe a
Just Transaction
nulltransaction{ttags :: [(Text, Text)]
ttags=[(Text
"txntag",Text
"")]}}
    ,String -> Assertion -> TestTree
test String
"cur:" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
      let toSym :: Text -> Query
toSym = (Query -> Query)
-> (QueryOpt -> Query) -> Either Query QueryOpt -> Query
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either Query -> Query
forall a. a -> a
id (Query -> QueryOpt -> Query
forall a b. a -> b -> a
const (Query -> QueryOpt -> Query) -> Query -> QueryOpt -> Query
forall a b. (a -> b) -> a -> b
$ String -> Query
forall a. String -> a
error' String
"No query opts") (Either Query QueryOpt -> Query)
-> (Text -> Either Query QueryOpt) -> Text -> Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Either Query QueryOpt)
-> (Either Query QueryOpt -> Either Query QueryOpt)
-> Either String (Either Query QueryOpt)
-> Either Query QueryOpt
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> Either Query QueryOpt
forall a. String -> a
error' Either Query QueryOpt -> Either Query QueryOpt
forall a. a -> a
id (Either String (Either Query QueryOpt) -> Either Query QueryOpt)
-> (Text -> Either String (Either Query QueryOpt))
-> Text
-> Either Query QueryOpt
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Day -> Text -> Either String (Either Query QueryOpt)
parseQueryTerm (Integer -> Int -> Int -> Day
fromGregorian Integer
2000 Int
01 Int
01) (Text -> Either String (Either Query QueryOpt))
-> (Text -> Text) -> Text -> Either String (Either Query QueryOpt)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text
"cur:"Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>)
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Text -> Query
toSym Text
"$" Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pamount :: MixedAmount
pamount=[Amount] -> MixedAmount
Mixed [Quantity -> Amount
usd Quantity
1]} -- becomes "^$$", ie testing for null symbol
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Text -> Query
toSym Text
"\\$") Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pamount :: MixedAmount
pamount=[Amount] -> MixedAmount
Mixed [Quantity -> Amount
usd Quantity
1]} -- have to quote $ for regexpr
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Text -> Query
toSym Text
"shekels") Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pamount :: MixedAmount
pamount=[Amount] -> MixedAmount
Mixed [Amount
nullamt{acommodity :: Text
acommodity=Text
"shekels"}]}
      HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Text -> Query
toSym Text
"shek") Query -> Posting -> Bool
`matchesPosting` Posting
nullposting{pamount :: MixedAmount
pamount=[Amount] -> MixedAmount
Mixed [Amount
nullamt{acommodity :: Text
acommodity=Text
"shekels"}]}
  ]

  ,String -> Assertion -> TestTree
test String
"matchesTransaction" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Query
Any Query -> Transaction -> Bool
`matchesTransaction` Transaction
nulltransaction
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"x x") Query -> Transaction -> Bool
`matchesTransaction` Transaction
nulltransaction{tdescription :: Text
tdescription=Text
"x"}
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Query
Desc (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"x x") Query -> Transaction -> Bool
`matchesTransaction` Transaction
nulltransaction{tdescription :: Text
tdescription=Text
"x x"}
     -- see posting for more tag tests
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"foo") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"a")) Query -> Transaction -> Bool
`matchesTransaction` Transaction
nulltransaction{ttags :: [(Text, Text)]
ttags=[(Text
"foo",Text
"bar")]}
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"payee") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"payee")) Query -> Transaction -> Bool
`matchesTransaction` Transaction
nulltransaction{tdescription :: Text
tdescription=Text
"payee|note"}
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"note") (Regexp -> Maybe Regexp
forall a. a -> Maybe a
Just (Regexp -> Maybe Regexp) -> Regexp -> Maybe Regexp
forall a b. (a -> b) -> a -> b
$ String -> Regexp
toRegex' String
"note")) Query -> Transaction -> Bool
`matchesTransaction` Transaction
nulltransaction{tdescription :: Text
tdescription=Text
"payee|note"}
     -- a tag match on a transaction also matches posting tags
     HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
assertBool String
"" (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ (Regexp -> Maybe Regexp -> Query
Tag (String -> Regexp
toRegex' String
"postingtag") Maybe Regexp
forall a. Maybe a
Nothing) Query -> Transaction -> Bool
`matchesTransaction` Transaction
nulltransaction{tpostings :: [Posting]
tpostings=[Posting
nullposting{ptags :: [(Text, Text)]
ptags=[(Text
"postingtag",Text
"")]}]}

 ]