1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 module Hcompta.Model.Filter.Read where
5 import Prelude hiding (filter)
6 import Control.Applicative ((<$>){-, (<*>)-}, (<*))
7 import Control.Monad (liftM)
8 -- import Control.Monad.Trans.Except (ExceptT(..), throwE)
9 import qualified Data.Char
11 import qualified Data.Foldable
12 import Data.Functor.Identity (Identity)
13 import qualified Text.Parsec.Expr as R
14 import qualified Text.Parsec as R hiding
26 -- import qualified Text.Parsec.Expr as R
27 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
28 import Data.String (fromString)
29 import qualified Data.Text as Text
30 import Data.Text (Text)
31 import Data.Typeable ()
33 import qualified Hcompta.Lib.Regex as Regex
34 -- import Hcompta.Lib.Regex (Regex)
35 import qualified Hcompta.Model.Account as Account
36 import qualified Hcompta.Model.Filter as Filter
37 import Hcompta.Model.Filter
40 , Test_Account_Section(..)
46 , Test_Transaction(..)
49 import qualified Hcompta.Lib.Parsec as R
58 } deriving (Data, Eq, Show, Typeable)
71 -- ** Read 'Test_Text'
73 :: (Stream s m Char, Monad r)
74 => ParsecT s u m (String -> r Test_Text)
77 [ R.char '~' >> return (\s -> Regex.of_StringM s >>= (return . Test_Text_Regex))
78 , R.char '=' >> return (\s -> return (Test_Text_Exact $ Text.pack s))
79 , return (\s -> return (Test_Text_Exact $ Text.pack s))
84 :: (Stream s m Char, Ord o)
85 => ParsecT s u m (o -> m (Test_Ord o))
88 [ R.string "=" >> return (return . Test_Ord_Eq)
89 , R.string "<=" >> return (return . Test_Ord_Lt_Eq)
90 , R.string ">=" >> return (return . Test_Ord_Gt_Eq)
91 , R.string "<" >> return (return . Test_Ord_Lt)
92 , R.string ">" >> return (return . Test_Ord_Gt)
95 -- ** Read 'Test_Num_Abs'
97 :: (Stream s m Char, Num n)
98 => ParsecT s u m (Test_Ord n -> m (Either (Test_Ord n) (Test_Num_Abs n)))
101 [ R.char '+' >> return (return . Right . Test_Num_Abs)
102 , return (return . Left)
105 text :: Stream s m Char => String -> ParsecT s Context m Text
110 , R.many $ R.noneOf ("() " ++ none_of)
113 borders = R.between (R.char '(') (R.char ')')
114 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
115 preserve_inside = inside >>= (\x -> return $ '(':(x++')':[]))
117 -- ** Read 'Test_Bool'
121 => [ParsecT s Context m (ParsecT s Context m t)]
122 -> ParsecT s Context m (Test_Bool t)
124 R.buildExpressionParser
126 (test_bool_term terms)
131 => R.OperatorTable s u m (Filter.Test_Bool t)
132 test_bool_operators =
133 [ [ prefix "- " Filter.Not
134 , prefix "not " Filter.Not
136 , [ binary " & " Filter.And R.AssocLeft
137 , binary " and " Filter.And R.AssocLeft
138 , binary " - " (flip Filter.And . Filter.Not) R.AssocLeft
139 , binary " but " (flip Filter.And . Filter.Not) R.AssocLeft
141 , [ binary " + " Filter.Or R.AssocLeft
142 , binary " or " Filter.Or R.AssocLeft
146 binary name fun assoc = R.Infix (test_bool_operator name >> return fun) assoc
147 prefix name fun = R.Prefix (test_bool_operator name >> return fun)
148 -- postfix name fun = Text.Parsec.Expr.Postfix (test_bool_operator name >> return fun)
152 => String -> ParsecT s u m ()
153 test_bool_operator name =
156 >> R.notFollowedBy test_bool_operator_letter
157 <?> ("end of " ++ show name))
159 test_bool_operator_letter
160 :: Stream s m Char => ParsecT s u m Char
161 test_bool_operator_letter =
162 R.oneOf ['+', '-', '&']
166 => [ParsecT s Context m (ParsecT s Context m t)]
167 -> ParsecT s Context m (Test_Bool t)
168 test_bool_term terms = do
170 ( (R.lookAhead (R.try $ R.char '(')
171 >> (return $ parens $
172 Data.Foldable.foldr Filter.And Filter.Any <$>
173 R.many (R.spaces >> expr) ))
174 : map ((Filter.Bool <$>) <$>) terms
175 ) <* R.spaces <?> "filter expression"
179 R.lookAhead (R.try R.anyToken)
180 >> R.notFollowedBy (R.char ')')
185 => ParsecT s u m a -> ParsecT s u m a
186 lexeme p = p <* R.spaces
190 => ParsecT s u m a -> ParsecT s u m a
191 parens = R.between (lexeme $ R.char '(') (lexeme $ R.char ')')
193 bool :: Stream s m Char => ParsecT s u m Bool
208 -- ** Read Account.'Account.Name'
209 account_name :: Stream s m Char => ParsecT s u m Account.Name
212 R.many1 $ R.try account_name_char
214 account_name_char :: Stream s m Char => ParsecT s u m Char
215 account_name_char = do
218 -- _ | c == comment_begin -> R.parserZero
219 -- _ | c == account_name_sep -> R.parserZero
220 _ | R.is_space_horizontal c -> do
221 _ <- R.notFollowedBy $ R.space_horizontal
222 return c <* (R.lookAhead $ R.try $
223 ( R.try (R.char account_name_sep)
224 <|> account_name_char
226 _ | not (Data.Char.isSpace c) -> return c
229 -- ** Read 'Test_Account_Section'
232 => (String -> ParsecT s u m Test_Text)
233 -> ParsecT s u m Test_Account_Section
234 test_account_section make_test_text = do
237 <* R.lookAhead account_section_end
238 >> return Test_Account_Section_Any
239 , R.many1 (R.satisfy (\c -> c /= account_name_sep && not (Data.Char.isSpace c)))
240 >>= (liftM Test_Account_Section_Text . make_test_text)
241 , R.lookAhead account_section_end
242 >> R.many (R.try (R.char account_name_sep >> R.lookAhead (R.try account_section_end)))
243 >> return Test_Account_Section_Many
246 account_section_end =
248 [ R.char account_name_sep >> return ()
249 , R.space_horizontal >> return ()
253 -- ** Read 'Test_Account'
254 account_name_sep :: Char
255 account_name_sep = ':'
259 => ParsecT s u m Test_Account
261 R.notFollowedBy $ R.space_horizontal
262 make_test_text <- test_text
263 R.many1_separated (test_account_section make_test_text) $
264 R.char account_name_sep
266 -- ** Read 'Test_Posting'
268 :: (Stream s m Char, Filter.Posting t)
269 => ParsecT s Context m (Test_Bool (Test_Posting t))
271 Data.Foldable.foldr Filter.And Filter.Any <$>
274 >> R.lookAhead R.anyToken
275 >> test_bool test_posting_terms
278 :: (Stream s m Char, Filter.Posting t)
279 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
282 ( Filter.Test_Posting_Account
286 -- ** Read 'Test_Transaction'
288 :: (Stream s m Char, Filter.Transaction t)
289 => ParsecT s Context m (Test_Bool (Test_Transaction t))
291 Data.Foldable.foldr Filter.And Filter.Any <$>
294 >> R.lookAhead R.anyToken
295 >> test_bool test_transaction_terms
297 test_transaction_terms
298 :: (Stream s m Char, Filter.Transaction t)
299 => [ParsecT s Context m (ParsecT s Context m (Test_Transaction t))]
300 test_transaction_terms =
302 ( Filter.Test_Transaction_Posting
303 . Filter.Test_Posting_Account
305 -- , jump [ "account","acct" ] comp_text test_account
306 -- , jump [ "amount", "amt" ] comp_num parseFilterAmount
307 -- , jump [ "atag" ] comp_text parseFilterATag
308 -- , jump [ "balance", "bal" ] comp_num parseFilterBalance
309 -- , jump [ "code" ] comp_text parseFilterCode
310 -- , jump [ "date" ] (R.char '=') parseFilterDate
311 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
312 -- , jump [ "depth" ] comp_num parseFilterDepth
313 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
314 -- , jump [ "real" ] (R.char '=') parseFilterReal
315 -- , jump [ "status" ] (R.char '=') parseFilterStatus
316 -- , jump [ "sym" ] comp_text parseFilterSym
317 -- , jump [ "tag" ] comp_text parseFilterTag
318 -- , R.lookAhead comp_num >> return parseFilterAmount
321 -- jump :: Stream s m Char => [String] -> ParsecT s u m b -> a -> ParsecT s u m a
324 -- (map (\s -> R.string s >> return r) l)
325 -- <* R.lookAhead next
327 -- ** Read 'Test_Balance'
329 :: (Stream s m Char, Filter.Balance t)
330 => ParsecT s Context m (Test_Bool (Test_Balance t))
332 Data.Foldable.foldr Filter.And Filter.Any <$>
335 >> R.lookAhead R.anyToken
336 >> test_bool test_balance_terms
339 :: (Stream s m Char, Filter.Balance t)
340 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
343 ( Filter.Test_Balance_Account
349 ( Stream s (R.Error_State Error Identity) Char
352 => ParsecT s Context (R.Error_State Error Identity) (Test_Bool t)
353 -> s -> Either [R.Error Error] (Test_Bool t)
355 R.runParser_with_Error t context "" s
359 account :: Stream s m Char => ParsecT s Context m Filter
361 o <- R.optionMaybe comp_text
362 liftM (Filter.Account $ fromMaybe Comp_Text_Exact o)
363 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
365 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
366 parseFilterAmount = do
372 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
375 liftM (uncurry (ATag c))
378 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
379 --parseFilterCode = do
387 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
390 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
391 parseFilterBalance = do
394 a <- parseAmount Nothing
395 return $ Bal (nc, absc) a
397 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
402 periodexprdatespan (qCtxDay ctx)
404 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
405 parseFilterDate2 = do
409 periodexprdatespan (qCtxDay ctx)
411 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
417 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
418 parseFilterDepth = do
420 liftM (Depth c . fromIntegral) $
423 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
428 -- | Read the boolean value part of a "status:" query, allowing "*" as
429 -- another way to spell True, similar to the journal file format.
430 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
431 parseFilterStatus = do
434 try (R.char '*' >> return True) <|> bool
436 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
437 --parseFilterSym = do
442 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
445 liftM (uncurry (Tag c))