{-# LANGUAGE LambdaCase #-}
module PbtCli.Glob (
isGlob,
matchSegment,
expandGlob,
) where
import Control.Monad (filterM, foldM)
import Data.List (sort)
import System.Directory (doesDirectoryExist, doesPathExist, listDirectory)
import System.FilePath (splitDirectories, (</>))
isGlob :: String -> Bool
isGlob :: FilePath -> Bool
isGlob FilePath
s = (Char -> Bool) -> FilePath -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Char -> FilePath -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (FilePath
"*?" :: String)) FilePath
s
matchSegment :: String -> String -> Bool
matchSegment :: FilePath -> FilePath -> Bool
matchSegment FilePath
pat FilePath
name
| Bool -> Bool
not (FilePath -> Bool
startsWithDot FilePath
pat) Bool -> Bool -> Bool
&& FilePath -> Bool
startsWithDot FilePath
name = Bool
False
| Bool
otherwise = FilePath -> FilePath -> Bool
go FilePath
pat FilePath
name
where
startsWithDot :: FilePath -> Bool
startsWithDot = \case
(Char
'.' : FilePath
_) -> Bool
True
FilePath
_ -> Bool
False
go :: FilePath -> FilePath -> Bool
go [] [] = Bool
True
go [] FilePath
_ = Bool
False
go (Char
'*' : FilePath
ps) FilePath
cs = FilePath -> FilePath -> Bool
go FilePath
ps FilePath
cs Bool -> Bool -> Bool
|| (Bool -> Bool
not (FilePath -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null FilePath
cs) Bool -> Bool -> Bool
&& FilePath -> FilePath -> Bool
go (Char
'*' Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
ps) (Int -> FilePath -> FilePath
forall a. Int -> [a] -> [a]
drop Int
1 FilePath
cs))
go (Char
'?' : FilePath
ps) (Char
_ : FilePath
cs) = FilePath -> FilePath -> Bool
go FilePath
ps FilePath
cs
go (Char
'?' : FilePath
_) [] = Bool
False
go (Char
p : FilePath
ps) (Char
c : FilePath
cs) = Char
p Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
c Bool -> Bool -> Bool
&& FilePath -> FilePath -> Bool
go FilePath
ps FilePath
cs
go (Char
_ : FilePath
_) [] = Bool
False
expandGlob :: FilePath -> String -> IO [FilePath]
expandGlob :: FilePath -> FilePath -> IO [FilePath]
expandGlob FilePath
base FilePath
pattern
| Bool -> Bool
not (FilePath -> Bool
isGlob FilePath
pattern) = do
let p :: FilePath
p = FilePath
base FilePath -> FilePath -> FilePath
</> FilePath
pattern
Bool
exists <- FilePath -> IO Bool
doesPathExist FilePath
p
[FilePath] -> IO [FilePath]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure [FilePath
p | Bool
exists]
| Bool
otherwise = do
let segments :: [FilePath]
segments = (FilePath -> Bool) -> [FilePath] -> [FilePath]
forall a. (a -> Bool) -> [a] -> [a]
filter (FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath
".") (FilePath -> [FilePath]
splitDirectories FilePath
pattern)
[FilePath] -> [FilePath]
forall a. Ord a => [a] -> [a]
sort ([FilePath] -> [FilePath]) -> IO [FilePath] -> IO [FilePath]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ([FilePath] -> FilePath -> IO [FilePath])
-> [FilePath] -> [FilePath] -> IO [FilePath]
forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM [FilePath] -> FilePath -> IO [FilePath]
step [FilePath
base] [FilePath]
segments
where
step :: [FilePath] -> String -> IO [FilePath]
step :: [FilePath] -> FilePath -> IO [FilePath]
step [FilePath]
dirs FilePath
seg
| Bool -> Bool
not (FilePath -> Bool
isGlob FilePath
seg) =
(FilePath -> IO Bool) -> [FilePath] -> IO [FilePath]
forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM FilePath -> IO Bool
doesPathExist ((FilePath -> FilePath) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map (FilePath -> FilePath -> FilePath
</> FilePath
seg) [FilePath]
dirs)
| Bool
otherwise =
[[FilePath]] -> [FilePath]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[FilePath]] -> [FilePath]) -> IO [[FilePath]] -> IO [FilePath]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (FilePath -> IO [FilePath]) -> [FilePath] -> IO [[FilePath]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (FilePath -> FilePath -> IO [FilePath]
matchesIn FilePath
seg) [FilePath]
dirs
matchesIn :: String -> FilePath -> IO [FilePath]
matchesIn :: FilePath -> FilePath -> IO [FilePath]
matchesIn FilePath
seg FilePath
dir = do
Bool
isDir <- FilePath -> IO Bool
doesDirectoryExist FilePath
dir
if Bool -> Bool
not Bool
isDir
then [FilePath] -> IO [FilePath]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure []
else do
[FilePath]
entries <- FilePath -> IO [FilePath]
listDirectory FilePath
dir
[FilePath] -> IO [FilePath]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure [FilePath
dir FilePath -> FilePath -> FilePath
</> FilePath
e | FilePath
e <- [FilePath] -> [FilePath]
forall a. Ord a => [a] -> [a]
sort [FilePath]
entries, FilePath -> FilePath -> Bool
matchSegment FilePath
seg FilePath
e]