{-# LANGUAGE CPP #-}

#ifndef MIN_VERSION_filepath
#if __GLASGOW_HASKELL__ >= 709
#define MIN_VERSION_filepath(a,b,c) 1
#else
#define MIN_VERSION_filepath(a,b,c) 0
#endif
#endif

-- | A module for 'FilePath' operations exposing "System.FilePath" plus some additional operations.
--
--   /Windows note:/ The extension methods ('<.>', 'takeExtension' etc) use the Posix variants since on
--   Windows @\"\/\/\*\" '<.>' \"txt\"@ produces @\"\/\/\*\\\\.txt\"@
--   (which is bad for 'Development.Shake.FilePattern' values).
module Development.Shake.FilePath(
    module System.FilePath, module System.FilePath.Posix,
    dropDirectory1, takeDirectory1, normaliseEx,
#if !MIN_VERSION_filepath(1,4,0)
    (-<.>),
#endif
    toNative, toStandard,
    exe
    ) where

import System.Info.Extra
import qualified System.FilePath as Native

import System.FilePath hiding
    (splitExtension, takeExtension, replaceExtension, dropExtension, addExtension
    ,hasExtension, (<.>), splitExtensions, takeExtensions, dropExtensions
#if MIN_VERSION_filepath(1,4,0)
    ,(-<.>)
#endif
    )
import System.FilePath.Posix
    (splitExtension, takeExtension, replaceExtension, dropExtension, addExtension
    ,hasExtension, (<.>), splitExtensions, takeExtensions, dropExtensions
#if MIN_VERSION_filepath(1,4,0)
    ,(-<.>)
#endif
    )

#if !MIN_VERSION_filepath(1,4,0)
infixr 7  -<.>

-- | Remove the current extension and add another, an alias for 'replaceExtension'.
(-<.>) :: FilePath -> String -> FilePath
(-<.>) = replaceExtension
#endif


-- | Drop the first directory from a 'FilePath'. Should only be used on
--   relative paths.
--
-- > dropDirectory1 "aaa/bbb" == "bbb"
-- > dropDirectory1 "aaa/" == ""
-- > dropDirectory1 "aaa" == ""
-- > dropDirectory1 "" == ""
dropDirectory1 :: FilePath -> FilePath
dropDirectory1 :: FilePath -> FilePath
dropDirectory1 = Int -> FilePath -> FilePath
forall a. Int -> [a] -> [a]
drop 1 (FilePath -> FilePath)
-> (FilePath -> FilePath) -> FilePath -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> FilePath -> FilePath
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isPathSeparator)


-- | Take the first component of a 'FilePath'. Should only be used on
--   relative paths.
--
-- > takeDirectory1 "aaa/bbb" == "aaa"
-- > takeDirectory1 "aaa/" == "aaa"
-- > takeDirectory1 "aaa" == "aaa"
takeDirectory1 :: FilePath -> FilePath
takeDirectory1 :: FilePath -> FilePath
takeDirectory1 = (Char -> Bool) -> FilePath -> FilePath
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isPathSeparator)


-- | Normalise a 'FilePath', applying the rules:
--
-- * All 'pathSeparators' become 'pathSeparator' (@\/@ on Linux, @\\@ on Windows)
--
-- * @foo\/bar\/..\/baz@ becomes @foo\/baz@ (not universally true in the presence of symlinks)
--
-- * @foo\/.\/bar@ becomes @foo\/bar@
--
-- * @foo\/\/bar@ becomes @foo\/bar@
--
--   This function is not based on the 'normalise' function from the @filepath@ library, as that function
--   is quite broken.
normaliseEx :: FilePath -> FilePath
normaliseEx :: FilePath -> FilePath
normaliseEx xs :: FilePath
xs | a :: Char
a:b :: Char
b:xs :: FilePath
xs <- FilePath
xs, Bool
isWindows Bool -> Bool -> Bool
&& Char -> Bool
sep Char
a Bool -> Bool -> Bool
&& Char -> Bool
sep Char
b = '/' Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath -> FilePath
f ('/'Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
:FilePath
xs) -- account for UNC paths being double //
               | Bool
otherwise = FilePath -> FilePath
f FilePath
xs
    where
        sep :: Char -> Bool
sep = Char -> Bool
Native.isPathSeparator
        f :: FilePath -> FilePath
f o :: FilePath
o = FilePath -> FilePath
toNative (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> FilePath
deslash FilePath
o (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ (FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++"/") (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ (FilePath -> FilePath) -> [FilePath] -> FilePath
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ('/'Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
:) ([FilePath] -> FilePath) -> [FilePath] -> FilePath
forall a b. (a -> b) -> a -> b
$ [FilePath] -> [FilePath]
forall a. [a] -> [a]
reverse ([FilePath] -> [FilePath]) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> a -> b
$ Int -> [FilePath] -> [FilePath]
g 0 ([FilePath] -> [FilePath]) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> a -> b
$ [FilePath] -> [FilePath]
forall a. [a] -> [a]
reverse ([FilePath] -> [FilePath]) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> a -> b
$ FilePath -> [FilePath]
split FilePath
o

        deslash :: FilePath -> FilePath -> FilePath
deslash o :: FilePath
o x :: FilePath
x
            | FilePath
x FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
== "/" = case (Bool
pre,Bool
pos) of
                (True,True) -> "/"
                (True,False) -> "/."
                (False,True) -> "./"
                (False,False) -> "."
            | Bool
otherwise = (if Bool
pre then FilePath -> FilePath
forall a. a -> a
id else FilePath -> FilePath
forall a. [a] -> [a]
tail) (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ (if Bool
pos then FilePath -> FilePath
forall a. a -> a
id else FilePath -> FilePath
forall a. [a] -> [a]
init) FilePath
x
            where pre :: Bool
pre = Char -> Bool
sep (Char -> Bool) -> Char -> Bool
forall a b. (a -> b) -> a -> b
$ FilePath -> Char
forall a. [a] -> a
head (FilePath -> Char) -> FilePath -> Char
forall a b. (a -> b) -> a -> b
$ FilePath
o FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ " "
                  pos :: Bool
pos = Char -> Bool
sep (Char -> Bool) -> Char -> Bool
forall a b. (a -> b) -> a -> b
$ FilePath -> Char
forall a. [a] -> a
last (FilePath -> Char) -> FilePath -> Char
forall a b. (a -> b) -> a -> b
$ " " FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
o

        g :: Int -> [FilePath] -> [FilePath]
g i :: Int
i [] = Int -> FilePath -> [FilePath]
forall a. Int -> a -> [a]
replicate Int
i ".."
        g i :: Int
i ("..":xs :: [FilePath]
xs) = Int -> [FilePath] -> [FilePath]
g (Int
iInt -> Int -> Int
forall a. Num a => a -> a -> a
+1) [FilePath]
xs
        g i :: Int
i (".":xs :: [FilePath]
xs) = Int -> [FilePath] -> [FilePath]
g Int
i [FilePath]
xs
        g 0 (x :: FilePath
x:xs :: [FilePath]
xs) = FilePath
x FilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
: Int -> [FilePath] -> [FilePath]
g 0 [FilePath]
xs
        g i :: Int
i (x :: FilePath
x:xs :: [FilePath]
xs) = Int -> [FilePath] -> [FilePath]
g (Int
iInt -> Int -> Int
forall a. Num a => a -> a -> a
-1) [FilePath]
xs

        split :: FilePath -> [FilePath]
split xs :: FilePath
xs = if FilePath -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null FilePath
ys then [] else FilePath
a FilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
: FilePath -> [FilePath]
split FilePath
b
            where (a :: FilePath
a,b :: FilePath
b) = (Char -> Bool) -> FilePath -> (FilePath, FilePath)
forall a. (a -> Bool) -> [a] -> ([a], [a])
break Char -> Bool
sep FilePath
ys
                  ys :: FilePath
ys = (Char -> Bool) -> FilePath -> FilePath
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
sep FilePath
xs


-- | Convert to native path separators, namely @\\@ on Windows. 
toNative :: FilePath -> FilePath
toNative :: FilePath -> FilePath
toNative = if Bool
isWindows then (Char -> Char) -> FilePath -> FilePath
forall a b. (a -> b) -> [a] -> [b]
map (\x :: Char
x -> if Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '/' then '\\' else Char
x) else FilePath -> FilePath
forall a. a -> a
id

-- | Convert all path separators to @/@, even on Windows.
toStandard :: FilePath -> FilePath
toStandard :: FilePath -> FilePath
toStandard = if Bool
isWindows then (Char -> Char) -> FilePath -> FilePath
forall a b. (a -> b) -> [a] -> [b]
map (\x :: Char
x -> if Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '\\' then '/' else Char
x) else FilePath -> FilePath
forall a. a -> a
id


-- | The extension of executables, @\"exe\"@ on Windows and @\"\"@ otherwise.
exe :: String
exe :: FilePath
exe = if Bool
isWindows then "exe" else ""