module Issue.Filter ( Filter, filterArg, applyFilters, ) where import Control.Applicative (liftA2, (<|>)) import Data.Attoparsec.Text qualified as A import Data.Text (Text) import Data.Text qualified as T import Issue (Issue (..)) import Issue.Tag (tagKey, tagValue) import Issue.TypedValue (castDef) import Options.Applicative qualified as O -- TODO Revise filter negation -- -- Currently the following syntax in valid: -- -- ``` -- --filter !@t (*) -- --filter !@t v -- --filter !@t modeParser <*> simpleFilterParser where modeParser :: A.Parser Mode modeParser = (const Exclude <$> A.string "!") <|> (pure Include) simpleFilterParser :: A.Parser SimpleFilter simpleFilterParser = A.choice [ byTagParser ] byTagParser :: A.Parser SimpleFilter byTagParser = ByTag <$> (A.string "@" *> A.takeWhile1 (/= ' ') <* A.skipSpace) <*> ((Just <$> opParser (A.takeWhile1 (/= '\n'))) <|> pure Nothing) opParser :: A.Parser a -> A.Parser (Op, a) opParser p = (,) <$> ( A.choice [ A.try (pure Ge <* A.string ">="), A.try (pure Le <* A.string "<="), pure Gt <* A.string ">", pure Lt <* A.string "<", pure Eq ] ) <*> p data Op = Eq | Ge | Gt | Le | Lt deriving (Show) filterArg :: O.Parser [Filter] filterArg = O.many ( O.option (O.eitherReader (A.parseOnly filterParser . T.pack)) ( O.long "filter" <> O.short 'f' <> O.metavar "FILTER" <> O.help "Filters selected issues. Examples: @assigned, !@assigned joe" ) ) applyFilters :: [Filter] -> ([Issue] -> [Issue]) applyFilters fs = filter (filtersPredicate fs) filtersPredicate :: [Filter] -> (Issue -> Bool) filtersPredicate [] = \_ -> True filtersPredicate fs = foldr1 (liftA2 (&&)) (map filterPredicate fs) filterPredicate :: Filter -> (Issue -> Bool) filterPredicate (Filter m sf) = modePredicate m (simpleFilterPredicate sf) modePredicate :: Mode -> (Issue -> Bool) -> (Issue -> Bool) modePredicate Include p = p modePredicate Exclude p = not . p simpleFilterPredicate :: SimpleFilter -> (Issue -> Bool) simpleFilterPredicate (ByTag k v) i = any ((&&) <$> matchKey <*> matchValue) (i.tags ++ i.internalTags) where matchKey = (==) k . tagKey matchValue t = case (v, tagValue t) of (Just (o, v'), Just w') -> castDef False (op o) v' w' (Just _, Nothing) -> False (Nothing, _) -> True op :: Ord a => Op -> (a -> a -> Bool) op (Eq) = flip (==) op (Ge) = flip (>=) op (Gt) = flip (>) op (Le) = flip (<=) op (Lt) = flip (<)