path-0.7.0: src/Path/Include.hs
-- This template expects CPP definitions for:
-- PLATFORM_NAME = Posix | Windows
-- IS_WINDOWS = False | True
-- | This library provides a well-typed representation of paths in a filesystem
-- directory tree.
--
-- __Note__: This module is for working with PLATFORM_NAME style paths. Importing
-- "Path" is usually better.
--
-- A path is represented by a number of path components separated by a path
-- separator which is a @/@ on POSIX systems and can be a @/@ or @\\@ on Windows.
-- The root of the tree is represented by a @/@ on POSIX and a drive letter
-- followed by a @/@ or @\\@ on Windows (e.g. @C:\\@). Paths can be absolute
-- or relative. An absolute path always starts from the root of the tree (e.g.
-- @\/x/y@) whereas a relative path never starts with the root (e.g. @x/y@).
-- Just like we represent the notion of an absolute root by "@/@", the same way
-- we represent the notion of a relative root by "@.@". The relative root denotes
-- the directory which contains the first component of a relative path.
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleInstances #-}
module Path.PLATFORM_NAME
(-- * Types
Path
,Abs
,Rel
,File
,Dir
-- * Exceptions
,PathException(..)
-- * QuasiQuoters
-- | Using the following requires the QuasiQuotes language extension.
--
-- __For Windows users__, the QuasiQuoters are especially beneficial because they
-- prevent Haskell from treating @\\@ as an escape character.
-- This makes Windows paths easier to write.
--
-- @
-- [absfile|C:\\chris\\foo.txt|]
-- @
,absdir
,reldir
,absfile
,relfile
-- * Operations
,(</>)
,stripProperPrefix
,isProperPrefixOf
,parent
,filename
,dirname
,addExtension
,splitExtension
,fileExtension
,replaceExtension
-- * Parsing
,parseAbsDir
,parseRelDir
,parseAbsFile
,parseRelFile
-- * Conversion
,toFilePath
,fromAbsDir
,fromRelDir
,fromAbsFile
,fromRelFile
-- * TemplateHaskell constructors
-- | These require the TemplateHaskell language extension.
,mkAbsDir
,mkRelDir
,mkAbsFile
,mkRelFile
-- * Deprecated
,PathParseException
,stripDir
,isParentOf
,addFileExtension
,(<.>)
,setFileExtension
,(-<.>)
)
where
import Control.Exception (Exception(..))
import Control.Monad (liftM, when)
import Control.Monad.Catch (MonadThrow(..))
import Data.Aeson (FromJSON (..), FromJSONKey(..))
import qualified Data.Aeson.Types as Aeson
import Data.Data
import qualified Data.Text as T
import Data.List
import Data.Maybe
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (lift)
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import Path.Internal
import qualified System.FilePath.PLATFORM_NAME as FilePath
--------------------------------------------------------------------------------
-- Types
-- | An absolute path.
data Abs deriving (Typeable)
-- | A relative path; one without a root. Note that a @..@ path component to
-- represent the parent directory is not allowed by this library.
data Rel deriving (Typeable)
-- | A file path.
data File deriving (Typeable)
-- | A directory path.
data Dir deriving (Typeable)
instance FromJSON (Path Abs File) where
parseJSON = parseJSONWith parseAbsFile
{-# INLINE parseJSON #-}
instance FromJSON (Path Rel File) where
parseJSON = parseJSONWith parseRelFile
{-# INLINE parseJSON #-}
instance FromJSON (Path Abs Dir) where
parseJSON = parseJSONWith parseAbsDir
{-# INLINE parseJSON #-}
instance FromJSON (Path Rel Dir) where
parseJSON = parseJSONWith parseRelDir
{-# INLINE parseJSON #-}
parseJSONWith :: (Show e, FromJSON a)
=> (a -> Either e b) -> Aeson.Value -> Aeson.Parser b
parseJSONWith f x =
do fp <- parseJSON x
case f fp of
Right p -> return p
Left e -> fail (show e)
{-# INLINE parseJSONWith #-}
instance FromJSONKey (Path Abs File) where
fromJSONKey = fromJSONKeyWith parseAbsFile
{-# INLINE fromJSONKey #-}
instance FromJSONKey (Path Rel File) where
fromJSONKey = fromJSONKeyWith parseRelFile
{-# INLINE fromJSONKey #-}
instance FromJSONKey (Path Abs Dir) where
fromJSONKey = fromJSONKeyWith parseAbsDir
{-# INLINE fromJSONKey #-}
instance FromJSONKey (Path Rel Dir) where
fromJSONKey = fromJSONKeyWith parseRelDir
{-# INLINE fromJSONKey #-}
fromJSONKeyWith :: (Show e)
=> (String -> Either e b) -> Aeson.FromJSONKeyFunction b
fromJSONKeyWith f =
Aeson.FromJSONKeyTextParser $ \t ->
case f (T.unpack t) of
Left e -> fail (show e)
Right rf -> pure rf
{-# INLINE fromJSONKeyWith #-}
-- | Exceptions that can occur during path operations.
--
-- @since 0.6.0
data PathException
= InvalidAbsDir FilePath
| InvalidRelDir FilePath
| InvalidAbsFile FilePath
| InvalidRelFile FilePath
| NotAProperPrefix FilePath FilePath
| HasNoExtension FilePath
| InvalidExtension String
deriving (Show,Eq,Typeable)
instance Exception PathException where
displayException (InvalidExtension ext) = concat
[ "Invalid extension ["
, ext
, "]. A valid extension starts with a '.' followed by one or more "
, "characters other than '.', and it must be a valid filename, "
, "notably it cannot include a path separator."
]
displayException x = show x
--------------------------------------------------------------------------------
-- QuasiQuoters
qq :: (String -> Q Exp) -> QuasiQuoter
qq quoteExp' =
QuasiQuoter
{ quoteExp = quoteExp'
, quotePat = \_ ->
fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"
, quoteType = \_ ->
fail "illegal QuasiQuote (allowed as expression only, used as a type)"
, quoteDec = \_ ->
fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"
}
-- | Construct a 'Path' 'Abs' 'Dir' using QuasiQuotes.
--
-- @
-- [absdir|/|]
--
-- [absdir|\/home\/chris|]
-- @
--
-- Remember: due to the nature of absolute paths a path like @[absdir|\/home\/chris|]@
-- may compile on your platform, but it may not compile on another
-- platform (Windows).
--
-- @since 0.5.13
absdir :: QuasiQuoter
absdir = qq mkAbsDir
-- | Construct a 'Path' 'Rel' 'Dir' using QuasiQuotes.
--
-- @
-- [absdir|\/home|]\<\/>[reldir|chris|]
-- @
--
-- @since 0.5.13
reldir :: QuasiQuoter
reldir = qq mkRelDir
-- | Construct a 'Path' 'Abs' 'File' using QuasiQuotes.
--
-- @
-- [absfile|\/home\/chris\/foo.txt|]
-- @
--
-- Remember: due to the nature of absolute paths a path like @[absdir|\/home\/chris\/foo.txt|]@
-- may compile on your platform, but it may not compile on another
-- platform (Windows).
--
-- @since 0.5.13
absfile :: QuasiQuoter
absfile = qq mkAbsFile
-- | Construct a 'Path' 'Rel' 'File' using QuasiQuotes.
--
-- @
-- [absdir|\/home\/chris|]\<\/>[relfile|foo.txt|]
-- @
--
-- @since 0.5.13
relfile :: QuasiQuoter
relfile = qq mkRelFile
--------------------------------------------------------------------------------
-- Operations
-- | Append two paths.
--
-- The following cases are valid and the equalities hold:
--
-- @$(mkAbsDir x) \<\/> $(mkRelDir y) = $(mkAbsDir (x ++ \"/\" ++ y))@
--
-- @$(mkAbsDir x) \<\/> $(mkRelFile y) = $(mkAbsFile (x ++ \"/\" ++ y))@
--
-- @$(mkRelDir x) \<\/> $(mkRelDir y) = $(mkRelDir (x ++ \"/\" ++ y))@
--
-- @$(mkRelDir x) \<\/> $(mkRelFile y) = $(mkRelFile (x ++ \"/\" ++ y))@
--
-- The following are proven not possible to express:
--
-- @$(mkAbsFile …) \<\/> x@
--
-- @$(mkRelFile …) \<\/> x@
--
-- @x \<\/> $(mkAbsFile …)@
--
-- @x \<\/> $(mkAbsDir …)@
--
infixr 5 </>
(</>) :: Path b Dir -> Path Rel t -> Path b t
(</>) (Path a) (Path b) = Path (a ++ b)
-- | If the directory in the first argument is a proper prefix of the path in
-- the second argument strip it from the second argument, generating a path
-- relative to the directory.
-- Throws 'NotAProperPrefix' if the directory is not a proper prefix of the
-- path.
--
-- The following properties hold:
--
-- @stripProperPrefix x (x \<\/> y) = y@
--
-- Cases which are proven not possible:
--
-- @stripProperPrefix (a :: Path Abs …) (b :: Path Rel …)@
--
-- @stripProperPrefix (a :: Path Rel …) (b :: Path Abs …)@
--
-- In other words the bases must match.
--
-- @since 0.6.0
stripProperPrefix :: MonadThrow m
=> Path b Dir -> Path b t -> m (Path Rel t)
stripProperPrefix (Path p) (Path l) =
case stripPrefix p l of
Nothing -> throwM (NotAProperPrefix p l)
Just "" -> throwM (NotAProperPrefix p l)
Just ok -> return (Path ok)
-- | Determines if the path in the first parameter is a proper prefix of the
-- path in the second parameter.
--
-- The following properties hold:
--
-- @not (x \`isProperPrefixOf\` x)@
--
-- @x \`isProperPrefixOf\` (x \<\/\> y)@
--
-- @since 0.6.0
isProperPrefixOf :: Path b Dir -> Path b t -> Bool
isProperPrefixOf p l = isJust (stripProperPrefix p l)
-- | Take the parent path component from a path.
--
-- The following properties hold:
--
-- @
-- parent (x \<\/> y) == x
-- parent \"\/x\" == \"\/\"
-- parent \"x\" == \".\"
-- @
--
-- On the root (absolute or relative), getting the parent is idempotent:
--
-- @
-- parent \"\/\" = \"\/\"
-- parent \"\.\" = \"\.\"
-- @
--
parent :: Path b t -> Path b Dir
parent (Path "") = Path ""
parent (Path fp) | FilePath.isDrive fp = Path fp
parent (Path fp) =
Path
$ normalizeDir
$ FilePath.takeDirectory
$ FilePath.dropTrailingPathSeparator fp
-- | Extract the file part of a path.
--
-- The following properties hold:
--
-- @filename (p \<\/> a) == filename a@
--
filename :: Path b File -> Path Rel File
filename (Path l) =
Path (FilePath.takeFileName l)
-- | Extract the last directory name of a path.
--
-- The following properties hold:
--
-- @dirname $(mkRelDir ".") == $(mkRelDir ".")@
--
-- @dirname (p \<\/> a) == dirname a@
--
dirname :: Path b Dir -> Path Rel Dir
dirname (Path "") = Path ""
dirname (Path l) | FilePath.isDrive l = Path ""
dirname (Path l) = Path (last (FilePath.splitPath l))
-- | 'splitExtension' is the inverse of 'addExtension'. It splits the given
-- file path into a valid filename and a valid extension.
--
-- >>> splitExtension $(mkRelFile "name.foo" ) == Just ($(mkRelFile "name" ), ".foo" )
-- >>> splitExtension $(mkRelFile "name.foo." ) == Just ($(mkRelFile "name" ), ".foo." )
-- >>> splitExtension $(mkRelFile "name.foo.." ) == Just ($(mkRelFile "name" ), ".foo..")
-- >>> splitExtension $(mkRelFile "name.bar.foo" ) == Just ($(mkRelFile "name.bar"), ".foo" )
-- >>> splitExtension $(mkRelFile ".name.foo" ) == Just ($(mkRelFile ".name" ), ".foo" )
-- >>> splitExtension $(mkRelFile "name..foo" ) == Just ($(mkRelFile "name." ), ".foo" )
-- >>> splitExtension $(mkRelFile "....foo" ) == Just ($(mkRelFile "..." ), ".foo" )
--
-- Throws 'HasNoExtension' exception if the filename does not have an extension
-- or in other words it cannot be split into a valid filename and a valid
-- extension. The following cases throw an exception, please note that "." and
-- ".." are not valid filenames:
--
-- >>> splitExtension $(mkRelFile "name" )
-- >>> splitExtension $(mkRelFile "name." )
-- >>> splitExtension $(mkRelFile "name.." )
-- >>> splitExtension $(mkRelFile ".name" )
-- >>> splitExtension $(mkRelFile "..name" )
-- >>> splitExtension $(mkRelFile "...name")
--
-- 'splitExtension' and 'addExtension' are inverses of each other, the
-- following laws hold:
--
-- @
-- uncurry addExtension . swap >=> splitExtension == return
-- splitExtension >=> uncurry addExtension . swap == return
-- @
--
-- @since 0.7.0
splitExtension :: MonadThrow m => Path b File -> m (Path b File, String)
splitExtension (Path fpath) =
if nameDot == [] || ext == []
then throwM $ HasNoExtension fpath
else let fname = init nameDot
in if fname == [] || fname == "." || fname == ".."
then throwM $ HasNoExtension fpath
else return ( Path (normalizeDrive drv ++ dir ++ fname)
, FilePath.extSeparator : ext
)
where
-- trailing separators are ignored for the split and considered part of the
-- second component in the split.
splitLast isSep str =
let rstr = reverse str
notSep = not . isSep
name = (dropWhile notSep . dropWhile isSep) rstr
trailingSeps = takeWhile isSep rstr
xtn = (takeWhile notSep . dropWhile isSep) rstr
in (reverse name, reverse xtn ++ trailingSeps)
normalizeDrive
| IS_WINDOWS = normalizeTrailingSeps
| otherwise = id
(drv, pth) = FilePath.splitDrive fpath
(dir, file) = splitLast FilePath.isPathSeparator pth
(nameDot, ext) = splitLast FilePath.isExtSeparator file
-- | Get extension from given file path. Throws 'HasNoExtension' exception if
-- the file does not have an extension. The following laws hold:
--
-- @
-- flip addExtension file >=> fileExtension == return
-- fileExtension == (fmap snd) . splitExtension
-- @
--
-- @since 0.5.11
fileExtension :: MonadThrow m => Path b File -> m String
fileExtension = (liftM snd) . splitExtension
-- | Add extension to given file path.
--
-- >>> addExtension ".foo" $(mkRelFile "name" ) == Just $(mkRelFile "name.foo" )
-- >>> addExtension ".foo." $(mkRelFile "name" ) == Just $(mkRelFile "name.foo." )
-- >>> addExtension ".foo.." $(mkRelFile "name" ) == Just $(mkRelFile "name.foo.." )
-- >>> addExtension ".foo" $(mkRelFile "name.bar" ) == Just $(mkRelFile "name.bar.foo")
-- >>> addExtension ".foo" $(mkRelFile ".name" ) == Just $(mkRelFile ".name.foo" )
-- >>> addExtension ".foo" $(mkRelFile "name." ) == Just $(mkRelFile "name..foo" )
-- >>> addExtension ".foo" $(mkRelFile "..." ) == Just $(mkRelFile "....foo" )
--
-- Throws an 'InvalidExtension' exception if the extension is not valid. A
-- valid extension starts with a @.@ followed by one or more characters not
-- including @.@ followed by zero or more @.@ in trailing position. Moreover,
-- an extension must be a valid filename, notably it cannot include path
-- separators. Particularly, @.foo.bar@ is an invalid extension, instead you
-- have to first set @.foo@ and then @.bar@ individually. Some examples of
-- invalid extensions are:
--
-- >>> addExtension "foo" $(mkRelFile "name")
-- >>> addExtension "..foo" $(mkRelFile "name")
-- >>> addExtension ".foo.bar" $(mkRelFile "name")
-- >>> addExtension ".foo/bar" $(mkRelFile "name")
--
-- @since 0.7.0
addExtension :: MonadThrow m
=> String -- ^ Extension to add
-> Path b File -- ^ Old file name
-> m (Path b File) -- ^ New file name with the desired extension added at the end
addExtension ext (Path path) = do
validateExtension ext
return $ Path (path ++ ext)
where
validateExtension ex@(sep:xs) = do
-- has to start with a "."
when (not $ FilePath.isExtSeparator sep) $
throwM $ InvalidExtension ex
-- just a "." is not a valid extension
when (xs == []) $
throwM $ InvalidExtension ex
-- cannot have path separators
when (any FilePath.isPathSeparator xs) $
throwM $ InvalidExtension ex
-- All "."s is not a valid extension
let ys = dropWhile FilePath.isExtSeparator (reverse xs)
when (ys == []) $
throwM $ InvalidExtension ex
-- Cannot have "."s except in trailing position
when (any FilePath.isExtSeparator ys) $
throwM $ InvalidExtension ex
-- must be valid as a filename
_ <- parseRelFile ex
return ()
validateExtension ex = throwM $ InvalidExtension ex
-- | Add extension to given file path. Throws if the
-- resulting filename does not parse.
--
-- >>> addFileExtension "txt $(mkRelFile "foo")
-- "foo.txt"
-- >>> addFileExtension "symbols" $(mkRelFile "Data.List")
-- "Data.List.symbols"
-- >>> addFileExtension ".symbols" $(mkRelFile "Data.List")
-- "Data.List.symbols"
-- >>> addFileExtension "symbols" $(mkRelFile "Data.List.")
-- "Data.List..symbols"
-- >>> addFileExtension ".symbols" $(mkRelFile "Data.List.")
-- "Data.List..symbols"
-- >>> addFileExtension "evil/" $(mkRelFile "Data.List")
-- *** Exception: InvalidRelFile "Data.List.evil/"
--
-- @since 0.6.1
{-# DEPRECATED addFileExtension "Please use addExtension instead." #-}
addFileExtension :: MonadThrow m
=> String -- ^ Extension to add
-> Path b File -- ^ Old file name
-> m (Path b File) -- ^ New file name with the desired extension added at the end
addFileExtension ext (Path path) =
if FilePath.isAbsolute path
then liftM coercePath (parseAbsFile (FilePath.addExtension path ext))
else liftM coercePath (parseRelFile (FilePath.addExtension path ext))
where coercePath :: Path a b -> Path a' b'
coercePath (Path a) = Path a
-- | A synonym for 'addFileExtension' in the form of an infix operator.
-- See more examples there.
--
-- >>> $(mkRelFile "Data.List") <.> "symbols"
-- "Data.List.symbols"
-- >>> $(mkRelFile "Data.List") <.> "evil/"
-- *** Exception: InvalidRelFile "Data.List.evil/"
--
-- @since 0.6.1
infixr 7 <.>
{-# DEPRECATED (<.>) "Please use addExtension instead." #-}
(<.>) :: MonadThrow m
=> Path b File -- ^ Old file name
-> String -- ^ Extension to add
-> m (Path b File) -- ^ New file name with the desired extension added at the end
(<.>) = flip addFileExtension
-- | If the file has an extension replace it with the given extension otherwise
-- add the new extension to it. Throws an 'InvalidExtension' exception if the
-- new extension is not a valid extension (see 'fileExtension' for validity
-- rules).
--
-- The following law holds:
--
-- @(fileExtension >=> flip replaceExtension file) file == return file@
--
-- @since 0.7.0
replaceExtension :: MonadThrow m
=> String -- ^ Extension to set
-> Path b File -- ^ Old file name
-> m (Path b File) -- ^ New file name with the desired extension
replaceExtension ext path =
addExtension ext (maybe path fst $ splitExtension path)
-- | Replace\/add extension to given file path. Throws if the
-- resulting filename does not parse.
--
-- @since 0.5.11
{-# DEPRECATED setFileExtension "Please use replaceExtension instead." #-}
setFileExtension :: MonadThrow m
=> String -- ^ Extension to set
-> Path b File -- ^ Old file name
-> m (Path b File) -- ^ New file name with the desired extension
setFileExtension ext (Path path) =
if FilePath.isAbsolute path
then liftM coercePath (parseAbsFile (FilePath.replaceExtension path ext))
else liftM coercePath (parseRelFile (FilePath.replaceExtension path ext))
where coercePath :: Path a b -> Path a' b'
coercePath (Path a) = Path a
-- | A synonym for 'setFileExtension' in the form of an operator.
--
-- @since 0.6.0
infixr 7 -<.>
{-# DEPRECATED (-<.>) "Please use replaceExtension instead." #-}
(-<.>) :: MonadThrow m
=> Path b File -- ^ Old file name
-> String -- ^ Extension to set
-> m (Path b File) -- ^ New file name with the desired extension
(-<.>) = flip setFileExtension
--------------------------------------------------------------------------------
-- Parsers
-- | Convert an absolute 'FilePath' to a normalized absolute dir 'Path'.
--
-- Throws: 'InvalidAbsDir' when the supplied path:
--
-- * is not an absolute path
-- * contains a @..@ path component representing the parent directory
-- * is not a valid path (See 'FilePath.isValid')
--
parseAbsDir :: MonadThrow m
=> FilePath -> m (Path Abs Dir)
parseAbsDir filepath =
if FilePath.isAbsolute filepath &&
not (hasParentDir filepath) &&
FilePath.isValid filepath
then return (Path (normalizeDir filepath))
else throwM (InvalidAbsDir filepath)
-- | Convert a relative 'FilePath' to a normalized relative dir 'Path'.
--
-- Throws: 'InvalidRelDir' when the supplied path:
--
-- * is not a relative path
-- * is @""@
-- * contains a @..@ path component representing the parent directory
-- * is not a valid path (See 'FilePath.isValid')
-- * is all path separators
--
parseRelDir :: MonadThrow m
=> FilePath -> m (Path Rel Dir)
parseRelDir filepath =
if not (FilePath.isAbsolute filepath) &&
not (hasParentDir filepath) &&
not (null filepath) &&
not (all FilePath.isPathSeparator filepath) &&
FilePath.isValid filepath
then return (Path (normalizeDir filepath))
else throwM (InvalidRelDir filepath)
-- | Convert an absolute 'FilePath' to a normalized absolute file 'Path'.
--
-- Throws: 'InvalidAbsFile' when the supplied path:
--
-- * is not an absolute path
-- * is a directory path i.e.
--
-- * has a trailing path separator
-- * is @.@ or ends in @/.@
--
-- * contains a @..@ path component representing the parent directory
-- * is not a valid path (See 'FilePath.isValid')
--
parseAbsFile :: MonadThrow m
=> FilePath -> m (Path Abs File)
parseAbsFile filepath =
case validAbsFile filepath of
True
| normalized <- normalizeFilePath filepath
, validAbsFile normalized ->
return (Path normalized)
_ -> throwM (InvalidAbsFile filepath)
-- | Is the string a valid absolute file?
validAbsFile :: FilePath -> Bool
validAbsFile filepath =
FilePath.isAbsolute filepath &&
not (FilePath.hasTrailingPathSeparator filepath) &&
not (hasParentDir filepath) &&
FilePath.isValid filepath
-- | Convert a relative 'FilePath' to a normalized relative file 'Path'.
--
-- Throws: 'InvalidRelFile' when the supplied path:
--
-- * is not a relative path
-- * is @""@
-- * is a directory path i.e.
--
-- * has a trailing path separator
-- * is @.@ or ends in @/.@
--
-- * contains a @..@ path component representing the parent directory
-- * is not a valid path (See 'FilePath.isValid')
--
parseRelFile :: MonadThrow m
=> FilePath -> m (Path Rel File)
parseRelFile filepath =
case validRelFile filepath of
True
| normalized <- normalizeFilePath filepath
, validRelFile normalized -> return (Path normalized)
_ -> throwM (InvalidRelFile filepath)
-- | Is the string a valid relative file?
validRelFile :: FilePath -> Bool
validRelFile filepath =
not
(FilePath.isAbsolute filepath || FilePath.hasTrailingPathSeparator filepath) &&
not (null filepath) &&
not (hasParentDir filepath) &&
filepath /= "." && FilePath.isValid filepath
--------------------------------------------------------------------------------
-- Conversion
-- | Convert absolute path to directory to 'FilePath' type.
fromAbsDir :: Path Abs Dir -> FilePath
fromAbsDir = toFilePath
-- | Convert relative path to directory to 'FilePath' type.
fromRelDir :: Path Rel Dir -> FilePath
fromRelDir = toFilePath
-- | Convert absolute path to file to 'FilePath' type.
fromAbsFile :: Path Abs File -> FilePath
fromAbsFile = toFilePath
-- | Convert relative path to file to 'FilePath' type.
fromRelFile :: Path Rel File -> FilePath
fromRelFile = toFilePath
--------------------------------------------------------------------------------
-- Constructors
-- | Make a 'Path' 'Abs' 'Dir'.
--
-- Remember: due to the nature of absolute paths this (e.g. @\/home\/foo@)
-- may compile on your platform, but it may not compile on another
-- platform (Windows).
mkAbsDir :: FilePath -> Q Exp
mkAbsDir = either (error . show) lift . parseAbsDir
-- | Make a 'Path' 'Rel' 'Dir'.
mkRelDir :: FilePath -> Q Exp
mkRelDir = either (error . show) lift . parseRelDir
-- | Make a 'Path' 'Abs' 'File'.
--
-- Remember: due to the nature of absolute paths this (e.g. @\/home\/foo@)
-- may compile on your platform, but it may not compile on another
-- platform (Windows).
mkAbsFile :: FilePath -> Q Exp
mkAbsFile = either (error . show) lift . parseAbsFile
-- | Make a 'Path' 'Rel' 'File'.
mkRelFile :: FilePath -> Q Exp
mkRelFile = either (error . show) lift . parseRelFile
--------------------------------------------------------------------------------
-- Internal functions
-- | Normalizes directory path with platform-specific rules.
normalizeDir :: FilePath -> FilePath
normalizeDir =
normalizeRelDir
. FilePath.addTrailingPathSeparator
. normalizeFilePath
where -- Represent a "." in relative dir path as "" internally so that it
-- composes without having to renormalize the path.
normalizeRelDir p
| p == relRootFP = ""
| otherwise = p
-- | Replaces consecutive path seps with single sep and replaces alt sep with standard sep.
normalizeAllSeps :: FilePath -> FilePath
normalizeAllSeps = foldr normSeps []
where normSeps ch [] = [ch]
normSeps ch path@(p0:_)
| FilePath.isPathSeparator ch && FilePath.isPathSeparator p0 = path
| FilePath.isPathSeparator ch = FilePath.pathSeparator:path
| otherwise = ch:path
-- | Normalizes seps in whole path, but if there are 2+ seps at the beginning,
-- they are normalized to exactly 2 to preserve UNC and Unicode prefixed paths.
normalizeWindowsSeps :: FilePath -> FilePath
normalizeWindowsSeps path = normLeadingSeps ++ normalizeAllSeps rest
where (leadingSeps, rest) = span FilePath.isPathSeparator path
normLeadingSeps = replicate (min 2 (length leadingSeps)) FilePath.pathSeparator
-- | Normalizes seps only at the beginning of a path.
normalizeLeadingSeps :: FilePath -> FilePath
normalizeLeadingSeps path = normLeadingSep ++ rest
where (leadingSeps, rest) = span FilePath.isPathSeparator path
normLeadingSep = replicate (min 1 (length leadingSeps)) FilePath.pathSeparator
-- | Normalizes seps only at the end of a path.
normalizeTrailingSeps :: FilePath -> FilePath
normalizeTrailingSeps = reverse . normalizeLeadingSeps . reverse
-- | Applies platform-specific sep normalization following @FilePath.normalise@.
normalizeFilePath :: FilePath -> FilePath
normalizeFilePath
| IS_WINDOWS = normalizeWindowsSeps . FilePath.normalise
| otherwise = normalizeLeadingSeps . FilePath.normalise
--------------------------------------------------------------------------------
-- Deprecated
{-# DEPRECATED PathParseException "Please use PathException instead." #-}
-- | Same as 'PathException'.
type PathParseException = PathException
{-# DEPRECATED stripDir "Please use stripProperPrefix instead." #-}
-- | Same as 'stripProperPrefix'.
stripDir :: MonadThrow m
=> Path b Dir -> Path b t -> m (Path Rel t)
stripDir = stripProperPrefix
{-# DEPRECATED isParentOf "Please use isProperPrefixOf instead." #-}
-- | Same as 'isProperPrefixOf'.
isParentOf :: Path b Dir -> Path b t -> Bool
isParentOf = isProperPrefixOf