module Cabal.Internal.Glob where
import Control.Monad (filterM, liftM2)
import Control.Monad.IO.Class (MonadIO (..))
import Data.Functor (void)
import Data.List (stripPrefix)
import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.FilePath.Posix ((</>))
import Text.ParserCombinators.ReadP
data FilePathGlob = FilePathGlob FilePathRoot FilePathGlobRel
deriving (Eq, Show)
data FilePathGlobRel
= GlobDir Glob FilePathGlobRel
| GlobFile Glob
| GlobDirTrailing
deriving (Eq, Show)
type Glob = [GlobPiece]
data GlobPiece = WildCard
| Literal String
| Union [Glob]
deriving (Eq, Show)
data FilePathRoot
= FilePathRelative
| FilePathRoot FilePath
| FilePathHomeDir
deriving (Eq, Show)
parseFilePathGlobRel :: ReadP FilePathGlobRel
parseFilePathGlobRel =
parseGlob >>= \globpieces ->
asDir globpieces
<++ asTDir globpieces
<++ asFile globpieces
where
asDir glob = do dirSep
GlobDir glob <$> parseFilePathGlobRel
asTDir glob = do dirSep
return (GlobDir glob GlobDirTrailing)
asFile glob = return (GlobFile glob)
dirSep = void (char '/')
+++ (do _ <- char '\\'
following <- look
case following of
(c:_) | isGlobEscapedChar c -> pfail
_ -> return ())
parseGlob :: ReadP Glob
parseGlob = many1 parsePiece
where
parsePiece = literal +++ wildcard +++ union'
wildcard = char '*' >> return WildCard
union' = between (char '{') (char '}') $
fmap Union (sepBy1 parseGlob (char ','))
literal = Literal `fmap` litchars1
litchar = normal +++ escape
normal = satisfy (\c -> not (isGlobEscapedChar c)
&& c /= '/' && c /= '\\')
escape = char '\\' >> satisfy isGlobEscapedChar
litchars1 :: ReadP [Char]
litchars1 = liftM2 (:) litchar litchars
litchars :: ReadP [Char]
litchars = litchars1 <++ return []
isGlobEscapedChar :: Char -> Bool
isGlobEscapedChar '*' = True
isGlobEscapedChar '{' = True
isGlobEscapedChar '}' = True
isGlobEscapedChar ',' = True
isGlobEscapedChar _ = False
expandRelGlob :: MonadIO m => FilePath -> FilePathGlobRel -> m [FilePath]
expandRelGlob root glob0 = liftIO $ go glob0 ""
where
go (GlobFile glob) dir = do
entries <- getDirectoryContents (root </> dir)
let files = filter (matchGlob glob) entries
return (map (dir </>) files)
go (GlobDir glob globPath) dir = do
entries <- getDirectoryContents (root </> dir)
subdirs <- filterM (\subdir -> doesDirectoryExist
(root </> dir </> subdir))
$ filter (matchGlob glob) entries
concat <$> mapM (\subdir -> go globPath (dir </> subdir)) subdirs
go GlobDirTrailing dir = return [dir]
matchGlob :: Glob -> FilePath -> Bool
matchGlob = goStart
where
go, goStart :: [GlobPiece] -> String -> Bool
goStart (WildCard:_) ('.':_) = False
goStart (Union globs:rest) cs = any (\glob -> goStart (glob ++ rest) cs)
globs
goStart rest cs = go rest cs
go [] "" = True
go (Literal lit:rest) cs
| Just cs' <- stripPrefix lit cs
= go rest cs'
| otherwise = False
go [WildCard] "" = True
go (WildCard:rest) (c:cs) = go rest (c:cs) || go (WildCard:rest) cs
go (Union globs:rest) cs = any (\glob -> go (glob ++ rest) cs) globs
go [] (_:_) = False
go (_:_) "" = False