packages feed

khph-0.1.0: src/Khph/Query/Eval.hs

-- This file is part of khph.
--
-- Copyright 2016 Bryan Gardiner <bog@khumba.net>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE CPP #-}

-- | Evaluator for a parsed query.
module Khph.Query.Eval (queryApply) where

#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>), (<*>))
#endif
import Data.List (intercalate, isInfixOf, isPrefixOf, isSuffixOf)
import qualified Data.Set as Set
import Khph.Config (configSourceDirs)
import Khph.Project.Base
import Khph.Project.Monad
import Khph.Query.Base
import Khph.Util
import System.FilePath (pathSeparator)

-- | Evaluates a query for a specific entry, returning whether the query matches
-- the entry.
queryApply :: MonadProject m => Query -> Entry m -> m Bool
queryApply logic entry = logicApply logic $ \query -> case query of
  QueryLink listPredicate -> do
    links <- (++) <$>
             (map (\path -> (path, HardLink)) <$> entryHardLinks entry) <*>
             (map (\path -> (path, SoftLink)) <$> entrySoftLinks entry)
    listPredicateApply listPredicate queryLink links
  QueryTag listPredicate -> do
    tags <- entryTags entry
    listPredicateApply listPredicate (\q xs -> queryTree tagToComponents q xs) $
      map tagToComponents tags

queryLink :: MonadProject m => LinkQuery -> (ProjectPath, LinkType) -> m Bool
queryLink logic (projectPath, linkType) = logicApply logic $ \query -> case query of
  LinkTypeIs typ -> return $ typ == linkType
  LinkMatchesEntrySpec entrySpec -> case entrySpec of
    EntrySpecAbsolute components ->
      return $ projectPathToComponents projectPath == components
    EntrySpecRelative components -> do
      currentPath <- getCurrentPathOrDie $ "running query " ++ show query
      return $ projectPath == projectPathAppendComponents currentPath components
    EntrySpecByName name ->
      return $ maybeLast (projectPathToComponents projectPath) == Just name
  LinkIsSourcePath ->
    any (\sourceDir -> projectPathIsPrefixOf sourceDir projectPath) .
    Set.toList .
    configSourceDirs <$>
    getConfig
  LinkStringQuery maybeComponent stringQuery ->
    queryString stringQuery $
    intercalate [pathSeparator] $
    (case maybeComponent of
      Nothing -> id
      Just DirectoryComponent -> butLast
      Just FileComponent -> (:[]) . last) $
    projectPathToComponents projectPath
  LinkTreeQuery treeQuery ->
    queryTree id treeQuery $ projectPathToComponents projectPath

queryString :: MonadProject m => StringQuery -> String -> m Bool
queryString logic xs = logicApply logic $ \query -> return $ case query of
  StringEq ys -> xs == ys
  StringContains ys -> ys `isInfixOf` xs  -- TODO This is terribly inefficient.
  StringStartsWith ys -> ys `isPrefixOf` xs
  StringEndsWith ys -> ys `isSuffixOf` xs

queryTree :: MonadProject m => Eq b => (a -> [b]) -> TreeQuery a -> [b] -> m Bool
queryTree resolve logic xs = logicApply logic $ \query -> return $ case query of
  TreeAt ys' -> xs == resolve ys'
  TreeAtAbove ys' -> xs `isPrefixOf` resolve ys'
  TreeAbove ys' -> let ys = resolve ys'
                   in xs `isPrefixOf` ys && length xs < length ys
  TreeAtBelow ys' -> resolve ys' `isPrefixOf` xs
  TreeBelow ys' -> let ys = resolve ys'
                   in ys `isPrefixOf` xs && length ys < length xs

logicApply :: MonadProject m => Logic q -> (q -> m Bool) -> m Bool
logicApply logic eval = case logic of
  LogicQuery query -> eval query
  LogicTrue -> return True
  LogicFalse -> return False
  LogicNot logic' -> not <$> logicApply logic' eval
  LogicAnd logics -> allM (flip logicApply eval) logics
  LogicOr logics -> anyM (flip logicApply eval) logics

listPredicateApply :: MonadProject m => ListPredicate q -> (q -> a -> m Bool) -> [a] -> m Bool
listPredicateApply p f xs = case p of
  All q -> allM (f q) xs
  All1 q -> if null xs then return False else allM (f q) xs
  Some q -> anyM (f q) xs
  None q -> not <$> anyM (f q) xs
  Exists -> return $ not $ null xs
  DoesNotExist -> return $ null xs