{-# LANGUAGE PatternGuards #-}

-- This template expects CPP definitions for:
--     MODULE_NAME = Posix | Windows
--     IS_WINDOWS  = False | True

-- |
-- Module      :  System.FilePath.MODULE_NAME
-- Copyright   :  (c) Neil Mitchell 2005-2014
-- License     :  BSD3
--
-- Maintainer  :  ndmitchell@gmail.com
-- Stability   :  stable
-- Portability :  portable
--
-- A library for 'FilePath' manipulations, using MODULE_NAME style paths on
-- all platforms. Importing "System.FilePath" is usually better.
--
-- Given the example 'FilePath': @\/directory\/file.ext@
--
-- We can use the following functions to extract pieces.
--
-- * 'takeFileName' gives @\"file.ext\"@
--
-- * 'takeDirectory' gives @\"\/directory\"@
--
-- * 'takeExtension' gives @\".ext\"@
--
-- * 'dropExtension' gives @\"\/directory\/file\"@
--
-- * 'takeBaseName' gives @\"file\"@
--
-- And we could have built an equivalent path with the following expressions:
--
-- * @\"\/directory\" '</>' \"file.ext\"@.
--
-- * @\"\/directory\/file" '<.>' \"ext\"@.
--
-- * @\"\/directory\/file.txt" '-<.>' \"ext\"@.
--
-- Each function in this module is documented with several examples,
-- which are also used as tests.
--
-- Here are a few examples of using the @filepath@ functions together:
--
-- /Example 1:/ Find the possible locations of a Haskell module @Test@ imported from module @Main@:
--
-- @['replaceFileName' path_to_main \"Test\" '<.>' ext | ext <- [\"hs\",\"lhs\"] ]@
--
-- /Example 2:/ Download a file from @url@ and save it to disk:
--
-- @do let file = 'makeValid' url
--   System.Directory.createDirectoryIfMissing True ('takeDirectory' file)@
--
-- /Example 3:/ Compile a Haskell file, putting the @.hi@ file under @interface@:
--
-- @'takeDirectory' file '</>' \"interface\" '</>' ('takeFileName' file '-<.>' \"hi\")@
--
-- References:
-- [1] <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx Naming Files, Paths and Namespaces> (Microsoft MSDN)
module System.FilePath.Posix
    (
    -- * Separator predicates
    FilePath,
    pathSeparator, pathSeparators, isPathSeparator,
    searchPathSeparator, isSearchPathSeparator,
    extSeparator, isExtSeparator,

    -- * @$PATH@ methods
    splitSearchPath, getSearchPath,

    -- * Extension functions
    splitExtension,
    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),
    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,
    stripExtension,

    -- * Filename\/directory functions
    splitFileName,
    takeFileName, replaceFileName, dropFileName,
    takeBaseName, replaceBaseName,
    takeDirectory, replaceDirectory,
    combine, (</>),
    splitPath, joinPath, splitDirectories,

    -- * Drive functions
    splitDrive, joinDrive,
    takeDrive, hasDrive, dropDrive, isDrive,

    -- * Trailing slash functions
    hasTrailingPathSeparator,
    addTrailingPathSeparator,
    dropTrailingPathSeparator,

    -- * File name manipulations
    normalise, equalFilePath,
    makeRelative,
    isRelative, isAbsolute,
    isValid, makeValid
    )
    where

import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)
import Data.Maybe(isJust)
import Data.List(stripPrefix, isSuffixOf)

import System.Environment(getEnv)


infixr 7  <.>, -<.>
infixr 5  </>





---------------------------------------------------------------------
-- Platform Abstraction Methods (private)

-- | Is the operating system Unix or Linux like
isPosix :: Bool
isPosix :: Bool
isPosix = Bool -> Bool
not Bool
isWindows

-- | Is the operating system Windows like
isWindows :: Bool
isWindows :: Bool
isWindows = Bool
False


---------------------------------------------------------------------
-- The basic functions

-- | The character that separates directories. In the case where more than
--   one character is possible, 'pathSeparator' is the \'ideal\' one.
--
-- > Windows: pathSeparator == '\\'
-- > Posix:   pathSeparator ==  '/'
-- > isPathSeparator pathSeparator
pathSeparator :: Char
pathSeparator :: Char
pathSeparator = if Bool
isWindows then Char
'\\' else Char
'/'

-- | The list of all possible separators.
--
-- > Windows: pathSeparators == ['\\', '/']
-- > Posix:   pathSeparators == ['/']
-- > pathSeparator `elem` pathSeparators
pathSeparators :: [Char]
pathSeparators :: [Char]
pathSeparators = if Bool
isWindows then [Char]
"\\/" else [Char]
"/"

-- | Rather than using @(== 'pathSeparator')@, use this. Test if something
--   is a path separator.
--
-- > isPathSeparator a == (a `elem` pathSeparators)
isPathSeparator :: Char -> Bool
isPathSeparator :: Char -> Bool
isPathSeparator Char
'/' = Bool
True
isPathSeparator Char
'\\' = Bool
isWindows
isPathSeparator Char
_ = Bool
False


-- | The character that is used to separate the entries in the $PATH environment variable.
--
-- > Windows: searchPathSeparator == ';'
-- > Posix:   searchPathSeparator == ':'
searchPathSeparator :: Char
searchPathSeparator :: Char
searchPathSeparator = if Bool
isWindows then Char
';' else Char
':'

-- | Is the character a file separator?
--
-- > isSearchPathSeparator a == (a == searchPathSeparator)
isSearchPathSeparator :: Char -> Bool
isSearchPathSeparator :: Char -> Bool
isSearchPathSeparator = (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
searchPathSeparator)


-- | File extension character
--
-- > extSeparator == '.'
extSeparator :: Char
extSeparator :: Char
extSeparator = Char
'.'

-- | Is the character an extension character?
--
-- > isExtSeparator a == (a == extSeparator)
isExtSeparator :: Char -> Bool
isExtSeparator :: Char -> Bool
isExtSeparator = (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
extSeparator)


---------------------------------------------------------------------
-- Path methods (environment $PATH)

-- | Take a string, split it on the 'searchPathSeparator' character.
--   Blank items are ignored on Windows, and converted to @.@ on Posix.
--   On Windows path elements are stripped of quotes.
--
--   Follows the recommendations in
--   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>
--
-- > Posix:   splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]
-- > Posix:   splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]
-- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]
-- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]
-- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]
splitSearchPath :: String -> [FilePath]
splitSearchPath :: [Char] -> [[Char]]
splitSearchPath = [Char] -> [[Char]]
f
    where
    f :: [Char] -> [[Char]]
f [Char]
xs = case (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break Char -> Bool
isSearchPathSeparator [Char]
xs of
           ([Char]
pre, []    ) -> [Char] -> [[Char]]
g [Char]
pre
           ([Char]
pre, Char
_:[Char]
post) -> [Char] -> [[Char]]
g [Char]
pre [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [Char] -> [[Char]]
f [Char]
post

    g :: [Char] -> [[Char]]
g [Char]
"" = [[Char]
"." | Bool
isPosix]
    g (Char
'\"':x :: [Char]
x@(Char
_:[Char]
_)) | Bool
isWindows Bool -> Bool -> Bool
&& [Char] -> Char
forall a. [a] -> a
last [Char]
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\"' = [[Char] -> [Char]
forall a. [a] -> [a]
init [Char]
x]
    g [Char]
x = [[Char]
x]


-- | Get a list of 'FilePath's in the $PATH variable.
getSearchPath :: IO [FilePath]
getSearchPath :: IO [[Char]]
getSearchPath = ([Char] -> [[Char]]) -> IO [Char] -> IO [[Char]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [Char] -> [[Char]]
splitSearchPath ([Char] -> IO [Char]
getEnv [Char]
"PATH")


---------------------------------------------------------------------
-- Extension methods

-- | Split on the extension. 'addExtension' is the inverse.
--
-- > splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- > uncurry (++) (splitExtension x) == x
-- > Valid x => uncurry addExtension (splitExtension x) == x
-- > splitExtension "file.txt" == ("file",".txt")
-- > splitExtension "file" == ("file","")
-- > splitExtension "file/file.txt" == ("file/file",".txt")
-- > splitExtension "file.txt/boris" == ("file.txt/boris","")
-- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- > splitExtension "file/path.txt/" == ("file/path.txt/","")
splitExtension :: FilePath -> (String, String)
splitExtension :: [Char] -> ([Char], [Char])
splitExtension [Char]
x = case [Char]
nameDot of
                       [Char]
"" -> ([Char]
x,[Char]
"")
                       [Char]
_ -> ([Char]
dir [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> [Char]
forall a. [a] -> [a]
init [Char]
nameDot, Char
extSeparator Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
: [Char]
ext)
    where
        ([Char]
dir,[Char]
file) = [Char] -> ([Char], [Char])
splitFileName_ [Char]
x
        ([Char]
nameDot,[Char]
ext) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
breakEnd Char -> Bool
isExtSeparator [Char]
file

-- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.
--
-- > takeExtension "/directory/path.ext" == ".ext"
-- > takeExtension x == snd (splitExtension x)
-- > Valid x => takeExtension (addExtension x "ext") == ".ext"
-- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"
takeExtension :: FilePath -> String
takeExtension :: [Char] -> [Char]
takeExtension = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> b
snd (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitExtension

-- | Remove the current extension and add another, equivalent to 'replaceExtension'.
--
-- > "/directory/path.txt" -<.> "ext" == "/directory/path.ext"
-- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext"
-- > "foo.o" -<.> "c" == "foo.c"
(-<.>) :: FilePath -> String -> FilePath
-<.> :: [Char] -> [Char] -> [Char]
(-<.>) = [Char] -> [Char] -> [Char]
replaceExtension

-- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.
--
-- > replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext"
-- > replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext"
-- > replaceExtension "file.txt" ".bob" == "file.bob"
-- > replaceExtension "file.txt" "bob" == "file.bob"
-- > replaceExtension "file" ".bob" == "file.bob"
-- > replaceExtension "file.txt" "" == "file"
-- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt"
-- > replaceExtension x y == addExtension (dropExtension x) y
replaceExtension :: FilePath -> String -> FilePath
replaceExtension :: [Char] -> [Char] -> [Char]
replaceExtension [Char]
x [Char]
y = [Char] -> [Char]
dropExtension [Char]
x [Char] -> [Char] -> [Char]
<.> [Char]
y

-- | Add an extension, even if there is already one there, equivalent to 'addExtension'.
--
-- > "/directory/path" <.> "ext" == "/directory/path.ext"
-- > "/directory/path" <.> ".ext" == "/directory/path.ext"
(<.>) :: FilePath -> String -> FilePath
<.> :: [Char] -> [Char] -> [Char]
(<.>) = [Char] -> [Char] -> [Char]
addExtension

-- | Remove last extension, and the \".\" preceding it.
--
-- > dropExtension "/directory/path.ext" == "/directory/path"
-- > dropExtension x == fst (splitExtension x)
dropExtension :: FilePath -> FilePath
dropExtension :: [Char] -> [Char]
dropExtension = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> a
fst (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitExtension

-- | Add an extension, even if there is already one there, equivalent to '<.>'.
--
-- > addExtension "/directory/path" "ext" == "/directory/path.ext"
-- > addExtension "file.txt" "bib" == "file.txt.bib"
-- > addExtension "file." ".bib" == "file..bib"
-- > addExtension "file" ".bib" == "file.bib"
-- > addExtension "/" "x" == "/.x"
-- > addExtension x "" == x
-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"
-- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"
addExtension :: FilePath -> String -> FilePath
addExtension :: [Char] -> [Char] -> [Char]
addExtension [Char]
file [Char]
"" = [Char]
file
addExtension [Char]
file xs :: [Char]
xs@(Char
x:[Char]
_) = [Char] -> [Char] -> [Char]
joinDrive [Char]
a [Char]
res
    where
        res :: [Char]
res = if Char -> Bool
isExtSeparator Char
x then [Char]
b [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
xs
              else [Char]
b [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char
extSeparator] [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
xs

        ([Char]
a,[Char]
b) = [Char] -> ([Char], [Char])
splitDrive [Char]
file

-- | Does the given filename have an extension?
--
-- > hasExtension "/directory/path.ext" == True
-- > hasExtension "/directory/path" == False
-- > null (takeExtension x) == not (hasExtension x)
hasExtension :: FilePath -> Bool
hasExtension :: [Char] -> Bool
hasExtension = (Char -> Bool) -> [Char] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Char -> Bool
isExtSeparator ([Char] -> Bool) -> ([Char] -> [Char]) -> [Char] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
takeFileName


-- | Does the given filename have the specified extension?
--
-- > "png" `isExtensionOf` "/directory/file.png" == True
-- > ".png" `isExtensionOf` "/directory/file.png" == True
-- > ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True
-- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False
-- > "png" `isExtensionOf` "/directory/file.png.jpg" == False
-- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False
isExtensionOf :: String -> FilePath -> Bool
isExtensionOf :: [Char] -> [Char] -> Bool
isExtensionOf ext :: [Char]
ext@(Char
'.':[Char]
_) = [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isSuffixOf [Char]
ext ([Char] -> Bool) -> ([Char] -> [Char]) -> [Char] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
takeExtensions
isExtensionOf [Char]
ext         = [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isSuffixOf (Char
'.'Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:[Char]
ext) ([Char] -> Bool) -> ([Char] -> [Char]) -> [Char] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
takeExtensions

-- | Drop the given extension from a FilePath, and the @\".\"@ preceding it.
--   Returns 'Nothing' if the FilePath does not have the given extension, or
--   'Just' and the part before the extension if it does.
--
--   This function can be more predictable than 'dropExtensions', especially if the filename
--   might itself contain @.@ characters.
--
-- > stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x"
-- > stripExtension "hi.o" "foo.x.hs.o" == Nothing
-- > dropExtension x == fromJust (stripExtension (takeExtension x) x)
-- > dropExtensions x == fromJust (stripExtension (takeExtensions x) x)
-- > stripExtension ".c.d" "a.b.c.d"  == Just "a.b"
-- > stripExtension ".c.d" "a.b..c.d" == Just "a.b."
-- > stripExtension "baz"  "foo.bar"  == Nothing
-- > stripExtension "bar"  "foobar"   == Nothing
-- > stripExtension ""     x          == Just x
stripExtension :: String -> FilePath -> Maybe FilePath
stripExtension :: [Char] -> [Char] -> Maybe [Char]
stripExtension []        [Char]
path = [Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
path
stripExtension ext :: [Char]
ext@(Char
x:[Char]
_) [Char]
path = [Char] -> [Char] -> Maybe [Char]
forall a. Eq a => [a] -> [a] -> Maybe [a]
stripSuffix [Char]
dotExt [Char]
path
    where dotExt :: [Char]
dotExt = if Char -> Bool
isExtSeparator Char
x then [Char]
ext else Char
'.'Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:[Char]
ext


-- | Split on all extensions.
--
-- > splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- > uncurry (++) (splitExtensions x) == x
-- > Valid x => uncurry addExtension (splitExtensions x) == x
-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")
splitExtensions :: FilePath -> (FilePath, String)
splitExtensions :: [Char] -> ([Char], [Char])
splitExtensions [Char]
x = ([Char]
a [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
c, [Char]
d)
    where
        ([Char]
a,[Char]
b) = [Char] -> ([Char], [Char])
splitFileName_ [Char]
x
        ([Char]
c,[Char]
d) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break Char -> Bool
isExtSeparator [Char]
b

-- | Drop all extensions.
--
-- > dropExtensions "/directory/path.ext" == "/directory/path"
-- > dropExtensions "file.tar.gz" == "file"
-- > not $ hasExtension $ dropExtensions x
-- > not $ any isExtSeparator $ takeFileName $ dropExtensions x
dropExtensions :: FilePath -> FilePath
dropExtensions :: [Char] -> [Char]
dropExtensions = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> a
fst (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitExtensions

-- | Get all extensions.
--
-- > takeExtensions "/directory/path.ext" == ".ext"
-- > takeExtensions "file.tar.gz" == ".tar.gz"
takeExtensions :: FilePath -> String
takeExtensions :: [Char] -> [Char]
takeExtensions = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> b
snd (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitExtensions


-- | Replace all extensions of a file with a new extension. Note
--   that 'replaceExtension' and 'addExtension' both work for adding
--   multiple extensions, so only required when you need to drop
--   all extensions first.
--
-- > replaceExtensions "file.fred.bob" "txt" == "file.txt"
-- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"
replaceExtensions :: FilePath -> String -> FilePath
replaceExtensions :: [Char] -> [Char] -> [Char]
replaceExtensions [Char]
x [Char]
y = [Char] -> [Char]
dropExtensions [Char]
x [Char] -> [Char] -> [Char]
<.> [Char]
y



---------------------------------------------------------------------
-- Drive methods

-- | Is the given character a valid drive letter?
-- only a-z and A-Z are letters, not isAlpha which is more unicodey
isLetter :: Char -> Bool
isLetter :: Char -> Bool
isLetter Char
x = Char -> Bool
isAsciiLower Char
x Bool -> Bool -> Bool
|| Char -> Bool
isAsciiUpper Char
x


-- | Split a path into a drive and a path.
--   On Posix, \/ is a Drive.
--
-- > uncurry (++) (splitDrive x) == x
-- > Windows: splitDrive "file" == ("","file")
-- > Windows: splitDrive "c:/file" == ("c:/","file")
-- > Windows: splitDrive "c:\\file" == ("c:\\","file")
-- > Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- > Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- > Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- > Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- > Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- > Windows: splitDrive "/d" == ("","/d")
-- > Posix:   splitDrive "/test" == ("/","test")
-- > Posix:   splitDrive "//test" == ("//","test")
-- > Posix:   splitDrive "test/file" == ("","test/file")
-- > Posix:   splitDrive "file" == ("","file")
splitDrive :: FilePath -> (FilePath, FilePath)
splitDrive :: [Char] -> ([Char], [Char])
splitDrive [Char]
x | Bool
isPosix = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'/') [Char]
x
splitDrive [Char]
x | Just ([Char], [Char])
y <- [Char] -> Maybe ([Char], [Char])
readDriveLetter [Char]
x = ([Char], [Char])
y
splitDrive [Char]
x | Just ([Char], [Char])
y <- [Char] -> Maybe ([Char], [Char])
readDriveUNC [Char]
x = ([Char], [Char])
y
splitDrive [Char]
x | Just ([Char], [Char])
y <- [Char] -> Maybe ([Char], [Char])
readDriveShare [Char]
x = ([Char], [Char])
y
splitDrive [Char]
x = ([Char]
"",[Char]
x)

addSlash :: FilePath -> FilePath -> (FilePath, FilePath)
addSlash :: [Char] -> [Char] -> ([Char], [Char])
addSlash [Char]
a [Char]
xs = ([Char]
a[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
c,[Char]
d)
    where ([Char]
c,[Char]
d) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span Char -> Bool
isPathSeparator [Char]
xs

-- See [1].
-- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"
readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)
readDriveUNC :: [Char] -> Maybe ([Char], [Char])
readDriveUNC (Char
s1:Char
s2:Char
'?':Char
s3:[Char]
xs) | (Char -> Bool) -> [Char] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isPathSeparator [Char
s1,Char
s2,Char
s3] =
    case (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toUpper [Char]
xs of
        (Char
'U':Char
'N':Char
'C':Char
s4:[Char]
_) | Char -> Bool
isPathSeparator Char
s4 ->
            let ([Char]
a,[Char]
b) = [Char] -> ([Char], [Char])
readDriveShareName (Int -> [Char] -> [Char]
forall a. Int -> [a] -> [a]
drop Int
4 [Char]
xs)
            in ([Char], [Char]) -> Maybe ([Char], [Char])
forall a. a -> Maybe a
Just (Char
s1Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Char
s2Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Char
'?'Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Char
s3Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Int -> [Char] -> [Char]
forall a. Int -> [a] -> [a]
take Int
4 [Char]
xs [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
a, [Char]
b)
        [Char]
_ -> case [Char] -> Maybe ([Char], [Char])
readDriveLetter [Char]
xs of
                 -- Extended-length path.
                 Just ([Char]
a,[Char]
b) -> ([Char], [Char]) -> Maybe ([Char], [Char])
forall a. a -> Maybe a
Just (Char
s1Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Char
s2Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Char
'?'Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Char
s3Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:[Char]
a,[Char]
b)
                 Maybe ([Char], [Char])
Nothing -> Maybe ([Char], [Char])
forall a. Maybe a
Nothing
readDriveUNC [Char]
_ = Maybe ([Char], [Char])
forall a. Maybe a
Nothing

{- c:\ -}
readDriveLetter :: String -> Maybe (FilePath, FilePath)
readDriveLetter :: [Char] -> Maybe ([Char], [Char])
readDriveLetter (Char
x:Char
':':Char
y:[Char]
xs) | Char -> Bool
isLetter Char
x Bool -> Bool -> Bool
&& Char -> Bool
isPathSeparator Char
y = ([Char], [Char]) -> Maybe ([Char], [Char])
forall a. a -> Maybe a
Just (([Char], [Char]) -> Maybe ([Char], [Char]))
-> ([Char], [Char]) -> Maybe ([Char], [Char])
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> ([Char], [Char])
addSlash [Char
x,Char
':'] (Char
yChar -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:[Char]
xs)
readDriveLetter (Char
x:Char
':':[Char]
xs) | Char -> Bool
isLetter Char
x = ([Char], [Char]) -> Maybe ([Char], [Char])
forall a. a -> Maybe a
Just ([Char
x,Char
':'], [Char]
xs)
readDriveLetter [Char]
_ = Maybe ([Char], [Char])
forall a. Maybe a
Nothing

{- \\sharename\ -}
readDriveShare :: String -> Maybe (FilePath, FilePath)
readDriveShare :: [Char] -> Maybe ([Char], [Char])
readDriveShare (Char
s1:Char
s2:[Char]
xs) | Char -> Bool
isPathSeparator Char
s1 Bool -> Bool -> Bool
&& Char -> Bool
isPathSeparator Char
s2 =
        ([Char], [Char]) -> Maybe ([Char], [Char])
forall a. a -> Maybe a
Just (Char
s1Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:Char
s2Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:[Char]
a,[Char]
b)
    where ([Char]
a,[Char]
b) = [Char] -> ([Char], [Char])
readDriveShareName [Char]
xs
readDriveShare [Char]
_ = Maybe ([Char], [Char])
forall a. Maybe a
Nothing

{- assume you have already seen \\ -}
{- share\bob -> "share\", "bob" -}
readDriveShareName :: String -> (FilePath, FilePath)
readDriveShareName :: [Char] -> ([Char], [Char])
readDriveShareName [Char]
name = [Char] -> [Char] -> ([Char], [Char])
addSlash [Char]
a [Char]
b
    where ([Char]
a,[Char]
b) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break Char -> Bool
isPathSeparator [Char]
name



-- | Join a drive and the rest of the path.
--
-- > Valid x => uncurry joinDrive (splitDrive x) == x
-- > Windows: joinDrive "C:" "foo" == "C:foo"
-- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"
-- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"
-- > Windows: joinDrive "/:" "foo" == "/:\\foo"
joinDrive :: FilePath -> FilePath -> FilePath
joinDrive :: [Char] -> [Char] -> [Char]
joinDrive = [Char] -> [Char] -> [Char]
combineAlways

-- | Get the drive from a filepath.
--
-- > takeDrive x == fst (splitDrive x)
takeDrive :: FilePath -> FilePath
takeDrive :: [Char] -> [Char]
takeDrive = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> a
fst (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitDrive

-- | Delete the drive, if it exists.
--
-- > dropDrive x == snd (splitDrive x)
dropDrive :: FilePath -> FilePath
dropDrive :: [Char] -> [Char]
dropDrive = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> b
snd (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitDrive

-- | Does a path have a drive.
--
-- > not (hasDrive x) == null (takeDrive x)
-- > Posix:   hasDrive "/foo" == True
-- > Windows: hasDrive "C:\\foo" == True
-- > Windows: hasDrive "C:foo" == True
-- >          hasDrive "foo" == False
-- >          hasDrive "" == False
hasDrive :: FilePath -> Bool
hasDrive :: [Char] -> Bool
hasDrive = Bool -> Bool
not (Bool -> Bool) -> ([Char] -> Bool) -> [Char] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([Char] -> Bool) -> ([Char] -> [Char]) -> [Char] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
takeDrive


-- | Is an element a drive
--
-- > Posix:   isDrive "/" == True
-- > Posix:   isDrive "/foo" == False
-- > Windows: isDrive "C:\\" == True
-- > Windows: isDrive "C:\\foo" == False
-- >          isDrive "" == False
isDrive :: FilePath -> Bool
isDrive :: [Char] -> Bool
isDrive [Char]
x = Bool -> Bool
not ([Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
x) Bool -> Bool -> Bool
&& [Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([Char] -> [Char]
dropDrive [Char]
x)


---------------------------------------------------------------------
-- Operations on a filepath, as a list of directories

-- | Split a filename into directory and file. '</>' is the inverse.
--   The first component will often end with a trailing slash.
--
-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")
-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"
-- > Valid x => isValid (fst (splitFileName x))
-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")
-- > splitFileName "file/" == ("file/", "")
-- > splitFileName "bob" == ("./", "bob")
-- > Posix:   splitFileName "/" == ("/","")
-- > Windows: splitFileName "c:" == ("c:","")
splitFileName :: FilePath -> (String, String)
splitFileName :: [Char] -> ([Char], [Char])
splitFileName [Char]
x = (if [Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
dir then [Char]
"./" else [Char]
dir, [Char]
name)
    where
        ([Char]
dir, [Char]
name) = [Char] -> ([Char], [Char])
splitFileName_ [Char]
x

-- version of splitFileName where, if the FilePath has no directory
-- component, the returned directory is "" rather than "./".  This
-- is used in cases where we are going to combine the returned
-- directory to make a valid FilePath, and having a "./" appear would
-- look strange and upset simple equality properties.  See
-- e.g. replaceFileName.
splitFileName_ :: FilePath -> (String, String)
splitFileName_ :: [Char] -> ([Char], [Char])
splitFileName_ [Char]
x = ([Char]
drv [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
dir, [Char]
file)
    where
        ([Char]
drv,[Char]
pth) = [Char] -> ([Char], [Char])
splitDrive [Char]
x
        ([Char]
dir,[Char]
file) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
breakEnd Char -> Bool
isPathSeparator [Char]
pth

-- | Set the filename.
--
-- > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext"
-- > Valid x => replaceFileName x (takeFileName x) == x
replaceFileName :: FilePath -> String -> FilePath
replaceFileName :: [Char] -> [Char] -> [Char]
replaceFileName [Char]
x [Char]
y = [Char]
a [Char] -> [Char] -> [Char]
</> [Char]
y where ([Char]
a,[Char]
_) = [Char] -> ([Char], [Char])
splitFileName_ [Char]
x

-- | Drop the filename. Unlike 'takeDirectory', this function will leave
--   a trailing path separator on the directory.
--
-- > dropFileName "/directory/file.ext" == "/directory/"
-- > dropFileName x == fst (splitFileName x)
dropFileName :: FilePath -> FilePath
dropFileName :: [Char] -> [Char]
dropFileName = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> a
fst (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitFileName


-- | Get the file name.
--
-- > takeFileName "/directory/file.ext" == "file.ext"
-- > takeFileName "test/" == ""
-- > takeFileName x `isSuffixOf` x
-- > takeFileName x == snd (splitFileName x)
-- > Valid x => takeFileName (replaceFileName x "fred") == "fred"
-- > Valid x => takeFileName (x </> "fred") == "fred"
-- > Valid x => isRelative (takeFileName x)
takeFileName :: FilePath -> FilePath
takeFileName :: [Char] -> [Char]
takeFileName = ([Char], [Char]) -> [Char]
forall a b. (a, b) -> b
snd (([Char], [Char]) -> [Char])
-> ([Char] -> ([Char], [Char])) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> ([Char], [Char])
splitFileName

-- | Get the base name, without an extension or path.
--
-- > takeBaseName "/directory/file.ext" == "file"
-- > takeBaseName "file/test.txt" == "test"
-- > takeBaseName "dave.ext" == "dave"
-- > takeBaseName "" == ""
-- > takeBaseName "test" == "test"
-- > takeBaseName (addTrailingPathSeparator x) == ""
-- > takeBaseName "file/file.tar.gz" == "file.tar"
takeBaseName :: FilePath -> String
takeBaseName :: [Char] -> [Char]
takeBaseName = [Char] -> [Char]
dropExtension ([Char] -> [Char]) -> ([Char] -> [Char]) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
takeFileName

-- | Set the base name.
--
-- > replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext"
-- > replaceBaseName "file/test.txt" "bob" == "file/bob.txt"
-- > replaceBaseName "fred" "bill" == "bill"
-- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar"
-- > Valid x => replaceBaseName x (takeBaseName x) == x
replaceBaseName :: FilePath -> String -> FilePath
replaceBaseName :: [Char] -> [Char] -> [Char]
replaceBaseName [Char]
pth [Char]
nam = [Char] -> [Char] -> [Char]
combineAlways [Char]
a ([Char]
nam [Char] -> [Char] -> [Char]
<.> [Char]
ext)
    where
        ([Char]
a,[Char]
b) = [Char] -> ([Char], [Char])
splitFileName_ [Char]
pth
        ext :: [Char]
ext = [Char] -> [Char]
takeExtension [Char]
b

-- | Is an item either a directory or the last character a path separator?
--
-- > hasTrailingPathSeparator "test" == False
-- > hasTrailingPathSeparator "test/" == True
hasTrailingPathSeparator :: FilePath -> Bool
hasTrailingPathSeparator :: [Char] -> Bool
hasTrailingPathSeparator [Char]
"" = Bool
False
hasTrailingPathSeparator [Char]
x = Char -> Bool
isPathSeparator ([Char] -> Char
forall a. [a] -> a
last [Char]
x)


hasLeadingPathSeparator :: FilePath -> Bool
hasLeadingPathSeparator :: [Char] -> Bool
hasLeadingPathSeparator [Char]
"" = Bool
False
hasLeadingPathSeparator [Char]
x = Char -> Bool
isPathSeparator ([Char] -> Char
forall a. [a] -> a
head [Char]
x)


-- | Add a trailing file path separator if one is not already present.
--
-- > hasTrailingPathSeparator (addTrailingPathSeparator x)
-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x
-- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"
addTrailingPathSeparator :: FilePath -> FilePath
addTrailingPathSeparator :: [Char] -> [Char]
addTrailingPathSeparator [Char]
x = if [Char] -> Bool
hasTrailingPathSeparator [Char]
x then [Char]
x else [Char]
x [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char
pathSeparator]


-- | Remove any trailing path separators
--
-- > dropTrailingPathSeparator "file/test/" == "file/test"
-- >           dropTrailingPathSeparator "/" == "/"
-- > Windows:  dropTrailingPathSeparator "\\" == "\\"
-- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x
dropTrailingPathSeparator :: FilePath -> FilePath
dropTrailingPathSeparator :: [Char] -> [Char]
dropTrailingPathSeparator [Char]
x =
    if [Char] -> Bool
hasTrailingPathSeparator [Char]
x Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char] -> Bool
isDrive [Char]
x)
    then let x' :: [Char]
x' = (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd Char -> Bool
isPathSeparator [Char]
x
         in if [Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
x' then [[Char] -> Char
forall a. [a] -> a
last [Char]
x] else [Char]
x'
    else [Char]
x


-- | Get the directory name, move up one level.
--
-- >           takeDirectory "/directory/other.ext" == "/directory"
-- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."
-- >           takeDirectory "foo" == "."
-- >           takeDirectory "/" == "/"
-- >           takeDirectory "/foo" == "/"
-- >           takeDirectory "/foo/bar/baz" == "/foo/bar"
-- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"
-- >           takeDirectory "foo/bar/baz" == "foo/bar"
-- > Windows:  takeDirectory "foo\\bar" == "foo"
-- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar"
-- > Windows:  takeDirectory "C:\\" == "C:\\"
takeDirectory :: FilePath -> FilePath
takeDirectory :: [Char] -> [Char]
takeDirectory = [Char] -> [Char]
dropTrailingPathSeparator ([Char] -> [Char]) -> ([Char] -> [Char]) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
dropFileName

-- | Set the directory, keeping the filename the same.
--
-- > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"
-- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x
replaceDirectory :: FilePath -> String -> FilePath
replaceDirectory :: [Char] -> [Char] -> [Char]
replaceDirectory [Char]
x [Char]
dir = [Char] -> [Char] -> [Char]
combineAlways [Char]
dir ([Char] -> [Char]
takeFileName [Char]
x)


-- | An alias for '</>'.
combine :: FilePath -> FilePath -> FilePath
combine :: [Char] -> [Char] -> [Char]
combine [Char]
a [Char]
b | [Char] -> Bool
hasLeadingPathSeparator [Char]
b Bool -> Bool -> Bool
|| [Char] -> Bool
hasDrive [Char]
b = [Char]
b
            | Bool
otherwise = [Char] -> [Char] -> [Char]
combineAlways [Char]
a [Char]
b

-- | Combine two paths, assuming rhs is NOT absolute.
combineAlways :: FilePath -> FilePath -> FilePath
combineAlways :: [Char] -> [Char] -> [Char]
combineAlways [Char]
a [Char]
b | [Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
a = [Char]
b
                  | [Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
b = [Char]
a
                  | [Char] -> Bool
hasTrailingPathSeparator [Char]
a = [Char]
a [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
b
                  | Bool
otherwise = case [Char]
a of
                      [Char
a1,Char
':'] | Bool
isWindows Bool -> Bool -> Bool
&& Char -> Bool
isLetter Char
a1 -> [Char]
a [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
b
                      [Char]
_ -> [Char]
a [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char
pathSeparator] [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
b


-- | Combine two paths with a path separator.
--   If the second path starts with a path separator or a drive letter, then it returns the second.
--   The intention is that @readFile (dir '</>' file)@ will access the same file as
--   @setCurrentDirectory dir; readFile file@.
--
-- > Posix:   "/directory" </> "file.ext" == "/directory/file.ext"
-- > Windows: "/directory" </> "file.ext" == "/directory\\file.ext"
-- >          "directory" </> "/file.ext" == "/file.ext"
-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x
--
--   Combined:
--
-- > Posix:   "/" </> "test" == "/test"
-- > Posix:   "home" </> "bob" == "home/bob"
-- > Posix:   "x:" </> "foo" == "x:/foo"
-- > Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"
-- > Windows: "home" </> "bob" == "home\\bob"
--
--   Not combined:
--
-- > Posix:   "home" </> "/bob" == "/bob"
-- > Windows: "home" </> "C:\\bob" == "C:\\bob"
--
--   Not combined (tricky):
--
--   On Windows, if a filepath starts with a single slash, it is relative to the
--   root of the current drive. In [1], this is (confusingly) referred to as an
--   absolute path.
--   The current behavior of '</>' is to never combine these forms.
--
-- > Windows: "home" </> "/bob" == "/bob"
-- > Windows: "home" </> "\\bob" == "\\bob"
-- > Windows: "C:\\home" </> "\\bob" == "\\bob"
--
--   On Windows, from [1]: "If a file name begins with only a disk designator
--   but not the backslash after the colon, it is interpreted as a relative path
--   to the current directory on the drive with the specified letter."
--   The current behavior of '</>' is to never combine these forms.
--
-- > Windows: "D:\\foo" </> "C:bar" == "C:bar"
-- > Windows: "C:\\foo" </> "C:bar" == "C:bar"
(</>) :: FilePath -> FilePath -> FilePath
</> :: [Char] -> [Char] -> [Char]
(</>) = [Char] -> [Char] -> [Char]
combine


-- | Split a path by the directory separator.
--
-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]
-- > concat (splitPath x) == x
-- > splitPath "test//item/" == ["test//","item/"]
-- > splitPath "test/item/file" == ["test/","item/","file"]
-- > splitPath "" == []
-- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"]
-- > Posix:   splitPath "/file/test" == ["/","file/","test"]
splitPath :: FilePath -> [FilePath]
splitPath :: [Char] -> [[Char]]
splitPath [Char]
x = [[Char]
drive | [Char]
drive [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
/= [Char]
""] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [Char] -> [[Char]]
f [Char]
path
    where
        ([Char]
drive,[Char]
path) = [Char] -> ([Char], [Char])
splitDrive [Char]
x

        f :: [Char] -> [[Char]]
f [Char]
"" = []
        f [Char]
y = ([Char]
a[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
c) [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: [Char] -> [[Char]]
f [Char]
d
            where
                ([Char]
a,[Char]
b) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break Char -> Bool
isPathSeparator [Char]
y
                ([Char]
c,[Char]
d) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span  Char -> Bool
isPathSeparator [Char]
b

-- | Just as 'splitPath', but don't add the trailing slashes to each element.
--
-- >          splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]
-- >          splitDirectories "test/file" == ["test","file"]
-- >          splitDirectories "/test/file" == ["/","test","file"]
-- > Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]
-- >          Valid x => joinPath (splitDirectories x) `equalFilePath` x
-- >          splitDirectories "" == []
-- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]
-- >          splitDirectories "/test///file" == ["/","test","file"]
splitDirectories :: FilePath -> [FilePath]
splitDirectories :: [Char] -> [[Char]]
splitDirectories = ([Char] -> [Char]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> [Char]
dropTrailingPathSeparator ([[Char]] -> [[Char]])
-> ([Char] -> [[Char]]) -> [Char] -> [[Char]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [[Char]]
splitPath


-- | Join path elements back together.
--
-- > joinPath a == foldr (</>) "" a
-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"
-- > Valid x => joinPath (splitPath x) == x
-- > joinPath [] == ""
-- > Posix: joinPath ["test","file","path"] == "test/file/path"
joinPath :: [FilePath] -> FilePath
-- Note that this definition on c:\\c:\\, join then split will give c:\\.
joinPath :: [[Char]] -> [Char]
joinPath = ([Char] -> [Char] -> [Char]) -> [Char] -> [[Char]] -> [Char]
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr [Char] -> [Char] -> [Char]
combine [Char]
""






---------------------------------------------------------------------
-- File name manipulators

-- | Equality of two 'FilePath's.
--   If you call @System.Directory.canonicalizePath@
--   first this has a much better chance of working.
--   Note that this doesn't follow symlinks or DOSNAM~1s.
--
-- Similar to 'normalise', this does not expand @".."@, because of symlinks.
--
-- >          x == y ==> equalFilePath x y
-- >          normalise x == normalise y ==> equalFilePath x y
-- >          equalFilePath "foo" "foo/"
-- >          not (equalFilePath "/a/../c" "/c")
-- >          not (equalFilePath "foo" "/foo")
-- > Posix:   not (equalFilePath "foo" "FOO")
-- > Windows: equalFilePath "foo" "FOO"
-- > Windows: not (equalFilePath "C:" "C:/")
equalFilePath :: FilePath -> FilePath -> Bool
equalFilePath :: [Char] -> [Char] -> Bool
equalFilePath [Char]
a [Char]
b = [Char] -> [Char]
f [Char]
a [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char] -> [Char]
f [Char]
b
    where
        f :: [Char] -> [Char]
f [Char]
x | Bool
isWindows = [Char] -> [Char]
dropTrailingPathSeparator ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
normalise [Char]
x
            | Bool
otherwise = [Char] -> [Char]
dropTrailingPathSeparator ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
normalise [Char]
x


-- | Contract a filename, based on a relative path. Note that the resulting path
--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@
--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see
--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.
--
--   The corresponding @makeAbsolute@ function can be found in
--   @System.Directory@.
--
-- >          makeRelative "/directory" "/directory/file.ext" == "file.ext"
-- >          Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x
-- >          makeRelative x x == "."
-- >          Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x
-- > Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob"
-- > Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob"
-- > Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"
-- > Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"
-- > Windows: makeRelative "/Home" "/home/bob" == "bob"
-- > Windows: makeRelative "/" "//" == "//"
-- > Posix:   makeRelative "/Home" "/home/bob" == "/home/bob"
-- > Posix:   makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"
-- > Posix:   makeRelative "/fred" "bob" == "bob"
-- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred"
-- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/"
-- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"
makeRelative :: FilePath -> FilePath -> FilePath
makeRelative :: [Char] -> [Char] -> [Char]
makeRelative [Char]
root [Char]
path
 | [Char] -> [Char] -> Bool
equalFilePath [Char]
root [Char]
path = [Char]
"."
 | [Char] -> [Char]
takeAbs [Char]
root [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
/= [Char] -> [Char]
takeAbs [Char]
path = [Char]
path
 | Bool
otherwise = [Char] -> [Char] -> [Char]
f ([Char] -> [Char]
dropAbs [Char]
root) ([Char] -> [Char]
dropAbs [Char]
path)
    where
        f :: [Char] -> [Char] -> [Char]
f [Char]
"" [Char]
y = (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isPathSeparator [Char]
y
        f [Char]
x [Char]
y = let ([Char]
x1,[Char]
x2) = [Char] -> ([Char], [Char])
g [Char]
x
                    ([Char]
y1,[Char]
y2) = [Char] -> ([Char], [Char])
g [Char]
y
                in if [Char] -> [Char] -> Bool
equalFilePath [Char]
x1 [Char]
y1 then [Char] -> [Char] -> [Char]
f [Char]
x2 [Char]
y2 else [Char]
path

        g :: [Char] -> ([Char], [Char])
g [Char]
x = ((Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isPathSeparator [Char]
a, (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isPathSeparator [Char]
b)
            where ([Char]
a,[Char]
b) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break Char -> Bool
isPathSeparator ([Char] -> ([Char], [Char])) -> [Char] -> ([Char], [Char])
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isPathSeparator [Char]
x

        -- on windows, need to drop '/' which is kind of absolute, but not a drive
        dropAbs :: [Char] -> [Char]
dropAbs [Char]
x | [Char] -> Bool
hasLeadingPathSeparator [Char]
x Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char] -> Bool
hasDrive [Char]
x) = [Char] -> [Char]
forall a. [a] -> [a]
tail [Char]
x
        dropAbs [Char]
x = [Char] -> [Char]
dropDrive [Char]
x

        takeAbs :: [Char] -> [Char]
takeAbs [Char]
x | [Char] -> Bool
hasLeadingPathSeparator [Char]
x Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char] -> Bool
hasDrive [Char]
x) = [Char
pathSeparator]
        takeAbs [Char]
x = (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map (\Char
y -> if Char -> Bool
isPathSeparator Char
y then Char
pathSeparator else Char -> Char
toLower Char
y) ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
takeDrive [Char]
x

-- | Normalise a file
--
-- * \/\/ outside of the drive can be made blank
--
-- * \/ -> 'pathSeparator'
--
-- * .\/ -> \"\"
--
-- Does not remove @".."@, because of symlinks.
--
-- > Posix:   normalise "/file/\\test////" == "/file/\\test/"
-- > Posix:   normalise "/file/./test" == "/file/test"
-- > Posix:   normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"
-- > Posix:   normalise "../bob/fred/" == "../bob/fred/"
-- > Posix:   normalise "/a/../c" == "/a/../c"
-- > Posix:   normalise "./bob/fred/" == "bob/fred/"
-- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"
-- > Windows: normalise "c:\\" == "C:\\"
-- > Windows: normalise "C:.\\" == "C:"
-- > Windows: normalise "\\\\server\\test" == "\\\\server\\test"
-- > Windows: normalise "//server/test" == "\\\\server\\test"
-- > Windows: normalise "c:/file" == "C:\\file"
-- > Windows: normalise "/file" == "\\file"
-- > Windows: normalise "\\" == "\\"
-- > Windows: normalise "/./" == "\\"
-- >          normalise "." == "."
-- > Posix:   normalise "./" == "./"
-- > Posix:   normalise "./." == "./"
-- > Posix:   normalise "/./" == "/"
-- > Posix:   normalise "/" == "/"
-- > Posix:   normalise "bob/fred/." == "bob/fred/"
-- > Posix:   normalise "//home" == "/home"
normalise :: FilePath -> FilePath
normalise :: [Char] -> [Char]
normalise [Char]
path = [Char]
result [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char
pathSeparator | Bool
addPathSeparator]
    where
        ([Char]
drv,[Char]
pth) = [Char] -> ([Char], [Char])
splitDrive [Char]
path
        result :: [Char]
result = [Char] -> [Char] -> [Char]
joinDrive' ([Char] -> [Char]
normaliseDrive [Char]
drv) ([Char] -> [Char]
f [Char]
pth)

        joinDrive' :: [Char] -> [Char] -> [Char]
joinDrive' [Char]
"" [Char]
"" = [Char]
"."
        joinDrive' [Char]
d [Char]
p = [Char] -> [Char] -> [Char]
joinDrive [Char]
d [Char]
p

        addPathSeparator :: Bool
addPathSeparator = [Char] -> Bool
isDirPath [Char]
pth
            Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char] -> Bool
hasTrailingPathSeparator [Char]
result)
            Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char] -> Bool
isRelativeDrive [Char]
drv)

        isDirPath :: [Char] -> Bool
isDirPath [Char]
xs = [Char] -> Bool
hasTrailingPathSeparator [Char]
xs
            Bool -> Bool -> Bool
|| Bool -> Bool
not ([Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
xs) Bool -> Bool -> Bool
&& [Char] -> Char
forall a. [a] -> a
last [Char]
xs Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.' Bool -> Bool -> Bool
&& [Char] -> Bool
hasTrailingPathSeparator ([Char] -> [Char]
forall a. [a] -> [a]
init [Char]
xs)

        f :: [Char] -> [Char]
f = [[Char]] -> [Char]
joinPath ([[Char]] -> [Char]) -> ([Char] -> [[Char]]) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[Char]] -> [[Char]]
dropDots ([[Char]] -> [[Char]])
-> ([Char] -> [[Char]]) -> [Char] -> [[Char]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[Char]] -> [[Char]]
propSep ([[Char]] -> [[Char]])
-> ([Char] -> [[Char]]) -> [Char] -> [[Char]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [[Char]]
splitDirectories

        propSep :: [[Char]] -> [[Char]]
propSep ([Char]
x:[[Char]]
xs) | (Char -> Bool) -> [Char] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isPathSeparator [Char]
x = [Char
pathSeparator] [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: [[Char]]
xs
                       | Bool
otherwise = [Char]
x [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: [[Char]]
xs
        propSep [] = []

        dropDots :: [[Char]] -> [[Char]]
dropDots = ([Char] -> Bool) -> [[Char]] -> [[Char]]
forall a. (a -> Bool) -> [a] -> [a]
filter ([Char]
"." [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
/=)

normaliseDrive :: FilePath -> FilePath
normaliseDrive :: [Char] -> [Char]
normaliseDrive [Char]
"" = [Char]
""
normaliseDrive [Char]
_ | Bool
isPosix = [Char
pathSeparator]
normaliseDrive [Char]
drive = if Maybe ([Char], [Char]) -> Bool
forall a. Maybe a -> Bool
isJust (Maybe ([Char], [Char]) -> Bool) -> Maybe ([Char], [Char]) -> Bool
forall a b. (a -> b) -> a -> b
$ [Char] -> Maybe ([Char], [Char])
readDriveLetter [Char]
x2
                       then (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toUpper [Char]
x2
                       else [Char]
x2
    where
        x2 :: [Char]
x2 = (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
repSlash [Char]
drive

        repSlash :: Char -> Char
repSlash Char
x = if Char -> Bool
isPathSeparator Char
x then Char
pathSeparator else Char
x

-- Information for validity functions on Windows. See [1].
isBadCharacter :: Char -> Bool
isBadCharacter :: Char -> Bool
isBadCharacter Char
x = Char
x Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= Char
'\0' Bool -> Bool -> Bool
&& Char
x Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'\31' Bool -> Bool -> Bool
|| Char
x Char -> [Char] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
":*?><|\""

badElements :: [FilePath]
badElements :: [[Char]]
badElements =
    [[Char]
"CON",[Char]
"PRN",[Char]
"AUX",[Char]
"NUL",[Char]
"CLOCK$"
    ,[Char]
"COM1",[Char]
"COM2",[Char]
"COM3",[Char]
"COM4",[Char]
"COM5",[Char]
"COM6",[Char]
"COM7",[Char]
"COM8",[Char]
"COM9"
    ,[Char]
"LPT1",[Char]
"LPT2",[Char]
"LPT3",[Char]
"LPT4",[Char]
"LPT5",[Char]
"LPT6",[Char]
"LPT7",[Char]
"LPT8",[Char]
"LPT9"]


-- | Is a FilePath valid, i.e. could you create a file like it? This function checks for invalid names,
--   and invalid characters, but does not check if length limits are exceeded, as these are typically
--   filesystem dependent.
--
-- >          isValid "" == False
-- >          isValid "\0" == False
-- > Posix:   isValid "/random_ path:*" == True
-- > Posix:   isValid x == not (null x)
-- > Windows: isValid "c:\\test" == True
-- > Windows: isValid "c:\\test:of_test" == False
-- > Windows: isValid "test*" == False
-- > Windows: isValid "c:\\test\\nul" == False
-- > Windows: isValid "c:\\test\\prn.txt" == False
-- > Windows: isValid "c:\\nul\\file" == False
-- > Windows: isValid "\\\\" == False
-- > Windows: isValid "\\\\\\foo" == False
-- > Windows: isValid "\\\\?\\D:file" == False
-- > Windows: isValid "foo\tbar" == False
-- > Windows: isValid "nul .txt" == False
-- > Windows: isValid " nul.txt" == True
isValid :: FilePath -> Bool
isValid :: [Char] -> Bool
isValid [Char]
"" = Bool
False
isValid [Char]
x | Char
'\0' Char -> [Char] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
x = Bool
False
isValid [Char]
_ | Bool
isPosix = Bool
True
isValid [Char]
path =
        Bool -> Bool
not ((Char -> Bool) -> [Char] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Char -> Bool
isBadCharacter [Char]
x2) Bool -> Bool -> Bool
&&
        Bool -> Bool
not (([Char] -> Bool) -> [[Char]] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any [Char] -> Bool
f ([[Char]] -> Bool) -> [[Char]] -> Bool
forall a b. (a -> b) -> a -> b
$ [Char] -> [[Char]]
splitDirectories [Char]
x2) Bool -> Bool -> Bool
&&
        Bool -> Bool
not (Maybe ([Char], [Char]) -> Bool
forall a. Maybe a -> Bool
isJust ([Char] -> Maybe ([Char], [Char])
readDriveShare [Char]
x1) Bool -> Bool -> Bool
&& (Char -> Bool) -> [Char] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isPathSeparator [Char]
x1) Bool -> Bool -> Bool
&&
        Bool -> Bool
not (Maybe ([Char], [Char]) -> Bool
forall a. Maybe a -> Bool
isJust ([Char] -> Maybe ([Char], [Char])
readDriveUNC [Char]
x1) Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char] -> Bool
hasTrailingPathSeparator [Char]
x1))
    where
        ([Char]
x1,[Char]
x2) = [Char] -> ([Char], [Char])
splitDrive [Char]
path
        f :: [Char] -> Bool
f [Char]
x = (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toUpper ((Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ') ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
dropExtensions [Char]
x) [Char] -> [[Char]] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
badElements


-- | Take a FilePath and make it valid; does not change already valid FilePaths.
--
-- > isValid (makeValid x)
-- > isValid x ==> makeValid x == x
-- > makeValid "" == "_"
-- > makeValid "file\0name" == "file_name"
-- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"
-- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"
-- > Windows: makeValid "test*" == "test_"
-- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"
-- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"
-- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"
-- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"
-- > Windows: makeValid "\\\\\\foo" == "\\\\drive"
-- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"
-- > Windows: makeValid "nul .txt" == "nul _.txt"
makeValid :: FilePath -> FilePath
makeValid :: [Char] -> [Char]
makeValid [Char]
"" = [Char]
"_"
makeValid [Char]
path
        | Bool
isPosix = (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map (\Char
x -> if Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\0' then Char
'_' else Char
x) [Char]
path
        | Maybe ([Char], [Char]) -> Bool
forall a. Maybe a -> Bool
isJust ([Char] -> Maybe ([Char], [Char])
readDriveShare [Char]
drv) Bool -> Bool -> Bool
&& (Char -> Bool) -> [Char] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isPathSeparator [Char]
drv = Int -> [Char] -> [Char]
forall a. Int -> [a] -> [a]
take Int
2 [Char]
drv [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"drive"
        | Maybe ([Char], [Char]) -> Bool
forall a. Maybe a -> Bool
isJust ([Char] -> Maybe ([Char], [Char])
readDriveUNC [Char]
drv) Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char] -> Bool
hasTrailingPathSeparator [Char]
drv) =
            [Char] -> [Char]
makeValid ([Char]
drv [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char
pathSeparator] [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
pth)
        | Bool
otherwise = [Char] -> [Char] -> [Char]
joinDrive [Char]
drv ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
validElements ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
validChars [Char]
pth
    where
        ([Char]
drv,[Char]
pth) = [Char] -> ([Char], [Char])
splitDrive [Char]
path

        validChars :: [Char] -> [Char]
validChars = (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
f
        f :: Char -> Char
f Char
x = if Char -> Bool
isBadCharacter Char
x then Char
'_' else Char
x

        validElements :: [Char] -> [Char]
validElements [Char]
x = [[Char]] -> [Char]
joinPath ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall a b. (a -> b) -> a -> b
$ ([Char] -> [Char]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> [Char]
g ([[Char]] -> [[Char]]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> a -> b
$ [Char] -> [[Char]]
splitPath [Char]
x
        g :: [Char] -> [Char]
g [Char]
x = [Char] -> [Char]
h [Char]
a [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
b
            where ([Char]
a,[Char]
b) = (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break Char -> Bool
isPathSeparator [Char]
x
        h :: [Char] -> [Char]
h [Char]
x = if (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toUpper ((Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ') [Char]
a) [Char] -> [[Char]] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
badElements then [Char]
a [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"_" [Char] -> [Char] -> [Char]
<.> [Char]
b else [Char]
x
            where ([Char]
a,[Char]
b) = [Char] -> ([Char], [Char])
splitExtensions [Char]
x


-- | Is a path relative, or is it fixed to the root?
--
-- > Windows: isRelative "path\\test" == True
-- > Windows: isRelative "c:\\test" == False
-- > Windows: isRelative "c:test" == True
-- > Windows: isRelative "c:\\" == False
-- > Windows: isRelative "c:/" == False
-- > Windows: isRelative "c:" == True
-- > Windows: isRelative "\\\\foo" == False
-- > Windows: isRelative "\\\\?\\foo" == False
-- > Windows: isRelative "\\\\?\\UNC\\foo" == False
-- > Windows: isRelative "/foo" == True
-- > Windows: isRelative "\\foo" == True
-- > Posix:   isRelative "test/path" == True
-- > Posix:   isRelative "/test" == False
-- > Posix:   isRelative "/" == False
--
-- According to [1]:
--
-- * "A UNC name of any format [is never relative]."
--
-- * "You cannot use the "\\?\" prefix with a relative path."
isRelative :: FilePath -> Bool
isRelative :: [Char] -> Bool
isRelative [Char]
x = [Char] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
drive Bool -> Bool -> Bool
|| [Char] -> Bool
isRelativeDrive [Char]
drive
    where drive :: [Char]
drive = [Char] -> [Char]
takeDrive [Char]
x


{- c:foo -}
-- From [1]: "If a file name begins with only a disk designator but not the
-- backslash after the colon, it is interpreted as a relative path to the
-- current directory on the drive with the specified letter."
isRelativeDrive :: String -> Bool
isRelativeDrive :: [Char] -> Bool
isRelativeDrive [Char]
x =
    Bool
-> (([Char], [Char]) -> Bool) -> Maybe ([Char], [Char]) -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (Bool -> Bool
not (Bool -> Bool)
-> (([Char], [Char]) -> Bool) -> ([Char], [Char]) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Bool
hasTrailingPathSeparator ([Char] -> Bool)
-> (([Char], [Char]) -> [Char]) -> ([Char], [Char]) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Char], [Char]) -> [Char]
forall a b. (a, b) -> a
fst) ([Char] -> Maybe ([Char], [Char])
readDriveLetter [Char]
x)


-- | @not . 'isRelative'@
--
-- > isAbsolute x == not (isRelative x)
isAbsolute :: FilePath -> Bool
isAbsolute :: [Char] -> Bool
isAbsolute = Bool -> Bool
not (Bool -> Bool) -> ([Char] -> Bool) -> [Char] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Bool
isRelative


-----------------------------------------------------------------------------
-- dropWhileEnd (>2) [1,2,3,4,1,2,3,4] == [1,2,3,4,1,2])
-- Note that Data.List.dropWhileEnd is only available in base >= 4.5.
dropWhileEnd :: (a -> Bool) -> [a] -> [a]
dropWhileEnd :: (a -> Bool) -> [a] -> [a]
dropWhileEnd a -> Bool
p = [a] -> [a]
forall a. [a] -> [a]
reverse ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile a -> Bool
p ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> [a]
forall a. [a] -> [a]
reverse

-- takeWhileEnd (>2) [1,2,3,4,1,2,3,4] == [3,4])
takeWhileEnd :: (a -> Bool) -> [a] -> [a]
takeWhileEnd :: (a -> Bool) -> [a] -> [a]
takeWhileEnd a -> Bool
p = [a] -> [a]
forall a. [a] -> [a]
reverse ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile a -> Bool
p ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> [a]
forall a. [a] -> [a]
reverse

-- spanEnd (>2) [1,2,3,4,1,2,3,4] = ([1,2,3,4,1,2], [3,4])
spanEnd :: (a -> Bool) -> [a] -> ([a], [a])
spanEnd :: (a -> Bool) -> [a] -> ([a], [a])
spanEnd a -> Bool
p [a]
xs = ((a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd a -> Bool
p [a]
xs, (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
takeWhileEnd a -> Bool
p [a]
xs)

-- breakEnd (< 2) [1,2,3,4,1,2,3,4] == ([1,2,3,4,1],[2,3,4])
breakEnd :: (a -> Bool) -> [a] -> ([a], [a])
breakEnd :: (a -> Bool) -> [a] -> ([a], [a])
breakEnd a -> Bool
p = (a -> Bool) -> [a] -> ([a], [a])
forall a. (a -> Bool) -> [a] -> ([a], [a])
spanEnd (Bool -> Bool
not (Bool -> Bool) -> (a -> Bool) -> a -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Bool
p)

-- | The stripSuffix function drops the given suffix from a list. It returns
-- Nothing if the list did not end with the suffix given, or Just the list
-- before the suffix, if it does.
stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]
stripSuffix :: [a] -> [a] -> Maybe [a]
stripSuffix [a]
xs [a]
ys = ([a] -> [a]) -> Maybe [a] -> Maybe [a]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [a] -> [a]
forall a. [a] -> [a]
reverse (Maybe [a] -> Maybe [a]) -> Maybe [a] -> Maybe [a]
forall a b. (a -> b) -> a -> b
$ [a] -> [a] -> Maybe [a]
forall a. Eq a => [a] -> [a] -> Maybe [a]
stripPrefix ([a] -> [a]
forall a. [a] -> [a]
reverse [a]
xs) ([a] -> [a]
forall a. [a] -> [a]
reverse [a]
ys)