1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE FlexibleInstances #-}
4 {-# LANGUAGE NamedFieldPuns #-}
5 {-# LANGUAGE ScopedTypeVariables #-}
6 {-# LANGUAGE StandaloneDeriving #-}
7 {-# LANGUAGE TupleSections #-}
8 {-# LANGUAGE TypeFamilies #-}
9 {-# OPTIONS_GHC -fno-warn-orphans #-}
10 module Hcompta.Filter.Read where
12 import Control.Applicative ((<$>), (<*))
13 import Control.Exception (assert)
14 import Control.Monad (Monad(..), liftM, join, when, (=<<), (>=>), void, forM)
18 import Data.Either (Either(..))
19 import Data.Eq (Eq(..))
20 import qualified Data.Foldable
21 import Data.Foldable (Foldable(..))
22 import Data.Functor (Functor(..))
23 import Data.Functor.Identity (Identity)
24 import Data.List ((++), concat, map, reverse)
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 Text.Parsec (Stream, ParsecT, (<|>), (<?>))
47 import Text.Show (Show(..))
49 -- import qualified Hcompta.Amount as Amount
50 import Hcompta.Date (Date)
51 import qualified Hcompta.Date as Date
53 import qualified Hcompta.Filter as Filter
54 import qualified Hcompta.Filter.Amount as Filter.Amount
55 import qualified Hcompta.Filter.Amount.Read as Amount.Read
56 import qualified Hcompta.Filter.Date.Read as Date.Read
57 import Hcompta.Lib.Interval (Interval)
58 import qualified Hcompta.Lib.Interval as Interval
59 import qualified Hcompta.Lib.Parsec as R
60 import qualified Hcompta.Lib.Regex as Regex
61 import Hcompta.Posting (Posting(..))
62 -- import Hcompta.Polarize
63 -- import qualified Hcompta.Quantity as Quantity
64 import qualified Hcompta.Unit as Unit
69 { context_date :: Date
70 } deriving (Data, Eq, Show, Typeable)
75 { context_date = Date.nil
81 | Error_Filter_Date Date.Read.Error
82 | Error_Filter_Date_Interval (Integer, Integer)
87 ( Stream s (R.Error_State Error Identity) Char
90 => ParsecT s Context (R.Error_State Error Identity) (Filter_Bool t)
91 -> s -> IO (Either [R.Error Error] (Filter_Bool t))
93 context_date <- Time.getCurrentTime
95 R.runParser_with_Error t context{context_date} "" s
97 -- * Read 'Filter_Text'
99 :: (Stream s m Char, Monad r)
100 => ParsecT s u m (String -> r Filter_Text)
103 [ R.char '~' >> return (Regex.of_StringM >=> (return . Filter_Text_Regex))
104 , R.char '=' >> return (\s -> return (Filter_Text_Exact $ Text.pack s))
105 , return (\s -> return (Filter_Text_Exact $ Text.pack s))
110 => ParsecT s u m String
111 filter_text_operator =
117 -- * Read 'Filter_Ord'
119 :: (Stream s m Char, Ord o)
120 => ParsecT s u m (o -> Filter_Ord o)
123 [ R.string "=" >> return (Filter_Ord Eq)
124 , R.string "<=" >> return (Filter_Ord Le) -- NOTE: before "<"
125 , R.string ">=" >> return (Filter_Ord Ge) -- NOTE: before ">"
126 , R.string "<" >> return (Filter_Ord Lt)
127 , R.string ">" >> return (Filter_Ord Gt)
132 => ParsecT s u m String
133 filter_ord_operator =
142 -- * Read 'Filter_Num_Abs'
144 :: (Stream s m Char, Num n)
145 => ParsecT s u m (Filter_Ord n -> m (Either (Filter_Ord n) (Filter_Num_Abs n)))
148 [ R.char '+' >> return (return . Right . Filter_Num_Abs)
149 , return (return . Left)
152 text :: Stream s m Char => String -> ParsecT s Context m Text
157 , R.many $ R.noneOf ("() " ++ none_of)
160 borders = R.between (R.char '(') (R.char ')')
161 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
162 preserve_inside = inside >>= (\x -> return $ '(':(x++[')']))
164 -- * Read 'Filter_Bool'
167 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
168 -> ParsecT s u m (Filter_Bool t)
170 R.buildExpressionParser
171 filter_bool_operators
172 (filter_bool_term terms)
175 filter_bool_operators
177 => R.OperatorTable s u m (Filter.Filter_Bool t)
178 filter_bool_operators =
179 [ [ prefix "- " Filter.Not
181 , [ binary " & " Filter.And R.AssocLeft
183 , [ binary " + " Filter.Or R.AssocLeft
184 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
188 binary name fun = R.Infix (filter_bool_operator name >> return fun)
189 prefix name fun = R.Prefix (filter_bool_operator name >> return fun)
190 -- postfix name fun = Text.Parsec.Expr.Postfix (filter_bool_operator name >> return fun)
194 => String -> ParsecT s u m ()
195 filter_bool_operator name =
198 >> R.notFollowedBy filter_bool_operator_letter
202 filter_bool_operator_letter
203 :: Stream s m Char => ParsecT s u m Char
204 filter_bool_operator_letter =
205 R.oneOf ['-', '&', '+']
209 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
210 -> ParsecT s u m (Filter_Bool t)
211 filter_bool_term terms = (do
213 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
214 >> (return $ parens $
215 Data.Foldable.foldr Filter.And Filter.Any <$>
216 R.many (R.try (R.spaces >> expr)) ))
218 ) <* R.spaces) <?> "boolean-term"
221 R.lookAhead (R.try R.anyToken)
222 >> R.notFollowedBy (R.char ')')
231 (R.spaces >> R.char '(')
232 (R.spaces >> R.char ')')
234 bool :: Stream s m Char => ParsecT s u m Bool
249 jump :: Stream s m Char
254 jump prefixes next r =
255 R.choice_try (map (\s -> R.string s >> return r) prefixes)
256 <* R.lookAhead (R.try next)
258 -- * Read 'Filter_Account'
259 account_section_sep :: Char
260 account_section_sep = ':'
264 => ParsecT s u m (Filter_Path Account_Section)
265 filter_account_path = do
266 R.notFollowedBy $ R.space_horizontal
268 R.option (Filter_Ord Eq ()) $ R.try $
269 (\f -> f ()) <$> filter_ord
272 (R.many (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c))))
273 (R.char account_section_sep)
274 sections <- forM strings $ \s ->
276 "" -> return Filter_Path_Section_Many
277 "*" -> return Filter_Path_Section_Any
278 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
279 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
280 return $ Filter_Path o $
281 (if null sections then [Filter_Path_Section_Many] else sections)
286 ) => ParsecT s Context m (Filter_Account a)
288 Data.Foldable.foldr Filter.And Filter.Any <$>
291 >> R.lookAhead R.anyToken
292 >> filter_bool filter_account_terms
294 filter_account_terms ::
297 ) => [ParsecT s Context m (ParsecT s Context m (Filter_Account a))]
298 filter_account_terms =
299 [ jump [ "AT" ] filter_account_operator $
300 Bool . Filter.Filter_Account_Tag <$>
303 Bool . Filter.Filter_Account_Path <$>
307 filter_account_operator
309 => ParsecT s u m String
310 filter_account_operator =
312 [ filter_ord_operator
315 -- * Read 'Filter_Amount'
317 :: (Stream s m Char, Amount a, Amount_Quantity a ~ Filter.Amount.Quantity)
318 => (Filter_Ord (Amount_Quantity a) -> Filter_Polarized (Amount_Quantity a))
319 -> ParsecT s u m (Filter_Amount a)
320 filter_amount flt_polarized = do
321 R.notFollowedBy $ R.space_horizontal
325 amt <- Amount.Read.amount
327 (Bool $ Filter_Amount_Section_Quantity (flt_polarized $ flt_ord $ Filter.Amount.amount_quantity amt))
328 (case Unit.unit_text $ Filter.amount_unit amt of
329 u | Text.null u -> Any
330 u -> Bool $ Filter_Amount_Section_Unit (Filter_Unit (Filter_Text_Exact u)))
333 unit_ <- Amount.Read.unit >>= flt_ord . Text.unpack . Unit.unit_text
334 return $ Bool $ Filter_Amount_Section_Unit (Filter_Unit unit_)
337 filter_amount_operator
339 => ParsecT s u m String
340 filter_amount_operator =
342 [ filter_ord_operator
343 , filter_text_operator
346 -- * Read 'Filter_Date'
348 :: (Stream s (R.Error_State Error m) Char, Monad m)
349 => ParsecT s Context (R.Error_State Error m) (Filter_Bool Filter_Date)
353 (return $ read_date_pattern)
354 , filter_ord >>= \tst ->
357 let (year, _, _) = Date.gregorian $ context_date ctx
358 liftM (Bool . Filter_Date_UTC . tst) $
359 Date.Read.date Error_Filter_Date (Just year)
363 :: (Stream s (R.Error_State Error m) Char, Monad m)
364 => ParsecT s u (R.Error_State Error m) (Filter_Bool Filter_Date)
365 read_date_pattern = (do
366 let read2 = of_digits <$> (R.try (R.count 2 R.digit) <|> R.count 1 R.digit)
367 n0 <- read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
368 n1 <- R.option Nothing $ R.try $ do
370 Just <$> read_interval Error_Filter_Date_Interval read2
371 n2 <- R.option Nothing $ R.try $ do
373 Just <$> read_interval Error_Filter_Date_Interval read2
374 let (year, month, dom) =
376 (Nothing, Nothing) ->
379 , Interval.unlimited )
380 (Just d1, Nothing) ->
384 (Nothing, Just _d2) -> assert False undefined
385 (Just d1, Just d2) ->
389 (hour, minute, second) <-
390 R.option (Interval.unlimited, Interval.unlimited, Interval.unlimited) $ R.try $ do
392 hour <- read_interval Error_Filter_Date_Interval read2
393 sep <- Date.Read.hour_separator
394 minute <- read_interval Error_Filter_Date_Interval read2
395 second <- R.option Interval.unlimited $ R.try $ do
397 read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
398 -- tz <- R.option Time.utc $ R.try $ do
399 -- -- R.skipMany $ R.space_horizontal
400 -- Date.Read.time_zone
409 [ just_when_limited (Filter_Date_Year . Filter_Interval_In) year
410 , just_when_limited (Filter_Date_Month . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) month)
411 , just_when_limited (Filter_Date_DoM . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) dom)
412 , just_when_limited (Filter_Date_Hour . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) hour)
413 , just_when_limited (Filter_Date_Minute . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) minute)
414 , just_when_limited (Filter_Date_Second . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) second)
418 of_digits :: Num n => [Char] -> n
419 of_digits = fromInteger . R.integer_of_digits 10
420 just_when_limited f x =
421 if x == Interval.unlimited
423 else Just $ Bool $ f x
426 :: (Stream s (R.Error_State e m) Char, Monad m, Ord x)
428 -> ParsecT s u (R.Error_State e m) x
429 -> ParsecT s u (R.Error_State e m) (Interval (Interval.Unlimitable x))
430 read_interval err read_digits = do
432 [ R.string ".." >> return Interval.Unlimited_low
433 , Interval.Limited <$> read_digits
436 [ when (l /= Interval.Unlimited_low)
437 (void $ R.string "..") >> do
439 [ Interval.Limited <$> read_digits
440 , return Interval.Unlimited_high
442 case (Interval.<=..<=) l h of
443 Nothing -> R.fail_with "interval" (err $ (Interval.limited l, Interval.limited h))
447 Interval.Limited _ -> Interval.point l
448 _ -> Interval.unlimited
453 => ParsecT s u m String
454 filter_date_operator =
457 -- * Read 'Filter_Description'
460 => ParsecT s u m Filter_Text
461 filter_description = (do
462 make_filter_text <- filter_text
463 R.between (R.char '"') (R.char '"') $
464 make_filter_text =<< (R.many $ R.try $ R.satisfy (/= '"'))
467 filter_description_operator
469 => ParsecT s u m String
470 filter_description_operator =
473 -- * Read 'Filter_Tag'
476 => ParsecT s u m Filter_Tags
478 R.notFollowedBy $ R.space_horizontal
479 Filter_Ord o () <- (\f -> f ()) <$> filter_ord
482 [ R.char '<' >> return Filter_Tag_Value_First
483 , R.char '>' >> return Filter_Tag_Value_Last
484 , return Filter_Tag_Value_Any
488 (R.many (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c))))
489 (R.char account_section_sep)
490 sections <- forM strings $ \s ->
492 "" -> return Filter_Path_Section_Many
493 "*" -> return Filter_Path_Section_Any
494 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
495 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
496 case reverse sections of
498 [p] -> return $ Bool $ Filter_Tag_Path $ Filter_Path o [p]
501 (Bool $ Filter_Tag_Path $ Filter_Path o $ reverse rev_path)
502 (Bool $ Filter_Tag_Value $ filter_tag_value $
504 Filter_Path_Section_Any -> Filter_Text_Any
505 Filter_Path_Section_Many -> Filter_Text_Any
506 Filter_Path_Section_Text ft -> ft
511 => ParsecT s u m String
512 filter_tag_operator = do
513 void filter_ord_operator
520 -- * Read 'Filter_Posting'
524 , Amount_Quantity (Posting_Amount p) ~ Filter.Amount.Quantity
525 ) => ParsecT s Context m (Filter_Bool (Filter_Posting p))
527 Data.Foldable.foldr Filter.And Filter.Any <$>
530 >> R.lookAhead R.anyToken
531 >> filter_bool filter_posting_terms
533 filter_posting_terms ::
536 , Amount_Quantity (Posting_Amount p) ~ Filter.Amount.Quantity
537 ) => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Posting p)))]
538 filter_posting_terms =
539 [ jump [ "a", "amount" ] filter_amount_operator $
540 Bool . Filter.Filter_Posting_Amount <$>
541 filter_amount Filter_Polarized_Sum
542 , jump [ "[]" ] (return ()) $
543 return $ Bool $ Filter_Posting_Type $
544 Filter_Posting_Type_Exact Posting_Type_Virtual
546 Bool . Filter_Posting_Account <$>
550 -- * Read 'Filter_Transaction'
551 filter_transaction ::
552 ( Stream s (R.Error_State Error m) Char
554 , Filter.Transaction t
555 , Amount_Quantity (Posting_Amount (Transaction_Posting t)) ~ Filter.Amount.Quantity
556 ) => ParsecT s Context (R.Error_State Error m)
557 (Filter_Bool (Filter_Transaction t))
559 glue_posting . Data.Foldable.foldr Filter.And Filter.Any <$>
562 >> R.lookAhead R.anyToken
563 >> filter_bool filter_transaction_terms
567 => Filter_Bool (Filter_Transaction t)
568 -> Filter_Bool (Filter_Transaction t)
573 (Or b0 b1) -> Or (glue_posting b0) (glue_posting b1)
575 case glue_posting b of
576 Bool (Filter_Transaction_Posting p) ->
577 Bool $ Filter_Transaction_Posting $ Not p
580 case (glue_posting b0, glue_posting b1) of
581 ( Bool (Filter_Transaction_Posting p0),
582 Bool (Filter_Transaction_Posting p1)
583 ) -> Bool $ Filter_Transaction_Posting $ And p0 p1
584 (Bool (Filter_Transaction_Posting p0), Any) ->
585 Bool $ Filter_Transaction_Posting $ p0
586 (Any, Bool (Filter_Transaction_Posting p1)) ->
587 Bool $ Filter_Transaction_Posting $ p1
588 (b0', b1') -> And b0' b1'
590 filter_transaction_terms ::
591 ( Stream s (R.Error_State Error m) Char
592 , Filter.Transaction t
594 , Amount_Quantity (Posting_Amount (Transaction_Posting t)) ~ Filter.Amount.Quantity
595 ) => [ParsecT s Context (R.Error_State Error m)
596 (ParsecT s Context (R.Error_State Error m)
597 (Filter_Bool (Filter_Transaction t)))]
598 filter_transaction_terms =
599 -- , jump [ "atag" ] comp_text parseFilterATag
600 -- , jump [ "code" ] comp_text parseFilterCode
601 [ jump [ "AT" ] filter_account_operator $
602 Bool . Filter_Transaction_Posting .
603 Bool . Filter_Posting_Account .
604 Bool . Filter_Account_Tag <$>
606 , jump [ "date", "d" ] filter_date_operator
607 (Bool . Filter.Filter_Transaction_Date <$> filter_date)
608 , jump [ "tag", "T" ] filter_tag_operator
609 (Bool . Filter.Filter_Transaction_Tag <$> filter_tag)
610 , jump [ "debit", "D" ] filter_amount_operator
612 . Filter_Transaction_Posting
614 . Filter_Posting_Amount
615 ) <$> filter_amount Filter_Polarized_Positive)
616 , jump [ "credit", "C" ] filter_amount_operator
618 . Filter_Transaction_Posting
620 . Filter_Posting_Amount
621 ) <$> filter_amount Filter_Polarized_Negative)
622 , jump [ "wording", "W" ] filter_description_operator
623 (Bool . Filter.Filter_Transaction_Description <$> filter_description)
624 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
625 -- , jump [ "real" ] (R.char '=') parseFilterReal
626 -- , jump [ "status" ] (R.char '=') parseFilterStatus
627 -- , jump [ "sym" ] comp_text parseFilterSym
628 -- , R.lookAhead comp_num >> return parseFilterAmount
630 Bool . Filter_Transaction_Posting .
631 Bool . Filter_Posting_Account <$>
635 -- * Read 'Filter_Balance'
639 , Amount_Quantity (Filter.Balance_Amount t) ~ Filter.Amount.Quantity
640 ) => ParsecT s Context m (Filter_Bool (Filter_Balance t))
642 Data.Foldable.foldr Filter.And Filter.Any <$>
645 >> R.lookAhead R.anyToken
646 >> filter_bool filter_balance_terms
648 filter_balance_terms ::
651 , Amount_Quantity (Filter.Balance_Amount t) ~ Filter.Amount.Quantity
652 ) => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Balance t)))]
653 filter_balance_terms =
654 [ jump [ "AT" ] filter_account_operator $
655 Bool . Filter_Balance_Account .
656 Bool . Filter_Account_Tag <$>
658 , jump [ "RD", "debit" ] filter_amount_operator
659 ( Bool . Filter_Balance_Amount
660 <$> filter_amount Filter_Polarized_Positive)
661 , jump [ "RC", "credit" ] filter_amount_operator
662 ( Bool . Filter_Balance_Amount
663 <$> filter_amount Filter_Polarized_Negative)
664 , jump [ "RB", "balance" ] filter_amount_operator
665 ( Bool . Filter_Balance_Amount
666 <$> filter_amount Filter_Polarized_Sum)
668 Bool . Filter_Balance_Account <$>
672 -- * Read 'Filter_GL'
676 , Amount_Quantity (Filter.GL_Amount t) ~ Filter.Amount.Quantity
677 ) => ParsecT s Context m (Filter_Bool (Filter_GL t))
679 Data.Foldable.foldr Filter.And Filter.Any <$>
682 >> R.lookAhead R.anyToken
683 >> filter_bool filter_gl_terms
688 , Amount_Quantity (Filter.GL_Amount t) ~ Filter.Amount.Quantity
689 ) => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_GL t)))]
691 [ jump [ "AT" ] filter_account_operator $
692 Bool . Filter_GL_Account .
693 Bool . Filter_Account_Tag <$>
695 , jump [ "D", "debit" ] filter_amount_operator
698 <$> filter_amount Filter_Polarized_Positive)
699 , jump [ "C", "credit" ] filter_amount_operator
702 <$> filter_amount Filter_Polarized_Negative)
703 , jump [ "B", "balance" ] filter_amount_operator
706 <$> filter_amount Filter_Polarized_Sum)
707 , jump [ "RD", "running-debit" ] filter_amount_operator
708 ( Bool . Filter_GL_Sum
709 <$> filter_amount Filter_Polarized_Positive)
710 , jump [ "RC", "running-credit" ] filter_amount_operator
711 ( Bool . Filter_GL_Sum
712 <$> filter_amount Filter_Polarized_Negative)
713 , jump [ "RB", "running-balance" ] filter_amount_operator
714 ( Bool . Filter_GL_Sum
715 <$> filter_amount Filter_Polarized_Sum)
717 Bool . Filter_GL_Account <$>