    -- | A more type-safe version of file paths
    --
    -- This module is intended to replace imports of System.FilePath, and
    -- additionally exports thin wrappers around common IO functions.  To facilitate
    -- importing this module unqualified we also re-export some  definitions from
    -- System.IO (importing both would likely lead to name clashes).
    --
    -- Note that his module does not import any other modules from Hackage.Security;
    -- everywhere else we use Path instead of FilePath directly.
    module Hackage.Security.Util.Path (
        -- * Paths
        Path(..)
      , castRoot
        -- * FilePath-like operations on paths with arbitrary roots
      , takeDirectory
      , takeFileName
      , (<.>)
      , splitExtension
      , takeExtension
        -- * Unrooted paths
      , Unrooted
      , (</>)
      , rootPath
      , unrootPath
      , toUnrootedFilePath
      , fromUnrootedFilePath
      , fragment
      , joinFragments
      , splitFragments
      , isPathPrefixOf
        -- * File-system paths
      , Relative
      , Absolute
      , HomeDir
      , FsRoot(..)
      , FsPath(..)
        -- ** Conversions
      , toFilePath
      , fromFilePath
      , makeAbsolute
      , fromAbsoluteFilePath
        -- ** Wrappers around System.IO
      , withFile
      , openTempFile'
        -- ** Wrappers around Data.ByteString
      , readLazyByteString
      , readStrictByteString
      , writeLazyByteString
      , writeStrictByteString
        -- ** Wrappers around System.Directory
      , copyFile
      , createDirectory
      , createDirectoryIfMissing
      , removeDirectory
      , doesFileExist
      , doesDirectoryExist
      , getModificationTime
      , removeFile
      , getTemporaryDirectory
      , getDirectoryContents
      , getRecursiveContents
      , renameFile
      , getCurrentDirectory
        -- * Wrappers around Codec.Archive.Tar
      , Tar
      , tarIndexLookup
      , tarAppend
        -- * Wrappers around Network.URI
      , Web
      , toURIPath
      , fromURIPath
      , uriPath
      , modifyUriPath
        -- * Re-exports
      , IOMode(..)
      , BufferMode(..)
      , Handle
      , SeekMode(..)
      , IO.hSetBuffering
      , IO.hClose
      , IO.hFileSize
      , IO.hSeek
      ) where

    import Prelude
    import Control.Monad
    import Data.List (isPrefixOf)
    import System.IO (IOMode(..), BufferMode(..), Handle, SeekMode(..))
    import System.IO.Unsafe (unsafeInterleaveIO)
    import Data.Time (UTCTime)
    import qualified Data.ByteString         as BS
    import qualified Data.ByteString.Lazy    as BS.L
    import qualified System.FilePath         as FP.Native
    import qualified System.FilePath.Posix   as FP.Posix
    import qualified System.IO               as IO
    import qualified System.Directory        as Dir
    import qualified Codec.Archive.Tar       as Tar
    import qualified Codec.Archive.Tar.Index as TarIndex
    import qualified Network.URI             as URI

    import Hackage.Security.Util.Pretty

    {-------------------------------------------------------------------------------
      Paths
    -------------------------------------------------------------------------------}

    -- | Paths
    --
    -- A 'Path' is simply a 'FilePath' with a type-level tag indicating where this
    -- path is rooted (relative to the current directory, absolute path, relative to
    -- a web domain, whatever). Most operations on 'Path' are just lifted versions
    -- of the operations on the underlying 'FilePath'. The tag however allows us to
    -- give a lot of operations a more meaningful type. For instance, it does not
    -- make sense to append two absolute paths together; instead, we can only append
    -- an unrooted path to another path. It also means we avoid bugs where we use
    -- one kind of path where we expect another.
    newtype Path a = Path FilePath -- always a Posix style path internally
      deriving (Int -> Path a -> ShowS
    [Path a] -> ShowS
    Path a -> String
    (Int -> Path a -> ShowS)
    -> (Path a -> String) -> ([Path a] -> ShowS) -> Show (Path a)
    forall a. Int -> Path a -> ShowS
    forall a. [Path a] -> ShowS
    forall a. Path a -> String
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: forall a. Int -> Path a -> ShowS
    showsPrec :: Int -> Path a -> ShowS
    $cshow :: forall a. Path a -> String
    show :: Path a -> String
    $cshowList :: forall a. [Path a] -> ShowS
    showList :: [Path a] -> ShowS
    Show, Path a -> Path a -> Bool
    (Path a -> Path a -> Bool)
    -> (Path a -> Path a -> Bool) -> Eq (Path a)
    forall a. Path a -> Path a -> Bool
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: forall a. Path a -> Path a -> Bool
    == :: Path a -> Path a -> Bool
    $c/= :: forall a. Path a -> Path a -> Bool
    /= :: Path a -> Path a -> Bool
    Eq, Eq (Path a)
    Eq (Path a) =>
    (Path a -> Path a -> Ordering)
    -> (Path a -> Path a -> Bool)
    -> (Path a -> Path a -> Bool)
    -> (Path a -> Path a -> Bool)
    -> (Path a -> Path a -> Bool)
    -> (Path a -> Path a -> Path a)
    -> (Path a -> Path a -> Path a)
    -> Ord (Path a)
    Path a -> Path a -> Bool
    Path a -> Path a -> Ordering
    Path a -> Path a -> Path a
    forall a. Eq (Path a)
    forall a.
    Eq a =>
    (a -> a -> Ordering)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> a)
    -> (a -> a -> a)
    -> Ord a
    forall a. Path a -> Path a -> Bool
    forall a. Path a -> Path a -> Ordering
    forall a. Path a -> Path a -> Path a
    $ccompare :: forall a. Path a -> Path a -> Ordering
    compare :: Path a -> Path a -> Ordering
    $c< :: forall a. Path a -> Path a -> Bool
    < :: Path a -> Path a -> Bool
    $c<= :: forall a. Path a -> Path a -> Bool
    <= :: Path a -> Path a -> Bool
    $c> :: forall a. Path a -> Path a -> Bool
    > :: Path a -> Path a -> Bool
    $c>= :: forall a. Path a -> Path a -> Bool
    >= :: Path a -> Path a -> Bool
    $cmax :: forall a. Path a -> Path a -> Path a
    max :: Path a -> Path a -> Path a
    $cmin :: forall a. Path a -> Path a -> Path a
    min :: Path a -> Path a -> Path a
    Ord)

    mkPathNative :: FilePath -> Path a
    mkPathNative :: forall a. String -> Path a
    mkPathNative = String -> Path a
    forall a. String -> Path a
    Path (String -> Path a) -> ShowS -> String -> Path a
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [String] -> String
    FP.Posix.joinPath ([String] -> String) -> (String -> [String]) -> ShowS
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> [String]
    FP.Native.splitDirectories

    unPathNative :: Path a -> FilePath
    unPathNative :: forall a. Path a -> String
    unPathNative (Path String
    fp) = [String] -> String
    FP.Native.joinPath ([String] -> String) -> (String -> [String]) -> ShowS
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> [String]
    FP.Posix.splitDirectories ShowS -> ShowS
    forall a b. (a -> b) -> a -> b
    $ String
    fp

    mkPathPosix :: FilePath -> Path a
    mkPathPosix :: forall a. String -> Path a
    mkPathPosix = String -> Path a
    forall a. String -> Path a
    Path

    unPathPosix :: Path a -> FilePath
    unPathPosix :: forall a. Path a -> String
    unPathPosix (Path String
    fp) = String
    fp

    -- | Reinterpret the root of a path
    --
    -- This literally just changes the type-level tag; use with caution!
    castRoot :: Path root -> Path root'
    castRoot :: forall root root'. Path root -> Path root'
    castRoot (Path String
    fp) = String -> Path root'
    forall a. String -> Path a
    Path String
    fp

    {-------------------------------------------------------------------------------
      FilePath-like operations on paths with an arbitrary root
    -------------------------------------------------------------------------------}

    takeDirectory :: Path a -> Path a
    takeDirectory :: forall a. Path a -> Path a
    takeDirectory = ShowS -> Path a -> Path a
    forall a b. ShowS -> Path a -> Path b
    liftFP ShowS
    FP.Posix.takeDirectory

    takeFileName :: Path a -> String
    takeFileName :: forall a. Path a -> String
    takeFileName = ShowS -> Path a -> String
    forall x a. (String -> x) -> Path a -> x
    liftFromFP ShowS
    FP.Posix.takeFileName

    (<.>) :: Path a -> String -> Path a
    Path a
    fp <.> :: forall a. Path a -> String -> Path a
    <.> String
    ext = ShowS -> Path a -> Path a
    forall a b. ShowS -> Path a -> Path b
    liftFP (String -> ShowS
    FP.Posix.<.> String
    ext) Path a
    fp

    splitExtension :: Path a -> (Path a, String)
    splitExtension :: forall a. Path a -> (Path a, String)
    splitExtension (Path String
    fp) = (String -> Path a
    forall a. String -> Path a
    Path String
    fp', String
    ext)
      where
        (String
    fp', String
    ext) = String -> (String, String)
    FP.Posix.splitExtension String
    fp

    takeExtension :: Path a -> String
    takeExtension :: forall a. Path a -> String
    takeExtension (Path String
    fp) = ShowS
    FP.Posix.takeExtension String
    fp

    {-------------------------------------------------------------------------------
      Unrooted paths
    -------------------------------------------------------------------------------}

    -- | Type-level tag for unrooted paths
    --
    -- Unrooted paths need a root before they can be interpreted.
    data Unrooted

    instance Pretty (Path Unrooted) where
      pretty :: Path Unrooted -> String
    pretty (Path String
    fp) = String
    fp

    (</>) :: Path a -> Path Unrooted -> Path a
    </> :: forall a. Path a -> Path Unrooted -> Path a
    (</>) = (String -> ShowS) -> Path a -> Path Unrooted -> Path a
    forall a b c. (String -> ShowS) -> Path a -> Path b -> Path c
    liftFP2 String -> ShowS
    (FP.Posix.</>)

    -- | Reinterpret an unrooted path
    --
    -- This is an alias for 'castRoot'; see comments there.
    rootPath :: Path Unrooted -> Path root
    rootPath :: forall root. Path Unrooted -> Path root
    rootPath (Path String
    fp) = String -> Path root
    forall a. String -> Path a
    Path String
    fp

    -- | Forget a path's root
    --
    -- This is an alias for 'castRoot'; see comments there.
    unrootPath :: Path root -> Path Unrooted
    unrootPath :: forall root. Path root -> Path Unrooted
    unrootPath (Path String
    fp) = String -> Path Unrooted
    forall a. String -> Path a
    Path String
    fp

    -- | Convert a relative\/unrooted Path to a FilePath (using POSIX style
    -- directory separators).
    --
    -- See also 'toAbsoluteFilePath'
    --
    toUnrootedFilePath :: Path Unrooted -> FilePath
    toUnrootedFilePath :: Path Unrooted -> String
    toUnrootedFilePath = Path Unrooted -> String
    forall a. Path a -> String
    unPathPosix

    -- | Convert from a relative\/unrooted FilePath (using POSIX style directory
    -- separators).
    --
    fromUnrootedFilePath :: FilePath -> Path Unrooted
    fromUnrootedFilePath :: String -> Path Unrooted
    fromUnrootedFilePath = String -> Path Unrooted
    forall a. String -> Path a
    mkPathPosix

    -- | A path fragment (like a single directory or filename)
    fragment :: String -> Path Unrooted
    fragment :: String -> Path Unrooted
    fragment = String -> Path Unrooted
    forall a. String -> Path a
    Path

    joinFragments :: [String] -> Path Unrooted
    joinFragments :: [String] -> Path Unrooted
    joinFragments = ([String] -> String) -> [String] -> Path Unrooted
    forall x a. (x -> String) -> x -> Path a
    liftToFP [String] -> String
    FP.Posix.joinPath

    splitFragments :: Path Unrooted -> [String]
    splitFragments :: Path Unrooted -> [String]
    splitFragments (Path String
    fp) = String -> [String]
    FP.Posix.splitDirectories String
    fp

    isPathPrefixOf :: Path Unrooted -> Path Unrooted -> Bool
    isPathPrefixOf :: Path Unrooted -> Path Unrooted -> Bool
    isPathPrefixOf = (String -> String -> Bool)
    -> Path Unrooted -> Path Unrooted -> Bool
    forall x a b. (String -> String -> x) -> Path a -> Path b -> x
    liftFromFP2 String -> String -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    isPrefixOf

    {-------------------------------------------------------------------------------
      File-system paths
    -------------------------------------------------------------------------------}

    data Relative
    data Absolute
    data HomeDir

    instance Pretty (Path Absolute) where
      pretty :: Path Absolute -> String
    pretty (Path String
    fp) = String
    fp

    instance Pretty (Path Relative) where
      pretty :: Path Relative -> String
    pretty (Path String
    fp) = String
    "./" String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ String
    fp

    instance Pretty (Path HomeDir) where
      pretty :: Path HomeDir -> String
    pretty (Path String
    fp) = String
    "~/" String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ String
    fp

    -- | A file system root can be interpreted as an (absolute) FilePath
    class FsRoot root where
      -- | Convert a Path to an absolute FilePath (using native style directory separators).
      --
      toAbsoluteFilePath :: Path root -> IO FilePath

    instance FsRoot Relative where
        toAbsoluteFilePath :: Path Relative -> IO String
    toAbsoluteFilePath Path Relative
    p = String -> IO String
    Dir.makeAbsolute (Path Relative -> String
    forall a. Path a -> String
    unPathNative Path Relative
    p)

    instance FsRoot Absolute where
        toAbsoluteFilePath :: Path Absolute -> IO String
    toAbsoluteFilePath = String -> IO String
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (String -> IO String)
    -> (Path Absolute -> String) -> Path Absolute -> IO String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Path Absolute -> String
    forall a. Path a -> String
    unPathNative

    instance FsRoot HomeDir where
        toAbsoluteFilePath :: Path HomeDir -> IO String
    toAbsoluteFilePath Path HomeDir
    p = do
          home <- IO String
    Dir.getHomeDirectory
          return $ home FP.Native.</> unPathNative p

    -- | Abstract over a file system root
    --
    -- see 'fromFilePath'
    data FsPath = forall root. FsRoot root => FsPath (Path root)

    {-------------------------------------------------------------------------------
      Conversions
    -------------------------------------------------------------------------------}

    toFilePath :: Path Absolute -> FilePath
    toFilePath :: Path Absolute -> String
    toFilePath = Path Absolute -> String
    forall a. Path a -> String
    unPathNative

    fromFilePath :: FilePath -> FsPath
    fromFilePath :: String -> FsPath
    fromFilePath String
    fp
        | String -> Bool
    FP.Native.isAbsolute String
    fp = Path Absolute -> FsPath
    forall root. FsRoot root => Path root -> FsPath
    FsPath (String -> Path Absolute
    forall a. String -> Path a
    mkPathNative String
    fp  :: Path Absolute)
        | Just String
    fp' <- String -> Maybe String
    atHome String
    fp   = Path HomeDir -> FsPath
    forall root. FsRoot root => Path root -> FsPath
    FsPath (String -> Path HomeDir
    forall a. String -> Path a
    mkPathNative String
    fp' :: Path HomeDir)
        | Bool
    otherwise               = Path Relative -> FsPath
    forall root. FsRoot root => Path root -> FsPath
    FsPath (String -> Path Relative
    forall a. String -> Path a
    mkPathNative String
    fp  :: Path Relative)
      where
        -- TODO: I don't know if there a standard way that Windows users refer to
        -- their home directory. For now, we'll only interpret '~'. Everybody else
        -- can specify an absolute path if this doesn't work.
        atHome :: FilePath -> Maybe FilePath
        atHome :: String -> Maybe String
    atHome String
    "~" = String -> Maybe String
    forall a. a -> Maybe a
    Just String
    ""
        atHome (Char
    '~':Char
    sep:String
    fp') | Char -> Bool
    FP.Native.isPathSeparator Char
    sep = String -> Maybe String
    forall a. a -> Maybe a
    Just String
    fp'
        atHome String
    _otherwise = Maybe String
    forall a. Maybe a
    Nothing

    makeAbsolute :: FsPath -> IO (Path Absolute)
    makeAbsolute :: FsPath -> IO (Path Absolute)
    makeAbsolute (FsPath Path root
    p) = String -> Path Absolute
    forall a. String -> Path a
    mkPathNative (String -> Path Absolute) -> IO String -> IO (Path Absolute)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    p

    fromAbsoluteFilePath :: FilePath -> Path Absolute
    fromAbsoluteFilePath :: String -> Path Absolute
    fromAbsoluteFilePath String
    fp
      | String -> Bool
    FP.Native.isAbsolute String
    fp = String -> Path Absolute
    forall a. String -> Path a
    mkPathNative String
    fp
      | Bool
    otherwise               = String -> Path Absolute
    forall a. HasCallStack => String -> a
    error (String
    "fromAbsoluteFilePath: not an absolute path: " String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ String
    fp)

    {-------------------------------------------------------------------------------
      Wrappers around System.IO
    -------------------------------------------------------------------------------}

    -- | Wrapper around 'withFile'
    withFile :: FsRoot root => Path root -> IOMode -> (Handle -> IO r) -> IO r
    withFile :: forall root r.
    FsRoot root =>
    Path root -> IOMode -> (Handle -> IO r) -> IO r
    withFile Path root
    path IOMode
    mode Handle -> IO r
    callback = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        IO.withFile filePath mode callback

    -- | Wrapper around 'openBinaryTempFileWithDefaultPermissions'
    --
    -- NOTE: The caller is responsible for cleaning up the temporary file.
    openTempFile' :: FsRoot root => Path root -> String -> IO (Path Absolute, Handle)
    openTempFile' :: forall root.
    FsRoot root =>
    Path root -> String -> IO (Path Absolute, Handle)
    openTempFile' Path root
    path String
    template = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        (tempFilePath, h) <- IO.openBinaryTempFileWithDefaultPermissions filePath template
        return (fromAbsoluteFilePath tempFilePath, h)

    {-------------------------------------------------------------------------------
      Wrappers around Data.ByteString.*
    -------------------------------------------------------------------------------}

    readLazyByteString :: FsRoot root => Path root -> IO BS.L.ByteString
    readLazyByteString :: forall root. FsRoot root => Path root -> IO ByteString
    readLazyByteString Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        BS.L.readFile filePath

    readStrictByteString :: FsRoot root => Path root -> IO BS.ByteString
    readStrictByteString :: forall root. FsRoot root => Path root -> IO ByteString
    readStrictByteString Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        BS.readFile filePath

    writeLazyByteString :: FsRoot root => Path root -> BS.L.ByteString -> IO ()
    writeLazyByteString :: forall root. FsRoot root => Path root -> ByteString -> IO ()
    writeLazyByteString Path root
    path ByteString
    bs = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        BS.L.writeFile filePath bs

    writeStrictByteString :: FsRoot root => Path root -> BS.ByteString -> IO ()
    writeStrictByteString :: forall root. FsRoot root => Path root -> ByteString -> IO ()
    writeStrictByteString Path root
    path ByteString
    bs = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        BS.writeFile filePath bs

    {-------------------------------------------------------------------------------
      Wrappers around System.Directory
    -------------------------------------------------------------------------------}

    copyFile :: (FsRoot root, FsRoot root') => Path root -> Path root' -> IO ()
    copyFile :: forall root root'.
    (FsRoot root, FsRoot root') =>
    Path root -> Path root' -> IO ()
    copyFile Path root
    src Path root'
    dst = do
        src' <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    src
        dst' <- toAbsoluteFilePath dst
        Dir.copyFile src' dst'

    createDirectory :: FsRoot root => Path root -> IO ()
    createDirectory :: forall root. FsRoot root => Path root -> IO ()
    createDirectory Path root
    path = String -> IO ()
    Dir.createDirectory (String -> IO ()) -> IO String -> IO ()
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path

    createDirectoryIfMissing :: FsRoot root => Bool -> Path root -> IO ()
    createDirectoryIfMissing :: forall root. FsRoot root => Bool -> Path root -> IO ()
    createDirectoryIfMissing Bool
    createParents Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        Dir.createDirectoryIfMissing createParents filePath

    removeDirectory :: FsRoot root => Path root -> IO ()
    removeDirectory :: forall root. FsRoot root => Path root -> IO ()
    removeDirectory Path root
    path = String -> IO ()
    Dir.removeDirectory (String -> IO ()) -> IO String -> IO ()
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path

    doesFileExist :: FsRoot root => Path root -> IO Bool
    doesFileExist :: forall root. FsRoot root => Path root -> IO Bool
    doesFileExist Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        Dir.doesFileExist filePath

    doesDirectoryExist :: FsRoot root => Path root -> IO Bool
    doesDirectoryExist :: forall root. FsRoot root => Path root -> IO Bool
    doesDirectoryExist Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        Dir.doesDirectoryExist filePath

    getModificationTime :: FsRoot root => Path root -> IO UTCTime
    getModificationTime :: forall root. FsRoot root => Path root -> IO UTCTime
    getModificationTime Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        Dir.getModificationTime filePath

    removeFile :: FsRoot root => Path root -> IO ()
    removeFile :: forall root. FsRoot root => Path root -> IO ()
    removeFile Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        Dir.removeFile filePath

    getTemporaryDirectory :: IO (Path Absolute)
    getTemporaryDirectory :: IO (Path Absolute)
    getTemporaryDirectory = String -> Path Absolute
    fromAbsoluteFilePath (String -> Path Absolute) -> IO String -> IO (Path Absolute)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> IO String
    Dir.getTemporaryDirectory

    -- | Return the immediate children of a directory
    --
    -- Filters out @"."@ and @".."@.
    getDirectoryContents :: FsRoot root => Path root -> IO [Path Unrooted]
    getDirectoryContents :: forall root. FsRoot root => Path root -> IO [Path Unrooted]
    getDirectoryContents Path root
    path = do
        filePath <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    path
        fragments <$> Dir.getDirectoryContents filePath
      where
        fragments :: [String] -> [Path Unrooted]
        fragments :: [String] -> [Path Unrooted]
    fragments = (String -> Path Unrooted) -> [String] -> [Path Unrooted]
    forall a b. (a -> b) -> [a] -> [b]
    map String -> Path Unrooted
    fragment ([String] -> [Path Unrooted])
    -> ([String] -> [String]) -> [String] -> [Path Unrooted]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (String -> Bool) -> [String] -> [String]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Bool -> Bool
    not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> Bool
    skip)

        skip :: String -> Bool
        skip :: String -> Bool
    skip String
    "."  = Bool
    True
        skip String
    ".." = Bool
    True
        skip String
    _    = Bool
    False

    -- | Recursive traverse a directory structure
    --
    -- Returns a set of paths relative to the directory specified. The list is
    -- lazily constructed, so that directories are only read when required.
    -- (This is also essential to ensure that this function does not build the
    -- entire result in memory before returning, potentially running out of heap.)
    getRecursiveContents :: FsRoot root => Path root -> IO [Path Unrooted]
    getRecursiveContents :: forall root. FsRoot root => Path root -> IO [Path Unrooted]
    getRecursiveContents Path root
    root = Path Unrooted -> IO [Path Unrooted]
    go Path Unrooted
    emptyPath
      where
        go :: Path Unrooted -> IO [Path Unrooted]
        go :: Path Unrooted -> IO [Path Unrooted]
    go Path Unrooted
    subdir = IO [Path Unrooted] -> IO [Path Unrooted]
    forall a. IO a -> IO a
    unsafeInterleaveIO (IO [Path Unrooted] -> IO [Path Unrooted])
    -> IO [Path Unrooted] -> IO [Path Unrooted]
    forall a b. (a -> b) -> a -> b
    $ do
          entries <- Path root -> IO [Path Unrooted]
    forall root. FsRoot root => Path root -> IO [Path Unrooted]
    getDirectoryContents (Path root
    root Path root -> Path Unrooted -> Path root
    forall a. Path a -> Path Unrooted -> Path a
    </> Path Unrooted
    subdir)
          liftM concat $ forM entries $ \Path Unrooted
    entry -> do
            let path :: Path Unrooted
    path = Path Unrooted
    subdir Path Unrooted -> Path Unrooted -> Path Unrooted
    forall a. Path a -> Path Unrooted -> Path a
    </> Path Unrooted
    entry
            isDirectory <- Path root -> IO Bool
    forall root. FsRoot root => Path root -> IO Bool
    doesDirectoryExist (Path root
    root Path root -> Path Unrooted -> Path root
    forall a. Path a -> Path Unrooted -> Path a
    </> Path Unrooted
    path)
            if isDirectory then go path
                           else return [path]

        emptyPath :: Path Unrooted
        emptyPath :: Path Unrooted
    emptyPath = [String] -> Path Unrooted
    joinFragments []

    renameFile :: (FsRoot root, FsRoot root')
               => Path root  -- ^ Old
               -> Path root' -- ^ New
               -> IO ()
    renameFile :: forall root root'.
    (FsRoot root, FsRoot root') =>
    Path root -> Path root' -> IO ()
    renameFile Path root
    old Path root'
    new = do
        old' <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    old
        new' <- toAbsoluteFilePath new
        Dir.renameFile old' new'

    getCurrentDirectory :: IO (Path Absolute)
    getCurrentDirectory :: IO (Path Absolute)
    getCurrentDirectory = do
        cwd <- IO String
    Dir.getCurrentDirectory
        makeAbsolute $ fromFilePath cwd

    {-------------------------------------------------------------------------------
      Wrappers around Codec.Archive.Tar.*
    -------------------------------------------------------------------------------}

    data Tar

    instance Pretty (Path Tar) where
      pretty :: Path Tar -> String
    pretty (Path String
    fp) = String
    "<tarball>/" String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ String
    fp

    tarIndexLookup :: TarIndex.TarIndex -> Path Tar -> Maybe TarIndex.TarIndexEntry
    tarIndexLookup :: TarIndex -> Path Tar -> Maybe TarIndexEntry
    tarIndexLookup TarIndex
    index Path Tar
    path = TarIndex -> String -> Maybe TarIndexEntry
    TarIndex.lookup TarIndex
    index String
    path'
      where
        path' :: FilePath
        path' :: String
    path' = Path Unrooted -> String
    toUnrootedFilePath (Path Unrooted -> String) -> Path Unrooted -> String
    forall a b. (a -> b) -> a -> b
    $ Path Tar -> Path Unrooted
    forall root. Path root -> Path Unrooted
    unrootPath Path Tar
    path

    tarAppend :: (FsRoot root, FsRoot root')
              => Path root   -- ^ Path of the @.tar@ file
              -> Path root'  -- ^ Base directory
              -> [Path Tar]  -- ^ Files to add, relative to the base dir
              -> IO ()
    tarAppend :: forall root root'.
    (FsRoot root, FsRoot root') =>
    Path root -> Path root' -> [Path Tar] -> IO ()
    tarAppend Path root
    tarFile Path root'
    baseDir [Path Tar]
    contents = do
        tarFile' <- Path root -> IO String
    forall root. FsRoot root => Path root -> IO String
    toAbsoluteFilePath Path root
    tarFile
        baseDir' <- toAbsoluteFilePath baseDir
        Tar.append tarFile' baseDir' contents'
      where
        contents' :: [FilePath]
        contents' :: [String]
    contents' = (Path Tar -> String) -> [Path Tar] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (Path Unrooted -> String
    forall a. Path a -> String
    unPathNative (Path Unrooted -> String)
    -> (Path Tar -> Path Unrooted) -> Path Tar -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Path Tar -> Path Unrooted
    forall root. Path root -> Path Unrooted
    unrootPath) [Path Tar]
    contents

    {-------------------------------------------------------------------------------
      Wrappers around Network.URI
    -------------------------------------------------------------------------------}

    data Web

    toURIPath :: FilePath -> Path Web
    toURIPath :: String -> Path Web
    toURIPath = Path Unrooted -> Path Web
    forall root. Path Unrooted -> Path root
    rootPath (Path Unrooted -> Path Web)
    -> (String -> Path Unrooted) -> String -> Path Web
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> Path Unrooted
    fromUnrootedFilePath

    fromURIPath :: Path Web -> FilePath
    fromURIPath :: Path Web -> String
    fromURIPath = Path Unrooted -> String
    toUnrootedFilePath (Path Unrooted -> String)
    -> (Path Web -> Path Unrooted) -> Path Web -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Path Web -> Path Unrooted
    forall root. Path root -> Path Unrooted
    unrootPath

    uriPath :: URI.URI -> Path Web
    uriPath :: URI -> Path Web
    uriPath = String -> Path Web
    toURIPath (String -> Path Web) -> (URI -> String) -> URI -> Path Web
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . URI -> String
    URI.uriPath

    modifyUriPath :: URI.URI -> (Path Web -> Path Web) -> URI.URI
    modifyUriPath :: URI -> (Path Web -> Path Web) -> URI
    modifyUriPath URI
    uri Path Web -> Path Web
    f = URI
    uri { URI.uriPath = f' (URI.uriPath uri) }
      where
        f' :: FilePath -> FilePath
        f' :: ShowS
    f' = Path Web -> String
    fromURIPath (Path Web -> String) -> (String -> Path Web) -> ShowS
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Path Web -> Path Web
    f (Path Web -> Path Web)
    -> (String -> Path Web) -> String -> Path Web
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> Path Web
    toURIPath

    {-------------------------------------------------------------------------------
      Auxiliary
    -------------------------------------------------------------------------------}

    liftFP :: (FilePath -> FilePath) -> Path a -> Path b
    liftFP :: forall a b. ShowS -> Path a -> Path b
    liftFP ShowS
    f (Path String
    fp) = String -> Path b
    forall a. String -> Path a
    Path (ShowS
    f String
    fp)

    liftFP2 :: (FilePath -> FilePath -> FilePath) -> Path a -> Path b -> Path c
    liftFP2 :: forall a b c. (String -> ShowS) -> Path a -> Path b -> Path c
    liftFP2 String -> ShowS
    f (Path String
    fp) (Path String
    fp') = String -> Path c
    forall a. String -> Path a
    Path (String -> ShowS
    f String
    fp String
    fp')

    liftFromFP :: (FilePath -> x) -> Path a -> x
    liftFromFP :: forall x a. (String -> x) -> Path a -> x
    liftFromFP String -> x
    f (Path String
    fp) = String -> x
    f String
    fp

    liftFromFP2 :: (FilePath -> FilePath -> x) -> Path a -> Path b -> x
    liftFromFP2 :: forall x a b. (String -> String -> x) -> Path a -> Path b -> x
    liftFromFP2 String -> String -> x
    f (Path String
    fp) (Path String
    fp') = String -> String -> x
    f String
    fp String
    fp'

    liftToFP :: (x -> FilePath) -> x -> Path a
    liftToFP :: forall x a. (x -> String) -> x -> Path a
    liftToFP x -> String
    f x
    x = String -> Path a
    forall a. String -> Path a
    Path (x -> String
    f x
    x)