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 Control.Applicative ((<$>), (<*))
10 import Control.Exception (assert)
11 import Control.Monad (Monad(..), liftM, join, when, (=<<), (>=>), void, forM)
12 -- import Control.Monad.Trans.Except (ExceptT(..), throwE)
16 import Data.Either (Either(..))
17 import Data.Eq (Eq(..))
18 import qualified Data.Foldable
19 import Data.Foldable (Foldable(..))
20 import Data.Functor (Functor(..))
21 import Data.Functor.Identity (Identity)
22 import Data.List ((++), concat, map, reverse)
23 -- import Data.List.NonEmpty (NonEmpty(..))
24 -- import qualified Data.List.NonEmpty as NonEmpty
25 import Data.Maybe (Maybe(..), catMaybes)
26 import Data.Ord (Ord(..))
27 import Data.String (String, fromString)
28 import Data.Text (Text)
29 import qualified Data.Text as Text
30 import qualified Data.Time.Clock as Time
31 import Data.Typeable ()
32 import Prelude (($), (.), Integer, IO, Num(..), undefined)
33 import qualified Text.Parsec.Expr as R
34 import qualified Text.Parsec as R hiding
46 -- import qualified Text.Parsec.Expr as R
47 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
48 import Text.Show (Show(..))
50 import qualified Hcompta.Account as Account
51 import qualified Hcompta.Account.Read as Account.Read
52 import Hcompta.Amount (Amount)
53 import qualified Hcompta.Amount as Amount
54 import qualified Hcompta.Amount.Read as Amount.Read
55 import qualified Hcompta.Amount.Unit as Unit
56 import Hcompta.Date (Date)
57 import qualified Hcompta.Date as Date
58 import qualified Hcompta.Date.Read as Date.Read
59 import qualified Hcompta.Filter as Filter
60 import Hcompta.Filter hiding (Amount)
61 import Hcompta.Lib.Interval (Interval)
62 import qualified Hcompta.Lib.Interval as Interval
63 import qualified Hcompta.Lib.Parsec as R
64 -- import Hcompta.Lib.Regex (Regex)
65 import qualified Hcompta.Lib.Regex as Regex
73 { context_date :: Date
74 } deriving (Data, Eq, Show, Typeable)
79 { context_date = Date.nil
86 | Error_Filter_Date Date.Read.Error
87 | Error_Filter_Date_Interval (Integer, Integer)
93 ( Stream s (R.Error_State Error Identity) Char
96 => ParsecT s Context (R.Error_State Error Identity) (Filter_Bool t)
97 -> s -> IO (Either [R.Error Error] (Filter_Bool t))
99 context_date <- Time.getCurrentTime
101 R.runParser_with_Error t context{context_date} "" s
103 -- ** Read 'Filter_Text'
105 :: (Stream s m Char, Monad r)
106 => ParsecT s u m (String -> r Filter_Text)
109 [ R.char '~' >> return (Regex.of_StringM >=> (return . Filter_Text_Regex))
110 , R.char '=' >> return (\s -> return (Filter_Text_Exact $ Text.pack s))
111 , return (\s -> return (Filter_Text_Exact $ Text.pack s))
116 => ParsecT s u m String
117 filter_text_operator =
123 -- ** Read 'Filter_Ord'
125 :: (Stream s m Char, Ord o)
126 => ParsecT s u m (o -> Filter_Ord o)
129 [ R.string "=" >> return (Filter_Ord Eq)
130 , R.string "<=" >> return (Filter_Ord Le) -- NOTE: before "<"
131 , R.string ">=" >> return (Filter_Ord Ge) -- NOTE: before ">"
132 , R.string "<" >> return (Filter_Ord Lt)
133 , R.string ">" >> return (Filter_Ord Gt)
138 => ParsecT s u m String
139 filter_ord_operator =
148 -- ** Read 'Filter_Num_Abs'
150 :: (Stream s m Char, Num n)
151 => ParsecT s u m (Filter_Ord n -> m (Either (Filter_Ord n) (Filter_Num_Abs n)))
154 [ R.char '+' >> return (return . Right . Filter_Num_Abs)
155 , return (return . Left)
158 text :: Stream s m Char => String -> ParsecT s Context m Text
163 , R.many $ R.noneOf ("() " ++ none_of)
166 borders = R.between (R.char '(') (R.char ')')
167 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
168 preserve_inside = inside >>= (\x -> return $ '(':(x++[')']))
170 -- ** Read 'Filter_Bool'
174 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
175 -> ParsecT s u m (Filter_Bool t)
177 R.buildExpressionParser
178 filter_bool_operators
179 (filter_bool_term terms)
182 filter_bool_operators
184 => R.OperatorTable s u m (Filter.Filter_Bool t)
185 filter_bool_operators =
186 [ [ prefix "- " Filter.Not
188 , [ binary " & " Filter.And R.AssocLeft
190 , [ binary " + " Filter.Or R.AssocLeft
191 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
195 binary name fun = R.Infix (filter_bool_operator name >> return fun)
196 prefix name fun = R.Prefix (filter_bool_operator name >> return fun)
197 -- postfix name fun = Text.Parsec.Expr.Postfix (filter_bool_operator name >> return fun)
201 => String -> ParsecT s u m ()
202 filter_bool_operator name =
205 >> R.notFollowedBy filter_bool_operator_letter
209 filter_bool_operator_letter
210 :: Stream s m Char => ParsecT s u m Char
211 filter_bool_operator_letter =
212 R.oneOf ['-', '&', '+']
216 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
217 -> ParsecT s u m (Filter_Bool t)
218 filter_bool_term terms = (do
220 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
221 >> (return $ parens $
222 Data.Foldable.foldr Filter.And Filter.Any <$>
223 R.many (R.try (R.spaces >> expr)) ))
225 ) <* R.spaces) <?> "boolean-term"
228 R.lookAhead (R.try R.anyToken)
229 >> R.notFollowedBy (R.char ')')
238 (R.spaces >> R.char '(')
239 (R.spaces >> R.char ')')
241 bool :: Stream s m Char => ParsecT s u m Bool
256 jump :: Stream s m Char
261 jump prefixes next r =
262 R.choice_try (map (\s -> R.string s >> return r) prefixes)
263 <* R.lookAhead (R.try next)
265 -- ** Read 'Filter_Account'
268 -- | Return the 'Filter_Posting_Type' and stripped 'Account' of the given 'Account'.
269 account_posting_type :: [String] -> (Filter_Posting_Type, [String])
270 account_posting_type acct =
272 (Filter_Posting_Type_Any, acct)
273 (Filter_Posting_Type_Exact Posting_Type_Virtual,) $ do
279 ']':rs -> Just $ [reverse rs]
282 let rs = reverse ns in
283 case reverse $ Data.List.head rs of
284 ']':ln -> Just $ fn : reverse (reverse ln : Data.List.tail rs)
291 => ParsecT s u m (Filter_Path Account.Account_Section)
292 filter_account_path = do
293 R.notFollowedBy $ R.space_horizontal
295 R.option (Filter_Ord Eq ()) $ R.try $
296 (\f -> f ()) <$> filter_ord
299 (R.many (R.satisfy (\c -> c /= Account.Read.section_sep && not (Data.Char.isSpace c))))
300 (R.char Account.Read.section_sep)
301 sections <- forM strings $ \s ->
303 "" -> return Filter_Path_Section_Many
304 "*" -> return Filter_Path_Section_Any
305 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
306 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
307 return $ Filter_Path o $
308 (if null sections then [Filter_Path_Section_Many] else sections)
313 ) => ParsecT s Context m (Filter_Account a)
315 Data.Foldable.foldr Filter.And Filter.Any <$>
318 >> R.lookAhead R.anyToken
319 >> filter_bool filter_account_terms
321 filter_account_terms ::
324 ) => [ParsecT s Context m (ParsecT s Context m (Filter_Account a))]
325 filter_account_terms =
326 [ jump [ "AT" ] filter_account_operator $
327 Bool . Filter.Filter_Account_Tag <$>
330 Bool . Filter.Filter_Account_Path <$>
334 filter_account_operator
336 => ParsecT s u m String
337 filter_account_operator =
339 [ filter_ord_operator
342 -- ** Read 'Filter_Amount'
345 => ParsecT s u m (Filter_Amount Amount)
347 R.notFollowedBy $ R.space_horizontal
351 amt <- Amount.Read.amount
353 (Bool $ Filter_Amount_Section_Quantity (tst $ Amount.quantity amt))
354 (case Unit.text $ Amount.unit amt of
355 unit | Text.null unit -> Any
356 unit -> Bool $ Filter_Amount_Section_Unit (Filter_Unit (Filter_Text_Exact unit)))
359 unit <- Amount.Read.unit >>= tst . Text.unpack . Unit.text
360 return $ Bool $ Filter_Amount_Section_Unit (Filter_Unit unit)
363 filter_amount_operator
365 => ParsecT s u m String
366 filter_amount_operator =
368 [ filter_ord_operator
369 , filter_text_operator
372 -- ** Read 'Filter_Date'
374 :: (Stream s (R.Error_State Error m) Char, Monad m)
375 => ParsecT s Context (R.Error_State Error m) (Filter_Bool Filter_Date)
379 (return $ read_date_pattern)
380 , filter_ord >>= \tst ->
383 let (year, _, _) = Date.gregorian $ context_date ctx
384 liftM (Bool . Filter_Date_UTC . tst) $
385 Date.Read.date Error_Filter_Date (Just year)
389 :: (Stream s (R.Error_State Error m) Char, Monad m)
390 => ParsecT s u (R.Error_State Error m) (Filter_Bool Filter_Date)
391 read_date_pattern = (do
392 let read2 = of_digits <$> (R.try (R.count 2 R.digit) <|> R.count 1 R.digit)
393 n0 <- read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
394 n1 <- R.option Nothing $ R.try $ do
396 Just <$> read_interval Error_Filter_Date_Interval read2
397 n2 <- R.option Nothing $ R.try $ do
399 Just <$> read_interval Error_Filter_Date_Interval read2
400 let (year, month, dom) =
402 (Nothing, Nothing) ->
405 , Interval.unlimited )
406 (Just d1, Nothing) ->
410 (Nothing, Just _d2) -> assert False undefined
411 (Just d1, Just d2) ->
415 (hour, minute, second) <-
416 R.option (Interval.unlimited, Interval.unlimited, Interval.unlimited) $ R.try $ do
418 hour <- read_interval Error_Filter_Date_Interval read2
419 sep <- Date.Read.hour_separator
420 minute <- read_interval Error_Filter_Date_Interval read2
421 second <- R.option Interval.unlimited $ R.try $ do
423 read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
424 -- tz <- R.option Time.utc $ R.try $ do
425 -- -- R.skipMany $ R.space_horizontal
426 -- Date.Read.time_zone
435 [ just_when_limited (Filter_Date_Year . Filter_Interval_In) year
436 , just_when_limited (Filter_Date_Month . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) month)
437 , just_when_limited (Filter_Date_DoM . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) dom)
438 , just_when_limited (Filter_Date_Hour . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) hour)
439 , just_when_limited (Filter_Date_Minute . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) minute)
440 , just_when_limited (Filter_Date_Second . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) second)
444 of_digits :: Num n => [Char] -> n
445 of_digits = fromInteger . R.integer_of_digits 10
446 just_when_limited f x =
447 if x == Interval.unlimited
449 else Just $ Bool $ f x
452 :: (Stream s (R.Error_State e m) Char, Monad m, Ord x)
454 -> ParsecT s u (R.Error_State e m) x
455 -> ParsecT s u (R.Error_State e m) (Interval (Interval.Unlimitable x))
456 read_interval err read_digits = do
458 [ R.string ".." >> return Interval.Unlimited_low
459 , Interval.Limited <$> read_digits
462 [ when (l /= Interval.Unlimited_low)
463 (void $ R.string "..") >> do
465 [ Interval.Limited <$> read_digits
466 , return Interval.Unlimited_high
468 case (Interval.<=..<=) l h of
469 Nothing -> R.fail_with "interval" (err $ (Interval.limited l, Interval.limited h))
473 Interval.Limited _ -> Interval.point l
474 _ -> Interval.unlimited
479 => ParsecT s u m String
480 filter_date_operator =
483 -- ** Read 'Filter_Description'
486 => ParsecT s u m Filter_Text
487 filter_description = (do
488 make_filter_text <- filter_text
489 R.between (R.char '"') (R.char '"') $
490 make_filter_text =<< (R.many $ R.try $ R.satisfy (/= '"'))
493 filter_description_operator
495 => ParsecT s u m String
496 filter_description_operator =
499 -- ** Read 'Filter_Tag'
502 => ParsecT s u m Filter_Tags
504 R.notFollowedBy $ R.space_horizontal
505 Filter_Ord o () <- (\f -> f ()) <$> filter_ord
508 [ R.char '<' >> return Filter_Tag_Value_First
509 , R.char '>' >> return Filter_Tag_Value_Last
510 , return Filter_Tag_Value_Any
514 (R.many (R.satisfy (\c -> c /= Account.Read.section_sep && not (Data.Char.isSpace c))))
515 (R.char Account.Read.section_sep)
516 sections <- forM strings $ \s ->
518 "" -> return Filter_Path_Section_Many
519 "*" -> return Filter_Path_Section_Any
520 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
521 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
522 case reverse sections of
524 [p] -> return $ Bool $ Filter_Tag_Path $ Filter_Path o [p]
527 (Bool $ Filter_Tag_Path $ Filter_Path o $ reverse rev_path)
528 (Bool $ Filter_Tag_Value $ filter_tag_value $
530 Filter_Path_Section_Any -> Filter_Text_Any
531 Filter_Path_Section_Many -> Filter_Text_Any
532 Filter_Path_Section_Text ft -> ft
537 => ParsecT s u m String
538 filter_tag_operator = do
539 void filter_ord_operator
546 -- ** Read 'Filter_Posting'
550 , Posting_Amount p ~ Amount
552 => ParsecT s Context m (Filter_Bool (Filter_Posting p))
554 Data.Foldable.foldr Filter.And Filter.Any <$>
557 >> R.lookAhead R.anyToken
558 >> filter_bool filter_posting_terms
560 filter_posting_terms ::
563 , Posting_Amount p ~ Amount
565 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Posting p)))]
566 filter_posting_terms =
567 [ jump [ "a", "amount" ] filter_amount_operator $
568 Bool . Filter.Filter_Posting_Amount <$>
570 , jump [ "[]" ] (return ()) $
571 return $ Bool $ Filter_Posting_Type $
572 Filter_Posting_Type_Exact Posting_Type_Virtual
574 Bool . Filter_Posting_Account <$>
578 -- ** Read 'Filter_Transaction'
579 filter_transaction ::
580 ( Stream s (R.Error_State Error m) Char
582 , Filter.Transaction t
583 , Posting_Amount (Transaction_Posting t) ~ Amount
584 ) => ParsecT s Context (R.Error_State Error m)
585 (Filter_Bool (Filter_Transaction t))
587 glue_posting . Data.Foldable.foldr Filter.And Filter.Any <$>
590 >> R.lookAhead R.anyToken
591 >> filter_bool filter_transaction_terms
595 => Filter_Bool (Filter_Transaction t)
596 -> Filter_Bool (Filter_Transaction t)
601 (Or b0 b1) -> Or (glue_posting b0) (glue_posting b1)
603 case glue_posting b of
604 Bool (Filter_Transaction_Posting p) ->
605 Bool $ Filter_Transaction_Posting $ Not p
608 case (glue_posting b0, glue_posting b1) of
609 ( Bool (Filter_Transaction_Posting p0),
610 Bool (Filter_Transaction_Posting p1)
611 ) -> Bool $ Filter_Transaction_Posting $ And p0 p1
612 (Bool (Filter_Transaction_Posting p0), Any) ->
613 Bool $ Filter_Transaction_Posting $ p0
614 (Any, Bool (Filter_Transaction_Posting p1)) ->
615 Bool $ Filter_Transaction_Posting $ p1
616 (b0', b1') -> And b0' b1'
618 filter_transaction_terms ::
619 ( Stream s (R.Error_State Error m) Char
620 , Filter.Transaction t
622 , Posting_Amount (Transaction_Posting t) ~ Amount
624 => [ParsecT s Context (R.Error_State Error m)
625 (ParsecT s Context (R.Error_State Error m)
626 (Filter_Bool (Filter_Transaction t)))]
627 filter_transaction_terms =
628 -- , jump [ "atag" ] comp_text parseFilterATag
629 -- , jump [ "code" ] comp_text parseFilterCode
630 [ jump [ "AT" ] filter_account_operator $
631 Bool . Filter_Transaction_Posting .
632 Bool . Filter_Posting_Account .
633 Bool . Filter_Account_Tag <$>
635 , jump [ "date", "d" ] filter_date_operator
636 (Bool . Filter.Filter_Transaction_Date <$> filter_date)
637 , jump [ "tag", "T" ] filter_tag_operator
638 (Bool . Filter.Filter_Transaction_Tag <$> filter_tag)
639 , jump [ "debit", "D" ] filter_amount_operator
641 . Filter_Transaction_Posting
643 . Filter_Posting_Positive
645 , jump [ "credit", "C" ] filter_amount_operator
647 . Filter_Transaction_Posting
649 . Filter_Posting_Negative
651 , jump [ "wording", "W" ] filter_description_operator
652 (Bool . Filter.Filter_Transaction_Description <$> filter_description)
653 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
654 -- , jump [ "real" ] (R.char '=') parseFilterReal
655 -- , jump [ "status" ] (R.char '=') parseFilterStatus
656 -- , jump [ "sym" ] comp_text parseFilterSym
657 -- , R.lookAhead comp_num >> return parseFilterAmount
659 Bool . Filter_Transaction_Posting .
660 Bool . Filter_Posting_Account <$>
664 -- ** Read 'Filter_Balance'
666 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
667 => ParsecT s Context m (Filter_Bool (Filter_Balance t))
669 Data.Foldable.foldr Filter.And Filter.Any <$>
672 >> R.lookAhead R.anyToken
673 >> filter_bool filter_balance_terms
676 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
677 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Balance t)))]
678 filter_balance_terms =
679 [ jump [ "AT" ] filter_account_operator $
680 Bool . Filter_Balance_Account .
681 Bool . Filter_Account_Tag <$>
683 , jump [ "RD", "debit" ] filter_amount_operator
684 ( Bool . Filter_Balance_Positive
686 , jump [ "RC", "credit" ] filter_amount_operator
687 ( Bool . Filter_Balance_Negative
689 , jump [ "RB", "balance" ] filter_amount_operator
690 ( Bool . Filter_Balance_Amount
693 Bool . Filter_Balance_Account <$>
697 -- ** Read 'Filter_GL'
699 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
700 => ParsecT s Context m (Filter_Bool (Filter_GL t))
702 Data.Foldable.foldr Filter.And Filter.Any <$>
705 >> R.lookAhead R.anyToken
706 >> filter_bool filter_gl_terms
709 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
710 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_GL t)))]
712 [ jump [ "AT" ] filter_account_operator $
713 Bool . Filter_GL_Account .
714 Bool . Filter_Account_Tag <$>
716 , jump [ "D", "debit" ] filter_amount_operator
717 ( Bool . Filter_GL_Amount_Positive
719 , jump [ "C", "credit" ] filter_amount_operator
720 ( Bool . Filter_GL_Amount_Negative
722 , jump [ "B", "balance" ] filter_amount_operator
723 ( Bool . Filter_GL_Amount_Balance
725 , jump [ "RD", "running-debit" ] filter_amount_operator
726 ( Bool . Filter_GL_Sum_Positive
728 , jump [ "RC", "running-credit" ] filter_amount_operator
729 ( Bool . Filter_GL_Sum_Negative
731 , jump [ "RB", "running-balance" ] filter_amount_operator
732 ( Bool . Filter_GL_Sum_Balance
735 Bool . Filter_GL_Account <$>