1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE ScopedTypeVariables #-}
5 module Hcompta.Model.Filter.Read where
7 import Prelude hiding (filter)
8 import Control.Applicative ((<$>), (<*))
9 import Control.Exception (assert)
10 import Control.Monad (liftM)
11 -- import Control.Monad.Trans.Except (ExceptT(..), throwE)
12 import qualified Data.Char
14 import qualified Data.Foldable
15 import Data.Functor.Identity (Identity)
16 import Data.Maybe (catMaybes)
17 import qualified Data.Time.Calendar as Time
18 import qualified Data.Time.Clock as Time
19 import qualified Text.Parsec.Expr as R
20 import qualified Text.Parsec as R hiding
32 -- import qualified Text.Parsec.Expr as R
33 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
34 import Data.String (fromString)
35 import qualified Data.Text as Text
36 import Data.Text (Text)
37 import Data.Typeable ()
39 import qualified Hcompta.Lib.Regex as Regex
40 -- import Hcompta.Lib.Regex (Regex)
41 import qualified Hcompta.Model.Account as Account
42 import qualified Hcompta.Model.Date as Date
43 import Hcompta.Model.Date (Date)
44 import qualified Hcompta.Model.Date.Read as Date.Read
45 import qualified Hcompta.Model.Filter as Filter
46 import Hcompta.Model.Filter
47 import qualified Hcompta.Lib.Parsec as R
55 { context_date :: Date
56 } deriving (Data, Eq, Show, Typeable)
61 { context_date = Date.nil
68 | Error_Test_Date Date.Read.Error
74 ( Stream s (R.Error_State Error Identity) Char
77 => ParsecT s Context (R.Error_State Error Identity) (Test_Bool t)
78 -> s -> IO (Either [R.Error Error] (Test_Bool t))
80 context_date <- Time.getCurrentTime
82 R.runParser_with_Error t context{context_date} "" s
84 -- ** Read 'Test_Text'
86 :: (Stream s m Char, Monad r)
87 => ParsecT s u m (String -> r Test_Text)
90 [ R.char '~' >> return (\s -> Regex.of_StringM s >>= (return . Test_Text_Regex))
91 , R.char '=' >> return (\s -> return (Test_Text_Exact $ Text.pack s))
92 , return (\s -> return (Test_Text_Exact $ Text.pack s))
97 :: (Stream s m Char, Ord o)
98 => ParsecT s u m (o -> Test_Ord o)
101 [ R.string "=" >> return Test_Ord_Eq
102 , R.string "<=" >> return Test_Ord_Le
103 , R.string ">=" >> return Test_Ord_Ge
104 , R.string "<" >> return Test_Ord_Lt
105 , R.string ">" >> return Test_Ord_Gt
110 => ParsecT s u m String
120 -- ** Read 'Test_Num_Abs'
122 :: (Stream s m Char, Num n)
123 => ParsecT s u m (Test_Ord n -> m (Either (Test_Ord n) (Test_Num_Abs n)))
126 [ R.char '+' >> return (return . Right . Test_Num_Abs)
127 , return (return . Left)
130 text :: Stream s m Char => String -> ParsecT s Context m Text
135 , R.many $ R.noneOf ("() " ++ none_of)
138 borders = R.between (R.char '(') (R.char ')')
139 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
140 preserve_inside = inside >>= (\x -> return $ '(':(x++')':[]))
142 -- ** Read 'Test_Bool'
146 => [ParsecT s Context m (ParsecT s Context m t)]
147 -> ParsecT s Context m (Test_Bool t)
149 R.buildExpressionParser
151 (test_bool_term terms)
156 => R.OperatorTable s u m (Filter.Test_Bool t)
157 test_bool_operators =
158 [ [ prefix "- " Filter.Not
159 , prefix "not " Filter.Not
161 , [ binary " & " Filter.And R.AssocLeft
162 , binary " and " Filter.And R.AssocLeft
163 , binary " - " (flip Filter.And . Filter.Not) R.AssocLeft
164 , binary " but " (flip Filter.And . Filter.Not) R.AssocLeft
166 , [ binary " + " Filter.Or R.AssocLeft
167 , binary " or " Filter.Or R.AssocLeft
171 binary name fun assoc = R.Infix (test_bool_operator name >> return fun) assoc
172 prefix name fun = R.Prefix (test_bool_operator name >> return fun)
173 -- postfix name fun = Text.Parsec.Expr.Postfix (test_bool_operator name >> return fun)
177 => String -> ParsecT s u m ()
178 test_bool_operator name =
181 >> R.notFollowedBy test_bool_operator_letter
182 <?> ("end of " ++ show name))
184 test_bool_operator_letter
185 :: Stream s m Char => ParsecT s u m Char
186 test_bool_operator_letter =
187 R.oneOf ['+', '-', '&']
191 => [ParsecT s Context m (ParsecT s Context m t)]
192 -> ParsecT s Context m (Test_Bool t)
193 test_bool_term terms = do
195 ( (R.lookAhead (R.try $ R.char '(')
196 >> (return $ parens $
197 Data.Foldable.foldr Filter.And Filter.Any <$>
198 R.many (R.spaces >> expr) ))
199 : map ((Filter.Bool <$>) <$>) terms
200 ) <* R.spaces <?> "filter expression"
204 R.lookAhead (R.try R.anyToken)
205 >> R.notFollowedBy (R.char ')')
210 => ParsecT s u m a -> ParsecT s u m a
211 lexeme p = p <* R.spaces
215 => ParsecT s u m a -> ParsecT s u m a
216 parens = R.between (lexeme $ R.char '(') (lexeme $ R.char ')')
218 bool :: Stream s m Char => ParsecT s u m Bool
233 -- ** Read Account.'Account.Name'
234 account_name :: Stream s m Char => ParsecT s u m Account.Name
237 R.many1 $ R.try account_name_char
239 account_name_char :: Stream s m Char => ParsecT s u m Char
240 account_name_char = do
243 -- _ | c == comment_begin -> R.parserZero
244 -- _ | c == account_name_sep -> R.parserZero
245 _ | R.is_space_horizontal c -> do
246 _ <- R.notFollowedBy $ R.space_horizontal
247 return c <* (R.lookAhead $ R.try $
248 ( R.try (R.char account_name_sep)
249 <|> account_name_char
251 _ | not (Data.Char.isSpace c) -> return c
254 -- ** Read 'Test_Account_Section'
257 => (String -> ParsecT s u m Test_Text)
258 -> ParsecT s u m Test_Account_Section
259 test_account_section make_test_text = do
262 <* R.lookAhead account_section_end
263 >> return Test_Account_Section_Any
264 , R.many1 (R.satisfy (\c -> c /= account_name_sep && not (Data.Char.isSpace c)))
265 >>= (liftM Test_Account_Section_Text . make_test_text)
266 , R.lookAhead account_section_end
267 >> R.many (R.try (R.char account_name_sep >> R.lookAhead (R.try account_section_end)))
268 >> return Test_Account_Section_Many
271 account_section_end =
273 [ R.char account_name_sep >> return ()
274 , R.space_horizontal >> return ()
278 -- ** Read 'Test_Account'
279 account_name_sep :: Char
280 account_name_sep = ':'
284 => ParsecT s u m Test_Account
286 R.notFollowedBy $ R.space_horizontal
287 make_test_text <- test_text
288 R.many1_separated (test_account_section make_test_text) $
289 R.char account_name_sep
291 -- ** Read 'Test_Date'
293 :: (Stream s (R.Error_State Error m) Char, Monad m)
294 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
298 (return $ read_date_pattern)
299 , test_ord >>= \tst ->
302 let (year, _, _) = Date.gregorian $ context_date ctx
303 Date.Read.date Error_Test_Date (Just year)
304 >>= return . Bool . Test_Date_UTC . tst
308 :: (Stream s (R.Error_State e m) Char, Monad m)
309 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
310 read_date_pattern = (do
311 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
312 n0 <- read_range $ R.many1 R.digit
313 n1 <- R.option Nothing $ R.try $ do
315 Just <$> read_range read2
316 n2 <- R.option Nothing $ R.try $ do
318 Just <$> read_range read2
319 let (year, month, dom) =
321 (Nothing, Nothing) ->
325 (Just d1, Nothing) ->
329 (Nothing, Just _d2) -> assert False undefined
330 (Just d1, Just d2) ->
331 ( R.integer_of_digits 10 <$> n0
334 (hour, minute, second) <-
335 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
336 R.skipMany1 $ R.space_horizontal
337 hour <- read_range read2
338 sep <- Date.Read.hour_separator
339 minute <- read_range read2
340 second <- R.option test_range_all $ R.try $ do
342 read_range $ R.many1 R.digit
343 -- tz <- R.option Time.utc $ R.try $ do
344 -- R.skipMany $ R.space_horizontal
345 -- Date.Read.time_zone
348 , of_digits <$> minute
349 , of_digits <$> second
354 [ just_when_bounded Test_Date_Year year
355 , just_when_bounded Test_Date_Month month
356 , just_when_bounded Test_Date_DoM dom
357 , just_when_bounded Test_Date_Hour hour
358 , just_when_bounded Test_Date_Minute minute
359 , just_when_bounded Test_Date_Second second
363 of_digits :: Num n => [Char] -> n
364 of_digits = fromInteger . R.integer_of_digits 10
365 just_when_bounded f x =
367 Test_Range_In Nothing Nothing -> Nothing
368 _ -> Just $ Bool $ f x
370 read_range :: Stream s m Char
372 -> ParsecT s u m (Test_Range a)
373 read_range read_digits = do
375 [ R.char '*' >> return Nothing
376 , Just <$> read_digits
380 (Test_Range_In a0 <$> R.choice_try
381 [ R.char '*' >> return Nothing
382 , Just <$> read_digits
384 , return $ maybe test_range_all Test_Range_Eq a0
389 => ParsecT s u m String
393 -- ** Read 'Test_Posting'
395 :: (Stream s m Char, Filter.Posting t)
396 => ParsecT s Context m (Test_Bool (Test_Posting t))
398 Data.Foldable.foldr Filter.And Filter.Any <$>
401 >> R.lookAhead R.anyToken
402 >> test_bool test_posting_terms
405 :: (Stream s m Char, Filter.Posting t)
406 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
409 ( Filter.Test_Posting_Account
413 -- ** Read 'Test_Transaction'
415 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t)
416 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
418 Data.Foldable.foldr Filter.And Filter.Any <$>
421 >> R.lookAhead R.anyToken
422 >> test_bool test_transaction_terms
424 test_transaction_terms
425 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m)
426 => [ParsecT s Context (R.Error_State Error m)
427 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
428 test_transaction_terms =
429 -- , jump [ "account","acct" ] comp_text test_account
430 -- , jump [ "amount", "amt" ] comp_num parseFilterAmount
431 -- , jump [ "atag" ] comp_text parseFilterATag
432 -- , jump [ "balance", "bal" ] comp_num parseFilterBalance
433 -- , jump [ "code" ] comp_text parseFilterCode
434 [ jump [ "date" ] test_date_operator
435 (Filter.Test_Transaction_Date <$> test_date)
436 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
437 -- , jump [ "depth" ] comp_num parseFilterDepth
438 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
439 -- , jump [ "real" ] (R.char '=') parseFilterReal
440 -- , jump [ "status" ] (R.char '=') parseFilterStatus
441 -- , jump [ "sym" ] comp_text parseFilterSym
442 -- , jump [ "tag" ] comp_text parseFilterTag
443 -- , R.lookAhead comp_num >> return parseFilterAmount
445 ( Filter.Test_Transaction_Posting
446 . Filter.Test_Posting_Account
450 jump :: Stream s m Char
455 jump prefixes next r =
457 (map (\s -> R.string s >> return r) prefixes)
458 <* R.lookAhead (R.try next)
460 -- ** Read 'Test_Balance'
462 :: (Stream s m Char, Filter.Balance t)
463 => ParsecT s Context m (Test_Bool (Test_Balance t))
465 Data.Foldable.foldr Filter.And Filter.Any <$>
468 >> R.lookAhead R.anyToken
469 >> test_bool test_balance_terms
472 :: (Stream s m Char, Filter.Balance t)
473 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
476 ( Filter.Test_Balance_Account
482 account :: Stream s m Char => ParsecT s Context m Filter
484 o <- R.optionMaybe comp_text
485 liftM (Filter.Account $ fromMaybe Comp_Text_Exact o)
486 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
488 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
489 parseFilterAmount = do
495 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
498 liftM (uncurry (ATag c))
501 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
502 --parseFilterCode = do
510 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
513 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
514 parseFilterBalance = do
517 a <- parseAmount Nothing
518 return $ Bal (nc, absc) a
520 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
525 periodexprdatespan (qCtxDay ctx)
527 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
528 parseFilterDate2 = do
532 periodexprdatespan (qCtxDay ctx)
534 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
540 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
541 parseFilterDepth = do
543 liftM (Depth c . fromIntegral) $
546 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
551 -- | Read the boolean value part of a "status:" query, allowing "*" as
552 -- another way to spell True, similar to the journal file format.
553 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
554 parseFilterStatus = do
557 try (R.char '*' >> return True) <|> bool
559 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
560 --parseFilterSym = do
565 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
568 liftM (uncurry (Tag c))