feedback-0.1.0.2: src/Feedback/Loop/Filter.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Feedback.Loop.Filter where
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Lazy.Char8 as LB8
import Data.Conduit
import qualified Data.Conduit.Combinators as C
import qualified Data.Conduit.List as CL
import Data.List
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.Process.Typed as Typed
#ifdef MIN_VERSION_Win32
import System.Win32.MinTTY (isMinTTYHandle)
import System.Win32.Types (withHandleToHANDLE)
#endif
import UnliftIO
#ifdef MIN_VERSION_Win32
getMinTTY :: IO Bool
getMinTTY = withHandleToHANDLE stdin isMinTTYHandle
#else
getMinTTY :: IO Bool
getMinTTY = pure False
#endif
data Filter = Filter
{ filterDirFilter :: Path Abs Dir -> Bool,
filterFileFilter :: Path Abs File -> Bool
}
instance Semigroup Filter where
f1 <> f2 =
Filter
{ filterDirFilter = \d -> filterDirFilter f1 d && filterDirFilter f2 d,
filterFileFilter = \f -> filterFileFilter f1 f && filterFileFilter f2 f
}
instance Monoid Filter where
mempty = Filter {filterDirFilter = const True, filterFileFilter = const True}
mappend = (<>)
fileSetFilter :: Set (Path Abs File) -> Filter
fileSetFilter fileSet =
let dirSet = S.map parent fileSet
in Filter
{ filterDirFilter = (`S.member` dirSet),
filterFileFilter = (`S.member` fileSet)
}
mkCombinedFilter :: Path Abs Dir -> FilterSettings -> IO Filter
mkCombinedFilter here filterSettings =
mconcat
<$> sequence
[ mkGitFilter here filterSettings,
mkFindFilter here filterSettings,
pure $ standardFilter here
]
mkStdinFilter :: Path Abs Dir -> IO Filter
mkStdinFilter here = maybe mempty fileSetFilter <$> getStdinFiles here
getStdinFiles :: Path Abs Dir -> IO (Maybe (Set (Path Abs File)))
getStdinFiles here = do
isTerminal <- hIsTerminalDevice stdin
isMinTTY <- getMinTTY
if isTerminal || isMinTTY
then pure Nothing
else
(Just <$> handleFileSet here stdin)
`catch` (\(_ :: IOException) -> pure Nothing)
mkGitFilter :: Path Abs Dir -> FilterSettings -> IO Filter
mkGitFilter here FilterSettings {..} = do
if filterSettingGitignore
then do
mGitFiles <- gitLsFiles here
pure $ maybe mempty fileSetFilter mGitFiles
else pure mempty
gitLsFiles :: Path Abs Dir -> IO (Maybe (Set (Path Abs File)))
gitLsFiles here = do
-- If there is no git directory, we'll get a 'fatal' message on stderr.
-- We don't need the user to see this, so we setStderr nullStream.
let processConfig = setStderr nullStream $ shell "git ls-files"
(ec, out) <- readProcessStdout processConfig
set <- bytesFileSet here out
pure $ case ec of
ExitFailure _ -> Nothing
ExitSuccess -> Just set
mkFindFilter :: Path Abs Dir -> FilterSettings -> IO Filter
mkFindFilter here FilterSettings {..} = case filterSettingFind of
Nothing -> pure mempty
Just args -> fileSetFilter <$> filesFromFindArgs here args
filesFromFindArgs :: Path Abs Dir -> String -> IO (Set (Path Abs File))
filesFromFindArgs here args = do
let processConfig = setStdout createPipe $ shell $ "find " <> args
(ec, out) <- readProcessStdout processConfig
set <- bytesFileSet here out
case ec of
ExitFailure _ -> die $ "Find failed: " <> show ec
ExitSuccess -> pure set
bytesFileSet :: Path Abs Dir -> LB.ByteString -> IO (Set (Path Abs File))
bytesFileSet here lb =
runConduit $
CL.sourceList (LB8.lines lb)
.| C.map LB.toStrict
.| fileSetBuilder here
handleFileSet :: Path Abs Dir -> Handle -> IO (Set (Path Abs File))
handleFileSet here h =
runConduit $
C.sourceHandle h
.| C.linesUnboundedAscii
.| fileSetBuilder here
fileSetBuilder :: Path Abs Dir -> ConduitT ByteString Void IO (Set (Path Abs File))
fileSetBuilder here =
C.concatMap TE.decodeUtf8'
.| C.map T.unpack
.| C.mapM (resolveFile here)
.| C.foldMap S.singleton
standardFilter :: Path Abs Dir -> Filter
standardFilter here =
Filter
{ filterDirFilter = not . isHiddenIn here,
filterFileFilter = \f ->
and
[ not $ isHiddenIn here f,
-- It's not one of those files that vim makes
not $ "~" `isSuffixOf` fromAbsFile f,
filename f /= [relfile|4913|]
]
}
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