1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE ScopedTypeVariables #-}
5 {-# LANGUAGE TupleSections #-}
6 {-# LANGUAGE TypeFamilies #-}
7 module Hcompta.Filter.Read where
9 import Prelude hiding (filter)
10 -- import Control.Applicative ((<$>), (<*))
11 import Control.Exception (assert)
12 import Control.Monad (liftM, join, when, (>=>), void, forM)
13 -- import Control.Monad.Trans.Except (ExceptT(..), throwE)
14 import qualified Data.Char
16 import qualified Data.Foldable
17 import Data.Functor.Identity (Identity)
18 import qualified Data.List
19 import Data.Maybe (catMaybes)
20 import qualified Data.Time.Clock as Time
21 import qualified Text.Parsec.Expr as R
22 import qualified Text.Parsec as R hiding
34 -- import qualified Text.Parsec.Expr as R
35 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
36 import Data.String (fromString)
37 import qualified Data.Text as Text
38 import Data.Text (Text)
39 import Data.Typeable ()
41 import Hcompta.Lib.Interval (Interval)
42 import qualified Hcompta.Lib.Interval as Interval
43 import qualified Hcompta.Lib.Regex as Regex
44 -- import Hcompta.Lib.Regex (Regex)
45 import qualified Hcompta.Account.Read as Account.Read
46 import qualified Hcompta.Amount as Amount
47 import Hcompta.Amount (Amount)
48 import qualified Hcompta.Amount.Read as Amount.Read
49 import qualified Hcompta.Amount.Unit as Unit
50 import qualified Hcompta.Date as Date
51 import Hcompta.Date (Date)
52 import qualified Hcompta.Date.Read as Date.Read
53 import qualified Hcompta.Filter as Filter
54 import Hcompta.Filter hiding (Amount)
55 import qualified Hcompta.Lib.Parsec as R
63 { context_date :: Date
64 } deriving (Data, Eq, Show, Typeable)
69 { context_date = Date.nil
76 | Error_Filter_Date Date.Read.Error
77 | Error_Filter_Date_Interval (Integer, Integer)
83 ( Stream s (R.Error_State Error Identity) Char
86 => ParsecT s Context (R.Error_State Error Identity) (Filter_Bool t)
87 -> s -> IO (Either [R.Error Error] (Filter_Bool t))
89 context_date <- Time.getCurrentTime
91 R.runParser_with_Error t context{context_date} "" s
93 -- ** Read 'Filter_Text'
95 :: (Stream s m Char, Monad r)
96 => ParsecT s u m (String -> r Filter_Text)
99 [ R.char '~' >> return (Regex.of_StringM >=> (return . Filter_Text_Regex))
100 , R.char '=' >> return (\s -> return (Filter_Text_Exact $ Text.pack s))
101 , return (\s -> return (Filter_Text_Exact $ Text.pack s))
106 => ParsecT s u m String
107 filter_text_operator =
113 -- ** Read 'Filter_Ord'
115 :: (Stream s m Char, Ord o)
116 => ParsecT s u m (o -> Filter_Ord o)
119 [ R.string "=" >> return (Filter_Ord Eq)
120 , R.string "<=" >> return (Filter_Ord Le) -- NOTE: before "<"
121 , R.string ">=" >> return (Filter_Ord Ge) -- NOTE: before ">"
122 , R.string "<" >> return (Filter_Ord Lt)
123 , R.string ">" >> return (Filter_Ord Gt)
128 => ParsecT s u m String
129 filter_ord_operator =
138 -- ** Read 'Filter_Num_Abs'
140 :: (Stream s m Char, Num n)
141 => ParsecT s u m (Filter_Ord n -> m (Either (Filter_Ord n) (Filter_Num_Abs n)))
144 [ R.char '+' >> return (return . Right . Filter_Num_Abs)
145 , return (return . Left)
148 text :: Stream s m Char => String -> ParsecT s Context m Text
153 , R.many $ R.noneOf ("() " ++ none_of)
156 borders = R.between (R.char '(') (R.char ')')
157 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
158 preserve_inside = inside >>= (\x -> return $ '(':(x++[')']))
160 -- ** Read 'Filter_Bool'
164 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
165 -> ParsecT s u m (Filter_Bool t)
167 R.buildExpressionParser
168 filter_bool_operators
169 (filter_bool_term terms)
172 filter_bool_operators
174 => R.OperatorTable s u m (Filter.Filter_Bool t)
175 filter_bool_operators =
176 [ [ prefix "- " Filter.Not
178 , [ binary " & " Filter.And R.AssocLeft
180 , [ binary " + " Filter.Or R.AssocLeft
181 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
185 binary name fun = R.Infix (filter_bool_operator name >> return fun)
186 prefix name fun = R.Prefix (filter_bool_operator name >> return fun)
187 -- postfix name fun = Text.Parsec.Expr.Postfix (filter_bool_operator name >> return fun)
191 => String -> ParsecT s u m ()
192 filter_bool_operator name =
195 >> R.notFollowedBy filter_bool_operator_letter
199 filter_bool_operator_letter
200 :: Stream s m Char => ParsecT s u m Char
201 filter_bool_operator_letter =
202 R.oneOf ['-', '&', '+']
206 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
207 -> ParsecT s u m (Filter_Bool t)
208 filter_bool_term terms = do
210 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
211 >> (return $ parens $
212 Data.Foldable.foldr Filter.And Filter.Any <$>
213 R.many (R.try (R.spaces >> expr)) ))
215 ) <* R.spaces <?> "boolean-term")
218 R.lookAhead (R.try R.anyToken)
219 >> R.notFollowedBy (R.char ')')
228 (R.spaces >> R.char '(')
229 (R.spaces >> R.char ')')
231 bool :: Stream s m Char => ParsecT s u m Bool
246 jump :: Stream s m Char
251 jump prefixes next r =
253 (map (\s -> R.string s >> return r) prefixes)
254 <* R.lookAhead (R.try next)
256 -- ** Read 'Filter_Account_Section'
258 -- ** Read 'Filter_Account'
259 -- | Return the 'Filter_Posting_Type' and stripped 'Account' of the given 'Account'.
260 account_posting_type :: [String] -> (Filter_Posting_Type, [String])
261 account_posting_type acct =
263 (Filter_Posting_Type_Any, acct)
264 (Filter_Posting_Type_Exact Posting_Type_Virtual,) $ do
270 ']':rs -> Just $ [reverse rs]
273 let rs = reverse ns in
274 case reverse $ Data.List.head rs of
275 ']':ln -> Just $ fn : reverse (reverse ln : Data.List.tail rs)
281 => ParsecT s u m (Filter_Posting_Type, Filter_Account)
283 R.notFollowedBy $ R.space_horizontal
285 R.option (Filter_Ord Eq ()) $ R.try $
286 (\f -> f ()) <$> filter_ord
287 (Filter_Account o <$>) <$> account
289 account :: Stream s m Char => ParsecT s u m (Filter_Posting_Type, [Filter_Account_Section])
292 account_posting_type <$>
294 (R.many (R.satisfy (\c -> c /= Account.Read.section_sep && not (Data.Char.isSpace c))))
295 (R.char Account.Read.section_sep)
296 sections <- forM strings $ \s ->
298 "" -> return Filter_Account_Section_Many
299 "*" -> return Filter_Account_Section_Any
300 '~':t -> Filter_Account_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
301 t -> return $ Filter_Account_Section_Text $ Filter_Text_Exact $ Text.pack t
302 return (pt, if null sections then [Filter_Account_Section_Many] else sections)
304 filter_account_operator
306 => ParsecT s u m String
307 filter_account_operator =
310 -- ** Read 'Filter_Amount'
313 => ParsecT s u m (Filter_Amount Amount)
315 R.notFollowedBy $ R.space_horizontal
319 amt <- Amount.Read.amount
321 (Bool $ Filter_Amount_Section_Quantity (tst $ Amount.quantity amt))
322 (case Unit.text $ Amount.unit amt of
323 unit | Text.null unit -> Any
324 unit -> Bool $ Filter_Amount_Section_Unit (Filter_Unit (Filter_Text_Exact unit)))
327 unit <- Amount.Read.unit >>= tst . Text.unpack . Unit.text
328 return $ Bool $ Filter_Amount_Section_Unit (Filter_Unit unit)
331 filter_amount_operator
333 => ParsecT s u m String
334 filter_amount_operator =
336 [ filter_ord_operator
337 , filter_text_operator
340 -- ** Read 'Filter_Date'
342 :: (Stream s (R.Error_State Error m) Char, Monad m)
343 => ParsecT s Context (R.Error_State Error m) (Filter_Bool Filter_Date)
347 (return $ read_date_pattern)
348 , filter_ord >>= \tst ->
351 let (year, _, _) = Date.gregorian $ context_date ctx
352 liftM (Bool . Filter_Date_UTC . tst) $
353 Date.Read.date Error_Filter_Date (Just year)
357 :: (Stream s (R.Error_State Error m) Char, Monad m)
358 => ParsecT s u (R.Error_State Error m) (Filter_Bool Filter_Date)
359 read_date_pattern = (do
360 let read2 = of_digits <$> (R.try (R.count 2 R.digit) <|> R.count 1 R.digit)
361 n0 <- read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
362 n1 <- R.option Nothing $ R.try $ do
364 Just <$> read_interval Error_Filter_Date_Interval read2
365 n2 <- R.option Nothing $ R.try $ do
367 Just <$> read_interval Error_Filter_Date_Interval read2
368 let (year, month, dom) =
370 (Nothing, Nothing) ->
373 , Interval.unlimited )
374 (Just d1, Nothing) ->
378 (Nothing, Just _d2) -> assert False undefined
379 (Just d1, Just d2) ->
383 (hour, minute, second) <-
384 R.option (Interval.unlimited, Interval.unlimited, Interval.unlimited) $ R.try $ do
386 hour <- read_interval Error_Filter_Date_Interval read2
387 sep <- Date.Read.hour_separator
388 minute <- read_interval Error_Filter_Date_Interval read2
389 second <- R.option Interval.unlimited $ R.try $ do
391 read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
392 -- tz <- R.option Time.utc $ R.try $ do
393 -- -- R.skipMany $ R.space_horizontal
394 -- Date.Read.time_zone
403 [ just_when_limited (Filter_Date_Year . Filter_Interval_In) year
404 , just_when_limited (Filter_Date_Month . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) month)
405 , just_when_limited (Filter_Date_DoM . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) dom)
406 , just_when_limited (Filter_Date_Hour . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) hour)
407 , just_when_limited (Filter_Date_Minute . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) minute)
408 , just_when_limited (Filter_Date_Second . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) second)
412 of_digits :: Num n => [Char] -> n
413 of_digits = fromInteger . R.integer_of_digits 10
414 just_when_limited f x =
415 if x == Interval.unlimited
417 else Just $ Bool $ f x
420 :: (Stream s (R.Error_State e m) Char, Monad m, Ord x)
422 -> ParsecT s u (R.Error_State e m) x
423 -> ParsecT s u (R.Error_State e m) (Interval (Interval.Unlimitable x))
424 read_interval err read_digits = do
426 [ R.string ".." >> return Interval.Unlimited_low
427 , Interval.Limited <$> read_digits
430 [ when (l /= Interval.Unlimited_low)
431 (void $ R.string "..") >> do
433 [ Interval.Limited <$> read_digits
434 , return Interval.Unlimited_high
436 case (Interval.<=..<=) l h of
437 Nothing -> R.fail_with "interval" (err $ (Interval.limited l, Interval.limited h))
441 Interval.Limited _ -> Interval.point l
442 _ -> Interval.unlimited
447 => ParsecT s u m String
448 filter_date_operator =
451 -- ** Read 'Filter_Description'
454 => ParsecT s u m Filter_Text
455 filter_description = (do
456 make_filter_text <- filter_text
457 R.between (R.char '"') (R.char '"') $
458 make_filter_text =<< (R.many $ R.try $ R.satisfy (/= '"'))
461 filter_description_operator
463 => ParsecT s u m String
464 filter_description_operator =
467 -- ** Read 'Filter_Tag'
473 => ParsecT s u m Filter_Tag
475 make_filter_text <- filter_text
478 <* R.lookAhead filter_tag_name_end
479 >> return (Filter_Tag_Name Filter_Text_Any)
480 , R.many1 (R.notFollowedBy (R.try filter_tag_name_end) >> R.anyChar)
481 >>= (liftM Filter_Tag_Name . make_filter_text)
484 filter_tag_name_end =
486 [ void $ filter_text_operator
487 , void $ R.space_horizontal
492 => ParsecT s u m Filter_Tag
493 filter_tag_value = do
494 make_filter_text <- filter_text
497 <* R.lookAhead filter_tag_value_end
498 >> return (Filter_Tag_Value Filter_Text_Any)
499 , R.many1 (R.notFollowedBy (R.try filter_tag_value_end) >> R.anyChar)
500 >>= (liftM Filter_Tag_Value . make_filter_text)
503 filter_tag_value_end =
505 [ void $ R.space_horizontal
511 => ParsecT s u m (Filter_Bool Filter_Tag)
515 [ R.lookAhead (R.try $ filter_tag_operator)
516 >> And (Bool n) . Bool <$> filter_tag_value
522 => ParsecT s u m String
523 filter_tag_operator =
526 -- ** Read 'Filter_Posting'
530 , Posting_Amount p ~ Amount
532 => ParsecT s Context m (Filter_Bool (Filter_Posting p))
534 Data.Foldable.foldr Filter.And Filter.Any <$>
537 >> R.lookAhead R.anyToken
538 >> filter_bool filter_posting_terms
540 filter_posting_terms ::
543 , Posting_Amount p ~ Amount
545 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Posting p)))]
546 filter_posting_terms =
547 [ jump [ "a", "amount" ] filter_amount_operator
548 ((Bool . Filter.Filter_Posting_Amount) <$> filter_amount)
552 (Bool $ Filter_Posting_Account a)
553 (Bool $ Filter_Posting_Type pt)
557 -- ** Read 'Filter_Transaction'
558 filter_transaction ::
559 ( Stream s (R.Error_State Error m) Char
561 , Filter.Transaction t
562 , Posting_Amount (Transaction_Posting t) ~ Amount
563 ) => ParsecT s Context (R.Error_State Error m)
564 (Filter_Bool (Filter_Transaction t))
566 glue_posting . Data.Foldable.foldr Filter.And Filter.Any <$>
569 >> R.lookAhead R.anyToken
570 >> filter_bool filter_transaction_terms
574 => Filter_Bool (Filter_Transaction t)
575 -> Filter_Bool (Filter_Transaction t)
580 (Or b0 b1) -> Or (glue_posting b0) (glue_posting b1)
582 case glue_posting b of
583 Bool (Filter_Transaction_Posting p) ->
584 Bool $ Filter_Transaction_Posting $ Not p
587 case (glue_posting b0, glue_posting b1) of
588 ( Bool (Filter_Transaction_Posting p0),
589 Bool (Filter_Transaction_Posting p1)
590 ) -> Bool $ Filter_Transaction_Posting $ And p0 p1
591 (Bool (Filter_Transaction_Posting p0), Any) ->
592 Bool $ Filter_Transaction_Posting $ p0
593 (Any, Bool (Filter_Transaction_Posting p1)) ->
594 Bool $ Filter_Transaction_Posting $ p1
595 (b0', b1') -> And b0' b1'
597 filter_transaction_terms ::
598 ( Stream s (R.Error_State Error m) Char
599 , Filter.Transaction t
601 , Posting_Amount (Transaction_Posting t) ~ Amount
603 => [ParsecT s Context (R.Error_State Error m)
604 (ParsecT s Context (R.Error_State Error m) (Filter_Bool (Filter_Transaction t)))]
605 filter_transaction_terms =
606 -- , jump [ "atag" ] comp_text parseFilterATag
607 -- , jump [ "code" ] comp_text parseFilterCode
608 [ jump [ "d", "date" ] filter_date_operator
609 (Bool . Filter.Filter_Transaction_Date <$> filter_date)
610 , jump [ "T", "tag" ] filter_tag_operator
611 (Bool . Filter.Filter_Transaction_Tag <$> filter_tag)
612 , jump [ "D", "debit" ] filter_amount_operator
614 . Filter_Transaction_Posting
616 . Filter_Posting_Positive
618 , jump [ "C", "credit" ] filter_amount_operator
620 . Filter_Transaction_Posting
622 . Filter_Posting_Negative
624 , jump [ "W", "wording" ] filter_description_operator
625 (Bool . Filter.Filter_Transaction_Description <$> filter_description)
626 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
627 -- , jump [ "real" ] (R.char '=') parseFilterReal
628 -- , jump [ "status" ] (R.char '=') parseFilterStatus
629 -- , jump [ "sym" ] comp_text parseFilterSym
630 -- , R.lookAhead comp_num >> return parseFilterAmount
634 Filter_Transaction_Posting $
635 And (Bool $ Filter_Posting_Account a)
636 (Bool $ Filter_Posting_Type pt)
640 -- ** Read 'Filter_Balance'
642 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
643 => ParsecT s Context m (Filter_Bool (Filter_Balance t))
645 Data.Foldable.foldr Filter.And Filter.Any <$>
648 >> R.lookAhead R.anyToken
649 >> filter_bool filter_balance_terms
652 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
653 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Balance t)))]
654 filter_balance_terms =
655 [ jump [ "RD", "debit" ] filter_amount_operator
656 ( Bool . Filter_Balance_Positive
658 , jump [ "RC", "credit" ] filter_amount_operator
659 ( Bool . Filter_Balance_Negative
661 , jump [ "RB", "balance" ] filter_amount_operator
662 ( Bool . Filter_Balance_Amount
665 ( Bool . Filter_Balance_Account . snd
669 -- ** Read 'Filter_GL'
671 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
672 => ParsecT s Context m (Filter_Bool (Filter_GL t))
674 Data.Foldable.foldr Filter.And Filter.Any <$>
677 >> R.lookAhead R.anyToken
678 >> filter_bool filter_gl_terms
681 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
682 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_GL t)))]
684 [ jump [ "D", "debit" ] filter_amount_operator
685 ( Bool . Filter_GL_Amount_Positive
687 , jump [ "C", "credit" ] filter_amount_operator
688 ( Bool . Filter_GL_Amount_Negative
690 , jump [ "B", "balance" ] filter_amount_operator
691 ( Bool . Filter_GL_Amount_Balance
693 , jump [ "RD", "running-debit" ] filter_amount_operator
694 ( Bool . Filter_GL_Sum_Positive
696 , jump [ "RC", "running-credit" ] filter_amount_operator
697 ( Bool . Filter_GL_Sum_Negative
699 , jump [ "RB", "running-balance" ] filter_amount_operator
700 ( Bool . Filter_GL_Sum_Balance
703 ( Bool . Filter_GL_Account . snd