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 qualified Data.List.NonEmpty as NonEmpty
20 -- import Data.List.NonEmpty (NonEmpty(..))
21 import Data.Maybe (catMaybes)
22 import qualified Data.Time.Clock as Time
23 import qualified Text.Parsec.Expr as R
24 import qualified Text.Parsec as R hiding
36 -- import qualified Text.Parsec.Expr as R
37 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
38 import Data.String (fromString)
39 import qualified Data.Text as Text
40 import Data.Text (Text)
41 import Data.Typeable ()
43 import Hcompta.Lib.Interval (Interval)
44 import qualified Hcompta.Lib.Interval as Interval
45 import qualified Hcompta.Lib.Regex as Regex
46 -- import Hcompta.Lib.Regex (Regex)
47 import qualified Hcompta.Account.Read as Account.Read
48 import qualified Hcompta.Amount as Amount
49 import Hcompta.Amount (Amount)
50 import qualified Hcompta.Amount.Read as Amount.Read
51 import qualified Hcompta.Amount.Unit as Unit
52 import qualified Hcompta.Date as Date
53 import Hcompta.Date (Date)
54 import qualified Hcompta.Date.Read as Date.Read
55 import qualified Hcompta.Filter as Filter
56 import Hcompta.Filter hiding (Amount)
57 import qualified Hcompta.Lib.Parsec as R
65 { context_date :: Date
66 } deriving (Data, Eq, Show, Typeable)
71 { context_date = Date.nil
78 | Error_Filter_Date Date.Read.Error
79 | Error_Filter_Date_Interval (Integer, Integer)
85 ( Stream s (R.Error_State Error Identity) Char
88 => ParsecT s Context (R.Error_State Error Identity) (Filter_Bool t)
89 -> s -> IO (Either [R.Error Error] (Filter_Bool t))
91 context_date <- Time.getCurrentTime
93 R.runParser_with_Error t context{context_date} "" s
95 -- ** Read 'Filter_Text'
97 :: (Stream s m Char, Monad r)
98 => ParsecT s u m (String -> r Filter_Text)
101 [ R.char '~' >> return (Regex.of_StringM >=> (return . Filter_Text_Regex))
102 , R.char '=' >> return (\s -> return (Filter_Text_Exact $ Text.pack s))
103 , return (\s -> return (Filter_Text_Exact $ Text.pack s))
108 => ParsecT s u m String
109 filter_text_operator =
115 -- ** Read 'Filter_Ord'
117 :: (Stream s m Char, Ord o)
118 => ParsecT s u m (o -> Filter_Ord o)
121 [ R.string "=" >> return (Filter_Ord Eq)
122 , R.string "<=" >> return (Filter_Ord Le) -- NOTE: before "<"
123 , R.string ">=" >> return (Filter_Ord Ge) -- NOTE: before ">"
124 , R.string "<" >> return (Filter_Ord Lt)
125 , R.string ">" >> return (Filter_Ord Gt)
130 => ParsecT s u m String
131 filter_ord_operator =
140 -- ** Read 'Filter_Num_Abs'
142 :: (Stream s m Char, Num n)
143 => ParsecT s u m (Filter_Ord n -> m (Either (Filter_Ord n) (Filter_Num_Abs n)))
146 [ R.char '+' >> return (return . Right . Filter_Num_Abs)
147 , return (return . Left)
150 text :: Stream s m Char => String -> ParsecT s Context m Text
155 , R.many $ R.noneOf ("() " ++ none_of)
158 borders = R.between (R.char '(') (R.char ')')
159 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
160 preserve_inside = inside >>= (\x -> return $ '(':(x++[')']))
162 -- ** Read 'Filter_Bool'
166 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
167 -> ParsecT s u m (Filter_Bool t)
169 R.buildExpressionParser
170 filter_bool_operators
171 (filter_bool_term terms)
174 filter_bool_operators
176 => R.OperatorTable s u m (Filter.Filter_Bool t)
177 filter_bool_operators =
178 [ [ prefix "- " Filter.Not
180 , [ binary " & " Filter.And R.AssocLeft
182 , [ binary " + " Filter.Or R.AssocLeft
183 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
187 binary name fun = R.Infix (filter_bool_operator name >> return fun)
188 prefix name fun = R.Prefix (filter_bool_operator name >> return fun)
189 -- postfix name fun = Text.Parsec.Expr.Postfix (filter_bool_operator name >> return fun)
193 => String -> ParsecT s u m ()
194 filter_bool_operator name =
197 >> R.notFollowedBy filter_bool_operator_letter
201 filter_bool_operator_letter
202 :: Stream s m Char => ParsecT s u m Char
203 filter_bool_operator_letter =
204 R.oneOf ['-', '&', '+']
208 => [ParsecT s u m (ParsecT s u m (Filter_Bool t))]
209 -> ParsecT s u m (Filter_Bool t)
210 filter_bool_term terms = (do
212 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
213 >> (return $ parens $
214 Data.Foldable.foldr Filter.And Filter.Any <$>
215 R.many (R.try (R.spaces >> expr)) ))
217 ) <* R.spaces) <?> "boolean-term"
220 R.lookAhead (R.try R.anyToken)
221 >> R.notFollowedBy (R.char ')')
230 (R.spaces >> R.char '(')
231 (R.spaces >> R.char ')')
233 bool :: Stream s m Char => ParsecT s u m Bool
248 jump :: Stream s m Char
253 jump prefixes next r =
254 R.choice_try (map (\s -> R.string s >> return r) prefixes)
255 <* R.lookAhead (R.try next)
257 -- ** Read 'Filter_Account'
258 -- | Return the 'Filter_Posting_Type' and stripped 'Account' of the given 'Account'.
259 account_posting_type :: [String] -> (Filter_Posting_Type, [String])
260 account_posting_type acct =
262 (Filter_Posting_Type_Any, acct)
263 (Filter_Posting_Type_Exact Posting_Type_Virtual,) $ do
269 ']':rs -> Just $ [reverse rs]
272 let rs = reverse ns in
273 case reverse $ Data.List.head rs of
274 ']':ln -> Just $ fn : reverse (reverse ln : Data.List.tail rs)
280 => ParsecT s u m (Filter_Posting_Type, Filter_Account)
282 R.notFollowedBy $ R.space_horizontal
284 R.option (Filter_Ord Eq ()) $ R.try $
285 (\f -> f ()) <$> filter_ord
286 (Filter_Path o <$>) <$> account
288 account :: Stream s m Char => ParsecT s u m (Filter_Posting_Type, [Filter_Path_Section])
291 account_posting_type <$>
293 (R.many (R.satisfy (\c -> c /= Account.Read.section_sep && not (Data.Char.isSpace c))))
294 (R.char Account.Read.section_sep)
295 sections <- forM strings $ \s ->
297 "" -> return Filter_Path_Section_Many
298 "*" -> return Filter_Path_Section_Any
299 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
300 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
301 return (pt, if null sections then [Filter_Path_Section_Many] else sections)
303 -- ** Read 'Filter_Amount'
306 => ParsecT s u m (Filter_Amount Amount)
308 R.notFollowedBy $ R.space_horizontal
312 amt <- Amount.Read.amount
314 (Bool $ Filter_Amount_Section_Quantity (tst $ Amount.quantity amt))
315 (case Unit.text $ Amount.unit amt of
316 unit | Text.null unit -> Any
317 unit -> Bool $ Filter_Amount_Section_Unit (Filter_Unit (Filter_Text_Exact unit)))
320 unit <- Amount.Read.unit >>= tst . Text.unpack . Unit.text
321 return $ Bool $ Filter_Amount_Section_Unit (Filter_Unit unit)
324 filter_amount_operator
326 => ParsecT s u m String
327 filter_amount_operator =
329 [ filter_ord_operator
330 , filter_text_operator
333 -- ** Read 'Filter_Date'
335 :: (Stream s (R.Error_State Error m) Char, Monad m)
336 => ParsecT s Context (R.Error_State Error m) (Filter_Bool Filter_Date)
340 (return $ read_date_pattern)
341 , filter_ord >>= \tst ->
344 let (year, _, _) = Date.gregorian $ context_date ctx
345 liftM (Bool . Filter_Date_UTC . tst) $
346 Date.Read.date Error_Filter_Date (Just year)
350 :: (Stream s (R.Error_State Error m) Char, Monad m)
351 => ParsecT s u (R.Error_State Error m) (Filter_Bool Filter_Date)
352 read_date_pattern = (do
353 let read2 = of_digits <$> (R.try (R.count 2 R.digit) <|> R.count 1 R.digit)
354 n0 <- read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
355 n1 <- R.option Nothing $ R.try $ do
357 Just <$> read_interval Error_Filter_Date_Interval read2
358 n2 <- R.option Nothing $ R.try $ do
360 Just <$> read_interval Error_Filter_Date_Interval read2
361 let (year, month, dom) =
363 (Nothing, Nothing) ->
366 , Interval.unlimited )
367 (Just d1, Nothing) ->
371 (Nothing, Just _d2) -> assert False undefined
372 (Just d1, Just d2) ->
376 (hour, minute, second) <-
377 R.option (Interval.unlimited, Interval.unlimited, Interval.unlimited) $ R.try $ do
379 hour <- read_interval Error_Filter_Date_Interval read2
380 sep <- Date.Read.hour_separator
381 minute <- read_interval Error_Filter_Date_Interval read2
382 second <- R.option Interval.unlimited $ R.try $ do
384 read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
385 -- tz <- R.option Time.utc $ R.try $ do
386 -- -- R.skipMany $ R.space_horizontal
387 -- Date.Read.time_zone
396 [ just_when_limited (Filter_Date_Year . Filter_Interval_In) year
397 , just_when_limited (Filter_Date_Month . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) month)
398 , just_when_limited (Filter_Date_DoM . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) dom)
399 , just_when_limited (Filter_Date_Hour . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) hour)
400 , just_when_limited (Filter_Date_Minute . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) minute)
401 , just_when_limited (Filter_Date_Second . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) second)
405 of_digits :: Num n => [Char] -> n
406 of_digits = fromInteger . R.integer_of_digits 10
407 just_when_limited f x =
408 if x == Interval.unlimited
410 else Just $ Bool $ f x
413 :: (Stream s (R.Error_State e m) Char, Monad m, Ord x)
415 -> ParsecT s u (R.Error_State e m) x
416 -> ParsecT s u (R.Error_State e m) (Interval (Interval.Unlimitable x))
417 read_interval err read_digits = do
419 [ R.string ".." >> return Interval.Unlimited_low
420 , Interval.Limited <$> read_digits
423 [ when (l /= Interval.Unlimited_low)
424 (void $ R.string "..") >> do
426 [ Interval.Limited <$> read_digits
427 , return Interval.Unlimited_high
429 case (Interval.<=..<=) l h of
430 Nothing -> R.fail_with "interval" (err $ (Interval.limited l, Interval.limited h))
434 Interval.Limited _ -> Interval.point l
435 _ -> Interval.unlimited
440 => ParsecT s u m String
441 filter_date_operator =
444 -- ** Read 'Filter_Description'
447 => ParsecT s u m Filter_Text
448 filter_description = (do
449 make_filter_text <- filter_text
450 R.between (R.char '"') (R.char '"') $
451 make_filter_text =<< (R.many $ R.try $ R.satisfy (/= '"'))
454 filter_description_operator
456 => ParsecT s u m String
457 filter_description_operator =
460 -- ** Read 'Filter_Tag'
464 => ParsecT s u m Filter_Tag
466 R.notFollowedBy $ R.space_horizontal
467 Filter_Ord o () <- (\f -> f ()) <$> filter_ord
470 [ R.char '^' >> return Filter_Tag_Value_First
471 , R.char '$' >> return Filter_Tag_Value_Last
472 , return Filter_Tag_Value_Any
476 (R.many (R.satisfy (\c -> c /= Account.Read.section_sep && not (Data.Char.isSpace c))))
477 (R.char Account.Read.section_sep)
478 sections <- forM strings $ \s ->
480 "" -> return Filter_Path_Section_Many
481 "*" -> return Filter_Path_Section_Any
482 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
483 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
484 case reverse sections of
486 [p] -> return $ Bool $ Filter_Tag_Path $ Filter_Path o [p]
489 (Bool $ Filter_Tag_Path $ Filter_Path o $ reverse rev_path)
490 (Bool $ Filter_Tag_Value $ filter_tag_value $
492 Filter_Path_Section_Any -> Filter_Text_Any
493 Filter_Path_Section_Many -> Filter_Text_Any
494 Filter_Path_Section_Text ft -> ft
499 => ParsecT s u m String
500 filter_tag_operator = do
501 void filter_ord_operator
508 -- ** Read 'Filter_Posting'
512 , Posting_Amount p ~ Amount
514 => ParsecT s Context m (Filter_Bool (Filter_Posting p))
516 Data.Foldable.foldr Filter.And Filter.Any <$>
519 >> R.lookAhead R.anyToken
520 >> filter_bool filter_posting_terms
522 filter_posting_terms ::
525 , Posting_Amount p ~ Amount
527 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Posting p)))]
528 filter_posting_terms =
529 [ jump [ "a", "amount" ] filter_amount_operator
530 ((Bool . Filter.Filter_Posting_Amount) <$> filter_amount)
534 (Bool $ Filter_Posting_Account a)
535 (Bool $ Filter_Posting_Type pt)
539 -- ** Read 'Filter_Transaction'
540 filter_transaction ::
541 ( Stream s (R.Error_State Error m) Char
543 , Filter.Transaction t
544 , Posting_Amount (Transaction_Posting t) ~ Amount
545 ) => ParsecT s Context (R.Error_State Error m)
546 (Filter_Bool (Filter_Transaction t))
548 glue_posting . Data.Foldable.foldr Filter.And Filter.Any <$>
551 >> R.lookAhead R.anyToken
552 >> filter_bool filter_transaction_terms
556 => Filter_Bool (Filter_Transaction t)
557 -> Filter_Bool (Filter_Transaction t)
562 (Or b0 b1) -> Or (glue_posting b0) (glue_posting b1)
564 case glue_posting b of
565 Bool (Filter_Transaction_Posting p) ->
566 Bool $ Filter_Transaction_Posting $ Not p
569 case (glue_posting b0, glue_posting b1) of
570 ( Bool (Filter_Transaction_Posting p0),
571 Bool (Filter_Transaction_Posting p1)
572 ) -> Bool $ Filter_Transaction_Posting $ And p0 p1
573 (Bool (Filter_Transaction_Posting p0), Any) ->
574 Bool $ Filter_Transaction_Posting $ p0
575 (Any, Bool (Filter_Transaction_Posting p1)) ->
576 Bool $ Filter_Transaction_Posting $ p1
577 (b0', b1') -> And b0' b1'
579 filter_transaction_terms ::
580 ( Stream s (R.Error_State Error m) Char
581 , Filter.Transaction t
583 , Posting_Amount (Transaction_Posting t) ~ Amount
585 => [ParsecT s Context (R.Error_State Error m)
586 (ParsecT s Context (R.Error_State Error m)
587 (Filter_Bool (Filter_Transaction t)))]
588 filter_transaction_terms =
589 -- , jump [ "atag" ] comp_text parseFilterATag
590 -- , jump [ "code" ] comp_text parseFilterCode
591 [ jump [ "date", "d" ] filter_date_operator
592 (Bool . Filter.Filter_Transaction_Date <$> filter_date)
593 , jump [ "tag", "T" ] filter_tag_operator
594 (Bool . Filter.Filter_Transaction_Tag <$> filter_tag)
595 , jump [ "debit", "D" ] filter_amount_operator
597 . Filter_Transaction_Posting
599 . Filter_Posting_Positive
601 , jump [ "credit", "C" ] filter_amount_operator
603 . Filter_Transaction_Posting
605 . Filter_Posting_Negative
607 , jump [ "wording", "W" ] filter_description_operator
608 (Bool . Filter.Filter_Transaction_Description <$> filter_description)
609 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
610 -- , jump [ "real" ] (R.char '=') parseFilterReal
611 -- , jump [ "status" ] (R.char '=') parseFilterStatus
612 -- , jump [ "sym" ] comp_text parseFilterSym
613 -- , R.lookAhead comp_num >> return parseFilterAmount
617 Filter_Transaction_Posting $
618 And (Bool $ Filter_Posting_Account a)
619 (Bool $ Filter_Posting_Type pt)
623 -- ** Read 'Filter_Balance'
625 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
626 => ParsecT s Context m (Filter_Bool (Filter_Balance t))
628 Data.Foldable.foldr Filter.And Filter.Any <$>
631 >> R.lookAhead R.anyToken
632 >> filter_bool filter_balance_terms
635 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
636 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Balance t)))]
637 filter_balance_terms =
638 [ jump [ "RD", "debit" ] filter_amount_operator
639 ( Bool . Filter_Balance_Positive
641 , jump [ "RC", "credit" ] filter_amount_operator
642 ( Bool . Filter_Balance_Negative
644 , jump [ "RB", "balance" ] filter_amount_operator
645 ( Bool . Filter_Balance_Amount
648 ( Bool . Filter_Balance_Account . snd
652 -- ** Read 'Filter_GL'
654 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
655 => ParsecT s Context m (Filter_Bool (Filter_GL t))
657 Data.Foldable.foldr Filter.And Filter.Any <$>
660 >> R.lookAhead R.anyToken
661 >> filter_bool filter_gl_terms
664 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
665 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_GL t)))]
667 [ jump [ "D", "debit" ] filter_amount_operator
668 ( Bool . Filter_GL_Amount_Positive
670 , jump [ "C", "credit" ] filter_amount_operator
671 ( Bool . Filter_GL_Amount_Negative
673 , jump [ "B", "balance" ] filter_amount_operator
674 ( Bool . Filter_GL_Amount_Balance
676 , jump [ "RD", "running-debit" ] filter_amount_operator
677 ( Bool . Filter_GL_Sum_Positive
679 , jump [ "RC", "running-credit" ] filter_amount_operator
680 ( Bool . Filter_GL_Sum_Negative
682 , jump [ "RB", "running-balance" ] filter_amount_operator
683 ( Bool . Filter_GL_Sum_Balance
686 ( Bool . Filter_GL_Account . snd