module Filesystem.Path
( FilePath
, empty
, null
, root
, directory
, parent
, filename
, dirname
, basename
, absolute
, relative
, append
, (</>)
, concat
, commonPrefix
, stripPrefix
, collapse
, splitDirectories
, extension
, extensions
, hasExtension
, addExtension
, (<.>)
, dropExtension
, replaceExtension
, addExtensions
, dropExtensions
, replaceExtensions
, splitExtension
, splitExtensions
) where
import Prelude hiding (FilePath, concat, null)
import qualified Prelude as Prelude
import Data.List (foldl')
import Data.Maybe (isJust, isNothing)
import qualified Data.Monoid as M
import qualified Data.Text as T
import Filesystem.Path.Internal
instance M.Monoid FilePath where
mempty = empty
mappend = append
mconcat = concat
null :: FilePath -> Bool
null = (== empty)
root :: FilePath -> FilePath
root p = empty { pathRoot = pathRoot p }
directory :: FilePath -> FilePath
directory p = empty
{ pathRoot = pathRoot p
, pathDirectories = let
dot' | isJust (pathRoot p) = []
| Prelude.null (pathDirectories p) = [dot]
| otherwise = []
in dot' ++ pathDirectories p
}
parent :: FilePath -> FilePath
parent p = empty
{ pathRoot = pathRoot p
, pathDirectories = let
starts = map Just [dot, dots]
directories = if null (filename p)
then safeInit (pathDirectories p)
else pathDirectories p
dot' | safeHead directories `elem` starts = []
| isNothing (pathRoot p) = [dot]
| otherwise = []
in dot' ++ directories
}
filename :: FilePath -> FilePath
filename p = empty
{ pathBasename = pathBasename p
, pathExtensions = pathExtensions p
}
dirname :: FilePath -> FilePath
dirname p = case reverse (pathDirectories p) of
[] -> FilePath Nothing [] Nothing []
(d:_) -> case parseFilename d of
(base, exts) -> FilePath Nothing [] base exts
basename :: FilePath -> FilePath
basename p = empty
{ pathBasename = pathBasename p
}
absolute :: FilePath -> Bool
absolute p = case pathRoot p of
Just RootPosix -> True
Just RootWindowsVolume{} -> True
Just RootWindowsCurrentVolume -> False
Just RootWindowsUnc{} -> True
Just RootWindowsDoubleQMark -> True
Nothing -> False
relative :: FilePath -> Bool
relative p = case pathRoot p of
Just _ -> False
_ -> True
append :: FilePath -> FilePath -> FilePath
append x y = cased where
cased = case pathRoot y of
Just RootPosix -> y
Just RootWindowsVolume{} -> y
Just RootWindowsCurrentVolume -> case pathRoot x of
Just RootWindowsVolume{} -> y { pathRoot = pathRoot x }
_ -> y
Just RootWindowsUnc{} -> y
Just RootWindowsDoubleQMark -> y
Nothing -> xy
xy = y
{ pathRoot = pathRoot x
, pathDirectories = directories
}
directories = xDirectories ++ pathDirectories y
xDirectories = (pathDirectories x ++) $ if null (filename x)
then []
else [filenameChunk x]
(</>) :: FilePath -> FilePath -> FilePath
(</>) = append
concat :: [FilePath] -> FilePath
concat [] = empty
concat ps = foldr1 append ps
commonPrefix :: [FilePath] -> FilePath
commonPrefix [] = empty
commonPrefix ps = foldr1 step ps where
step x y = if pathRoot x /= pathRoot y
then empty
else let cs = commonDirectories x y in
if cs /= pathDirectories x || pathBasename x /= pathBasename y
then empty { pathRoot = pathRoot x, pathDirectories = cs }
else let exts = commonExtensions x y in
x { pathExtensions = exts }
commonDirectories x y = common (pathDirectories x) (pathDirectories y)
commonExtensions x y = common (pathExtensions x) (pathExtensions y)
common [] _ = []
common _ [] = []
common (x:xs) (y:ys) = if x == y
then x : common xs ys
else []
stripPrefix :: FilePath -> FilePath -> Maybe FilePath
stripPrefix x y = if pathRoot x /= pathRoot y
then case pathRoot x of
Nothing -> Just y
Just _ -> Nothing
else do
dirs <- strip (pathDirectories x) (pathDirectories y)
case dirs of
[] -> case (pathBasename x, pathBasename y) of
(Nothing, Nothing) -> do
exts <- strip (pathExtensions x) (pathExtensions y)
return (y { pathRoot = Nothing, pathDirectories = dirs, pathExtensions = exts })
(Nothing, Just _) -> case pathExtensions x of
[] -> Just (y { pathRoot = Nothing, pathDirectories = dirs })
_ -> Nothing
(Just x_b, Just y_b) | x_b == y_b -> do
exts <- strip (pathExtensions x) (pathExtensions y)
return (empty { pathExtensions = exts })
_ -> Nothing
_ -> case (pathBasename x, pathExtensions x) of
(Nothing, []) -> Just (y { pathRoot = Nothing, pathDirectories = dirs })
_ -> Nothing
strip :: Eq a => [a] -> [a] -> Maybe [a]
strip [] ys = Just ys
strip _ [] = Nothing
strip (x:xs) (y:ys) = if x == y
then strip xs ys
else Nothing
collapse :: FilePath -> FilePath
collapse p = p { pathDirectories = newDirs } where
newDirs = case pathRoot p of
Nothing -> reverse revNewDirs
Just _ -> dropWhile (\x -> x == dot || x == dots) (reverse revNewDirs)
(_, revNewDirs) = foldl' step (True, []) (pathDirectories p)
step (True, acc) c = (False, c:acc)
step (_, acc) c | c == dot = (False, acc)
step (_, acc) c | c == dots = case acc of
[] -> (False, c:acc)
(h:ts) | h == dot -> (False, c:ts)
| h == dots -> (False, c:acc)
| otherwise -> (False, ts)
step (_, acc) c = (False, c:acc)
splitDirectories :: FilePath -> [FilePath]
splitDirectories p = rootName ++ dirNames ++ fileName where
rootName = case pathRoot p of
Nothing -> []
r -> [asFile (rootChunk r)]
dirNames = map asFile (pathDirectories p)
fileName = case (pathBasename p, pathExtensions p) of
(Nothing, []) -> []
_ -> [filename p]
asFile :: Chunk -> FilePath
asFile c = case parseFilename c of
(base, exts) -> empty
{ pathBasename = base
, pathExtensions = exts
}
extension :: FilePath -> Maybe T.Text
extension p = case extensions p of
[] -> Nothing
es -> Just (last es)
extensions :: FilePath -> [T.Text]
extensions = map unescape' . pathExtensions
hasExtension :: FilePath -> T.Text -> Bool
hasExtension p e = extension p == Just e
addExtension :: FilePath -> T.Text -> FilePath
addExtension p ext = addExtensions p [ext]
addExtensions :: FilePath -> [T.Text] -> FilePath
addExtensions p exts = p { pathExtensions = newExtensions } where
newExtensions = pathExtensions p ++ map escape exts
(<.>) :: FilePath -> T.Text -> FilePath
(<.>) = addExtension
dropExtension :: FilePath -> FilePath
dropExtension p = p { pathExtensions = safeInit (pathExtensions p) }
dropExtensions :: FilePath -> FilePath
dropExtensions p = p { pathExtensions = [] }
replaceExtension :: FilePath -> T.Text -> FilePath
replaceExtension = addExtension . dropExtension
replaceExtensions :: FilePath -> [T.Text] -> FilePath
replaceExtensions = addExtensions . dropExtensions
splitExtension :: FilePath -> (FilePath, Maybe T.Text)
splitExtension p = (dropExtension p, extension p)
splitExtensions :: FilePath -> (FilePath, [T.Text])
splitExtensions p = (dropExtensions p, extensions p)
safeInit :: [a] -> [a]
safeInit xs = case xs of
[] -> []
_ -> init xs
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x