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, head, map, reverse, tail)
23 -- import Data.List.NonEmpty (NonEmpty(..))
24 -- import qualified Data.List.NonEmpty as NonEmpty
25 import Data.Maybe (Maybe(..), catMaybes, maybe)
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.Tuple (snd)
32 import Data.Typeable ()
33 import Prelude (($), (.), Integer, IO, Num(..), undefined)
34 import qualified Text.Parsec.Expr as R
35 import qualified Text.Parsec as R hiding
47 -- import qualified Text.Parsec.Expr as R
48 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
49 import Text.Show (Show(..))
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'
266 -- | Return the 'Filter_Posting_Type' and stripped 'Account' of the given 'Account'.
267 account_posting_type :: [String] -> (Filter_Posting_Type, [String])
268 account_posting_type acct =
270 (Filter_Posting_Type_Any, acct)
271 (Filter_Posting_Type_Exact Posting_Type_Virtual,) $ do
277 ']':rs -> Just $ [reverse rs]
280 let rs = reverse ns in
281 case reverse $ Data.List.head rs of
282 ']':ln -> Just $ fn : reverse (reverse ln : Data.List.tail rs)
288 => ParsecT s u m (Filter_Posting_Type, Filter_Account)
290 R.notFollowedBy $ R.space_horizontal
292 R.option (Filter_Ord Eq ()) $ R.try $
293 (\f -> f ()) <$> filter_ord
294 (Filter_Path o <$>) <$> account
296 account :: Stream s m Char => ParsecT s u m (Filter_Posting_Type, [Filter_Path_Section])
299 account_posting_type <$>
301 (R.many (R.satisfy (\c -> c /= Account.Read.section_sep && not (Data.Char.isSpace c))))
302 (R.char Account.Read.section_sep)
303 sections <- forM strings $ \s ->
305 "" -> return Filter_Path_Section_Many
306 "*" -> return Filter_Path_Section_Any
307 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
308 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
309 return (pt, if null sections then [Filter_Path_Section_Many] else sections)
311 -- ** Read 'Filter_Amount'
314 => ParsecT s u m (Filter_Amount Amount)
316 R.notFollowedBy $ R.space_horizontal
320 amt <- Amount.Read.amount
322 (Bool $ Filter_Amount_Section_Quantity (tst $ Amount.quantity amt))
323 (case Unit.text $ Amount.unit amt of
324 unit | Text.null unit -> Any
325 unit -> Bool $ Filter_Amount_Section_Unit (Filter_Unit (Filter_Text_Exact unit)))
328 unit <- Amount.Read.unit >>= tst . Text.unpack . Unit.text
329 return $ Bool $ Filter_Amount_Section_Unit (Filter_Unit unit)
332 filter_amount_operator
334 => ParsecT s u m String
335 filter_amount_operator =
337 [ filter_ord_operator
338 , filter_text_operator
341 -- ** Read 'Filter_Date'
343 :: (Stream s (R.Error_State Error m) Char, Monad m)
344 => ParsecT s Context (R.Error_State Error m) (Filter_Bool Filter_Date)
348 (return $ read_date_pattern)
349 , filter_ord >>= \tst ->
352 let (year, _, _) = Date.gregorian $ context_date ctx
353 liftM (Bool . Filter_Date_UTC . tst) $
354 Date.Read.date Error_Filter_Date (Just year)
358 :: (Stream s (R.Error_State Error m) Char, Monad m)
359 => ParsecT s u (R.Error_State Error m) (Filter_Bool Filter_Date)
360 read_date_pattern = (do
361 let read2 = of_digits <$> (R.try (R.count 2 R.digit) <|> R.count 1 R.digit)
362 n0 <- read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
363 n1 <- R.option Nothing $ R.try $ do
365 Just <$> read_interval Error_Filter_Date_Interval read2
366 n2 <- R.option Nothing $ R.try $ do
368 Just <$> read_interval Error_Filter_Date_Interval read2
369 let (year, month, dom) =
371 (Nothing, Nothing) ->
374 , Interval.unlimited )
375 (Just d1, Nothing) ->
379 (Nothing, Just _d2) -> assert False undefined
380 (Just d1, Just d2) ->
384 (hour, minute, second) <-
385 R.option (Interval.unlimited, Interval.unlimited, Interval.unlimited) $ R.try $ do
387 hour <- read_interval Error_Filter_Date_Interval read2
388 sep <- Date.Read.hour_separator
389 minute <- read_interval Error_Filter_Date_Interval read2
390 second <- R.option Interval.unlimited $ R.try $ do
392 read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
393 -- tz <- R.option Time.utc $ R.try $ do
394 -- -- R.skipMany $ R.space_horizontal
395 -- Date.Read.time_zone
404 [ just_when_limited (Filter_Date_Year . Filter_Interval_In) year
405 , just_when_limited (Filter_Date_Month . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) month)
406 , just_when_limited (Filter_Date_DoM . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) dom)
407 , just_when_limited (Filter_Date_Hour . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) hour)
408 , just_when_limited (Filter_Date_Minute . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) minute)
409 , just_when_limited (Filter_Date_Second . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) second)
413 of_digits :: Num n => [Char] -> n
414 of_digits = fromInteger . R.integer_of_digits 10
415 just_when_limited f x =
416 if x == Interval.unlimited
418 else Just $ Bool $ f x
421 :: (Stream s (R.Error_State e m) Char, Monad m, Ord x)
423 -> ParsecT s u (R.Error_State e m) x
424 -> ParsecT s u (R.Error_State e m) (Interval (Interval.Unlimitable x))
425 read_interval err read_digits = do
427 [ R.string ".." >> return Interval.Unlimited_low
428 , Interval.Limited <$> read_digits
431 [ when (l /= Interval.Unlimited_low)
432 (void $ R.string "..") >> do
434 [ Interval.Limited <$> read_digits
435 , return Interval.Unlimited_high
437 case (Interval.<=..<=) l h of
438 Nothing -> R.fail_with "interval" (err $ (Interval.limited l, Interval.limited h))
442 Interval.Limited _ -> Interval.point l
443 _ -> Interval.unlimited
448 => ParsecT s u m String
449 filter_date_operator =
452 -- ** Read 'Filter_Description'
455 => ParsecT s u m Filter_Text
456 filter_description = (do
457 make_filter_text <- filter_text
458 R.between (R.char '"') (R.char '"') $
459 make_filter_text =<< (R.many $ R.try $ R.satisfy (/= '"'))
462 filter_description_operator
464 => ParsecT s u m String
465 filter_description_operator =
468 -- ** Read 'Filter_Tag'
472 => ParsecT s u m Filter_Tag
474 R.notFollowedBy $ R.space_horizontal
475 Filter_Ord o () <- (\f -> f ()) <$> filter_ord
478 [ R.char '^' >> return Filter_Tag_Value_First
479 , R.char '$' >> return Filter_Tag_Value_Last
480 , return Filter_Tag_Value_Any
484 (R.many (R.satisfy (\c -> c /= Account.Read.section_sep && not (Data.Char.isSpace c))))
485 (R.char Account.Read.section_sep)
486 sections <- forM strings $ \s ->
488 "" -> return Filter_Path_Section_Many
489 "*" -> return Filter_Path_Section_Any
490 '~':t -> Filter_Path_Section_Text . Filter_Text_Regex <$> Regex.of_StringM t
491 t -> return $ Filter_Path_Section_Text $ Filter_Text_Exact $ Text.pack t
492 case reverse sections of
494 [p] -> return $ Bool $ Filter_Tag_Path $ Filter_Path o [p]
497 (Bool $ Filter_Tag_Path $ Filter_Path o $ reverse rev_path)
498 (Bool $ Filter_Tag_Value $ filter_tag_value $
500 Filter_Path_Section_Any -> Filter_Text_Any
501 Filter_Path_Section_Many -> Filter_Text_Any
502 Filter_Path_Section_Text ft -> ft
507 => ParsecT s u m String
508 filter_tag_operator = do
509 void filter_ord_operator
516 -- ** Read 'Filter_Posting'
520 , Posting_Amount p ~ Amount
522 => ParsecT s Context m (Filter_Bool (Filter_Posting p))
524 Data.Foldable.foldr Filter.And Filter.Any <$>
527 >> R.lookAhead R.anyToken
528 >> filter_bool filter_posting_terms
530 filter_posting_terms ::
533 , Posting_Amount p ~ Amount
535 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Posting p)))]
536 filter_posting_terms =
537 [ jump [ "a", "amount" ] filter_amount_operator
538 ((Bool . Filter.Filter_Posting_Amount) <$> filter_amount)
542 (Bool $ Filter_Posting_Account a)
543 (Bool $ Filter_Posting_Type pt)
547 -- ** Read 'Filter_Transaction'
548 filter_transaction ::
549 ( Stream s (R.Error_State Error m) Char
551 , Filter.Transaction t
552 , Posting_Amount (Transaction_Posting t) ~ Amount
553 ) => ParsecT s Context (R.Error_State Error m)
554 (Filter_Bool (Filter_Transaction t))
556 glue_posting . Data.Foldable.foldr Filter.And Filter.Any <$>
559 >> R.lookAhead R.anyToken
560 >> filter_bool filter_transaction_terms
564 => Filter_Bool (Filter_Transaction t)
565 -> Filter_Bool (Filter_Transaction t)
570 (Or b0 b1) -> Or (glue_posting b0) (glue_posting b1)
572 case glue_posting b of
573 Bool (Filter_Transaction_Posting p) ->
574 Bool $ Filter_Transaction_Posting $ Not p
577 case (glue_posting b0, glue_posting b1) of
578 ( Bool (Filter_Transaction_Posting p0),
579 Bool (Filter_Transaction_Posting p1)
580 ) -> Bool $ Filter_Transaction_Posting $ And p0 p1
581 (Bool (Filter_Transaction_Posting p0), Any) ->
582 Bool $ Filter_Transaction_Posting $ p0
583 (Any, Bool (Filter_Transaction_Posting p1)) ->
584 Bool $ Filter_Transaction_Posting $ p1
585 (b0', b1') -> And b0' b1'
587 filter_transaction_terms ::
588 ( Stream s (R.Error_State Error m) Char
589 , Filter.Transaction t
591 , Posting_Amount (Transaction_Posting t) ~ Amount
593 => [ParsecT s Context (R.Error_State Error m)
594 (ParsecT s Context (R.Error_State Error m)
595 (Filter_Bool (Filter_Transaction t)))]
596 filter_transaction_terms =
597 -- , jump [ "atag" ] comp_text parseFilterATag
598 -- , jump [ "code" ] comp_text parseFilterCode
599 [ jump [ "date", "d" ] filter_date_operator
600 (Bool . Filter.Filter_Transaction_Date <$> filter_date)
601 , jump [ "tag", "T" ] filter_tag_operator
602 (Bool . Filter.Filter_Transaction_Tag <$> filter_tag)
603 , jump [ "debit", "D" ] filter_amount_operator
605 . Filter_Transaction_Posting
607 . Filter_Posting_Positive
609 , jump [ "credit", "C" ] filter_amount_operator
611 . Filter_Transaction_Posting
613 . Filter_Posting_Negative
615 , jump [ "wording", "W" ] filter_description_operator
616 (Bool . Filter.Filter_Transaction_Description <$> filter_description)
617 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
618 -- , jump [ "real" ] (R.char '=') parseFilterReal
619 -- , jump [ "status" ] (R.char '=') parseFilterStatus
620 -- , jump [ "sym" ] comp_text parseFilterSym
621 -- , R.lookAhead comp_num >> return parseFilterAmount
625 Filter_Transaction_Posting $
626 And (Bool $ Filter_Posting_Account a)
627 (Bool $ Filter_Posting_Type pt)
631 -- ** Read 'Filter_Balance'
633 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
634 => ParsecT s Context m (Filter_Bool (Filter_Balance t))
636 Data.Foldable.foldr Filter.And Filter.Any <$>
639 >> R.lookAhead R.anyToken
640 >> filter_bool filter_balance_terms
643 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
644 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_Balance t)))]
645 filter_balance_terms =
646 [ jump [ "RD", "debit" ] filter_amount_operator
647 ( Bool . Filter_Balance_Positive
649 , jump [ "RC", "credit" ] filter_amount_operator
650 ( Bool . Filter_Balance_Negative
652 , jump [ "RB", "balance" ] filter_amount_operator
653 ( Bool . Filter_Balance_Amount
656 ( Bool . Filter_Balance_Account . snd
660 -- ** Read 'Filter_GL'
662 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
663 => ParsecT s Context m (Filter_Bool (Filter_GL t))
665 Data.Foldable.foldr Filter.And Filter.Any <$>
668 >> R.lookAhead R.anyToken
669 >> filter_bool filter_gl_terms
672 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
673 => [ParsecT s Context m (ParsecT s Context m (Filter_Bool (Filter_GL t)))]
675 [ jump [ "D", "debit" ] filter_amount_operator
676 ( Bool . Filter_GL_Amount_Positive
678 , jump [ "C", "credit" ] filter_amount_operator
679 ( Bool . Filter_GL_Amount_Negative
681 , jump [ "B", "balance" ] filter_amount_operator
682 ( Bool . Filter_GL_Amount_Balance
684 , jump [ "RD", "running-debit" ] filter_amount_operator
685 ( Bool . Filter_GL_Sum_Positive
687 , jump [ "RC", "running-credit" ] filter_amount_operator
688 ( Bool . Filter_GL_Sum_Negative
690 , jump [ "RB", "running-balance" ] filter_amount_operator
691 ( Bool . Filter_GL_Sum_Balance
694 ( Bool . Filter_GL_Account . snd