Cabal-1.1.6: Distribution/Compat/FilePath.hs
{-# OPTIONS -cpp #-}
-- #hide
module Distribution.Compat.FilePath
( -- * File path
FilePath
, splitFileName
, splitFileExt
, splitFilePath
, baseName
, dirName
, joinFileName
, joinFileExt
, joinPaths
, changeFileExt
, isRootedPath
, isAbsolutePath
, dropAbsolutePrefix
, breakFilePath
, dropPrefix
, pathParents
, commonParent
-- * Search path
, parseSearchPath
, mkSearchPath
-- * Separators
, isPathSeparator
, pathSeparator
, searchPathSeparator
, platformPath
-- * Filename extensions
, exeExtension
, objExtension
, dllExtension
) where
#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 604
#if __GLASGOW_HASKELL__ < 603
#include "config.h"
#else
#include "ghcconfig.h"
#endif
#endif
import Data.List(intersperse)
--------------------------------------------------------------
-- * FilePath
--------------------------------------------------------------
-- | Split the path into directory and file name
--
-- Examples:
--
-- \[Posix\]
--
-- > splitFileName "/" == ("/", ".")
-- > splitFileName "/foo/bar.ext" == ("/foo", "bar.ext")
-- > splitFileName "bar.ext" == (".", "bar.ext")
-- > splitFileName "/foo/." == ("/foo", ".")
-- > splitFileName "/foo/.." == ("/foo", "..")
--
-- \[Windows\]
--
-- > splitFileName "\\" == ("\\", "")
-- > splitFileName "c:\\foo\\bar.ext" == ("c:\\foo", "bar.ext")
-- > splitFileName "bar.ext" == (".", "bar.ext")
-- > splitFileName "c:\\foo\\." == ("c:\\foo", ".")
-- > splitFileName "c:\\foo\\.." == ("c:\\foo", "..")
--
-- The first case in the Windows examples returns an empty file name.
-- This is a special case because the \"\\\\\" path doesn\'t refer to
-- an object (file or directory) which resides within a directory.
splitFileName :: FilePath -> (String, String)
#if mingw32_HOST_OS || mingw32_TARGET_OS
splitFileName p = (reverse (path2++drive), reverse fname)
where
(path,drive) = case p of
(c:':':p) -> (reverse p,[':',c])
_ -> (reverse p,"")
(fname,path1) = break isPathSeparator path
path2 = case path1 of
[] -> "."
[_] -> path1 -- don't remove the trailing slash if
-- there is only one character
(c:path) | isPathSeparator c -> path
_ -> path1
#else
splitFileName p = (reverse path1, reverse fname1)
where
(fname,path) = break isPathSeparator (reverse p)
path1 = case path of
"" -> "."
_ -> case dropWhile isPathSeparator path of
"" -> [pathSeparator]
p -> p
fname1 = case fname of
"" -> "."
_ -> fname
#endif
-- | Split the path into file name and extension. If the file doesn\'t have extension,
-- the function will return empty string. The extension doesn\'t include a leading period.
--
-- Examples:
--
-- > splitFileExt "foo.ext" == ("foo", "ext")
-- > splitFileExt "foo" == ("foo", "")
-- > splitFileExt "." == (".", "")
-- > splitFileExt ".." == ("..", "")
-- > splitFileExt "foo.bar."== ("foo.bar.", "")
-- > splitFileExt "foo.tar.gz" == ("foo.tar","gz")
splitFileExt :: FilePath -> (String, String)
splitFileExt p =
case break (== '.') fname of
(suf@(_:_),_:pre) -> (reverse (pre++path), reverse suf)
_ -> (p, [])
where
(fname,path) = break isPathSeparator (reverse p)
-- | Split the path into directory, file name and extension.
-- The function is an optimized version of the following equation:
--
-- > splitFilePath path = (dir,name,ext)
-- > where
-- > (dir,basename) = splitFileName path
-- > (name,ext) = splitFileExt basename
splitFilePath :: FilePath -> (String, String, String)
splitFilePath path = case break (== '.') (reverse basename) of
(name_r, "") -> (dir, reverse name_r, "")
(ext_r, _:name_r) -> (dir, reverse name_r, reverse ext_r)
where
(dir, basename) = splitFileName path
baseName :: FilePath -> FilePath
baseName = snd . splitFileName
dirName :: FilePath -> FilePath
dirName = fst . splitFileName
-- | The 'joinFileName' function is the opposite of 'splitFileName'.
-- It joins directory and file names to form a complete file path.
--
-- The general rule is:
--
-- > dir `joinFileName` basename == path
-- > where
-- > (dir,basename) = splitFileName path
--
-- There might be an exceptions to the rule but in any case the
-- reconstructed path will refer to the same object (file or directory).
-- An example exception is that on Windows some slashes might be converted
-- to backslashes.
joinFileName :: String -> String -> FilePath
joinFileName "" fname = fname
joinFileName "." fname = fname
joinFileName dir "" = dir
joinFileName dir fname
| isPathSeparator (last dir) = dir++fname
| otherwise = dir++pathSeparator:fname
-- | The 'joinFileExt' function is the opposite of 'splitFileExt'.
-- It joins a file name and an extension to form a complete file path.
--
-- The general rule is:
--
-- > filename `joinFileExt` ext == path
-- > where
-- > (filename,ext) = splitFileExt path
joinFileExt :: String -> String -> FilePath
joinFileExt path "" = path
joinFileExt path ext = path ++ '.':ext
-- | Given a directory path \"dir\" and a file\/directory path \"rel\",
-- returns a merged path \"full\" with the property that
-- (cd dir; do_something_with rel) is equivalent to
-- (do_something_with full). If the \"rel\" path is an absolute path
-- then the returned path is equal to \"rel\"
joinPaths :: FilePath -> FilePath -> FilePath
joinPaths path1 path2
| isRootedPath path2 = path2
| otherwise =
#if mingw32_HOST_OS || mingw32_TARGET_OS
case path2 of
d:':':path2' | take 2 path1 == [d,':'] -> path1 `joinFileName` path2'
| otherwise -> path2
_ -> path1 `joinFileName` path2
#else
path1 `joinFileName` path2
#endif
-- | Changes the extension of a file path.
changeFileExt :: FilePath -- ^ The path information to modify.
-> String -- ^ The new extension (without a leading period).
-- Specify an empty string to remove an existing
-- extension from path.
-> FilePath -- ^ A string containing the modified path information.
changeFileExt path ext = joinFileExt name ext
where
(name,_) = splitFileExt path
-- | On Unix and Macintosh the 'isRootedPath' function is a synonym to 'isAbsolutePath'.
-- The difference is important only on Windows. The rooted path must start from the root
-- directory but may not include the drive letter while the absolute path always includes
-- the drive letter and the full file path.
isRootedPath :: FilePath -> Bool
isRootedPath (c:_) | isPathSeparator c = True
#if mingw32_HOST_OS || mingw32_TARGET_OS
isRootedPath (_:':':c:_) | isPathSeparator c = True -- path with drive letter
#endif
isRootedPath _ = False
-- | Returns 'True' if this path\'s meaning is independent of any OS
-- \"working directory\", or 'False' if it isn\'t.
isAbsolutePath :: FilePath -> Bool
#if mingw32_HOST_OS || mingw32_TARGET_OS
isAbsolutePath (_:':':c:_) | isPathSeparator c = True
#else
isAbsolutePath (c:_) | isPathSeparator c = True
#endif
isAbsolutePath _ = False
-- | If the function is applied to an absolute path then it returns a local path droping
-- the absolute prefix in the path. Under Windows the prefix is \"\\\", \"c:\" or \"c:\\\". Under
-- Unix the prefix is always \"\/\".
dropAbsolutePrefix :: FilePath -> FilePath
dropAbsolutePrefix (c:cs) | isPathSeparator c = cs
#if mingw32_HOST_OS || mingw32_TARGET_OS
dropAbsolutePrefix (_:':':c:cs) | isPathSeparator c = cs -- path with drive letter
dropAbsolutePrefix (_:':':cs) = cs
#endif
dropAbsolutePrefix cs = cs
-- | Split the path into a list of strings constituting the filepath
--
-- > breakFilePath "/usr/bin/ls" == ["/","usr","bin","ls"]
breakFilePath :: FilePath -> [String]
breakFilePath = worker []
where worker ac path
| less == path = less:ac
| otherwise = worker (current:ac) less
where (less,current) = splitFileName path
-- | Drops a specified prefix from a filepath.
--
-- > dropPrefix "." "Src/Test.hs" == "Src/Test.hs"
-- > dropPrefix "Src" "Src/Test.hs" == "Test.hs"
dropPrefix :: FilePath -> FilePath -> FilePath
dropPrefix prefix path
= worker (breakFilePath prefix) (breakFilePath path)
where worker (x:xs) (y:ys)
| x == y = worker xs ys
worker _ ys = foldr1 joinPaths ys
-- | Gets this path and all its parents.
-- The function is useful in case if you want to create
-- some file but you aren\'t sure whether all directories
-- in the path exist or if you want to search upward for some file.
--
-- Some examples:
--
-- \[Posix\]
--
-- > pathParents "/" == ["/"]
-- > pathParents "/dir1" == ["/", "/dir1"]
-- > pathParents "/dir1/dir2" == ["/", "/dir1", "/dir1/dir2"]
-- > pathParents "dir1" == [".", "dir1"]
-- > pathParents "dir1/dir2" == [".", "dir1", "dir1/dir2"]
--
-- \[Windows\]
--
-- > pathParents "c:" == ["c:."]
-- > pathParents "c:\\" == ["c:\\"]
-- > pathParents "c:\\dir1" == ["c:\\", "c:\\dir1"]
-- > pathParents "c:\\dir1\\dir2" == ["c:\\", "c:\\dir1", "c:\\dir1\\dir2"]
-- > pathParents "c:dir1" == ["c:.","c:dir1"]
-- > pathParents "dir1\\dir2" == [".", "dir1", "dir1\\dir2"]
--
-- Note that if the file is relative then the current directory (\".\")
-- will be explicitly listed.
pathParents :: FilePath -> [FilePath]
pathParents p =
root'' : map ((++) root') (dropEmptyPath $ inits path')
where
#if mingw32_HOST_OS || mingw32_TARGET_OS
(root,path) = case break (== ':') p of
(path, "") -> ("",path)
(root,_:path) -> (root++":",path)
#else
(root,path) = ("",p)
#endif
(root',root'',path') = case path of
(c:path) | isPathSeparator c -> (root++[pathSeparator],root++[pathSeparator],path)
_ -> (root ,root++"." ,path)
dropEmptyPath ("":paths) = paths
dropEmptyPath paths = paths
inits :: String -> [String]
inits [] = [""]
inits cs =
case pre of
"." -> inits suf
".." -> map (joinFileName pre) (dropEmptyPath $ inits suf)
_ -> "" : map (joinFileName pre) (inits suf)
where
(pre,suf) = case break isPathSeparator cs of
(pre,"") -> (pre, "")
(pre,_:suf) -> (pre,suf)
-- | Given a list of file paths, returns the longest common parent.
commonParent :: [FilePath] -> Maybe FilePath
commonParent [] = Nothing
commonParent paths@(p:ps) =
case common Nothing "" p ps of
#if mingw32_HOST_OS || mingw32_TARGET_OS
Nothing | all (not . isAbsolutePath) paths ->
let
getDrive (d:':':_) ds
| not (d `elem` ds) = d:ds
getDrive _ ds = ds
in
case foldr getDrive [] paths of
[] -> Just "."
[d] -> Just [d,':']
_ -> Nothing
#else
Nothing | all (not . isAbsolutePath) paths -> Just "."
#endif
mb_path -> mb_path
where
common i acc [] ps = checkSep i acc ps
common i acc (c:cs) ps
| isPathSeparator c = removeSep i acc cs [] ps
| otherwise = removeChar i acc c cs [] ps
checkSep i acc [] = Just (reverse acc)
checkSep i acc ([]:ps) = Just (reverse acc)
checkSep i acc ((c1:p):ps)
| isPathSeparator c1 = checkSep i acc ps
checkSep i acc ps = i
removeSep i acc cs pacc [] =
common (Just (reverse (pathSeparator:acc))) (pathSeparator:acc) cs pacc
removeSep i acc cs pacc ([] :ps) = Just (reverse acc)
removeSep i acc cs pacc ((c1:p):ps)
| isPathSeparator c1 = removeSep i acc cs (p:pacc) ps
removeSep i acc cs pacc ps = i
removeChar i acc c cs pacc [] = common i (c:acc) cs pacc
removeChar i acc c cs pacc ([] :ps) = i
removeChar i acc c cs pacc ((c1:p):ps)
| c == c1 = removeChar i acc c cs (p:pacc) ps
removeChar i acc c cs pacc ps = i
--------------------------------------------------------------
-- * Search path
--------------------------------------------------------------
-- | The function splits the given string to substrings
-- using the 'searchPathSeparator'.
parseSearchPath :: String -> [FilePath]
parseSearchPath path = split path
where
split :: String -> [String]
split s =
case rest' of
[] -> [chunk]
_:rest -> chunk : split rest
where
chunk =
case chunk' of
#ifdef mingw32_HOST_OS
('\"':xs@(_:_)) | last xs == '\"' -> init xs
#endif
_ -> chunk'
(chunk', rest') = break (==searchPathSeparator) s
-- | The function concatenates the given paths to form a
-- single string where the paths are separated with 'searchPathSeparator'.
mkSearchPath :: [FilePath] -> String
mkSearchPath paths = concat (intersperse [searchPathSeparator] paths)
--------------------------------------------------------------
-- * Separators
--------------------------------------------------------------
-- | Checks whether the character is a valid path separator for the host
-- platform. The valid character is a 'pathSeparator' but since the Windows
-- operating system also accepts a slash (\"\/\") since DOS 2, the function
-- checks for it on this platform, too.
isPathSeparator :: Char -> Bool
isPathSeparator ch =
#if mingw32_HOST_OS || mingw32_TARGET_OS
ch == '/' || ch == '\\'
#else
ch == '/'
#endif
-- | Provides a platform-specific character used to separate directory levels in
-- a path string that reflects a hierarchical file system organization. The
-- separator is a slash (@\"\/\"@) on Unix and Macintosh, and a backslash
-- (@\"\\\"@) on the Windows operating system.
pathSeparator :: Char
#if mingw32_HOST_OS || mingw32_TARGET_OS
pathSeparator = '\\'
#else
pathSeparator = '/'
#endif
-- | A platform-specific character used to separate search path strings in
-- environment variables. The separator is a colon (\":\") on Unix and Macintosh,
-- and a semicolon (\";\") on the Windows operating system.
searchPathSeparator :: Char
#if mingw32_HOST_OS || mingw32_TARGET_OS
searchPathSeparator = ';'
#else
searchPathSeparator = ':'
#endif
-- |Convert Unix-style path separators to the path separators for this platform.
platformPath :: FilePath -> FilePath
#if mingw32_HOST_OS || mingw32_TARGET_OS
platformPath = map slash
where slash '/' = '\\'
slash c = c
#else
platformPath = id
#endif
-- ToDo: This should be determined via autoconf (AC_EXEEXT)
-- | Extension for executable files
-- (typically @\"\"@ on Unix and @\"exe\"@ on Windows or OS\/2)
exeExtension :: String
#if mingw32_HOST_OS || mingw32_TARGET_OS
exeExtension = "exe"
#else
exeExtension = ""
#endif
-- ToDo: This should be determined via autoconf (AC_OBJEXT)
-- | Extension for object files. For GHC and NHC the extension is @\"o\"@.
-- Hugs uses either @\"o\"@ or @\"obj\"@ depending on the used C compiler.
objExtension :: String
objExtension = "o"
-- | Extension for dynamically linked (or shared) libraries
-- (typically @\"so\"@ on Unix and @\"dll\"@ on Windows)
dllExtension :: String
#if mingw32_HOST_OS || mingw32_TARGET_OS
dllExtension = "dll"
#else
dllExtension = "so"
#endif