packages feed

feedback-0.0.0.0: src/Feedback/Loop/Filter.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Feedback.Loop.Filter where

import Data.Conduit
import qualified Data.Conduit.Combinators as C
import Data.List
import Data.Maybe
import Data.Set
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Feedback.Common.OptParse
import Path
import Path.IO
import System.Exit
import System.FSNotify as FS
import System.Process.Typed as Typed
import UnliftIO

getStdinFiles :: Path Abs Dir -> IO (Maybe (Set FilePath))
getStdinFiles here = do
  isTerminal <- hIsTerminalDevice stdin
  if isTerminal
    then pure Nothing
    else
      (Just <$> handleFileSet here stdin)
        `catch` (\(_ :: IOException) -> pure Nothing)

mkEventFilter :: Path Abs Dir -> Maybe (Set FilePath) -> FilterSettings -> IO (FS.Event -> Bool)
mkEventFilter here mStdinFiles FilterSettings {..} = do
  let mFilter mSet event = maybe True (eventPath event `S.member`) mSet
  let stdinFilter = mFilter mStdinFiles
  mFindFiles <- mapM (filesFromFindArgs here) filterSettingFind
  let findFilter = mFilter mFindFiles
  mGitFiles <-
    if filterSettingGitingore
      then gitLsFiles here
      else pure Nothing
  let gitFilter = mFilter mGitFiles
  let standardFilter = standardEventFilter here
  pure $
    if isJust mStdinFiles
      then stdinFilter
      else
        if isJust mFindFiles
          then findFilter
          else combineFilters [standardFilter, gitFilter]

combineFilters :: [FS.Event -> Bool] -> FS.Event -> Bool
combineFilters filters event = all ($ event) filters

gitLsFiles :: Path Abs Dir -> IO (Maybe (Set FilePath))
gitLsFiles here = do
  let processConfig = setStdout createPipe $ shell "git ls-files"
  process <- startProcess processConfig
  ec <- waitExitCode process
  case ec of
    ExitFailure _ -> pure Nothing
    ExitSuccess -> Just <$> handleFileSet here (getStdout process)

filesFromFindArgs :: Path Abs Dir -> String -> IO (Set FilePath)
filesFromFindArgs here args = do
  let processConfig = setStdout createPipe $ shell $ "find " <> args
  process <- startProcess processConfig
  ec <- waitExitCode process
  case ec of
    ExitFailure _ -> die $ "Find failed: " <> show ec
    ExitSuccess -> handleFileSet here (getStdout process)

handleFileSet :: Path Abs Dir -> Handle -> IO (Set FilePath)
handleFileSet here h =
  runConduit $
    C.sourceHandle h
      .| C.linesUnboundedAscii
      .| C.concatMap TE.decodeUtf8'
      .| C.map T.unpack
      .| C.mapM (resolveFile here)
      .| C.map fromAbsFile
      .| C.foldMap S.singleton

standardEventFilter :: Path Abs Dir -> FS.Event -> Bool
standardEventFilter here fsEvent =
  and
    [ -- It's not one of those files that vim makes
      (filename <$> parseAbsFile (eventPath fsEvent)) /= Just [relfile|4913|],
      not $ "~" `isSuffixOf` eventPath fsEvent,
      -- It's not a hidden file
      not $ hiddenHere here (eventPath fsEvent)
    ]

hiddenHere :: Path Abs Dir -> FilePath -> Bool
hiddenHere here filePath =
  (hidden <$> (parseAbsFile filePath >>= stripProperPrefix here)) /= Just False

hidden :: Path Rel File -> Bool
hidden = goFile
  where
    goFile :: Path Rel File -> Bool
    goFile f = isHiddenIn (parent f) f || goDir (parent f)
    goDir :: Path Rel Dir -> Bool
    goDir f
      | parent f == f = False
      | otherwise = isHiddenIn (parent f) f || goDir (parent f)

isHiddenIn :: Path b Dir -> Path b t -> Bool
isHiddenIn curdir ad =
  case stripProperPrefix curdir ad of
    Nothing -> False
    Just rp -> "." `isPrefixOf` toFilePath rp