module Agda.Utils.FileName where
import Agda.Utils.TestHelpers
import Agda.Utils.QuickCheck
import Data.List
import Control.Monad
import System.Directory
splitFilePath :: FilePath -> (FilePath, String, String)
#ifdef mingw32_HOST_OS
splitFilePath (drive:':':s) = let (path, file, ext) = splitFilePath s
in (drive:':':path, file, ext)
#endif
splitFilePath s =
case span (`notElem` slashes) $ reverse s of
(elif, sl:htap)
| sl `elem` slashes -> let (n,e) = splitExt $ reverse elif in
(reverse (slash:htap), n, e)
(elif, "") -> let (n,e) = splitExt $ reverse elif in
("", n, e)
_ -> error $ "impossible: splitFilePath " ++ show s
splitExt :: FilePath -> (String, String)
splitExt x =
case span (/='.') $ reverse x of
(txe, '.':elif) -> (reverse elif, '.' : reverse txe)
(elif, "") -> (reverse elif, "")
_ -> error $ "impossible: splitExt " ++ show x
setExtension :: String -> FilePath -> FilePath
setExtension ext x = p ++ n ++ ext
where
(p,n,_) = splitFilePath x
splitPath :: FilePath -> [FilePath]
#ifdef mingw32_HOST_OS
splitPath (drive:':':cs) = case splitPath cs of
(path:paths) -> (drive:':':path):paths
[] -> [[drive,':',slash]]
#endif
splitPath "" = []
splitPath (c : cs) | c `elem` slashes = split cs
| otherwise = split (c : cs)
where
split path = case span (`notElem` slashes) path of
("", "") -> []
(dir, "") -> [dir]
(dir, _ : path) -> dir : split path
unsplitPath :: [FilePath] -> FilePath
#ifdef mingw32_HOST_OS
unsplitPath ((drive:':':path):paths) = drive:':':unsplitPath (path:paths)
#endif
unsplitPath dirs = concat $ intersperse [slash] $ "" : dirs ++ [""]
prop_splitPath_unsplitPath =
forAll (listOf name) $ \dirs ->
splitPath (unsplitPath dirs) == dirs
prop_splitPath =
forAll positive $ \n ->
forAll (vectorOf n nonEmptyName) $ \dirs ->
let path = concat $ intersperse [slash] dirs
in
genericLength (splitPath path) == n
&&
genericLength (splitPath $ slash : path) == n
&&
genericLength (splitPath $ path ++ [slash]) == n
&&
genericLength (splitPath $ slash : path ++ [slash]) == n
dropDirectory :: Integral i => i -> FilePath -> FilePath
dropDirectory n =
unsplitPath . reverse . genericDrop n . reverse . splitPath
prop_dropDirectory =
forAll natural $ \n ->
forAll path $ \p ->
forAll (pathOfLength n) $ \dirs ->
forAll nonEmptyName $ \name ->
dropDirectory n "" == "/"
&&
dropDirectory n [slash] == "/"
&&
dropDirectory n (addSlash p) == dropDirectory n p
&&
let p' = p ++ name in
dropDirectory n (p' ++ dirs) =^= p'
where
infix 4 =^=
p1 =^= p2 = surround p1 == surround p2
surround = addSlash . addInitSlash
addInitSlash cs@(c : _) | c == slash = cs
addInitSlash cs = slash : cs
#if 0
#ifdef mingw32_HOST_OS
canonify (drive:':':xs) ys =
case ys of
drive':':':ys'
| drive == drive' -> canonify' xs ys'
| otherwise -> ys
_ -> canonify' xs ys
#endif
canonify xs ys = canonify' xs ys
canonify' (x:xs) (y:ys)
| x == y = canonify' xs ys
canonify' [] ys = ys
canonify' (s:_) ys
| s == slash = ys
canonify' xs ys = dotdot xs ++ ys
dotdot [] = []
dotdot (s:xs)
| s == slash = slash : dotdot xs
dotdot xs =
case break (== slash) xs of
(_, xs) -> ".." ++ dotdot xs
#endif
addSlash "" = ""
addSlash [c]
| c `elem` slashes = [slash]
| otherwise = [c,slash]
addSlash (c:s) = c : addSlash s
#ifdef mingw32_HOST_OS
slash = '\\'
slashes = ['\\','/']
#else
slash = '/'
slashes = ['/']
#endif
nubFiles :: [FilePath] -> IO [FilePath]
nubFiles fs = do
infos <- mapM getInfo fs
return (map (either id fst) . nub $ infos)
where
getInfo f = do
ex <- doesFileExist f
if ex then do
f' <- canonicalizePath f
t <- getModificationTime f
return $ Right (f', t)
else
return $ Left f
nameChar :: Gen Char
nameChar = elements $ filter (not . (`elem` forbidden)) chars
where
chars = "." ++ ['a' .. 'g']
forbidden = [slash]
name :: Gen FilePath
name = listOf nameChar
nonEmptyName :: Gen FilePath
nonEmptyName = listOf1 nameChar
path :: Gen FilePath
path = listOfElements chars
where
chars = "/." ++ ['a' .. 'g']
pathOfLength :: Int -> Gen FilePath
pathOfLength n = fmap ((++ [slash]) . concat) $
vectorOf n (fmap (slash :) name)
prop_pathOfLength =
forAll natural $ \n ->
forAll (pathOfLength n) $ \path ->
dropDirectory n path == [slash]
&&
genericLength (filter (== slash) path) == n + 1
tests = runTests "Agda.Utils.FileName"
[ quickCheck' prop_splitPath_unsplitPath
, quickCheck' prop_splitPath
, quickCheck' prop_dropDirectory
, quickCheck' prop_pathOfLength
]