Compare commits
1 Commits
6bc5381108
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a19f54a34 |
@@ -7,18 +7,12 @@ 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]}}
|
||||
|
||||
|
||||
11
CHANGELOG
11
CHANGELOG
@@ -1,14 +1,3 @@
|
||||
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
|
||||
* 'copyDirRecursiveOverwrite', 'copyFileOverwrite', 'easyCopyOverwrite' and 'moveFileOverwrite' have been removed, instead use the versions without the *Overwrite suffix and pass in 'Strict' (for default behavior) or 'Overwrite' as the CopyMode argument
|
||||
* introduced a new 'RecursiveErrorMode' type to allow controlling recursive behavior of 'copyDirRecursive' (use 'FailEarly' for default behavior)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# HPath
|
||||
|
||||
[](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hackage.haskell.org/package/hpath) [](http://travis-ci.org/hasufell/hpath) [](http://packdeps.haskellers.com/feed?needle=hpath)
|
||||
[](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hackage.haskell.org/package/hpath) [](http://travis-ci.org/hasufell/hpath)
|
||||
|
||||
Support for well-typed paths in Haskell. Also provides ByteString based filepath
|
||||
manipulation.
|
||||
|
||||
31
hpath.cabal
31
hpath.cabal
@@ -1,5 +1,5 @@
|
||||
name: hpath
|
||||
version: 0.9.2
|
||||
version: 0.8.0
|
||||
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,9 +17,6 @@ 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)
|
||||
@@ -35,9 +32,9 @@ library
|
||||
System.Posix.FD,
|
||||
System.Posix.FilePath
|
||||
other-modules: HPath.Internal
|
||||
build-depends: base >= 4.6 && <5
|
||||
build-depends: base >= 4.2 && <5
|
||||
, IfElse
|
||||
, bytestring >= 0.10.0.0
|
||||
, bytestring >= 0.9.2.0
|
||||
, deepseq
|
||||
, exceptions
|
||||
, hspec
|
||||
@@ -49,9 +46,6 @@ 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
|
||||
@@ -63,15 +57,12 @@ 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 >= 0.10.0.0,
|
||||
bytestring,
|
||||
unix,
|
||||
hpath,
|
||||
doctest >= 0.8,
|
||||
@@ -79,23 +70,19 @@ 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.CreateDirRecursiveSpec
|
||||
HPath.IO.CreateDirSpec
|
||||
HPath.IO.CreateDirRecursiveSpec
|
||||
HPath.IO.CreateRegularFileSpec
|
||||
HPath.IO.CreateSymlinkSpec
|
||||
HPath.IO.DeleteDirRecursiveSpec
|
||||
@@ -105,20 +92,16 @@ 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 >= 0.10.0.0
|
||||
, bytestring
|
||||
, hpath
|
||||
, hspec >= 1.3
|
||||
, process
|
||||
|
||||
10
src/HPath.hs
10
src/HPath.hs
@@ -13,9 +13,7 @@
|
||||
{-# LANGUAGE CPP #-}
|
||||
{-# LANGUAGE DeriveDataTypeable #-}
|
||||
{-# LANGUAGE EmptyDataDecls #-}
|
||||
#if __GLASGOW_HASKELL__ >= 708
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
#endif
|
||||
|
||||
module HPath
|
||||
(
|
||||
@@ -27,10 +25,8 @@ module HPath
|
||||
,PathParseException
|
||||
,PathException
|
||||
,RelC
|
||||
#if __GLASGOW_HASKELL__ >= 708
|
||||
-- * PatternSynonyms/ViewPatterns
|
||||
,pattern Path
|
||||
#endif
|
||||
-- * Path Parsing
|
||||
,parseAbs
|
||||
,parseFn
|
||||
@@ -105,9 +101,7 @@ 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
|
||||
@@ -320,6 +314,10 @@ getAllParents (MkPath p)
|
||||
|
||||
-- | Extract the directory name of a path.
|
||||
--
|
||||
-- The following properties hold:
|
||||
--
|
||||
-- @dirname (p \<\/> a) == dirname p@
|
||||
--
|
||||
-- >>> dirname (MkPath "/abc/def/dod")
|
||||
-- "/abc/def"
|
||||
-- >>> dirname (MkPath "/")
|
||||
|
||||
346
src/HPath/IO.hs
346
src/HPath/IO.hs
@@ -8,11 +8,8 @@
|
||||
-- Portability : portable
|
||||
--
|
||||
-- This module provides high-level IO related file operations like
|
||||
-- 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.
|
||||
-- copy, delete, move and so on. It only operates on /Path Abs/ which
|
||||
-- guarantees us well-typed paths which are absolute.
|
||||
--
|
||||
-- Some functions are just path-safe wrappers around
|
||||
-- unix functions, others have stricter exception handling
|
||||
@@ -33,7 +30,6 @@
|
||||
-- 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 #-}
|
||||
|
||||
@@ -64,12 +60,6 @@ module HPath.IO
|
||||
-- * File renaming/moving
|
||||
, renameFile
|
||||
, moveFile
|
||||
-- * File reading
|
||||
, readFile
|
||||
, readFileEOF
|
||||
-- * File writing
|
||||
, writeFile
|
||||
, appendFile
|
||||
-- * File permissions
|
||||
, newFilePerms
|
||||
, newDirPerms
|
||||
@@ -79,7 +69,6 @@ module HPath.IO
|
||||
, getFileType
|
||||
-- * Others
|
||||
, canonicalizePath
|
||||
, toAbs
|
||||
)
|
||||
where
|
||||
|
||||
@@ -108,21 +97,6 @@ import Data.ByteString
|
||||
(
|
||||
ByteString
|
||||
)
|
||||
#if MIN_VERSION_bytestring(0,10,2)
|
||||
import Data.ByteString.Builder
|
||||
#else
|
||||
import Data.ByteString.Lazy.Builder
|
||||
#endif
|
||||
(
|
||||
Builder
|
||||
, byteString
|
||||
, toLazyByteString
|
||||
)
|
||||
import qualified Data.ByteString.Lazy as L
|
||||
import Data.ByteString.Unsafe
|
||||
(
|
||||
unsafePackCStringFinalizer
|
||||
)
|
||||
import Data.Foldable
|
||||
(
|
||||
for_
|
||||
@@ -138,11 +112,6 @@ import Data.Maybe
|
||||
(
|
||||
catMaybes
|
||||
)
|
||||
import Data.Monoid
|
||||
(
|
||||
(<>)
|
||||
, mempty
|
||||
)
|
||||
import Data.Word
|
||||
(
|
||||
Word8
|
||||
@@ -176,7 +145,7 @@ import GHC.IO.Exception
|
||||
import HPath
|
||||
import HPath.Internal
|
||||
import HPath.IO.Errors
|
||||
import Prelude hiding (appendFile, readFile, writeFile)
|
||||
import Prelude hiding (readFile)
|
||||
import System.IO.Error
|
||||
(
|
||||
catchIOError
|
||||
@@ -194,7 +163,6 @@ import System.Posix.ByteString
|
||||
import System.Posix.Directory.ByteString
|
||||
(
|
||||
createDirectory
|
||||
, getWorkingDirectory
|
||||
, removeDirectory
|
||||
)
|
||||
import System.Posix.Directory.Traversals
|
||||
@@ -337,11 +305,9 @@ data CopyMode = Strict -- ^ fail if any target exists
|
||||
-- Throws in `Strict` CopyMode only:
|
||||
--
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
--
|
||||
-- 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)
|
||||
copyDirRecursive :: Path Abs -- ^ source dir
|
||||
-> Path Abs -- ^ destination (parent dirs
|
||||
-- are not automatically created)
|
||||
-> CopyMode
|
||||
-> RecursiveErrorMode
|
||||
-> IO ()
|
||||
@@ -357,27 +323,27 @@ copyDirRecursive fromp destdirp cm rm
|
||||
(throwIO . RecursiveFailure $ collectedExceptions)
|
||||
where
|
||||
go :: IORef [(RecursiveFailureHint, IOException)]
|
||||
-> Path b1 -> Path b2 -> IO ()
|
||||
go ce fromp'@(MkPath fromBS) destdirp'@(MkPath destdirpBS) = do
|
||||
-> Path Abs -> Path Abs -> IO ()
|
||||
go ce fromp' destdirp' = 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 fromBS destdirpBS) ce [] $ do
|
||||
contents <- handleIOE (ReadContentsFailed fromp' destdirp') ce [] $ do
|
||||
contents <- getDirsFiles fromp'
|
||||
|
||||
-- create the destination dir and
|
||||
-- only return contents if we succeed
|
||||
handleIOE (CreateDirFailed fromBS destdirpBS) ce [] $ do
|
||||
fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus fromBS
|
||||
handleIOE (CreateDirFailed fromp' destdirp') ce [] $ do
|
||||
fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus (fromAbs fromp')
|
||||
case cm of
|
||||
Strict -> createDirectory destdirpBS fmode'
|
||||
Overwrite -> catchIOError (createDirectory destdirpBS
|
||||
Strict -> createDirectory (fromAbs destdirp') fmode'
|
||||
Overwrite -> catchIOError (createDirectory (fromAbs destdirp')
|
||||
fmode')
|
||||
$ \e ->
|
||||
case ioeGetErrorType e of
|
||||
AlreadyExists -> setFileMode destdirpBS
|
||||
AlreadyExists -> setFileMode (fromAbs destdirp')
|
||||
fmode'
|
||||
_ -> ioError e
|
||||
return contents
|
||||
@@ -391,10 +357,10 @@ copyDirRecursive fromp destdirp cm rm
|
||||
ftype <- getFileType f
|
||||
newdest <- (destdirp' </>) <$> basename f
|
||||
case ftype of
|
||||
SymbolicLink -> handleIOE (RecreateSymlinkFailed (toFilePath f) (toFilePath newdest)) ce ()
|
||||
SymbolicLink -> handleIOE (RecreateSymlinkFailed f newdest) ce ()
|
||||
$ recreateSymlink f newdest cm
|
||||
Directory -> go ce f newdest
|
||||
RegularFile -> handleIOE (CopyFileFailed (toFilePath f) (toFilePath newdest)) ce ()
|
||||
RegularFile -> handleIOE (CopyFileFailed f newdest) ce ()
|
||||
$ copyFile f newdest cm
|
||||
_ -> return ()
|
||||
|
||||
@@ -434,27 +400,24 @@ copyDirRecursive fromp destdirp cm rm
|
||||
--
|
||||
-- - `UnsatisfiedConstraints` if destination file is non-empty directory
|
||||
--
|
||||
-- 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
|
||||
-- Note: calls `symlink`
|
||||
recreateSymlink :: Path Abs -- ^ the old symlink file
|
||||
-> Path Abs -- ^ destination file
|
||||
-> CopyMode
|
||||
-> IO ()
|
||||
recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm
|
||||
recreateSymlink symsource newsym cm
|
||||
= do
|
||||
throwSameFile symsource newsym
|
||||
sympoint <- readSymbolicLink symsourceBS
|
||||
sympoint <- readSymbolicLink (fromAbs symsource)
|
||||
case cm of
|
||||
Strict -> return ()
|
||||
Overwrite -> do
|
||||
writable <- toAbs newsym >>= isWritable
|
||||
writable <- isWritable (dirname newsym)
|
||||
isfile <- doesFileExist newsym
|
||||
isdir <- doesDirectoryExist newsym
|
||||
when (writable && isfile) (deleteFile newsym)
|
||||
when (writable && isdir) (deleteDir newsym)
|
||||
createSymbolicLink sympoint newsymBS
|
||||
createSymbolicLink sympoint (fromAbs newsym)
|
||||
|
||||
|
||||
-- |Copies the given regular file to the given destination.
|
||||
@@ -491,12 +454,9 @@ recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm
|
||||
--
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
--
|
||||
-- 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
|
||||
-- Note: calls `sendfile` and possibly `read`/`write` as fallback
|
||||
copyFile :: Path Abs -- ^ source file
|
||||
-> Path Abs -- ^ destination file
|
||||
-> CopyMode
|
||||
-> IO ()
|
||||
copyFile from to cm = do
|
||||
@@ -515,8 +475,8 @@ copyFile from to cm = do
|
||||
-- figure out if we can still copy by deleting it first
|
||||
PermissionDenied -> do
|
||||
exists <- doesFileExist to
|
||||
writable <- toAbs to >>= isWritable
|
||||
if (exists && writable)
|
||||
writable <- isWritable (dirname to)
|
||||
if exists && writable
|
||||
then deleteFile to >> copyFile from to Strict
|
||||
else ioError e
|
||||
_ -> ioError e
|
||||
@@ -524,17 +484,18 @@ copyFile from to cm = do
|
||||
|
||||
_copyFile :: [SPDF.Flags]
|
||||
-> [SPDF.Flags]
|
||||
-> Path b1 -- ^ source file
|
||||
-> Path b2 -- ^ destination file
|
||||
-> Path Abs -- ^ source file
|
||||
-> Path Abs -- ^ destination file
|
||||
-> IO ()
|
||||
_copyFile sflags dflags (MkPath fromBS) to@(MkPath toBS)
|
||||
_copyFile sflags dflags from to
|
||||
=
|
||||
-- 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.
|
||||
catchErrno [eINVAL, eNOSYS]
|
||||
(sendFileCopy fromBS toBS)
|
||||
(void $ readWriteCopy fromBS toBS)
|
||||
withAbsPath to $ \to' -> withAbsPath from $ \from' ->
|
||||
catchErrno [eINVAL, eNOSYS]
|
||||
(sendFileCopy from' to')
|
||||
(void $ readWriteCopy from' to')
|
||||
where
|
||||
copyWith copyAction source dest =
|
||||
bracket (openFd source SPI.ReadOnly sflags Nothing)
|
||||
@@ -579,10 +540,8 @@ _copyFile sflags dflags (MkPath fromBS) to@(MkPath toBS)
|
||||
--
|
||||
-- * examines filetypes explicitly
|
||||
-- * calls `copyDirRecursive` for directories
|
||||
--
|
||||
-- Note: may call `getcwd` in Overwrite mode (if destination is a relative path)
|
||||
easyCopy :: Path b1
|
||||
-> Path b2
|
||||
easyCopy :: Path Abs
|
||||
-> Path Abs
|
||||
-> CopyMode
|
||||
-> RecursiveErrorMode
|
||||
-> IO ()
|
||||
@@ -611,8 +570,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 b -> IO ()
|
||||
deleteFile (MkPath p) = removeLink p
|
||||
deleteFile :: Path Abs -> IO ()
|
||||
deleteFile p = withAbsPath p removeLink
|
||||
|
||||
|
||||
-- |Deletes the given directory, which must be empty, never symlinks.
|
||||
@@ -626,8 +585,8 @@ deleteFile (MkPath p) = removeLink p
|
||||
-- - `PermissionDenied` if we can't open or write to parent directory
|
||||
--
|
||||
-- Notes: calls `rmdir`
|
||||
deleteDir :: Path b -> IO ()
|
||||
deleteDir (MkPath p) = removeDirectory p
|
||||
deleteDir :: Path Abs -> IO ()
|
||||
deleteDir p = withAbsPath p removeDirectory
|
||||
|
||||
|
||||
-- |Deletes the given directory recursively. Does not follow symbolic
|
||||
@@ -649,7 +608,7 @@ deleteDir (MkPath p) = removeDirectory p
|
||||
-- - `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 b -> IO ()
|
||||
deleteDirRecursive :: Path Abs -> IO ()
|
||||
deleteDirRecursive p =
|
||||
catchErrno [eNOTEMPTY, eEXIST]
|
||||
(deleteDir p)
|
||||
@@ -674,7 +633,7 @@ deleteDirRecursive p =
|
||||
--
|
||||
-- * examines filetypes explicitly
|
||||
-- * calls `deleteDirRecursive` for directories
|
||||
easyDelete :: Path b -> IO ()
|
||||
easyDelete :: Path Abs -> IO ()
|
||||
easyDelete p = do
|
||||
ftype <- getFileType p
|
||||
case ftype of
|
||||
@@ -693,18 +652,21 @@ easyDelete p = do
|
||||
|
||||
-- |Opens a file appropriately by invoking xdg-open. The file type
|
||||
-- is not checked. This forks a process.
|
||||
openFile :: Path b
|
||||
openFile :: Path Abs
|
||||
-> IO ProcessID
|
||||
openFile (MkPath fp) =
|
||||
SPP.forkProcess $ SPP.executeFile "xdg-open" True [fp] Nothing
|
||||
openFile p =
|
||||
withAbsPath p $ \fp ->
|
||||
SPP.forkProcess $ SPP.executeFile "xdg-open" True [fp] Nothing
|
||||
|
||||
|
||||
-- |Executes a program with the given arguments. This forks a process.
|
||||
executeFile :: Path b -- ^ program
|
||||
executeFile :: Path Abs -- ^ program
|
||||
-> [ByteString] -- ^ arguments
|
||||
-> IO ProcessID
|
||||
executeFile (MkPath fp) args =
|
||||
SPP.forkProcess $ SPP.executeFile fp True args Nothing
|
||||
executeFile fp args
|
||||
= withAbsPath fp $ \fpb ->
|
||||
SPP.forkProcess
|
||||
$ SPP.executeFile fpb True args Nothing
|
||||
|
||||
|
||||
|
||||
@@ -723,9 +685,9 @@ executeFile (MkPath fp) args =
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
-- - `NoSuchThing` if any of the parent components of the path
|
||||
-- do not exist
|
||||
createRegularFile :: FileMode -> Path b -> IO ()
|
||||
createRegularFile fm (MkPath destBS) =
|
||||
bracket (SPI.openFd destBS SPI.WriteOnly (Just fm)
|
||||
createRegularFile :: FileMode -> Path Abs -> IO ()
|
||||
createRegularFile fm dest =
|
||||
bracket (SPI.openFd (fromAbs dest) SPI.WriteOnly (Just fm)
|
||||
(SPI.defaultFileFlags { exclusive = True }))
|
||||
SPI.closeFd
|
||||
(\_ -> return ())
|
||||
@@ -739,8 +701,8 @@ createRegularFile fm (MkPath destBS) =
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
-- - `NoSuchThing` if any of the parent components of the path
|
||||
-- do not exist
|
||||
createDir :: FileMode -> Path b -> IO ()
|
||||
createDir fm (MkPath destBS) = createDirectory destBS fm
|
||||
createDir :: FileMode -> Path Abs -> IO ()
|
||||
createDir fm dest = createDirectory (fromAbs dest) fm
|
||||
|
||||
|
||||
-- |Create an empty directory at the given directory with the given filename.
|
||||
@@ -761,21 +723,15 @@ createDir fm (MkPath destBS) = createDirectory destBS fm
|
||||
-- exist and cannot be written to
|
||||
-- - `AlreadyExists` if destination already exists and
|
||||
-- is not a directory
|
||||
--
|
||||
-- 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
|
||||
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
|
||||
|
||||
|
||||
-- |Create a symlink.
|
||||
@@ -788,11 +744,11 @@ createDirRecursive fm p =
|
||||
-- do not exist
|
||||
--
|
||||
-- Note: calls `symlink`
|
||||
createSymlink :: Path b -- ^ destination file
|
||||
createSymlink :: Path Abs -- ^ destination file
|
||||
-> ByteString -- ^ path the symlink points to
|
||||
-> IO ()
|
||||
createSymlink (MkPath destBS) sympoint
|
||||
= createSymbolicLink sympoint destBS
|
||||
createSymlink dest sympoint
|
||||
= createSymbolicLink sympoint (fromAbs dest)
|
||||
|
||||
|
||||
|
||||
@@ -822,12 +778,12 @@ createSymlink (MkPath destBS) sympoint
|
||||
-- (`HPathIOException`)
|
||||
--
|
||||
-- Note: calls `rename` (but does not allow to rename over existing files)
|
||||
renameFile :: Path b1 -> Path b2 -> IO ()
|
||||
renameFile fromf@(MkPath fromfBS) tof@(MkPath tofBS) = do
|
||||
renameFile :: Path Abs -> Path Abs -> IO ()
|
||||
renameFile fromf tof = do
|
||||
throwSameFile fromf tof
|
||||
throwFileDoesExist tof
|
||||
throwDirDoesExist tof
|
||||
rename fromfBS tofBS
|
||||
rename (fromAbs fromf) (fromAbs tof)
|
||||
|
||||
|
||||
-- |Move a file. This also works across devices by copy-delete fallback.
|
||||
@@ -858,12 +814,9 @@ renameFile fromf@(MkPath fromfBS) tof@(MkPath tofBS) = do
|
||||
--
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
--
|
||||
-- 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
|
||||
-- Note: calls `rename` (but does not allow to rename over existing files)
|
||||
moveFile :: Path Abs -- ^ file to move
|
||||
-> Path Abs -- ^ destination
|
||||
-> CopyMode
|
||||
-> IO ()
|
||||
moveFile from to cm = do
|
||||
@@ -874,7 +827,7 @@ moveFile from to cm = do
|
||||
easyDelete from
|
||||
Overwrite -> do
|
||||
ft <- getFileType from
|
||||
writable <- toAbs to >>= isWritable
|
||||
writable <- isWritable $ dirname to
|
||||
case ft of
|
||||
RegularFile -> do
|
||||
exists <- doesFileExist to
|
||||
@@ -892,110 +845,6 @@ moveFile from to cm = do
|
||||
|
||||
|
||||
|
||||
--------------------
|
||||
--[ File Reading ]--
|
||||
--------------------
|
||||
|
||||
|
||||
-- |Read the given file at once into memory as a strict ByteString.
|
||||
-- Symbolic links are followed, no sanity checks on file size
|
||||
-- or file type. File must exist.
|
||||
--
|
||||
-- Note: the size of the file is determined in advance, as to only
|
||||
-- have one allocation.
|
||||
--
|
||||
-- Safety/reliability concerns:
|
||||
--
|
||||
-- * since amount of bytes to read is determined in advance,
|
||||
-- the file might be read partially only if something else is
|
||||
-- appending to it while reading
|
||||
-- * the whole file is read into memory!
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `InappropriateType` if file is not a regular file or a symlink
|
||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||
-- containting it
|
||||
-- - `NoSuchThing` if the file does not exist
|
||||
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
|
||||
SPB.fdRead fd (fromIntegral fsize)
|
||||
|
||||
|
||||
-- |Read the given file in chunks of size `8192` into memory until
|
||||
-- `fread` returns 0. Returns a lazy ByteString, because it uses
|
||||
-- Builders under the hood.
|
||||
--
|
||||
-- Safety/reliability concerns:
|
||||
--
|
||||
-- * the whole file is read into memory!
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `InappropriateType` if file is not a regular file or a symlink
|
||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||
-- containting it
|
||||
-- - `NoSuchThing` if the file does not exist
|
||||
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
|
||||
bufSize :: CSize
|
||||
bufSize = 8192
|
||||
read' :: Fd -> Ptr Word8 -> Builder -> IO L.ByteString
|
||||
read' fd buf builder = do
|
||||
size <- SPB.fdReadBuf fd buf bufSize
|
||||
if size == 0
|
||||
then return $ toLazyByteString builder
|
||||
else do
|
||||
readBS <- unsafePackCStringFinalizer buf
|
||||
(fromIntegral size)
|
||||
(return ())
|
||||
read' fd buf (builder <> byteString readBS)
|
||||
|
||||
|
||||
|
||||
|
||||
--------------------
|
||||
--[ File Writing ]--
|
||||
--------------------
|
||||
|
||||
|
||||
-- |Write a given ByteString to a file, truncating the file beforehand.
|
||||
-- The file must exist. Follows symlinks.
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `InappropriateType` if file is not a regular file or a symlink
|
||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||
-- containting it
|
||||
-- - `NoSuchThing` if the file does not exist
|
||||
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
|
||||
|
||||
|
||||
-- |Append a given ByteString to a file.
|
||||
-- The file must exist. Follows symlinks.
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `InappropriateType` if file is not a regular file or a symlink
|
||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||
-- containting it
|
||||
-- - `NoSuchThing` if the file does not exist
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
--[ File Permissions]--
|
||||
@@ -1041,14 +890,15 @@ 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 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
|
||||
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
|
||||
where
|
||||
parseMaybe :: ByteString -> Maybe (Path Fn)
|
||||
parseMaybe = parseFn
|
||||
@@ -1068,9 +918,9 @@ getDirsFiles p@(MkPath fp) = do
|
||||
--
|
||||
-- - `NoSuchThing` if the file does not exist
|
||||
-- - `PermissionDenied` if any part of the path is not accessible
|
||||
getFileType :: Path b -> IO FileType
|
||||
getFileType (MkPath fp) = do
|
||||
fs <- PF.getSymbolicLinkStatus fp
|
||||
getFileType :: Path Abs -> IO FileType
|
||||
getFileType p = do
|
||||
fs <- PF.getSymbolicLinkStatus (fromAbs p)
|
||||
decide fs
|
||||
where
|
||||
decide fs
|
||||
@@ -1091,29 +941,13 @@ getFileType (MkPath fp) = do
|
||||
|
||||
|
||||
|
||||
-- |Applies `realpath` on the given path.
|
||||
-- |Applies `realpath` on the given absolute path.
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `NoSuchThing` if the file at the given path does not exist
|
||||
-- - `NoSuchThing` if the symlink is broken
|
||||
canonicalizePath :: Path b -> IO (Path Abs)
|
||||
canonicalizePath :: Path Abs -> 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
|
||||
|
||||
@@ -3,6 +3,5 @@ module HPath.IO where
|
||||
|
||||
import HPath
|
||||
|
||||
canonicalizePath :: Path b -> IO (Path Abs)
|
||||
canonicalizePath :: Path Abs -> IO (Path Abs)
|
||||
|
||||
toAbs :: Path b -> IO (Path Abs)
|
||||
|
||||
@@ -84,14 +84,9 @@ import GHC.IO.Exception
|
||||
IOErrorType
|
||||
)
|
||||
import HPath
|
||||
import HPath.Internal
|
||||
(
|
||||
Path(..)
|
||||
)
|
||||
import {-# SOURCE #-} HPath.IO
|
||||
(
|
||||
canonicalizePath
|
||||
, toAbs
|
||||
)
|
||||
import System.IO.Error
|
||||
(
|
||||
@@ -124,10 +119,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 ByteString ByteString
|
||||
| CreateDirFailed ByteString ByteString
|
||||
| CopyFileFailed ByteString ByteString
|
||||
| RecreateSymlinkFailed ByteString ByteString
|
||||
data RecursiveFailureHint = ReadContentsFailed (Path Abs) (Path Abs)
|
||||
| CreateDirFailed (Path Abs) (Path Abs)
|
||||
| CopyFileFailed (Path Abs) (Path Abs)
|
||||
| RecreateSymlinkFailed (Path Abs) (Path Abs)
|
||||
deriving (Eq, Show)
|
||||
|
||||
|
||||
@@ -174,50 +169,51 @@ isRecreateSymlinkFailed _ = False
|
||||
|
||||
|
||||
-- |Throws `AlreadyExists` `IOError` if file exists.
|
||||
throwFileDoesExist :: Path b -> IO ()
|
||||
throwFileDoesExist fp@(MkPath bs) =
|
||||
throwFileDoesExist :: Path Abs -> IO ()
|
||||
throwFileDoesExist fp =
|
||||
whenM (doesFileExist fp)
|
||||
(ioError . mkIOError
|
||||
alreadyExistsErrorType
|
||||
"File already exists"
|
||||
Nothing
|
||||
$ (Just (toString $ bs))
|
||||
$ (Just (toString $ fromAbs fp))
|
||||
)
|
||||
|
||||
|
||||
-- |Throws `AlreadyExists` `IOError` if directory exists.
|
||||
throwDirDoesExist :: Path b -> IO ()
|
||||
throwDirDoesExist fp@(MkPath bs) =
|
||||
throwDirDoesExist :: Path Abs -> IO ()
|
||||
throwDirDoesExist fp =
|
||||
whenM (doesDirectoryExist fp)
|
||||
(ioError . mkIOError
|
||||
alreadyExistsErrorType
|
||||
"Directory already exists"
|
||||
Nothing
|
||||
$ (Just (toString $ bs))
|
||||
$ (Just (toString $ fromAbs fp))
|
||||
)
|
||||
|
||||
|
||||
-- |Uses `isSameFile` and throws `SameFile` if it returns True.
|
||||
throwSameFile :: Path b1
|
||||
-> Path b2
|
||||
throwSameFile :: Path Abs
|
||||
-> Path Abs
|
||||
-> IO ()
|
||||
throwSameFile fp1@(MkPath bs1) fp2@(MkPath bs2) =
|
||||
throwSameFile fp1 fp2 =
|
||||
whenM (sameFile fp1 fp2)
|
||||
(throwIO $ SameFile bs1 bs2)
|
||||
(throwIO $ SameFile (fromAbs fp1) (fromAbs fp2))
|
||||
|
||||
|
||||
-- |Check if the files are the same by examining device and file id.
|
||||
-- This follows symbolic links.
|
||||
sameFile :: Path b1 -> Path b2 -> IO Bool
|
||||
sameFile (MkPath fp1) (MkPath fp2) =
|
||||
handleIOError (\_ -> return False) $ do
|
||||
fs1 <- getFileStatus fp1
|
||||
fs2 <- getFileStatus fp2
|
||||
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'
|
||||
|
||||
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
|
||||
@@ -225,54 +221,54 @@ sameFile (MkPath fp1) (MkPath 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 b1 -- ^ source dir
|
||||
-> Path b2 -- ^ full destination, @dirname dest@
|
||||
-- must exist
|
||||
throwDestinationInSource :: Path Abs -- ^ source dir
|
||||
-> Path Abs -- ^ full destination, @dirname dest@
|
||||
-- must exist
|
||||
-> IO ()
|
||||
throwDestinationInSource (MkPath sbs) dest@(MkPath dbs) = do
|
||||
destAbs <- toAbs dest
|
||||
throwDestinationInSource source dest = do
|
||||
dest' <- (\x -> maybe x (\y -> x </> y) $ basename dest)
|
||||
<$> (canonicalizePath $ dirname destAbs)
|
||||
<$> (canonicalizePath $ dirname dest)
|
||||
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 sbs
|
||||
$ PF.getFileStatus (fromAbs source)
|
||||
when (elem sid dids)
|
||||
(throwIO $ DestinationInSource dbs sbs)
|
||||
(throwIO $ DestinationInSource (fromAbs dest)
|
||||
(fromAbs source))
|
||||
|
||||
|
||||
-- |Checks if the given file exists and is not a directory.
|
||||
-- Does not follow symlinks.
|
||||
doesFileExist :: Path b -> IO Bool
|
||||
doesFileExist (MkPath bs) =
|
||||
doesFileExist :: Path Abs -> IO Bool
|
||||
doesFileExist fp =
|
||||
handleIOError (\_ -> return False) $ do
|
||||
fs <- PF.getSymbolicLinkStatus bs
|
||||
fs <- PF.getSymbolicLinkStatus (fromAbs fp)
|
||||
return $ not . PF.isDirectory $ fs
|
||||
|
||||
|
||||
-- |Checks if the given file exists and is a directory.
|
||||
-- Does not follow symlinks.
|
||||
doesDirectoryExist :: Path b -> IO Bool
|
||||
doesDirectoryExist (MkPath bs) =
|
||||
doesDirectoryExist :: Path Abs -> IO Bool
|
||||
doesDirectoryExist fp =
|
||||
handleIOError (\_ -> return False) $ do
|
||||
fs <- PF.getSymbolicLinkStatus bs
|
||||
fs <- PF.getSymbolicLinkStatus (fromAbs fp)
|
||||
return $ PF.isDirectory fs
|
||||
|
||||
|
||||
-- |Checks whether a file or folder is writable.
|
||||
isWritable :: Path b -> IO Bool
|
||||
isWritable (MkPath bs) =
|
||||
isWritable :: Path Abs -> IO Bool
|
||||
isWritable fp =
|
||||
handleIOError (\_ -> return False) $
|
||||
fileAccess bs False True False
|
||||
fileAccess (fromAbs fp) False True False
|
||||
|
||||
|
||||
-- |Checks whether the directory at the given path exists and can be
|
||||
-- opened. This invokes `openDirStream` which follows symlinks.
|
||||
canOpenDirectory :: Path b -> IO Bool
|
||||
canOpenDirectory (MkPath bs) =
|
||||
canOpenDirectory :: Path Abs -> IO Bool
|
||||
canOpenDirectory fp =
|
||||
handleIOError (\_ -> return False) $ do
|
||||
bracket (PFD.openDirStream bs)
|
||||
bracket (PFD.openDirStream . fromAbs $ fp)
|
||||
PFD.closeDirStream
|
||||
(\_ -> return ())
|
||||
return True
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
module HPath.IO.AppendFileSpec where
|
||||
|
||||
|
||||
import Test.Hspec
|
||||
import System.IO.Error
|
||||
(
|
||||
ioeGetErrorType
|
||||
)
|
||||
import GHC.IO.Exception
|
||||
(
|
||||
IOErrorType(..)
|
||||
)
|
||||
import System.Process
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
upTmpDir :: IO ()
|
||||
upTmpDir = do
|
||||
setTmpDir "AppendFileSpec"
|
||||
createTmpDir
|
||||
|
||||
setupFiles :: IO ()
|
||||
setupFiles = do
|
||||
createRegularFile' "fileWithContent"
|
||||
createRegularFile' "fileWithoutContent"
|
||||
createSymlink' "inputFileSymL" "fileWithContent"
|
||||
createDir' "alreadyExistsD"
|
||||
createRegularFile' "noPerms"
|
||||
noPerms "noPerms"
|
||||
createDir' "noPermsD"
|
||||
createRegularFile' "noPermsD/inputFile"
|
||||
noPerms "noPermsD"
|
||||
writeFile' "fileWithContent" "BLKASL"
|
||||
|
||||
|
||||
cleanupFiles :: IO ()
|
||||
cleanupFiles = do
|
||||
deleteFile' "fileWithContent"
|
||||
deleteFile' "fileWithoutContent"
|
||||
deleteFile' "inputFileSymL"
|
||||
deleteDir' "alreadyExistsD"
|
||||
normalFilePerms "noPerms"
|
||||
deleteFile' "noPerms"
|
||||
normalDirPerms "noPermsD"
|
||||
deleteFile' "noPermsD/inputFile"
|
||||
deleteDir' "noPermsD"
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
describe "HPath.IO.appendFile" $ do
|
||||
|
||||
-- successes --
|
||||
it "appendFile file with content, everything clear" $ do
|
||||
appendFile' "fileWithContent" "blahfaselllll"
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` "BLKASLblahfaselllll"
|
||||
|
||||
it "appendFile file with content, everything clear" $ do
|
||||
appendFile' "fileWithContent" "gagagaga"
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` "BLKASLblahfaselllllgagagaga"
|
||||
|
||||
it "appendFile file with content, everything clear" $ do
|
||||
appendFile' "fileWithContent" ""
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` "BLKASLblahfaselllllgagagaga"
|
||||
|
||||
it "appendFile file without content, everything clear" $ do
|
||||
appendFile' "fileWithoutContent" "blahfaselllll"
|
||||
out <- readFile' "fileWithoutContent"
|
||||
out `shouldBe` "blahfaselllll"
|
||||
|
||||
it "appendFile, everything clear" $ do
|
||||
appendFile' "fileWithoutContent" "gagagaga"
|
||||
out <- readFile' "fileWithoutContent"
|
||||
out `shouldBe` "blahfaselllllgagagaga"
|
||||
|
||||
it "appendFile symlink, everything clear" $ do
|
||||
appendFile' "inputFileSymL" "blahfaselllll"
|
||||
out <- readFile' "inputFileSymL"
|
||||
out `shouldBe` "BLKASLblahfaselllllgagagagablahfaselllll"
|
||||
|
||||
it "appendFile symlink, everything clear" $ do
|
||||
appendFile' "inputFileSymL" "gagagaga"
|
||||
out <- readFile' "inputFileSymL"
|
||||
out `shouldBe` "BLKASLblahfaselllllgagagagablahfaselllllgagagaga"
|
||||
|
||||
|
||||
-- posix failures --
|
||||
it "appendFile to dir, inappropriate type" $ do
|
||||
appendFile' "alreadyExistsD" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
|
||||
|
||||
it "appendFile, no permissions to file" $ do
|
||||
appendFile' "noPerms" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "appendFile, no permissions to file" $ do
|
||||
appendFile' "noPermsD/inputFile" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "appendFile, file does not exist" $ do
|
||||
appendFile' "gaga" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
|
||||
@@ -1,86 +0,0 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
module HPath.IO.ReadFileEOFSpec where
|
||||
|
||||
|
||||
import Test.Hspec
|
||||
import System.IO.Error
|
||||
(
|
||||
ioeGetErrorType
|
||||
)
|
||||
import GHC.IO.Exception
|
||||
(
|
||||
IOErrorType(..)
|
||||
)
|
||||
import System.Process
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
upTmpDir :: IO ()
|
||||
upTmpDir = do
|
||||
setTmpDir "ReadFileEOFSpec"
|
||||
createTmpDir
|
||||
|
||||
setupFiles :: IO ()
|
||||
setupFiles = do
|
||||
createRegularFile' "fileWithContent"
|
||||
createRegularFile' "fileWithoutContent"
|
||||
createSymlink' "inputFileSymL" "fileWithContent"
|
||||
createDir' "alreadyExistsD"
|
||||
createRegularFile' "noPerms"
|
||||
noPerms "noPerms"
|
||||
createDir' "noPermsD"
|
||||
createRegularFile' "noPermsD/inputFile"
|
||||
noPerms "noPermsD"
|
||||
writeFile' "fileWithContent" "Blahfaselgagaga"
|
||||
|
||||
|
||||
cleanupFiles :: IO ()
|
||||
cleanupFiles = do
|
||||
deleteFile' "fileWithContent"
|
||||
deleteFile' "fileWithoutContent"
|
||||
deleteFile' "inputFileSymL"
|
||||
deleteDir' "alreadyExistsD"
|
||||
normalFilePerms "noPerms"
|
||||
deleteFile' "noPerms"
|
||||
normalDirPerms "noPermsD"
|
||||
deleteFile' "noPermsD/inputFile"
|
||||
deleteDir' "noPermsD"
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
describe "HPath.IO.readFileEOF" $ do
|
||||
|
||||
-- successes --
|
||||
it "readFileEOF (Strict) file with content, everything clear" $ do
|
||||
out <- readFileEOF' "fileWithContent"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFileEOF (Strict) symlink, everything clear" $ do
|
||||
out <- readFileEOF' "inputFileSymL"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFileEOF (Strict) empty file, everything clear" $ do
|
||||
out <- readFileEOF' "fileWithoutContent"
|
||||
out `shouldBe` ""
|
||||
|
||||
|
||||
-- posix failures --
|
||||
it "readFileEOF (Strict) directory, wrong file type" $ do
|
||||
readFileEOF' "alreadyExistsD"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
|
||||
|
||||
it "readFileEOF (Strict) file, no permissions" $ do
|
||||
readFileEOF' "noPerms"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFileEOF (Strict) file, no permissions on dir" $ do
|
||||
readFileEOF' "noPermsD/inputFile"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFileEOF (Strict) file, no such file" $ do
|
||||
readFileEOF' "lalala"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
|
||||
@@ -1,86 +0,0 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
module HPath.IO.ReadFileSpec where
|
||||
|
||||
|
||||
import Test.Hspec
|
||||
import System.IO.Error
|
||||
(
|
||||
ioeGetErrorType
|
||||
)
|
||||
import GHC.IO.Exception
|
||||
(
|
||||
IOErrorType(..)
|
||||
)
|
||||
import System.Process
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
upTmpDir :: IO ()
|
||||
upTmpDir = do
|
||||
setTmpDir "ReadFileSpec"
|
||||
createTmpDir
|
||||
|
||||
setupFiles :: IO ()
|
||||
setupFiles = do
|
||||
createRegularFile' "fileWithContent"
|
||||
createRegularFile' "fileWithoutContent"
|
||||
createSymlink' "inputFileSymL" "fileWithContent"
|
||||
createDir' "alreadyExistsD"
|
||||
createRegularFile' "noPerms"
|
||||
noPerms "noPerms"
|
||||
createDir' "noPermsD"
|
||||
createRegularFile' "noPermsD/inputFile"
|
||||
noPerms "noPermsD"
|
||||
writeFile' "fileWithContent" "Blahfaselgagaga"
|
||||
|
||||
|
||||
cleanupFiles :: IO ()
|
||||
cleanupFiles = do
|
||||
deleteFile' "fileWithContent"
|
||||
deleteFile' "fileWithoutContent"
|
||||
deleteFile' "inputFileSymL"
|
||||
deleteDir' "alreadyExistsD"
|
||||
normalFilePerms "noPerms"
|
||||
deleteFile' "noPerms"
|
||||
normalDirPerms "noPermsD"
|
||||
deleteFile' "noPermsD/inputFile"
|
||||
deleteDir' "noPermsD"
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
describe "HPath.IO.readFile" $ do
|
||||
|
||||
-- successes --
|
||||
it "readFile (Strict) file with content, everything clear" $ do
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFile (Strict) symlink, everything clear" $ do
|
||||
out <- readFile' "inputFileSymL"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFile (Strict) empty file, everything clear" $ do
|
||||
out <- readFile' "fileWithoutContent"
|
||||
out `shouldBe` ""
|
||||
|
||||
|
||||
-- posix failures --
|
||||
it "readFile (Strict) directory, wrong file type" $ do
|
||||
readFile' "alreadyExistsD"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
|
||||
|
||||
it "readFile (Strict) file, no permissions" $ do
|
||||
readFile' "noPerms"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFile (Strict) file, no permissions on dir" $ do
|
||||
readFile' "noPermsD/inputFile"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFile (Strict) file, no such file" $ do
|
||||
readFile' "lalala"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
|
||||
@@ -1,27 +0,0 @@
|
||||
{-# 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
|
||||
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
module HPath.IO.WriteFileSpec where
|
||||
|
||||
|
||||
import Test.Hspec
|
||||
import System.IO.Error
|
||||
(
|
||||
ioeGetErrorType
|
||||
)
|
||||
import GHC.IO.Exception
|
||||
(
|
||||
IOErrorType(..)
|
||||
)
|
||||
import System.Process
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
upTmpDir :: IO ()
|
||||
upTmpDir = do
|
||||
setTmpDir "WriteFileSpec"
|
||||
createTmpDir
|
||||
|
||||
setupFiles :: IO ()
|
||||
setupFiles = do
|
||||
createRegularFile' "fileWithContent"
|
||||
createRegularFile' "fileWithoutContent"
|
||||
createSymlink' "inputFileSymL" "fileWithContent"
|
||||
createDir' "alreadyExistsD"
|
||||
createRegularFile' "noPerms"
|
||||
noPerms "noPerms"
|
||||
createDir' "noPermsD"
|
||||
createRegularFile' "noPermsD/inputFile"
|
||||
noPerms "noPermsD"
|
||||
writeFile' "fileWithContent" "BLKASL"
|
||||
|
||||
|
||||
cleanupFiles :: IO ()
|
||||
cleanupFiles = do
|
||||
deleteFile' "fileWithContent"
|
||||
deleteFile' "fileWithoutContent"
|
||||
deleteFile' "inputFileSymL"
|
||||
deleteDir' "alreadyExistsD"
|
||||
normalFilePerms "noPerms"
|
||||
deleteFile' "noPerms"
|
||||
normalDirPerms "noPermsD"
|
||||
deleteFile' "noPermsD/inputFile"
|
||||
deleteDir' "noPermsD"
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
describe "HPath.IO.writeFile" $ do
|
||||
|
||||
-- successes --
|
||||
it "writeFile file with content, everything clear" $ do
|
||||
writeFile' "fileWithContent" "blahfaselllll"
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` "blahfaselllll"
|
||||
|
||||
it "writeFile file with content, everything clear" $ do
|
||||
writeFile' "fileWithContent" "gagagaga"
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` "gagagaga"
|
||||
|
||||
it "writeFile file with content, everything clear" $ do
|
||||
writeFile' "fileWithContent" ""
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` ""
|
||||
|
||||
it "writeFile file without content, everything clear" $ do
|
||||
writeFile' "fileWithoutContent" "blahfaselllll"
|
||||
out <- readFile' "fileWithoutContent"
|
||||
out `shouldBe` "blahfaselllll"
|
||||
|
||||
it "writeFile, everything clear" $ do
|
||||
writeFile' "fileWithoutContent" "gagagaga"
|
||||
out <- readFile' "fileWithoutContent"
|
||||
out `shouldBe` "gagagaga"
|
||||
|
||||
it "writeFile symlink, everything clear" $ do
|
||||
writeFile' "inputFileSymL" "blahfaselllll"
|
||||
out <- readFile' "inputFileSymL"
|
||||
out `shouldBe` "blahfaselllll"
|
||||
|
||||
it "writeFile symlink, everything clear" $ do
|
||||
writeFile' "inputFileSymL" "gagagaga"
|
||||
out <- readFile' "inputFileSymL"
|
||||
out `shouldBe` "gagagaga"
|
||||
|
||||
|
||||
-- posix failures --
|
||||
it "writeFile to dir, inappropriate type" $ do
|
||||
writeFile' "alreadyExistsD" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
|
||||
|
||||
it "writeFile, no permissions to file" $ do
|
||||
writeFile' "noPerms" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "writeFile, no permissions to file" $ do
|
||||
writeFile' "noPermsD/inputFile" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "writeFile, file does not exist" $ do
|
||||
writeFile' "gaga" ""
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
|
||||
@@ -28,7 +28,6 @@ import Data.IORef
|
||||
)
|
||||
import HPath.IO
|
||||
import HPath.IO.Errors
|
||||
import Prelude hiding (appendFile, readFile, writeFile)
|
||||
import Data.Maybe
|
||||
(
|
||||
fromJust
|
||||
@@ -47,7 +46,6 @@ import Data.ByteString
|
||||
(
|
||||
ByteString
|
||||
)
|
||||
import qualified Data.ByteString.Lazy as L
|
||||
import System.Posix.Files.ByteString
|
||||
(
|
||||
groupExecuteMode
|
||||
@@ -245,12 +243,6 @@ normalDirPerms path =
|
||||
withTmpDir path $ \p -> setFileMode (P.fromAbs p) newDirPerms
|
||||
|
||||
|
||||
normalFilePerms :: ByteString -> IO ()
|
||||
{-# NOINLINE normalFilePerms #-}
|
||||
normalFilePerms path =
|
||||
withTmpDir path $ \p -> setFileMode (P.fromAbs p) newFilePerms
|
||||
|
||||
|
||||
getFileType' :: ByteString -> IO FileType
|
||||
{-# NOINLINE getFileType' #-}
|
||||
getFileType' path = withTmpDir path getFileType
|
||||
@@ -284,13 +276,11 @@ canonicalizePath' p = withTmpDir p canonicalizePath
|
||||
writeFile' :: ByteString -> ByteString -> IO ()
|
||||
{-# NOINLINE writeFile' #-}
|
||||
writeFile' ip bs =
|
||||
withTmpDir ip $ \p -> writeFile p bs
|
||||
|
||||
|
||||
appendFile' :: ByteString -> ByteString -> IO ()
|
||||
{-# NOINLINE appendFile' #-}
|
||||
appendFile' ip bs =
|
||||
withTmpDir ip $ \p -> appendFile p bs
|
||||
withTmpDir ip $ \p -> do
|
||||
fd <- SPI.openFd (P.fromAbs p) SPI.WriteOnly Nothing
|
||||
SPI.defaultFileFlags
|
||||
_ <- SPB.fdWrite fd bs
|
||||
SPI.closeFd fd
|
||||
|
||||
|
||||
allDirectoryContents' :: ByteString -> IO [ByteString]
|
||||
@@ -298,13 +288,3 @@ allDirectoryContents' :: ByteString -> IO [ByteString]
|
||||
allDirectoryContents' ip =
|
||||
withTmpDir ip $ \p -> DT.allDirectoryContents' (P.fromAbs p)
|
||||
|
||||
|
||||
readFile' :: ByteString -> IO ByteString
|
||||
{-# NOINLINE readFile' #-}
|
||||
readFile' p = withTmpDir p readFile
|
||||
|
||||
|
||||
readFileEOF' :: ByteString -> IO L.ByteString
|
||||
{-# NOINLINE readFileEOF' #-}
|
||||
readFileEOF' p = withTmpDir p readFileEOF
|
||||
|
||||
|
||||
Reference in New Issue
Block a user