20 Commits

Author SHA1 Message Date
a0510eaec1 Add stack.yaml 2018-07-19 13:33:55 +08:00
cce2c68cab Update .gitignore 2018-07-19 13:33:55 +08:00
3c5f06f41d Remove redundant imports in tests 2018-07-19 13:33:55 +08:00
6bc5381108 Add hackage-deps badge 2018-06-02 22:38:50 +02:00
ef51863180 Document use of 'getcwd' 2018-04-12 14:28:37 +02:00
09062351f5 Release 0.9.2 2018-04-12 02:23:56 +02:00
ab4137572e Fix travis typo 2018-04-12 02:20:51 +02:00
c03a7ec18f Merge branch 'tighten-base-bound' of https://github.com/gwils/hpath 2018-04-12 02:19:17 +02:00
George Wilson
df298f187e Tighten base bound to prevent building before GHC 7.6 2018-04-12 10:15:08 +10:00
466c72924a Fix travis build for ghc-7.6.3
Cabal sandboxes were introduced in cabal-install 1.18.
2018-04-12 02:12:34 +02:00
9dfb803ba8 Update travis matrix 2018-04-12 02:01:17 +02:00
de46a0c568 Raise required bytestring version
We use Builders, which were introduced in 0.10.0.0.
2018-04-12 01:42:18 +02:00
d9ba67b6f0 Fix build with ghc-7.6 2018-04-12 01:41:32 +02:00
9342abeb7a Release 0.9.1 2018-04-11 23:03:35 +02:00
e8cbc632c9 Fix build with ghc-7.8 2018-04-11 22:36:40 +02:00
c556a3d3e4 Fix build with ghc-7.10 2018-04-11 22:22:03 +02:00
3aee719130 Make "unbuildable on windows" explicit 2018-04-11 12:31:11 +02:00
10662ee803 Bump to 0.9.0 2018-04-11 01:12:12 +02:00
672875f48f Small .cabal cleanup 2018-04-11 01:11:40 +02:00
3e6d93182a Abstract over Path more properly
We don't expect "Path Abs" everywhere anymore. The functions
have been made to be more generic. A user can still pass
absolute paths, so we don't lose any safety. However, some
function implementations may be more tricky.
2018-04-11 01:11:00 +02:00
16 changed files with 271 additions and 158 deletions

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@ tags
.stack-work/
.cabal-sandbox/
cabal.sandbox.config
dist-newstyle/
.ghc.environment.*

View File

@@ -7,12 +7,18 @@ dist: trusty
matrix:
include:
- env: CABALVER=1.18 GHCVER=7.6.3
addons: {apt: {packages: [cabal-install-1.18,ghc-7.6.3], sources: [hvr-ghc]}}
- env: CABALVER=1.22 GHCVER=7.8.4
addons: {apt: {packages: [cabal-install-1.22,ghc-7.8.4], sources: [hvr-ghc]}}
- env: CABALVER=1.24 GHCVER=7.10.2
addons: {apt: {packages: [cabal-install-1.24,ghc-7.10.2], sources: [hvr-ghc]}}
- env: CABALVER=1.24 GHCVER=8.0.1
addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.1], sources: [hvr-ghc]}}
- env: CABALVER=2.0 GHCVER=8.2.2
addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
- env: CABALVER=2.2 GHCVER=8.4.1
addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.1], sources: [hvr-ghc]}}
- env: CABALVER=head GHCVER=head
addons: {apt: {packages: [cabal-install-head,ghc-head], sources: [hvr-ghc]}}

View File

@@ -1,3 +1,12 @@
0.9.2
* fix build with ghc-7.6
* raise required bytestring version
* Tighten base bound to prevent building before GHC 7.6 (by George Wilson)
0.9.1
* fix build with ghc-7.8 and 7.10
0.9.0
* don't force "Path Abs" anymore in IO module, abstract more over Path types
* add 'toAbs'
0.8.1
* add 'readFile', 'readFileEOF', 'writeFile' and 'appendFile'
0.8.0

View File

@@ -1,6 +1,6 @@
# HPath
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath.svg?label=Hackage)](https://hackage.haskell.org/package/hpath) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath)
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath.svg?label=Hackage)](https://hackage.haskell.org/package/hpath) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hpath.svg)](http://packdeps.haskellers.com/feed?needle=hpath)
Support for well-typed paths in Haskell. Also provides ByteString based filepath
manipulation.

View File

@@ -1,5 +1,5 @@
name: hpath
version: 0.8.1
version: 0.9.2
synopsis: Support for well-typed paths
description: Support for well-typed paths, utilizing ByteString under the hood.
license: BSD3
@@ -9,7 +9,7 @@ maintainer: Julian Ospald <hasufell@posteo.de>
copyright: Julian Ospald 2016
category: Filesystem
build-type: Simple
cabal-version: >=1.14
cabal-version: 1.14
extra-source-files: README.md
CHANGELOG
cbits/dirutils.h
@@ -17,6 +17,9 @@ extra-source-files: README.md
doctests-posix.hs
library
if os(windows)
build-depends: unbuildable<0
buildable: False
hs-source-dirs: src/
default-language: Haskell2010
if impl(ghc >= 8.0)
@@ -32,9 +35,9 @@ library
System.Posix.FD,
System.Posix.FilePath
other-modules: HPath.Internal
build-depends: base >= 4.2 && <5
build-depends: base >= 4.6 && <5
, IfElse
, bytestring >= 0.9.2.0
, bytestring >= 0.10.0.0
, deepseq
, exceptions
, hspec
@@ -46,6 +49,9 @@ library
test-suite doctests-hpath
if os(windows)
build-depends: unbuildable<0
buildable: False
default-language: Haskell2010
type: exitcode-stdio-1.0
ghc-options: -threaded
@@ -57,12 +63,15 @@ test-suite doctests-hpath
, hpath
test-suite doctests-posix
if os(windows)
build-depends: unbuildable<0
buildable: False
default-language: Haskell2010
type: exitcode-stdio-1.0
ghc-options: -threaded
main-is: doctests-posix.hs
build-depends: base,
bytestring,
bytestring >= 0.10.0.0,
unix,
hpath,
doctest >= 0.8,
@@ -70,19 +79,23 @@ test-suite doctests-posix
QuickCheck
test-suite spec
if os(windows)
build-depends: unbuildable<0
buildable: False
Type: exitcode-stdio-1.0
Default-Language: Haskell2010
Hs-Source-Dirs: test
Main-Is: Main.hs
other-modules:
HPath.IO.AppendFileSpec
HPath.IO.CanonicalizePathSpec
HPath.IO.CopyDirRecursiveCollectFailuresSpec
HPath.IO.CopyDirRecursiveOverwriteSpec
HPath.IO.CopyDirRecursiveSpec
HPath.IO.CopyFileOverwriteSpec
HPath.IO.CopyFileSpec
HPath.IO.CreateDirSpec
HPath.IO.CreateDirRecursiveSpec
HPath.IO.CreateDirSpec
HPath.IO.CreateRegularFileSpec
HPath.IO.CreateSymlinkSpec
HPath.IO.DeleteDirRecursiveSpec
@@ -92,16 +105,20 @@ test-suite spec
HPath.IO.GetFileTypeSpec
HPath.IO.MoveFileOverwriteSpec
HPath.IO.MoveFileSpec
HPath.IO.ReadFileEOFSpec
HPath.IO.ReadFileSpec
HPath.IO.RecreateSymlinkOverwriteSpec
HPath.IO.RecreateSymlinkSpec
HPath.IO.RenameFileSpec
HPath.IO.ToAbsSpec
HPath.IO.WriteFileSpec
Spec
Utils
GHC-Options: -Wall
Build-Depends: base
, HUnit
, IfElse
, bytestring
, bytestring >= 0.10.0.0
, hpath
, hspec >= 1.3
, process

View File

@@ -13,7 +13,9 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
#if __GLASGOW_HASKELL__ >= 708
{-# LANGUAGE PatternSynonyms #-}
#endif
module HPath
(
@@ -25,8 +27,10 @@ module HPath
,PathParseException
,PathException
,RelC
#if __GLASGOW_HASKELL__ >= 708
-- * PatternSynonyms/ViewPatterns
,pattern Path
#endif
-- * Path Parsing
,parseAbs
,parseFn
@@ -101,7 +105,9 @@ instance RelC Fn
#if __GLASGOW_HASKELL__ >= 710
pattern Path :: ByteString -> Path a
#endif
#if __GLASGOW_HASKELL__ >= 708
pattern Path x <- (MkPath x)
#endif
--------------------------------------------------------------------------------
-- Path Parsers

View File

@@ -8,8 +8,11 @@
-- Portability : portable
--
-- This module provides high-level IO related file operations like
-- copy, delete, move and so on. It only operates on /Path Abs/ which
-- guarantees us well-typed paths which are absolute.
-- copy, delete, move and so on. It only operates on /Path x/ which
-- guarantees us well-typed paths. Passing in /Path Abs/ to any
-- of these functions generally increases safety. Passing /Path Rel/
-- may trigger looking up the current directory via `getcwd` in some
-- cases where it cannot be avoided.
--
-- Some functions are just path-safe wrappers around
-- unix functions, others have stricter exception handling
@@ -30,6 +33,7 @@
-- For other functions (like `copyFile`), the behavior on these file types is
-- unreliable/unsafe. Check the documentation of those functions for details.
{-# LANGUAGE CPP #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE OverloadedStrings #-}
@@ -75,6 +79,7 @@ module HPath.IO
, getFileType
-- * Others
, canonicalizePath
, toAbs
)
where
@@ -103,7 +108,11 @@ import Data.ByteString
(
ByteString
)
#if MIN_VERSION_bytestring(0,10,2)
import Data.ByteString.Builder
#else
import Data.ByteString.Lazy.Builder
#endif
(
Builder
, byteString
@@ -132,6 +141,7 @@ import Data.Maybe
import Data.Monoid
(
(<>)
, mempty
)
import Data.Word
(
@@ -184,6 +194,7 @@ import System.Posix.ByteString
import System.Posix.Directory.ByteString
(
createDirectory
, getWorkingDirectory
, removeDirectory
)
import System.Posix.Directory.Traversals
@@ -326,9 +337,11 @@ data CopyMode = Strict -- ^ fail if any target exists
-- Throws in `Strict` CopyMode only:
--
-- - `AlreadyExists` if destination already exists
copyDirRecursive :: Path Abs -- ^ source dir
-> Path Abs -- ^ destination (parent dirs
-- are not automatically created)
--
-- Note: may call `getcwd` (only if destination is a relative path)
copyDirRecursive :: Path b1 -- ^ source dir
-> Path b2 -- ^ destination (parent dirs
-- are not automatically created)
-> CopyMode
-> RecursiveErrorMode
-> IO ()
@@ -344,27 +357,27 @@ copyDirRecursive fromp destdirp cm rm
(throwIO . RecursiveFailure $ collectedExceptions)
where
go :: IORef [(RecursiveFailureHint, IOException)]
-> Path Abs -> Path Abs -> IO ()
go ce fromp' destdirp' = do
-> Path b1 -> Path b2 -> IO ()
go ce fromp'@(MkPath fromBS) destdirp'@(MkPath destdirpBS) = do
-- NOTE: order is important here, so we don't get empty directories
-- on failure
-- get the contents of the source dir
contents <- handleIOE (ReadContentsFailed fromp' destdirp') ce [] $ do
contents <- handleIOE (ReadContentsFailed fromBS destdirpBS) ce [] $ do
contents <- getDirsFiles fromp'
-- create the destination dir and
-- only return contents if we succeed
handleIOE (CreateDirFailed fromp' destdirp') ce [] $ do
fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus (fromAbs fromp')
handleIOE (CreateDirFailed fromBS destdirpBS) ce [] $ do
fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus fromBS
case cm of
Strict -> createDirectory (fromAbs destdirp') fmode'
Overwrite -> catchIOError (createDirectory (fromAbs destdirp')
Strict -> createDirectory destdirpBS fmode'
Overwrite -> catchIOError (createDirectory destdirpBS
fmode')
$ \e ->
case ioeGetErrorType e of
AlreadyExists -> setFileMode (fromAbs destdirp')
AlreadyExists -> setFileMode destdirpBS
fmode'
_ -> ioError e
return contents
@@ -378,10 +391,10 @@ copyDirRecursive fromp destdirp cm rm
ftype <- getFileType f
newdest <- (destdirp' </>) <$> basename f
case ftype of
SymbolicLink -> handleIOE (RecreateSymlinkFailed f newdest) ce ()
SymbolicLink -> handleIOE (RecreateSymlinkFailed (toFilePath f) (toFilePath newdest)) ce ()
$ recreateSymlink f newdest cm
Directory -> go ce f newdest
RegularFile -> handleIOE (CopyFileFailed f newdest) ce ()
RegularFile -> handleIOE (CopyFileFailed (toFilePath f) (toFilePath newdest)) ce ()
$ copyFile f newdest cm
_ -> return ()
@@ -421,24 +434,27 @@ copyDirRecursive fromp destdirp cm rm
--
-- - `UnsatisfiedConstraints` if destination file is non-empty directory
--
-- Note: calls `symlink`
recreateSymlink :: Path Abs -- ^ the old symlink file
-> Path Abs -- ^ destination file
-- Notes:
--
-- - calls `symlink`
-- - calls `getcwd` in Overwrite mode (if destination is a relative path)
recreateSymlink :: Path b1 -- ^ the old symlink file
-> Path b2 -- ^ destination file
-> CopyMode
-> IO ()
recreateSymlink symsource newsym cm
recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm
= do
throwSameFile symsource newsym
sympoint <- readSymbolicLink (fromAbs symsource)
sympoint <- readSymbolicLink symsourceBS
case cm of
Strict -> return ()
Overwrite -> do
writable <- isWritable (dirname newsym)
writable <- toAbs newsym >>= isWritable
isfile <- doesFileExist newsym
isdir <- doesDirectoryExist newsym
when (writable && isfile) (deleteFile newsym)
when (writable && isdir) (deleteDir newsym)
createSymbolicLink sympoint (fromAbs newsym)
createSymbolicLink sympoint newsymBS
-- |Copies the given regular file to the given destination.
@@ -475,9 +491,12 @@ recreateSymlink symsource newsym cm
--
-- - `AlreadyExists` if destination already exists
--
-- Note: calls `sendfile` and possibly `read`/`write` as fallback
copyFile :: Path Abs -- ^ source file
-> Path Abs -- ^ destination file
-- Notes:
--
-- - calls `sendfile` and possibly `read`/`write` as fallback
-- - may call `getcwd` in Overwrite mode (if destination is a relative path)
copyFile :: Path b1 -- ^ source file
-> Path b2 -- ^ destination file
-> CopyMode
-> IO ()
copyFile from to cm = do
@@ -496,8 +515,8 @@ copyFile from to cm = do
-- figure out if we can still copy by deleting it first
PermissionDenied -> do
exists <- doesFileExist to
writable <- isWritable (dirname to)
if exists && writable
writable <- toAbs to >>= isWritable
if (exists && writable)
then deleteFile to >> copyFile from to Strict
else ioError e
_ -> ioError e
@@ -505,18 +524,17 @@ copyFile from to cm = do
_copyFile :: [SPDF.Flags]
-> [SPDF.Flags]
-> Path Abs -- ^ source file
-> Path Abs -- ^ destination file
-> Path b1 -- ^ source file
-> Path b2 -- ^ destination file
-> IO ()
_copyFile sflags dflags from to
_copyFile sflags dflags (MkPath fromBS) to@(MkPath toBS)
=
-- from sendfile(2) manpage:
-- Applications may wish to fall back to read(2)/write(2) in
-- the case where sendfile() fails with EINVAL or ENOSYS.
withAbsPath to $ \to' -> withAbsPath from $ \from' ->
catchErrno [eINVAL, eNOSYS]
(sendFileCopy from' to')
(void $ readWriteCopy from' to')
catchErrno [eINVAL, eNOSYS]
(sendFileCopy fromBS toBS)
(void $ readWriteCopy fromBS toBS)
where
copyWith copyAction source dest =
bracket (openFd source SPI.ReadOnly sflags Nothing)
@@ -561,8 +579,10 @@ _copyFile sflags dflags from to
--
-- * examines filetypes explicitly
-- * calls `copyDirRecursive` for directories
easyCopy :: Path Abs
-> Path Abs
--
-- Note: may call `getcwd` in Overwrite mode (if destination is a relative path)
easyCopy :: Path b1
-> Path b2
-> CopyMode
-> RecursiveErrorMode
-> IO ()
@@ -591,8 +611,8 @@ easyCopy from to cm rm = do
-- - `InappropriateType` for wrong file type (directory)
-- - `NoSuchThing` if the file does not exist
-- - `PermissionDenied` if the directory cannot be read
deleteFile :: Path Abs -> IO ()
deleteFile p = withAbsPath p removeLink
deleteFile :: Path b -> IO ()
deleteFile (MkPath p) = removeLink p
-- |Deletes the given directory, which must be empty, never symlinks.
@@ -606,8 +626,8 @@ deleteFile p = withAbsPath p removeLink
-- - `PermissionDenied` if we can't open or write to parent directory
--
-- Notes: calls `rmdir`
deleteDir :: Path Abs -> IO ()
deleteDir p = withAbsPath p removeDirectory
deleteDir :: Path b -> IO ()
deleteDir (MkPath p) = removeDirectory p
-- |Deletes the given directory recursively. Does not follow symbolic
@@ -629,7 +649,7 @@ deleteDir p = withAbsPath p removeDirectory
-- - `InappropriateType` for wrong file type (regular file)
-- - `NoSuchThing` if directory does not exist
-- - `PermissionDenied` if we can't open or write to parent directory
deleteDirRecursive :: Path Abs -> IO ()
deleteDirRecursive :: Path b -> IO ()
deleteDirRecursive p =
catchErrno [eNOTEMPTY, eEXIST]
(deleteDir p)
@@ -654,7 +674,7 @@ deleteDirRecursive p =
--
-- * examines filetypes explicitly
-- * calls `deleteDirRecursive` for directories
easyDelete :: Path Abs -> IO ()
easyDelete :: Path b -> IO ()
easyDelete p = do
ftype <- getFileType p
case ftype of
@@ -673,21 +693,18 @@ easyDelete p = do
-- |Opens a file appropriately by invoking xdg-open. The file type
-- is not checked. This forks a process.
openFile :: Path Abs
openFile :: Path b
-> IO ProcessID
openFile p =
withAbsPath p $ \fp ->
SPP.forkProcess $ SPP.executeFile "xdg-open" True [fp] Nothing
openFile (MkPath fp) =
SPP.forkProcess $ SPP.executeFile "xdg-open" True [fp] Nothing
-- |Executes a program with the given arguments. This forks a process.
executeFile :: Path Abs -- ^ program
executeFile :: Path b -- ^ program
-> [ByteString] -- ^ arguments
-> IO ProcessID
executeFile fp args
= withAbsPath fp $ \fpb ->
SPP.forkProcess
$ SPP.executeFile fpb True args Nothing
executeFile (MkPath fp) args =
SPP.forkProcess $ SPP.executeFile fp True args Nothing
@@ -706,9 +723,9 @@ executeFile fp args
-- - `AlreadyExists` if destination already exists
-- - `NoSuchThing` if any of the parent components of the path
-- do not exist
createRegularFile :: FileMode -> Path Abs -> IO ()
createRegularFile fm dest =
bracket (SPI.openFd (fromAbs dest) SPI.WriteOnly (Just fm)
createRegularFile :: FileMode -> Path b -> IO ()
createRegularFile fm (MkPath destBS) =
bracket (SPI.openFd destBS SPI.WriteOnly (Just fm)
(SPI.defaultFileFlags { exclusive = True }))
SPI.closeFd
(\_ -> return ())
@@ -722,8 +739,8 @@ createRegularFile fm dest =
-- - `AlreadyExists` if destination already exists
-- - `NoSuchThing` if any of the parent components of the path
-- do not exist
createDir :: FileMode -> Path Abs -> IO ()
createDir fm dest = createDirectory (fromAbs dest) fm
createDir :: FileMode -> Path b -> IO ()
createDir fm (MkPath destBS) = createDirectory destBS fm
-- |Create an empty directory at the given directory with the given filename.
@@ -744,15 +761,21 @@ createDir fm dest = createDirectory (fromAbs dest) fm
-- exist and cannot be written to
-- - `AlreadyExists` if destination already exists and
-- is not a directory
createDirRecursive :: FileMode -> Path Abs -> IO ()
createDirRecursive fm dest =
catchIOError (createDirectory (fromAbs dest) fm) $ \e -> do
errno <- getErrno
case errno of
en | en == eEXIST -> unlessM (doesDirectoryExist dest) (ioError e)
| en == eNOENT -> createDirRecursive fm (dirname dest)
>> createDirectory (fromAbs dest) fm
| otherwise -> ioError e
--
-- Note: calls `getcwd` if the input path is a relative path
createDirRecursive :: FileMode -> Path b -> IO ()
createDirRecursive fm p =
toAbs p >>= go
where
go :: Path Abs -> IO ()
go dest@(MkPath destBS) = do
catchIOError (createDirectory destBS fm) $ \e -> do
errno <- getErrno
case errno of
en | en == eEXIST -> unlessM (doesDirectoryExist dest) (ioError e)
| en == eNOENT -> createDirRecursive fm (dirname dest)
>> createDirectory destBS fm
| otherwise -> ioError e
-- |Create a symlink.
@@ -765,11 +788,11 @@ createDirRecursive fm dest =
-- do not exist
--
-- Note: calls `symlink`
createSymlink :: Path Abs -- ^ destination file
createSymlink :: Path b -- ^ destination file
-> ByteString -- ^ path the symlink points to
-> IO ()
createSymlink dest sympoint
= createSymbolicLink sympoint (fromAbs dest)
createSymlink (MkPath destBS) sympoint
= createSymbolicLink sympoint destBS
@@ -799,12 +822,12 @@ createSymlink dest sympoint
-- (`HPathIOException`)
--
-- Note: calls `rename` (but does not allow to rename over existing files)
renameFile :: Path Abs -> Path Abs -> IO ()
renameFile fromf tof = do
renameFile :: Path b1 -> Path b2 -> IO ()
renameFile fromf@(MkPath fromfBS) tof@(MkPath tofBS) = do
throwSameFile fromf tof
throwFileDoesExist tof
throwDirDoesExist tof
rename (fromAbs fromf) (fromAbs tof)
rename fromfBS tofBS
-- |Move a file. This also works across devices by copy-delete fallback.
@@ -835,9 +858,12 @@ renameFile fromf tof = do
--
-- - `AlreadyExists` if destination already exists
--
-- Note: calls `rename` (but does not allow to rename over existing files)
moveFile :: Path Abs -- ^ file to move
-> Path Abs -- ^ destination
-- Notes:
--
-- - calls `rename` (but does not allow to rename over existing files)
-- - calls `getcwd` in Overwrite mode if destination is a relative path
moveFile :: Path b1 -- ^ file to move
-> Path b2 -- ^ destination
-> CopyMode
-> IO ()
moveFile from to cm = do
@@ -848,7 +874,7 @@ moveFile from to cm = do
easyDelete from
Overwrite -> do
ft <- getFileType from
writable <- isWritable $ dirname to
writable <- toAbs to >>= isWritable
case ft of
RegularFile -> do
exists <- doesFileExist to
@@ -891,8 +917,8 @@ moveFile from to cm = do
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
readFile :: Path Abs -> IO ByteString
readFile p = withAbsPath p $ \fp ->
readFile :: Path b -> IO ByteString
readFile (MkPath fp) =
bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd -> do
stat <- PF.getFdStatus fd
let fsize = PF.fileSize stat
@@ -913,8 +939,8 @@ readFile p = withAbsPath p $ \fp ->
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
readFileEOF :: Path Abs -> IO L.ByteString
readFileEOF p = withAbsPath p $ \fp ->
readFileEOF :: Path b -> IO L.ByteString
readFileEOF (MkPath fp) =
bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd ->
allocaBytes (fromIntegral bufSize) $ \buf -> read' fd buf mempty
where
@@ -928,7 +954,7 @@ readFileEOF p = withAbsPath p $ \fp ->
else do
readBS <- unsafePackCStringFinalizer buf
(fromIntegral size)
mempty
(return ())
read' fd buf (builder <> byteString readBS)
@@ -948,8 +974,8 @@ readFileEOF p = withAbsPath p $ \fp ->
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
writeFile :: Path Abs -> ByteString -> IO ()
writeFile p bs = withAbsPath p $ \fp ->
writeFile :: Path b -> ByteString -> IO ()
writeFile (MkPath fp) bs =
bracket (openFd fp SPI.WriteOnly [SPDF.oTrunc] Nothing) (SPI.closeFd) $ \fd ->
void $ SPB.fdWrite fd bs
@@ -963,8 +989,8 @@ writeFile p bs = withAbsPath p $ \fp ->
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
appendFile :: Path Abs -> ByteString -> IO ()
appendFile p bs = withAbsPath p $ \fp ->
appendFile :: Path b -> ByteString -> IO ()
appendFile (MkPath fp) bs =
bracket (openFd fp SPI.WriteOnly [SPDF.oAppend] Nothing)
(SPI.closeFd) $ \fd -> void $ SPB.fdWrite fd bs
@@ -1015,15 +1041,14 @@ newDirPerms
-- - `InappropriateType` if file type is wrong (symlink to file)
-- - `InappropriateType` if file type is wrong (symlink to dir)
-- - `PermissionDenied` if directory cannot be opened
getDirsFiles :: Path Abs -- ^ dir to read
-> IO [Path Abs]
getDirsFiles p =
withAbsPath p $ \fp -> do
fd <- openFd fp SPI.ReadOnly [SPDF.oNofollow] Nothing
return
. catMaybes
. fmap (\x -> (</>) p <$> (parseMaybe . snd $ x))
=<< getDirectoryContents' fd
getDirsFiles :: Path b -- ^ dir to read
-> IO [Path b]
getDirsFiles p@(MkPath fp) = do
fd <- openFd fp SPI.ReadOnly [SPDF.oNofollow] Nothing
return
. catMaybes
. fmap (\x -> (</>) p <$> (parseMaybe . snd $ x))
=<< getDirectoryContents' fd
where
parseMaybe :: ByteString -> Maybe (Path Fn)
parseMaybe = parseFn
@@ -1043,9 +1068,9 @@ getDirsFiles p =
--
-- - `NoSuchThing` if the file does not exist
-- - `PermissionDenied` if any part of the path is not accessible
getFileType :: Path Abs -> IO FileType
getFileType p = do
fs <- PF.getSymbolicLinkStatus (fromAbs p)
getFileType :: Path b -> IO FileType
getFileType (MkPath fp) = do
fs <- PF.getSymbolicLinkStatus fp
decide fs
where
decide fs
@@ -1066,13 +1091,29 @@ getFileType p = do
-- |Applies `realpath` on the given absolute path.
-- |Applies `realpath` on the given path.
--
-- Throws:
--
-- - `NoSuchThing` if the file at the given path does not exist
-- - `NoSuchThing` if the symlink is broken
canonicalizePath :: Path Abs -> IO (Path Abs)
canonicalizePath :: Path b -> IO (Path Abs)
canonicalizePath (MkPath l) = do
nl <- SPDT.realpath l
return $ MkPath nl
-- |Converts any path to an absolute path.
-- This is done in the following way:
--
-- - if the path is already an absolute one, just return it
-- - if it's a relative path, prepend the current directory to it
toAbs :: Path b -> IO (Path Abs)
toAbs (MkPath bs) = do
let mabs = parseAbs bs :: Maybe (Path Abs)
case mabs of
Just a -> return a
Nothing -> do
cwd <- getWorkingDirectory >>= parseAbs
rel <- parseRel bs -- we know it must be relative now
return $ cwd </> rel

View File

@@ -3,5 +3,6 @@ module HPath.IO where
import HPath
canonicalizePath :: Path Abs -> IO (Path Abs)
canonicalizePath :: Path b -> IO (Path Abs)
toAbs :: Path b -> IO (Path Abs)

View File

@@ -84,9 +84,14 @@ import GHC.IO.Exception
IOErrorType
)
import HPath
import HPath.Internal
(
Path(..)
)
import {-# SOURCE #-} HPath.IO
(
canonicalizePath
, toAbs
)
import System.IO.Error
(
@@ -119,10 +124,10 @@ data HPathIOException = SameFile ByteString ByteString
--
-- The first argument to the data constructor is always the
-- source and the second the destination.
data RecursiveFailureHint = ReadContentsFailed (Path Abs) (Path Abs)
| CreateDirFailed (Path Abs) (Path Abs)
| CopyFileFailed (Path Abs) (Path Abs)
| RecreateSymlinkFailed (Path Abs) (Path Abs)
data RecursiveFailureHint = ReadContentsFailed ByteString ByteString
| CreateDirFailed ByteString ByteString
| CopyFileFailed ByteString ByteString
| RecreateSymlinkFailed ByteString ByteString
deriving (Eq, Show)
@@ -169,51 +174,50 @@ isRecreateSymlinkFailed _ = False
-- |Throws `AlreadyExists` `IOError` if file exists.
throwFileDoesExist :: Path Abs -> IO ()
throwFileDoesExist fp =
throwFileDoesExist :: Path b -> IO ()
throwFileDoesExist fp@(MkPath bs) =
whenM (doesFileExist fp)
(ioError . mkIOError
alreadyExistsErrorType
"File already exists"
Nothing
$ (Just (toString $ fromAbs fp))
$ (Just (toString $ bs))
)
-- |Throws `AlreadyExists` `IOError` if directory exists.
throwDirDoesExist :: Path Abs -> IO ()
throwDirDoesExist fp =
throwDirDoesExist :: Path b -> IO ()
throwDirDoesExist fp@(MkPath bs) =
whenM (doesDirectoryExist fp)
(ioError . mkIOError
alreadyExistsErrorType
"Directory already exists"
Nothing
$ (Just (toString $ fromAbs fp))
$ (Just (toString $ bs))
)
-- |Uses `isSameFile` and throws `SameFile` if it returns True.
throwSameFile :: Path Abs
-> Path Abs
throwSameFile :: Path b1
-> Path b2
-> IO ()
throwSameFile fp1 fp2 =
throwSameFile fp1@(MkPath bs1) fp2@(MkPath bs2) =
whenM (sameFile fp1 fp2)
(throwIO $ SameFile (fromAbs fp1) (fromAbs fp2))
(throwIO $ SameFile bs1 bs2)
-- |Check if the files are the same by examining device and file id.
-- This follows symbolic links.
sameFile :: Path Abs -> Path Abs -> IO Bool
sameFile fp1 fp2 =
withAbsPath fp1 $ \fp1' -> withAbsPath fp2 $ \fp2' ->
handleIOError (\_ -> return False) $ do
fs1 <- getFileStatus fp1'
fs2 <- getFileStatus fp2'
sameFile :: Path b1 -> Path b2 -> IO Bool
sameFile (MkPath fp1) (MkPath fp2) =
handleIOError (\_ -> return False) $ do
fs1 <- getFileStatus fp1
fs2 <- getFileStatus fp2
if ((PF.deviceID fs1, PF.fileID fs1) ==
(PF.deviceID fs2, PF.fileID fs2))
then return True
else return False
if ((PF.deviceID fs1, PF.fileID fs1) ==
(PF.deviceID fs2, PF.fileID fs2))
then return True
else return False
-- TODO: make this more robust when destination does not exist
@@ -221,54 +225,54 @@ sameFile fp1 fp2 =
-- within the source directory by comparing the device+file ID of the
-- source directory with all device+file IDs of the parent directories
-- of the destination.
throwDestinationInSource :: Path Abs -- ^ source dir
-> Path Abs -- ^ full destination, @dirname dest@
-- must exist
throwDestinationInSource :: Path b1 -- ^ source dir
-> Path b2 -- ^ full destination, @dirname dest@
-- must exist
-> IO ()
throwDestinationInSource source dest = do
throwDestinationInSource (MkPath sbs) dest@(MkPath dbs) = do
destAbs <- toAbs dest
dest' <- (\x -> maybe x (\y -> x </> y) $ basename dest)
<$> (canonicalizePath $ dirname dest)
<$> (canonicalizePath $ dirname destAbs)
dids <- forM (getAllParents dest') $ \p -> do
fs <- PF.getSymbolicLinkStatus (fromAbs p)
return (PF.deviceID fs, PF.fileID fs)
sid <- fmap (\x -> (PF.deviceID x, PF.fileID x))
$ PF.getFileStatus (fromAbs source)
$ PF.getFileStatus sbs
when (elem sid dids)
(throwIO $ DestinationInSource (fromAbs dest)
(fromAbs source))
(throwIO $ DestinationInSource dbs sbs)
-- |Checks if the given file exists and is not a directory.
-- Does not follow symlinks.
doesFileExist :: Path Abs -> IO Bool
doesFileExist fp =
doesFileExist :: Path b -> IO Bool
doesFileExist (MkPath bs) =
handleIOError (\_ -> return False) $ do
fs <- PF.getSymbolicLinkStatus (fromAbs fp)
fs <- PF.getSymbolicLinkStatus bs
return $ not . PF.isDirectory $ fs
-- |Checks if the given file exists and is a directory.
-- Does not follow symlinks.
doesDirectoryExist :: Path Abs -> IO Bool
doesDirectoryExist fp =
doesDirectoryExist :: Path b -> IO Bool
doesDirectoryExist (MkPath bs) =
handleIOError (\_ -> return False) $ do
fs <- PF.getSymbolicLinkStatus (fromAbs fp)
fs <- PF.getSymbolicLinkStatus bs
return $ PF.isDirectory fs
-- |Checks whether a file or folder is writable.
isWritable :: Path Abs -> IO Bool
isWritable fp =
isWritable :: Path b -> IO Bool
isWritable (MkPath bs) =
handleIOError (\_ -> return False) $
fileAccess (fromAbs fp) False True False
fileAccess bs False True False
-- |Checks whether the directory at the given path exists and can be
-- opened. This invokes `openDirStream` which follows symlinks.
canOpenDirectory :: Path Abs -> IO Bool
canOpenDirectory fp =
canOpenDirectory :: Path b -> IO Bool
canOpenDirectory (MkPath bs) =
handleIOError (\_ -> return False) $ do
bracket (PFD.openDirStream . fromAbs $ fp)
bracket (PFD.openDirStream bs)
PFD.closeDirStream
(\_ -> return ())
return True

7
stack.yaml Normal file
View File

@@ -0,0 +1,7 @@
resolver: lts-12.1
packages:
- '.'
extra-deps:
- IfElse-0.85

View File

@@ -13,7 +13,6 @@ import GHC.IO.Exception
(
IOErrorType(..)
)
import System.Process
import Utils

View File

@@ -13,7 +13,6 @@ import GHC.IO.Exception
(
IOErrorType(..)
)
import System.Process
import Utils

View File

@@ -13,7 +13,6 @@ import GHC.IO.Exception
(
IOErrorType(..)
)
import System.Process
import Utils

View File

@@ -0,0 +1,27 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.ToAbsSpec where
import Test.Hspec
import HPath
import HPath.IO
spec :: Spec
spec = describe "HPath.IO.toAbs" $ do
-- successes --
it "toAbs returns absolute paths unchanged" $ do
p1 <- parseAbs "/a/b/c/d"
to <- toAbs p1
p1 `shouldBe` to
it "toAbs returns even existing absolute paths unchanged" $ do
p1 <- parseAbs "/home"
to <- toAbs p1
p1 `shouldBe` to

View File

@@ -13,7 +13,6 @@ import GHC.IO.Exception
(
IOErrorType(..)
)
import System.Process
import Utils

View File

@@ -61,9 +61,6 @@ import System.Posix.Files.ByteString
, unionFileModes
)
import qualified "unix" System.Posix.IO.ByteString as SPI
import qualified "unix-bytestring" System.Posix.IO.ByteString as SPB
baseTmpDir :: ByteString