hpath 0.9.2 → 0.10.0
raw patch · 41 files changed
+13/−6078 lines, 41 filesdep +hpath-filepathdep −HUnitdep −IfElsedep −QuickCheckdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: hpath-filepath
Dependencies removed: HUnit, IfElse, QuickCheck, doctest, hpath, hspec, process, simple-sendfile, unix, unix-bytestring, utf8-string
Dependency ranges changed: base
API changes (from Hackage documentation)
- HPath: instance GHC.Exception.Exception HPath.PathException
- HPath: instance GHC.Exception.Exception HPath.PathParseException
- HPath.IO: BlockDevice :: FileType
- HPath.IO: CharacterDevice :: FileType
- HPath.IO: CollectFailures :: RecursiveErrorMode
- HPath.IO: Directory :: FileType
- HPath.IO: FailEarly :: RecursiveErrorMode
- HPath.IO: NamedPipe :: FileType
- HPath.IO: Overwrite :: CopyMode
- HPath.IO: RegularFile :: FileType
- HPath.IO: Socket :: FileType
- HPath.IO: Strict :: CopyMode
- HPath.IO: SymbolicLink :: FileType
- HPath.IO: appendFile :: Path b -> ByteString -> IO ()
- HPath.IO: canonicalizePath :: Path b -> IO (Path Abs)
- HPath.IO: copyDirRecursive :: Path b1 -> Path b2 -> CopyMode -> RecursiveErrorMode -> IO ()
- HPath.IO: copyFile :: Path b1 -> Path b2 -> CopyMode -> IO ()
- HPath.IO: createDir :: FileMode -> Path b -> IO ()
- HPath.IO: createDirRecursive :: FileMode -> Path b -> IO ()
- HPath.IO: createRegularFile :: FileMode -> Path b -> IO ()
- HPath.IO: createSymlink :: Path b -> ByteString -> IO ()
- HPath.IO: data CopyMode
- HPath.IO: data FileType
- HPath.IO: data RecursiveErrorMode
- HPath.IO: deleteDir :: Path b -> IO ()
- HPath.IO: deleteDirRecursive :: Path b -> IO ()
- HPath.IO: deleteFile :: Path b -> IO ()
- HPath.IO: easyCopy :: Path b1 -> Path b2 -> CopyMode -> RecursiveErrorMode -> IO ()
- HPath.IO: easyDelete :: Path b -> IO ()
- HPath.IO: executeFile :: Path b -> [ByteString] -> IO ProcessID
- HPath.IO: getDirsFiles :: Path b -> IO [Path b]
- HPath.IO: getFileType :: Path b -> IO FileType
- HPath.IO: instance GHC.Classes.Eq HPath.IO.FileType
- HPath.IO: instance GHC.Show.Show HPath.IO.FileType
- HPath.IO: moveFile :: Path b1 -> Path b2 -> CopyMode -> IO ()
- HPath.IO: newDirPerms :: FileMode
- HPath.IO: newFilePerms :: FileMode
- HPath.IO: openFile :: Path b -> IO ProcessID
- HPath.IO: readFile :: Path b -> IO ByteString
- HPath.IO: readFileEOF :: Path b -> IO ByteString
- HPath.IO: recreateSymlink :: Path b1 -> Path b2 -> CopyMode -> IO ()
- HPath.IO: renameFile :: Path b1 -> Path b2 -> IO ()
- HPath.IO: toAbs :: Path b -> IO (Path Abs)
- HPath.IO: writeFile :: Path b -> ByteString -> IO ()
- HPath.IO.Errors: CopyFileFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: CreateDirFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: DestinationInSource :: ByteString -> ByteString -> HPathIOException
- HPath.IO.Errors: ReadContentsFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: RecreateSymlinkFailed :: ByteString -> ByteString -> RecursiveFailureHint
- HPath.IO.Errors: RecursiveFailure :: [(RecursiveFailureHint, IOException)] -> HPathIOException
- HPath.IO.Errors: SameFile :: ByteString -> ByteString -> HPathIOException
- HPath.IO.Errors: bracketeer :: IO a -> (a -> IO b) -> (a -> IO b) -> (a -> IO c) -> IO c
- HPath.IO.Errors: canOpenDirectory :: Path b -> IO Bool
- HPath.IO.Errors: catchErrno :: [Errno] -> IO a -> IO a -> IO a
- HPath.IO.Errors: data HPathIOException
- HPath.IO.Errors: data RecursiveFailureHint
- HPath.IO.Errors: doesDirectoryExist :: Path b -> IO Bool
- HPath.IO.Errors: doesFileExist :: Path b -> IO Bool
- HPath.IO.Errors: handleIOError :: (IOError -> IO a) -> IO a -> IO a
- HPath.IO.Errors: instance GHC.Classes.Eq HPath.IO.Errors.HPathIOException
- HPath.IO.Errors: instance GHC.Classes.Eq HPath.IO.Errors.RecursiveFailureHint
- HPath.IO.Errors: instance GHC.Exception.Exception HPath.IO.Errors.HPathIOException
- HPath.IO.Errors: instance GHC.Show.Show HPath.IO.Errors.HPathIOException
- HPath.IO.Errors: instance GHC.Show.Show HPath.IO.Errors.RecursiveFailureHint
- HPath.IO.Errors: isCopyFileFailed :: RecursiveFailureHint -> Bool
- HPath.IO.Errors: isCreateDirFailed :: RecursiveFailureHint -> Bool
- HPath.IO.Errors: isDestinationInSource :: HPathIOException -> Bool
- HPath.IO.Errors: isReadContentsFailed :: RecursiveFailureHint -> Bool
- HPath.IO.Errors: isRecreateSymlinkFailed :: RecursiveFailureHint -> Bool
- HPath.IO.Errors: isRecursiveFailure :: HPathIOException -> Bool
- HPath.IO.Errors: isSameFile :: HPathIOException -> Bool
- HPath.IO.Errors: isWritable :: Path b -> IO Bool
- HPath.IO.Errors: reactOnError :: IO a -> [(IOErrorType, IO a)] -> [(HPathIOException, IO a)] -> IO a
- HPath.IO.Errors: rethrowErrnoAs :: Exception e => [Errno] -> e -> IO a -> IO a
- HPath.IO.Errors: sameFile :: Path b1 -> Path b2 -> IO Bool
- HPath.IO.Errors: throwDestinationInSource :: Path b1 -> Path b2 -> IO ()
- HPath.IO.Errors: throwDirDoesExist :: Path b -> IO ()
- HPath.IO.Errors: throwFileDoesExist :: Path b -> IO ()
- HPath.IO.Errors: throwSameFile :: Path b1 -> Path b2 -> IO ()
- System.Posix.Directory.Foreign: DirType :: Int -> DirType
- System.Posix.Directory.Foreign: Flags :: Int -> Flags
- System.Posix.Directory.Foreign: UnsupportedFlag :: String -> Flags
- System.Posix.Directory.Foreign: data Flags
- System.Posix.Directory.Foreign: dtBlk :: DirType
- System.Posix.Directory.Foreign: dtChr :: DirType
- System.Posix.Directory.Foreign: dtDir :: DirType
- System.Posix.Directory.Foreign: dtFifo :: DirType
- System.Posix.Directory.Foreign: dtLnk :: DirType
- System.Posix.Directory.Foreign: dtReg :: DirType
- System.Posix.Directory.Foreign: dtSock :: DirType
- System.Posix.Directory.Foreign: dtUnknown :: DirType
- System.Posix.Directory.Foreign: instance GHC.Classes.Eq System.Posix.Directory.Foreign.DirType
- System.Posix.Directory.Foreign: instance GHC.Classes.Eq System.Posix.Directory.Foreign.Flags
- System.Posix.Directory.Foreign: instance GHC.Show.Show System.Posix.Directory.Foreign.DirType
- System.Posix.Directory.Foreign: instance GHC.Show.Show System.Posix.Directory.Foreign.Flags
- System.Posix.Directory.Foreign: isSupported :: Flags -> Bool
- System.Posix.Directory.Foreign: newtype DirType
- System.Posix.Directory.Foreign: oAppend :: Flags
- System.Posix.Directory.Foreign: oAsync :: Flags
- System.Posix.Directory.Foreign: oCloexec :: Flags
- System.Posix.Directory.Foreign: oCreat :: Flags
- System.Posix.Directory.Foreign: oDirectory :: Flags
- System.Posix.Directory.Foreign: oExcl :: Flags
- System.Posix.Directory.Foreign: oNoctty :: Flags
- System.Posix.Directory.Foreign: oNofollow :: Flags
- System.Posix.Directory.Foreign: oNonblock :: Flags
- System.Posix.Directory.Foreign: oRdonly :: Flags
- System.Posix.Directory.Foreign: oRdwr :: Flags
- System.Posix.Directory.Foreign: oSync :: Flags
- System.Posix.Directory.Foreign: oTrunc :: Flags
- System.Posix.Directory.Foreign: oWronly :: Flags
- System.Posix.Directory.Foreign: pathMax :: Int
- System.Posix.Directory.Foreign: unFlags :: Flags -> Int
- System.Posix.Directory.Foreign: unionFlags :: [Flags] -> CInt
- System.Posix.Directory.Traversals: allDirectoryContents :: RawFilePath -> IO [RawFilePath]
- System.Posix.Directory.Traversals: allDirectoryContents' :: RawFilePath -> IO [RawFilePath]
- System.Posix.Directory.Traversals: fdOpendir :: Fd -> IO DirStream
- System.Posix.Directory.Traversals: getDirectoryContents :: RawFilePath -> IO [(DirType, RawFilePath)]
- System.Posix.Directory.Traversals: getDirectoryContents' :: Fd -> IO [(DirType, RawFilePath)]
- System.Posix.Directory.Traversals: packDirStream :: Ptr CDir -> DirStream
- System.Posix.Directory.Traversals: readDirEnt :: DirStream -> IO (DirType, RawFilePath)
- System.Posix.Directory.Traversals: realpath :: RawFilePath -> IO RawFilePath
- System.Posix.Directory.Traversals: traverseDirectory :: (s -> RawFilePath -> IO s) -> s -> RawFilePath -> IO s
- System.Posix.Directory.Traversals: unpackDirStream :: DirStream -> Ptr CDir
- System.Posix.FD: openFd :: RawFilePath -> OpenMode -> [Flags] -> Maybe FileMode -> IO Fd
- System.Posix.FilePath: (<.>) :: RawFilePath -> ByteString -> RawFilePath
- System.Posix.FilePath: (</>) :: RawFilePath -> RawFilePath -> RawFilePath
- System.Posix.FilePath: addExtension :: RawFilePath -> ByteString -> RawFilePath
- System.Posix.FilePath: addTrailingPathSeparator :: RawFilePath -> RawFilePath
- System.Posix.FilePath: combine :: RawFilePath -> RawFilePath -> RawFilePath
- System.Posix.FilePath: dropExtension :: RawFilePath -> RawFilePath
- System.Posix.FilePath: dropExtensions :: RawFilePath -> RawFilePath
- System.Posix.FilePath: dropFileName :: RawFilePath -> RawFilePath
- System.Posix.FilePath: dropTrailingPathSeparator :: RawFilePath -> RawFilePath
- System.Posix.FilePath: equalFilePath :: RawFilePath -> RawFilePath -> Bool
- System.Posix.FilePath: extSeparator :: Word8
- System.Posix.FilePath: getSearchPath :: IO [RawFilePath]
- System.Posix.FilePath: hasExtension :: RawFilePath -> Bool
- System.Posix.FilePath: hasParentDir :: RawFilePath -> Bool
- System.Posix.FilePath: hasTrailingPathSeparator :: RawFilePath -> Bool
- System.Posix.FilePath: hiddenFile :: RawFilePath -> Bool
- System.Posix.FilePath: isAbsolute :: RawFilePath -> Bool
- System.Posix.FilePath: isExtSeparator :: Word8 -> Bool
- System.Posix.FilePath: isFileName :: RawFilePath -> Bool
- System.Posix.FilePath: isPathSeparator :: Word8 -> Bool
- System.Posix.FilePath: isRelative :: RawFilePath -> Bool
- System.Posix.FilePath: isSearchPathSeparator :: Word8 -> Bool
- System.Posix.FilePath: isValid :: RawFilePath -> Bool
- System.Posix.FilePath: joinPath :: [RawFilePath] -> RawFilePath
- System.Posix.FilePath: makeRelative :: RawFilePath -> RawFilePath -> RawFilePath
- System.Posix.FilePath: makeValid :: RawFilePath -> RawFilePath
- System.Posix.FilePath: normalise :: RawFilePath -> RawFilePath
- System.Posix.FilePath: pathSeparator :: Word8
- System.Posix.FilePath: replaceBaseName :: RawFilePath -> ByteString -> RawFilePath
- System.Posix.FilePath: replaceDirectory :: RawFilePath -> ByteString -> RawFilePath
- System.Posix.FilePath: replaceExtension :: RawFilePath -> ByteString -> RawFilePath
- System.Posix.FilePath: replaceFileName :: RawFilePath -> ByteString -> RawFilePath
- System.Posix.FilePath: searchPathSeparator :: Word8
- System.Posix.FilePath: splitDirectories :: RawFilePath -> [RawFilePath]
- System.Posix.FilePath: splitExtension :: RawFilePath -> (RawFilePath, ByteString)
- System.Posix.FilePath: splitExtensions :: RawFilePath -> (RawFilePath, ByteString)
- System.Posix.FilePath: splitFileName :: RawFilePath -> (RawFilePath, RawFilePath)
- System.Posix.FilePath: splitPath :: RawFilePath -> [RawFilePath]
- System.Posix.FilePath: splitSearchPath :: ByteString -> [RawFilePath]
- System.Posix.FilePath: stripExtension :: ByteString -> RawFilePath -> Maybe RawFilePath
- System.Posix.FilePath: takeBaseName :: RawFilePath -> ByteString
- System.Posix.FilePath: takeDirectory :: RawFilePath -> RawFilePath
- System.Posix.FilePath: takeExtension :: RawFilePath -> ByteString
- System.Posix.FilePath: takeExtensions :: RawFilePath -> ByteString
- System.Posix.FilePath: takeFileName :: RawFilePath -> RawFilePath
+ HPath: instance GHC.Exception.Type.Exception HPath.PathException
+ HPath: instance GHC.Exception.Type.Exception HPath.PathParseException
+ HPath: pattern Path :: ByteString -> Path a
+ HPath.Internal: MkPath :: ByteString -> Path b
+ HPath.Internal: data Path b
+ HPath.Internal: instance Control.DeepSeq.NFData (HPath.Internal.Path b)
+ HPath.Internal: instance GHC.Classes.Eq (HPath.Internal.Path b)
+ HPath.Internal: instance GHC.Classes.Ord (HPath.Internal.Path b)
+ HPath.Internal: instance GHC.Show.Show (HPath.Internal.Path b)
Files
- README.md +2/−52
- cbits/dirutils.c +0/−7
- cbits/dirutils.h +0/−13
- doctests-hpath.hs +0/−13
- doctests-posix.hs +0/−25
- hpath.cabal +11/−99
- src/HPath/IO.hs +0/−1104
- src/HPath/IO.hs-boot +0/−8
- src/HPath/IO/Errors.hs +0/−360
- src/System/Posix/Directory/Foreign.hsc +0/−55
- src/System/Posix/Directory/Traversals.hs +0/−264
- src/System/Posix/FD.hs +0/−75
- src/System/Posix/FilePath.hs +0/−823
- test/HPath/IO/AppendFileSpec.hs +0/−109
- test/HPath/IO/CanonicalizePathSpec.hs +0/−78
- test/HPath/IO/CopyDirRecursiveCollectFailuresSpec.hs +0/−247
- test/HPath/IO/CopyDirRecursiveOverwriteSpec.hs +0/−202
- test/HPath/IO/CopyDirRecursiveSpec.hs +0/−180
- test/HPath/IO/CopyFileOverwriteSpec.hs +0/−148
- test/HPath/IO/CopyFileSpec.hs +0/−143
- test/HPath/IO/CreateDirRecursiveSpec.hs +0/−78
- test/HPath/IO/CreateDirSpec.hs +0/−72
- test/HPath/IO/CreateRegularFileSpec.hs +0/−70
- test/HPath/IO/CreateSymlinkSpec.hs +0/−71
- test/HPath/IO/DeleteDirRecursiveSpec.hs +0/−116
- test/HPath/IO/DeleteDirSpec.hs +0/−114
- test/HPath/IO/DeleteFileSpec.hs +0/−84
- test/HPath/IO/GetDirsFilesSpec.hs +0/−100
- test/HPath/IO/GetFileTypeSpec.hs +0/−88
- test/HPath/IO/MoveFileOverwriteSpec.hs +0/−126
- test/HPath/IO/MoveFileSpec.hs +0/−129
- test/HPath/IO/ReadFileEOFSpec.hs +0/−86
- test/HPath/IO/ReadFileSpec.hs +0/−86
- test/HPath/IO/RecreateSymlinkOverwriteSpec.hs +0/−139
- test/HPath/IO/RecreateSymlinkSpec.hs +0/−130
- test/HPath/IO/RenameFileSpec.hs +0/−117
- test/HPath/IO/ToAbsSpec.hs +0/−27
- test/HPath/IO/WriteFileSpec.hs +0/−109
- test/Main.hs +0/−19
- test/Spec.hs +0/−2
- test/Utils.hs +0/−310
README.md view
@@ -1,9 +1,8 @@ # 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)+[](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) -Support for well-typed paths in Haskell. Also provides ByteString based filepath-manipulation.+Support for well-typed paths in Haskell. ## Motivation @@ -17,16 +16,10 @@ but the API turned out to be oddly complicated for my use case, so I decided to fork it. -Similarly, [posix-paths](https://github.com/JohnLato/posix-paths)-was exactly what I wanted for the low-level operations, but upstream seems dead,-so it is forked as well and merged into this library.- ## Goals * well-typed paths-* high-level API to file operations like recursive directory copy * safe filepath manipulation, never using String as filepath, but ByteString-* still allowing sufficient control to interact with the underlying low-level calls Note: this library was written for __posix__ systems and it will probably not support other systems. @@ -42,46 +35,3 @@ * uses simple doctest for testing * allows `~/` as relative path, because on posix level `~` is just a regular filename that does _NOT_ point to `$HOME` * remove TH, it sucks--## Differences to 'posix-paths'--* uses the `word8` package for save word8 literals instead of `OverloadedStrings`-* `hasTrailingPathSeparator` and `dropTrailingPathSeparator` behave in the same way as their `System.FilePath` counterpart-* added various functions:- * `equalFilePath`- * `getSearchPath`- * `hasParentDir`- * `hiddenFile`- * `isFileName`- * `isValid`- * `makeRelative`- * `makeValid`- * `normalise`- * `splitSearchPath`- * `stripExtension`-* has a custom versions of `openFd` which allows more control over the flags than its unix package counterpart-* adds a `getDirectoryContents'` version that works on Fd--## Examples in ghci--Start ghci via `cabal repl`:--```hs--- enable OverloadedStrings-:set -XOverloadedStrings--- import HPath.IO-import HPath.IO--- parse an absolute path-abspath <- parseAbs "/home"--- parse a relative path (e.g. user users home directory)-relpath <- parseRel "jule"--- concatenate paths-let newpath = abspath </> relpath--- get file type-getFileType newpath--- return all contents of that directory-getDirsFiles newpath--- return all contents of the parent directory-getDirsFiles (dirname newpath)-```-
− cbits/dirutils.c
@@ -1,7 +0,0 @@-#include "dirutils.h"-unsigned int- __posixdir_d_type(struct dirent* d)- {- return(d -> d_type);- }-
− cbits/dirutils.h
@@ -1,13 +0,0 @@-#ifndef POSIXPATHS_CBITS_DIRUTILS_H-#define POSIXPATHS_CBITS_DIRUTILS_H--#include <stdlib.h>-#include <dirent.h>-#include <sys/types.h>-#include <sys/stat.h>-#include <fcntl.h>--extern unsigned int- __posixdir_d_type(struct dirent* d)- ;-#endif
− doctests-hpath.hs
@@ -1,13 +0,0 @@-module Main where---import Test.DocTest-import Test.HUnit--main =- doctest- ["-isrc"- , "-XOverloadedStrings"- , "src/HPath.hs"- ]-
− doctests-posix.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Main where--import Control.Applicative-import System.Posix.Directory.Traversals--import Test.DocTest-import Test.HUnit--main = do- doctest- [ "-isrc"- , "-XOverloadedStrings"- , "System.Posix.FilePath"- ]- runTestTT unitTests---unitTests :: Test-unitTests = test- [ TestCase $ do- r <- (==) <$> allDirectoryContents "." <*> allDirectoryContents' "."- assertBool "allDirectoryContents == allDirectoryContents'" r- ]
hpath.cabal view
@@ -1,5 +1,5 @@ name: hpath-version: 0.9.2+version: 0.10.0 synopsis: Support for well-typed paths description: Support for well-typed paths, utilizing ByteString under the hood. license: BSD3@@ -10,11 +10,14 @@ category: Filesystem build-type: Simple cabal-version: 1.14+tested-with: GHC==7.10.3+ , GHC==8.0.2+ , GHC==8.2.2+ , GHC==8.4.4+ , GHC==8.6.5+ , GHC==8.8.1 extra-source-files: README.md CHANGELOG- cbits/dirutils.h- doctests-hpath.hs- doctests-posix.hs library if os(windows)@@ -26,105 +29,14 @@ ghc-options: -Wall -Wno-redundant-constraints else ghc-options: -Wall- c-sources: cbits/dirutils.c- exposed-modules: HPath,- HPath.IO,- HPath.IO.Errors,- System.Posix.Directory.Foreign,- System.Posix.Directory.Traversals,- System.Posix.FD,- System.Posix.FilePath- other-modules: HPath.Internal- build-depends: base >= 4.6 && <5- , IfElse+ exposed-modules: HPath+ HPath.Internal+ build-depends: base >= 4.8 && <5 , bytestring >= 0.10.0.0 , deepseq , exceptions- , hspec- , simple-sendfile >= 0.2.24- , unix >= 2.5- , unix-bytestring- , utf8-string+ , hpath-filepath >= 0.10 && < 0.11 , word8---test-suite doctests-hpath- if os(windows)- build-depends: unbuildable<0- buildable: False- default-language: Haskell2010- type: exitcode-stdio-1.0- ghc-options: -threaded- main-is: doctests-hpath.hs- build-depends: base- , HUnit- , QuickCheck- , doctest >= 0.8- , 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,- unix,- hpath,- doctest >= 0.8,- HUnit,- 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.CreateRegularFileSpec- HPath.IO.CreateSymlinkSpec- HPath.IO.DeleteDirRecursiveSpec- HPath.IO.DeleteDirSpec- HPath.IO.DeleteFileSpec- HPath.IO.GetDirsFilesSpec- 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- , hpath- , hspec >= 1.3- , process- , unix- , unix-bytestring- , utf8-string source-repository head type: git
− src/HPath/IO.hs
@@ -1,1104 +0,0 @@--- |--- Module : HPath.IO--- Copyright : © 2016 Julian Ospald--- License : BSD3------ Maintainer : Julian Ospald <hasufell@posteo.de>--- Stability : experimental--- 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.------ Some functions are just path-safe wrappers around--- unix functions, others have stricter exception handling--- and some implement functionality that doesn't have a unix--- counterpart (like `copyDirRecursive`).------ Some of these operations are due to their nature __not atomic__, which--- means they may do multiple syscalls which form one context. Some--- of them also have to examine the filetypes explicitly before the--- syscalls, so a reasonable decision can be made. That means--- the result is undefined if another process changes that context--- while the non-atomic operation is still happening. However, where--- possible, as few syscalls as possible are used and the underlying--- exception handling is kept.------ Note: `BlockDevice`, `CharacterDevice`, `NamedPipe` and `Socket`--- are ignored by some of the more high-level functions (like `easyCopy`).--- 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 #-}--module HPath.IO- (- -- * Types- FileType(..)- , RecursiveErrorMode(..)- , CopyMode(..)- -- * File copying- , copyDirRecursive- , recreateSymlink- , copyFile- , easyCopy- -- * File deletion- , deleteFile- , deleteDir- , deleteDirRecursive- , easyDelete- -- * File opening- , openFile- , executeFile- -- * File creation- , createRegularFile- , createDir- , createDirRecursive- , createSymlink- -- * File renaming/moving- , renameFile- , moveFile- -- * File reading- , readFile- , readFileEOF- -- * File writing- , writeFile- , appendFile- -- * File permissions- , newFilePerms- , newDirPerms- -- * Directory reading- , getDirsFiles- -- * Filetype operations- , getFileType- -- * Others- , canonicalizePath- , toAbs- )- where---import Control.Applicative- (- (<$>)- )-import Control.Exception- (- IOException- , bracket- , throwIO- )-import Control.Monad- (- unless- , void- , when- )-import Control.Monad.IfElse- (- unlessM- )-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_- )-import Data.IORef- (- IORef- , modifyIORef- , newIORef- , readIORef- )-import Data.Maybe- (- catMaybes- )-import Data.Monoid- (- (<>)- , mempty- )-import Data.Word- (- Word8- )-import Foreign.C.Error- (- eEXIST- , eINVAL- , eNOENT- , eNOSYS- , eNOTEMPTY- , eXDEV- , getErrno- )-import Foreign.C.Types- (- CSize- )-import Foreign.Marshal.Alloc- (- allocaBytes- )-import Foreign.Ptr- (- Ptr- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import HPath-import HPath.Internal-import HPath.IO.Errors-import Prelude hiding (appendFile, readFile, writeFile)-import System.IO.Error- (- catchIOError- , ioeGetErrorType- )-import System.Linux.Sendfile- (- sendfileFd- , FileRange(..)- )-import System.Posix.ByteString- (- exclusive- )-import System.Posix.Directory.ByteString- (- createDirectory- , getWorkingDirectory- , removeDirectory- )-import System.Posix.Directory.Traversals- (- getDirectoryContents'- )-import System.Posix.Files.ByteString- (- createSymbolicLink- , fileMode- , getFdStatus- , groupExecuteMode- , groupReadMode- , groupWriteMode- , otherExecuteMode- , otherReadMode- , otherWriteMode- , ownerModes- , ownerReadMode- , ownerWriteMode- , readSymbolicLink- , removeLink- , rename- , setFileMode- , unionFileModes- )-import qualified System.Posix.Files.ByteString as PF-import qualified "unix" System.Posix.IO.ByteString as SPI-import qualified "unix-bytestring" System.Posix.IO.ByteString as SPB-import System.Posix.FD- (- openFd- )-import qualified System.Posix.Directory.Traversals as SPDT-import qualified System.Posix.Directory.Foreign as SPDF-import qualified System.Posix.Process.ByteString as SPP-import System.Posix.Types- (- FileMode- , ProcessID- , Fd- )------ -------------- --[ Types ]--- ----------------data FileType = Directory- | RegularFile- | SymbolicLink- | BlockDevice- | CharacterDevice- | NamedPipe- | Socket- deriving (Eq, Show)------ |The error mode for recursive operations.------ On `FailEarly` the whole operation fails immediately if any of the--- recursive sub-operations fail, which is sort of the default--- for IO operations.------ On `CollectFailures` skips errors in the recursion and keeps on recursing.--- However all errors are collected in the `RecursiveFailure` error type,--- which is raised finally if there was any error. Also note that--- `RecursiveFailure` does not give any guarantees on the ordering--- of the collected exceptions.-data RecursiveErrorMode = FailEarly- | CollectFailures----- |The mode for copy and file moves.--- Overwrite mode is usually not very well defined, but is a convenience--- shortcut.-data CopyMode = Strict -- ^ fail if any target exists- | Overwrite -- ^ overwrite targets----- --------------------- --[ File Copying ]--- -------------------------- |Copies the contents of a directory recursively to the given destination, while preserving permissions.--- Does not follow symbolic links. This behaves more or less like--- the following, without descending into the destination if it--- already exists:------ @--- cp -a \/source\/dir \/destination\/somedir--- @------ For directory contents, this will ignore any file type that is not--- `RegularFile`, `SymbolicLink` or `Directory`.------ For `Overwrite` copy mode this does not prune destination directory--- contents, so the destination might contain more files than the source after--- the operation has completed. Permissions of existing directories are--- fixed.------ Safety/reliability concerns:------ * not atomic--- * examines filetypes explicitly--- * an explicit check `throwDestinationInSource` is carried out for the--- top directory for basic sanity, because otherwise we might end up--- with an infinite copy loop... however, this operation is not--- carried out recursively (because it's slow)------ Throws:------ - `NoSuchThing` if source directory does not exist--- - `PermissionDenied` if source directory can't be opened--- - `SameFile` if source and destination are the same file--- (`HPathIOException`)--- - `DestinationInSource` if destination is contained in source--- (`HPathIOException`)------ Throws in `FailEarly` RecursiveErrorMode only:------ - `PermissionDenied` if output directory is not writable--- - `InvalidArgument` if source directory is wrong type (symlink)--- - `InappropriateType` if source directory is wrong type (regular file)------ Throws in `CollectFailures` RecursiveErrorMode only:------ - `RecursiveFailure` if any of the recursive operations that are not--- part of the top-directory sanity-checks fail (`HPathIOException`)------ Throws in `Strict` CopyMode only:------ - `AlreadyExists` if destination already exists-copyDirRecursive :: Path b1 -- ^ source dir- -> Path b2 -- ^ destination (parent dirs- -- are not automatically created)- -> CopyMode- -> RecursiveErrorMode- -> IO ()-copyDirRecursive fromp destdirp cm rm- = do- ce <- newIORef []- -- for performance, sanity checks are only done for the top dir- throwSameFile fromp destdirp- throwDestinationInSource fromp destdirp- go ce fromp destdirp- collectedExceptions <- readIORef ce- unless (null collectedExceptions)- (throwIO . RecursiveFailure $ collectedExceptions)- where- go :: IORef [(RecursiveFailureHint, IOException)]- -> 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 fromBS destdirpBS) 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- case cm of- Strict -> createDirectory destdirpBS fmode'- Overwrite -> catchIOError (createDirectory destdirpBS- fmode')- $ \e ->- case ioeGetErrorType e of- AlreadyExists -> setFileMode destdirpBS- fmode'- _ -> ioError e- return contents-- -- NOTE: we can't use `easyCopy` here, because we want to call `go`- -- recursively to skip the top-level sanity checks-- -- if reading the contents and creating the destination dir worked,- -- then copy the contents to the destination too- for_ contents $ \f -> do- ftype <- getFileType f- newdest <- (destdirp' </>) <$> basename f- case ftype of- SymbolicLink -> handleIOE (RecreateSymlinkFailed (toFilePath f) (toFilePath newdest)) ce ()- $ recreateSymlink f newdest cm- Directory -> go ce f newdest- RegularFile -> handleIOE (CopyFileFailed (toFilePath f) (toFilePath newdest)) ce ()- $ copyFile f newdest cm- _ -> return ()-- -- helper to handle errors for both RecursiveErrorModes and return a- -- default value- handleIOE :: RecursiveFailureHint- -> IORef [(RecursiveFailureHint, IOException)]- -> a -> IO a -> IO a- handleIOE hint ce def = case rm of- FailEarly -> handleIOError throwIO- CollectFailures -> handleIOError (\e -> modifyIORef ce ((hint, e):)- >> return def)----- |Recreate a symlink.------ In `Overwrite` copy mode only files and empty directories are deleted.------ Safety/reliability concerns:------ * `Overwrite` mode is inherently non-atomic------ Throws:------ - `InvalidArgument` if source file is wrong type (not a symlink)--- - `PermissionDenied` if output directory cannot be written to--- - `PermissionDenied` if source directory cannot be opened--- - `SameFile` if source and destination are the same file--- (`HPathIOException`)--------- Throws in `Strict` mode only:------ - `AlreadyExists` if destination already exists------ Throws in `Overwrite` mode only:------ - `UnsatisfiedConstraints` if destination file is non-empty directory------ Note: calls `symlink`-recreateSymlink :: Path b1 -- ^ the old symlink file- -> Path b2 -- ^ destination file- -> CopyMode- -> IO ()-recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm- = do- throwSameFile symsource newsym- sympoint <- readSymbolicLink symsourceBS- case cm of- Strict -> return ()- Overwrite -> do- writable <- toAbs newsym >>= isWritable- isfile <- doesFileExist newsym- isdir <- doesDirectoryExist newsym- when (writable && isfile) (deleteFile newsym)- when (writable && isdir) (deleteDir newsym)- createSymbolicLink sympoint newsymBS----- |Copies the given regular file to the given destination.--- Neither follows symbolic links, nor accepts them.--- For "copying" symbolic links, use `recreateSymlink` instead.------ Note that this is still sort of a low-level function and doesn't--- examine file types. For a more high-level version, use `easyCopy`--- instead.------ In `Overwrite` copy mode only overwrites actual files, not directories.------ Safety/reliability concerns:------ * `Overwrite` mode is not atomic--- * when used on `CharacterDevice`, reads the "contents" and copies--- them to a regular file, which might take indefinitely--- * when used on `BlockDevice`, may either read the "contents"--- and copy them to a regular file (potentially hanging indefinitely)--- or may create a regular empty destination file--- * when used on `NamedPipe`, will hang indefinitely------ Throws:------ - `NoSuchThing` if source file does not exist--- - `NoSuchThing` if source file is a a `Socket`--- - `PermissionDenied` if output directory is not writable--- - `PermissionDenied` if source directory can't be opened--- - `InvalidArgument` if source file is wrong type (symlink or directory)--- - `SameFile` if source and destination are the same file--- (`HPathIOException`)------ Throws in `Strict` mode only:------ - `AlreadyExists` if destination already exists------ Note: calls `sendfile` and possibly `read`/`write` as fallback-copyFile :: Path b1 -- ^ source file- -> Path b2 -- ^ destination file- -> CopyMode- -> IO ()-copyFile from to cm = do- throwSameFile from to- - case cm of- Strict -> _copyFile [SPDF.oNofollow]- [SPDF.oNofollow, SPDF.oExcl]- from to- Overwrite -> - catchIOError (_copyFile [SPDF.oNofollow]- [SPDF.oNofollow, SPDF.oTrunc]- from to) $ \e ->- case ioeGetErrorType e of- -- if the destination file is not writable, we need to- -- figure out if we can still copy by deleting it first- PermissionDenied -> do- exists <- doesFileExist to- writable <- toAbs to >>= isWritable- if (exists && writable)- then deleteFile to >> copyFile from to Strict- else ioError e- _ -> ioError e---_copyFile :: [SPDF.Flags]- -> [SPDF.Flags]- -> Path b1 -- ^ source file- -> Path b2 -- ^ destination file- -> IO ()-_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.- catchErrno [eINVAL, eNOSYS]- (sendFileCopy fromBS toBS)- (void $ readWriteCopy fromBS toBS)- where- copyWith copyAction source dest =- bracket (openFd source SPI.ReadOnly sflags Nothing)- SPI.closeFd- $ \sfd -> do- fileM <- System.Posix.Files.ByteString.fileMode- <$> getFdStatus sfd- bracketeer (openFd dest SPI.WriteOnly- dflags $ Just fileM)- SPI.closeFd- (\fd -> SPI.closeFd fd >> deleteFile to)- $ \dfd -> copyAction sfd dfd- -- this is low-level stuff utilizing sendfile(2) for speed- sendFileCopy :: ByteString -> ByteString -> IO ()- sendFileCopy = copyWith- (\sfd dfd -> sendfileFd dfd sfd EntireFile $ return ())- -- low-level copy operation utilizing read(2)/write(2)- -- in case `sendFileCopy` fails/is unsupported- readWriteCopy :: ByteString -> ByteString -> IO Int- readWriteCopy = copyWith- (\sfd dfd -> allocaBytes (fromIntegral bufSize)- $ \buf -> write' sfd dfd buf 0)- where- bufSize :: CSize- bufSize = 8192- write' :: Fd -> Fd -> Ptr Word8 -> Int -> IO Int- write' sfd dfd buf totalsize = do- size <- SPB.fdReadBuf sfd buf bufSize- if size == 0- then return $ fromIntegral totalsize- else do rsize <- SPB.fdWriteBuf dfd buf size- when (rsize /= size) (ioError $ userError- "wrong size!")- write' sfd dfd buf (totalsize + fromIntegral size)----- |Copies a regular file, directory or symbolic link. In case of a--- symbolic link it is just recreated, even if it points to a directory.--- Any other file type is ignored.------ Safety/reliability concerns:------ * examines filetypes explicitly--- * calls `copyDirRecursive` for directories-easyCopy :: Path b1- -> Path b2- -> CopyMode- -> RecursiveErrorMode- -> IO ()-easyCopy from to cm rm = do- ftype <- getFileType from- case ftype of- SymbolicLink -> recreateSymlink from to cm- RegularFile -> copyFile from to cm- Directory -> copyDirRecursive from to cm rm- _ -> return ()------ ---------------------- --[ File Deletion ]--- -------------------------- |Deletes the given file. Raises `eISDIR`--- if run on a directory. Does not follow symbolic links.------ Throws:------ - `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----- |Deletes the given directory, which must be empty, never symlinks.------ Throws:------ - `InappropriateType` for wrong file type (symlink to directory)--- - `InappropriateType` for wrong file type (regular file)--- - `NoSuchThing` if directory does not exist--- - `UnsatisfiedConstraints` if directory is not empty--- - `PermissionDenied` if we can't open or write to parent directory------ Notes: calls `rmdir`-deleteDir :: Path b -> IO ()-deleteDir (MkPath p) = removeDirectory p----- |Deletes the given directory recursively. Does not follow symbolic--- links. Tries `deleteDir` first before attemtping a recursive--- deletion.------ On directory contents this behaves like `easyDelete`--- and thus will ignore any file type that is not `RegularFile`,--- `SymbolicLink` or `Directory`.------ Safety/reliability concerns:------ * not atomic--- * examines filetypes explicitly------ Throws:------ - `InappropriateType` for wrong file type (symlink to directory)--- - `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 p =- catchErrno [eNOTEMPTY, eEXIST]- (deleteDir p)- $ do- files <- getDirsFiles p- for_ files $ \file -> do- ftype <- getFileType file- case ftype of- SymbolicLink -> deleteFile file- Directory -> deleteDirRecursive file- RegularFile -> deleteFile file- _ -> return ()- removeDirectory . toFilePath $ p----- |Deletes a file, directory or symlink.--- In case of directory, performs recursive deletion. In case of--- a symlink, the symlink file is deleted.--- Any other file type is ignored.------ Safety/reliability concerns:------ * examines filetypes explicitly--- * calls `deleteDirRecursive` for directories-easyDelete :: Path b -> IO ()-easyDelete p = do- ftype <- getFileType p- case ftype of- SymbolicLink -> deleteFile p- Directory -> deleteDirRecursive p- RegularFile -> deleteFile p- _ -> return ()----- --------------------- --[ File Opening ]--- ------------------------- |Opens a file appropriately by invoking xdg-open. The file type--- is not checked. This forks a process.-openFile :: Path b- -> IO ProcessID-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 b -- ^ program- -> [ByteString] -- ^ arguments- -> IO ProcessID-executeFile (MkPath fp) args =- SPP.forkProcess $ SPP.executeFile fp True args Nothing----- ---------------------- --[ File Creation ]--- -------------------------- |Create an empty regular file at the given directory with the given--- filename.------ Throws:------ - `PermissionDenied` if output directory cannot be written to--- - `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)- (SPI.defaultFileFlags { exclusive = True }))- SPI.closeFd- (\_ -> return ())----- |Create an empty directory at the given directory with the given filename.------ Throws:------ - `PermissionDenied` if output directory cannot be written to--- - `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----- |Create an empty directory at the given directory with the given filename.--- All parent directories are created with the same filemode. This--- basically behaves like:------ @--- mkdir -p \/some\/dir--- @------ Safety/reliability concerns:------ * not atomic------ Throws:------ - `PermissionDenied` if any part of the path components do not--- exist and cannot be written to--- - `AlreadyExists` if destination already exists and--- is not a directory-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.------ Throws:------ - `PermissionDenied` if output directory cannot be written to--- - `AlreadyExists` if destination file already exists--- - `NoSuchThing` if any of the parent components of the path--- do not exist------ Note: calls `symlink`-createSymlink :: Path b -- ^ destination file- -> ByteString -- ^ path the symlink points to- -> IO ()-createSymlink (MkPath destBS) sympoint- = createSymbolicLink sympoint destBS---- ----------------------------- --[ File Renaming/Moving ]--- --------------------------------- |Rename a given file with the provided filename. Destination and source--- must be on the same device, otherwise `eXDEV` will be raised.------ Does not follow symbolic links, but renames the symbolic link file.------ Safety/reliability concerns:------ * has a separate set of exception handling, apart from the syscall------ Throws:------ - `NoSuchThing` if source file does not exist--- - `PermissionDenied` if output directory cannot be written to--- - `PermissionDenied` if source directory cannot be opened--- - `UnsupportedOperation` if source and destination are on different--- devices--- - `AlreadyExists` if destination already exists--- - `SameFile` if destination and source are the same file--- (`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- throwSameFile fromf tof- throwFileDoesExist tof- throwDirDoesExist tof- rename fromfBS tofBS----- |Move a file. This also works across devices by copy-delete fallback.--- And also works on directories.------ Does not follow symbolic links, but renames the symbolic link file.--------- Safety/reliability concerns:------ * `Overwrite` mode is not atomic--- * copy-delete fallback is inherently non-atomic--- * since this function calls `easyCopy` and `easyDelete` as a fallback--- to `renameFile`, file types that are not `RegularFile`, `SymbolicLink`--- or `Directory` may be ignored--- * for `Overwrite` mode, the destination will be deleted (not recursively)--- before moving------ Throws:------ - `NoSuchThing` if source file does not exist--- - `PermissionDenied` if output directory cannot be written to--- - `PermissionDenied` if source directory cannot be opened--- - `SameFile` if destination and source are the same file--- (`HPathIOException`)------ Throws in `Strict` mode only:------ - `AlreadyExists` if destination already exists------ Note: calls `rename` (but does not allow to rename over existing files)-moveFile :: Path b1 -- ^ file to move- -> Path b2 -- ^ destination- -> CopyMode- -> IO ()-moveFile from to cm = do- throwSameFile from to- case cm of- Strict -> catchErrno [eXDEV] (renameFile from to) $ do- easyCopy from to Strict FailEarly- easyDelete from- Overwrite -> do- ft <- getFileType from- writable <- toAbs to >>= isWritable- case ft of- RegularFile -> do- exists <- doesFileExist to- when (exists && writable) (deleteFile to)- SymbolicLink -> do- exists <- doesFileExist to- when (exists && writable) (deleteFile to)- Directory -> do- exists <- doesDirectoryExist to- when (exists && writable) (deleteDir to)- _ -> return ()- moveFile from to Strict------ --------------------- --[ 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]--- ---------------------------- |Default permissions for a new file.-newFilePerms :: FileMode-newFilePerms- = ownerWriteMode- `unionFileModes` ownerReadMode- `unionFileModes` groupWriteMode- `unionFileModes` groupReadMode- `unionFileModes` otherWriteMode- `unionFileModes` otherReadMode----- |Default permissions for a new directory.-newDirPerms :: FileMode-newDirPerms- = ownerModes- `unionFileModes` groupExecuteMode- `unionFileModes` groupReadMode- `unionFileModes` otherExecuteMode- `unionFileModes` otherReadMode---- -------------------------- --[ Directory reading ]--- ------------------------------ |Gets all filenames of the given directory. This excludes "." and "..".--- This version does not follow symbolic links.------ The contents are not sorted and there is no guarantee on the ordering.------ Throws:------ - `NoSuchThing` if directory does not exist--- - `InappropriateType` if file type is wrong (file)--- - `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- where- parseMaybe :: ByteString -> Maybe (Path Fn)- parseMaybe = parseFn----- ---------------------------- --[ FileType operations ]--- -------------------------------- |Get the file type of the file located at the given path. Does--- not follow symbolic links.------ Throws:------ - `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- decide fs- where- decide fs- | PF.isDirectory fs = return Directory- | PF.isRegularFile fs = return RegularFile- | PF.isSymbolicLink fs = return SymbolicLink- | PF.isBlockDevice fs = return BlockDevice- | PF.isCharacterDevice fs = return CharacterDevice- | PF.isNamedPipe fs = return NamedPipe- | PF.isSocket fs = return Socket- | otherwise = ioError $ userError "No filetype?!"---- --------------- --[ Others ]--- -------------------- |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 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
− src/HPath/IO.hs-boot
@@ -1,8 +0,0 @@-module HPath.IO where---import HPath--canonicalizePath :: Path b -> IO (Path Abs)--toAbs :: Path b -> IO (Path Abs)
− src/HPath/IO/Errors.hs
@@ -1,360 +0,0 @@--- |--- Module : HPath.IO.Errors--- Copyright : © 2016 Julian Ospald--- License : BSD3------ Maintainer : Julian Ospald <hasufell@posteo.de>--- Stability : experimental--- Portability : portable------ Provides error handling.--{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE ScopedTypeVariables #-}--module HPath.IO.Errors- (- -- * Types- HPathIOException(..)- , RecursiveFailureHint(..)-- -- * Exception identifiers- , isSameFile- , isDestinationInSource- , isRecursiveFailure- , isReadContentsFailed- , isCreateDirFailed- , isCopyFileFailed- , isRecreateSymlinkFailed-- -- * Path based functions- , throwFileDoesExist- , throwDirDoesExist- , throwSameFile- , sameFile- , throwDestinationInSource- , doesFileExist- , doesDirectoryExist- , isWritable- , canOpenDirectory-- -- * Error handling functions- , catchErrno- , rethrowErrnoAs- , handleIOError- , bracketeer- , reactOnError- )- where---import Control.Applicative- (- (<$>)- )-import Control.Exception-import Control.Monad- (- forM- , when- )-import Control.Monad.IfElse- (- whenM- )-import Data.ByteString- (- ByteString- )-import Data.ByteString.UTF8- (- toString- )-import Data.Typeable- (- Typeable- )-import Foreign.C.Error- (- getErrno- , Errno- )-import GHC.IO.Exception- (- IOErrorType- )-import HPath-import HPath.Internal- (- Path(..)- )-import {-# SOURCE #-} HPath.IO- (- canonicalizePath- , toAbs- )-import System.IO.Error- (- alreadyExistsErrorType- , catchIOError- , ioeGetErrorType- , mkIOError- )--import qualified System.Posix.Directory.ByteString as PFD-import System.Posix.Files.ByteString- (- fileAccess- , getFileStatus- )-import qualified System.Posix.Files.ByteString as PF----- |Additional generic IO exceptions that the posix functions--- do not provide.-data HPathIOException = SameFile ByteString ByteString- | DestinationInSource ByteString ByteString- | RecursiveFailure [(RecursiveFailureHint, IOException)]- deriving (Eq, Show, Typeable)----- |A type for giving failure hints on recursive failure, which allows--- to programmatically make choices without examining--- the weakly typed I/O error attributes (like `ioeGetFileName`).------ 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- deriving (Eq, Show)---instance Exception HPathIOException---toConstr :: HPathIOException -> String-toConstr SameFile {} = "SameFile"-toConstr DestinationInSource {} = "DestinationInSource"-toConstr RecursiveFailure {} = "RecursiveFailure"------ ------------------------------ --[ Exception identifiers ]--- --------------------------------isSameFile, isDestinationInSource, isRecursiveFailure :: HPathIOException -> Bool-isSameFile ex = toConstr (ex :: HPathIOException) == toConstr SameFile{}-isDestinationInSource ex = toConstr (ex :: HPathIOException) == toConstr DestinationInSource{}-isRecursiveFailure ex = toConstr (ex :: HPathIOException) == toConstr RecursiveFailure{}---isReadContentsFailed, isCreateDirFailed, isCopyFileFailed, isRecreateSymlinkFailed ::RecursiveFailureHint -> Bool-isReadContentsFailed ReadContentsFailed{} = True-isReadContentsFailed _ = False-isCreateDirFailed CreateDirFailed{} = True-isCreateDirFailed _ = False-isCopyFileFailed CopyFileFailed{} = True-isCopyFileFailed _ = False-isRecreateSymlinkFailed RecreateSymlinkFailed{} = True-isRecreateSymlinkFailed _ = False------ ----------------------------- --[ Path based functions ]--- --------------------------------- |Throws `AlreadyExists` `IOError` if file exists.-throwFileDoesExist :: Path b -> IO ()-throwFileDoesExist fp@(MkPath bs) =- whenM (doesFileExist fp)- (ioError . mkIOError- alreadyExistsErrorType- "File already exists"- Nothing- $ (Just (toString $ bs))- )----- |Throws `AlreadyExists` `IOError` if directory exists.-throwDirDoesExist :: Path b -> IO ()-throwDirDoesExist fp@(MkPath bs) =- whenM (doesDirectoryExist fp)- (ioError . mkIOError- alreadyExistsErrorType- "Directory already exists"- Nothing- $ (Just (toString $ bs))- )----- |Uses `isSameFile` and throws `SameFile` if it returns True.-throwSameFile :: Path b1- -> Path b2- -> IO ()-throwSameFile fp1@(MkPath bs1) fp2@(MkPath bs2) =- whenM (sameFile fp1 fp2)- (throwIO $ SameFile bs1 bs2)----- |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-- 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--- |Checks whether the destination directory is contained--- 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- -> IO ()-throwDestinationInSource (MkPath sbs) dest@(MkPath dbs) = do- destAbs <- toAbs dest- dest' <- (\x -> maybe x (\y -> x </> y) $ basename 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 sbs- when (elem sid dids)- (throwIO $ DestinationInSource dbs sbs)----- |Checks if the given file exists and is not a directory.--- Does not follow symlinks.-doesFileExist :: Path b -> IO Bool-doesFileExist (MkPath bs) =- handleIOError (\_ -> return False) $ do- 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 b -> IO Bool-doesDirectoryExist (MkPath bs) =- handleIOError (\_ -> return False) $ do- fs <- PF.getSymbolicLinkStatus bs- return $ PF.isDirectory fs----- |Checks whether a file or folder is writable.-isWritable :: Path b -> IO Bool-isWritable (MkPath bs) =- handleIOError (\_ -> return 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 b -> IO Bool-canOpenDirectory (MkPath bs) =- handleIOError (\_ -> return False) $ do- bracket (PFD.openDirStream bs)- PFD.closeDirStream- (\_ -> return ())- return True----- --------------------------------- --[ Error handling functions ]--- ------------------------------------- |Carries out an action, then checks if there is an IOException and--- a specific errno. If so, then it carries out another action, otherwise--- it rethrows the error.-catchErrno :: [Errno] -- ^ errno to catch- -> IO a -- ^ action to try, which can raise an IOException- -> IO a -- ^ action to carry out in case of an IOException and- -- if errno matches- -> IO a-catchErrno en a1 a2 =- catchIOError a1 $ \e -> do- errno <- getErrno- if errno `elem` en- then a2- else ioError e----- |Execute the given action and retrow IO exceptions as a new Exception--- that have the given errno. If errno does not match the exception is rethrown--- as is.-rethrowErrnoAs :: Exception e- => [Errno] -- ^ errno to catch- -> e -- ^ rethrow as if errno matches- -> IO a -- ^ action to try- -> IO a-rethrowErrnoAs en fmex action = catchErrno en action (throwIO fmex)------ |Like `catchIOError`, with arguments swapped.-handleIOError :: (IOError -> IO a) -> IO a -> IO a-handleIOError = flip catchIOError----- |Like `bracket`, but allows to have different clean-up--- actions depending on whether the in-between computation--- has raised an exception or not. -bracketeer :: IO a -- ^ computation to run first- -> (a -> IO b) -- ^ computation to run last, when- -- no exception was raised- -> (a -> IO b) -- ^ computation to run last,- -- when an exception was raised- -> (a -> IO c) -- ^ computation to run in-between- -> IO c-bracketeer before after afterEx thing =- mask $ \restore -> do- a <- before- r <- restore (thing a) `onException` afterEx a- _ <- after a- return r---reactOnError :: IO a- -> [(IOErrorType, IO a)] -- ^ reaction on IO errors- -> [(HPathIOException, IO a)] -- ^ reaction on HPathIOException- -> IO a-reactOnError a ios fmios =- a `catches` [iohandler, fmiohandler]- where- iohandler = Handler $- \(ex :: IOException) ->- foldr (\(t, a') y -> if ioeGetErrorType ex == t- then a'- else y)- (throwIO ex)- ios- fmiohandler = Handler $- \(ex :: HPathIOException) ->- foldr (\(t, a') y -> if toConstr ex == toConstr t- then a'- else y)- (throwIO ex)- fmios-
− src/System/Posix/Directory/Foreign.hsc
@@ -1,55 +0,0 @@-module System.Posix.Directory.Foreign where--import Data.Bits-import Data.List (foldl')-import Foreign.C.Types--#include <limits.h>-#include <stdlib.h>-#include <dirent.h>-#include <sys/types.h>-#include <sys/stat.h>-#include <fcntl.h>--newtype DirType = DirType Int deriving (Eq, Show)-data Flags = Flags Int | UnsupportedFlag String deriving (Eq, Show)--unFlags :: Flags -> Int-unFlags (Flags i) = i-unFlags (UnsupportedFlag name) = error (name ++ " is not supported on this platform")---- |Returns @True@ if posix-paths was compiled with support for the provided--- flag. (As of this writing, the only flag for which this check may be--- necessary is 'oCloexec'; all other flags will always yield @True@.)-isSupported :: Flags -> Bool-isSupported (Flags _) = True-isSupported _ = False---- |@O_CLOEXEC@ is not supported on every POSIX platform. Use--- @'isSupported' oCloexec@ to determine if support for @O_CLOEXEC@ was--- compiled into your version of posix-paths. (If not, using @oCloexec@ will--- throw an exception.)-oCloexec :: Flags-#ifdef O_CLOEXEC-oCloexec = Flags #{const O_CLOEXEC}-#else-{-# WARNING oCloexec- "This version of posix-paths was compiled without @O_CLOEXEC@ support." #-}-oCloexec = UnsupportedFlag "O_CLOEXEC"-#endif------ If these enum declarations occur earlier in the file, haddock--- gets royally confused about the above doc comments.--- Probably http://trac.haskell.org/haddock/ticket/138--#{enum DirType, DirType, DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK, DT_UNKNOWN}--#{enum Flags, Flags, O_APPEND, O_ASYNC, O_CREAT, O_DIRECTORY, O_EXCL, O_NOCTTY, O_NOFOLLOW, O_NONBLOCK, O_RDONLY, O_WRONLY, O_RDWR, O_SYNC, O_TRUNC}--pathMax :: Int-pathMax = #{const PATH_MAX}--unionFlags :: [Flags] -> CInt-unionFlags = fromIntegral . foldl' ((. unFlags) . (.|.)) 0
− src/System/Posix/Directory/Traversals.hs
@@ -1,264 +0,0 @@--- |--- Module : System.Posix.Directory.Traversals--- Copyright : © 2016 Julian Ospald--- License : BSD3------ Maintainer : Julian Ospald <hasufell@posteo.de>--- Stability : experimental--- Portability : portable------ Traversal and read operations on directories.---{-# LANGUAGE CPP #-}-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PackageImports #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE ViewPatterns #-}--{-# OPTIONS_GHC -Wall #-}---module System.Posix.Directory.Traversals (-- getDirectoryContents-, getDirectoryContents'--, allDirectoryContents-, allDirectoryContents'-, traverseDirectory---- lower-level stuff-, readDirEnt-, packDirStream-, unpackDirStream-, fdOpendir--, realpath-) where---#if __GLASGOW_HASKELL__ < 710-import Control.Applicative ((<$>))-#endif-import Control.Monad-import System.Posix.FilePath ((</>))-import System.Posix.Directory.Foreign--import qualified System.Posix as Posix-import System.IO.Error-import Control.Exception-import qualified Data.ByteString.Char8 as BS-import System.Posix.ByteString.FilePath-import System.Posix.Directory.ByteString as PosixBS-import System.Posix.Files.ByteString--import System.IO.Unsafe-import "unix" System.Posix.IO.ByteString (closeFd)-import Unsafe.Coerce (unsafeCoerce)-import Foreign.C.Error-import Foreign.C.String-import Foreign.C.Types-import Foreign.Marshal.Alloc (alloca,allocaBytes)-import Foreign.Ptr-import Foreign.Storable------------------------------------------------------------------- | Get all files from a directory and its subdirectories.------ Upon entering a directory, 'allDirectoryContents' will get all entries--- strictly. However the returned list is lazy in that directories will only--- be accessed on demand.------ Follows symbolic links for the input dir.-allDirectoryContents :: RawFilePath -> IO [RawFilePath]-allDirectoryContents topdir = do- namesAndTypes <- getDirectoryContents topdir- let properNames = filter ((`notElem` [".", ".."]) . snd) namesAndTypes- paths <- forM properNames $ \(typ,name) -> unsafeInterleaveIO $ do- let path = topdir </> name- case () of- () | typ == dtDir -> allDirectoryContents path- | typ == dtUnknown -> do- isDir <- isDirectory <$> getFileStatus path- if isDir- then allDirectoryContents path- else return [path]- | otherwise -> return [path]- return (topdir : concat paths)---- | Get all files from a directory and its subdirectories strictly.------ Follows symbolic links for the input dir.-allDirectoryContents' :: RawFilePath -> IO [RawFilePath]-allDirectoryContents' = fmap reverse . traverseDirectory (\acc fp -> return (fp:acc)) []--- this uses traverseDirectory because it's more efficient than forcing the--- lazy version.---- | Recursively apply the 'action' to the parent directory and all--- files/subdirectories.------ This function allows for memory-efficient traversals.------ Follows symbolic links for the input dir.-traverseDirectory :: (s -> RawFilePath -> IO s) -> s -> RawFilePath -> IO s-traverseDirectory act s0 topdir = toploop- where- toploop = do- isDir <- isDirectory <$> getFileStatus topdir- s' <- act s0 topdir- if isDir then actOnDirContents topdir s' loop- else return s'- loop typ path acc = do- isDir <- case () of- () | typ == dtDir -> return True- | typ == dtUnknown -> isDirectory <$> getFileStatus path- | otherwise -> return False- if isDir- then act acc path >>= \acc' -> actOnDirContents path acc' loop- else act acc path--actOnDirContents :: RawFilePath- -> b- -> (DirType -> RawFilePath -> b -> IO b)- -> IO b-actOnDirContents pathRelToTop b f =- modifyIOError ((`ioeSetFileName` (BS.unpack pathRelToTop)) .- (`ioeSetLocation` "findBSTypRel")) $- bracket- (openDirStream pathRelToTop)- Posix.closeDirStream- (\dirp -> loop dirp b)- where- loop dirp b' = do- (typ,e) <- readDirEnt dirp- if (e == "")- then return b'- else- if (e == "." || e == "..")- then loop dirp b'- else f typ (pathRelToTop </> e) b' >>= loop dirp---------------------------------------------------------------- dodgy stuff--type CDir = ()-type CDirent = ()---- Posix doesn't export DirStream, so to re-use that type we need to use--- unsafeCoerce. It's just a newtype, so this is a legitimate usage.--- ugly trick.-unpackDirStream :: DirStream -> Ptr CDir-unpackDirStream = unsafeCoerce--packDirStream :: Ptr CDir -> DirStream-packDirStream = unsafeCoerce---- the __hscore_* functions are defined in the unix package. We can import them and let--- the linker figure it out.-foreign import ccall unsafe "__hscore_readdir"- c_readdir :: Ptr CDir -> Ptr (Ptr CDirent) -> IO CInt--foreign import ccall unsafe "__hscore_free_dirent"- c_freeDirEnt :: Ptr CDirent -> IO ()--foreign import ccall unsafe "__hscore_d_name"- c_name :: Ptr CDirent -> IO CString--foreign import ccall unsafe "__posixdir_d_type"- c_type :: Ptr CDirent -> IO DirType--foreign import ccall "realpath"- c_realpath :: CString -> CString -> IO CString--foreign import ccall unsafe "fdopendir"- c_fdopendir :: Posix.Fd -> IO (Ptr ())--------------------------------------------------------------- less dodgy but still lower-level---readDirEnt :: DirStream -> IO (DirType, RawFilePath)-readDirEnt (unpackDirStream -> dirp) =- alloca $ \ptr_dEnt -> loop ptr_dEnt- where- loop ptr_dEnt = do- resetErrno- r <- c_readdir dirp ptr_dEnt- if (r == 0)- then do- dEnt <- peek ptr_dEnt- if (dEnt == nullPtr)- then return (dtUnknown,BS.empty)- else do- dName <- c_name dEnt >>= peekFilePath- dType <- c_type dEnt- c_freeDirEnt dEnt- return (dType, dName)- else do- errno <- getErrno- if (errno == eINTR)- then loop ptr_dEnt- else do- let (Errno eo) = errno- if (eo == 0)- then return (dtUnknown,BS.empty)- else throwErrno "readDirEnt"----- |Gets all directory contents (not recursively).-getDirectoryContents :: RawFilePath -> IO [(DirType, RawFilePath)]-getDirectoryContents path =- modifyIOError ((`ioeSetFileName` (BS.unpack path)) .- (`ioeSetLocation` "System.Posix.Directory.Traversals.getDirectoryContents")) $- bracket- (PosixBS.openDirStream path)- PosixBS.closeDirStream- _dirloop----- |Binding to @fdopendir(3)@.-fdOpendir :: Posix.Fd -> IO DirStream-fdOpendir fd =- packDirStream <$> throwErrnoIfNull "fdOpendir" (c_fdopendir fd)----- |Like `getDirectoryContents` except for a file descriptor.------ To avoid complicated error checks, the file descriptor is--- __always__ closed, even if `fdOpendir` fails. Usually, this--- only happens on successful `fdOpendir` and after the directory--- stream is closed. Also see the manpage of @fdopendir(3)@ for--- more details.-getDirectoryContents' :: Posix.Fd -> IO [(DirType, RawFilePath)]-getDirectoryContents' fd = do- dirstream <- fdOpendir fd `catchIOError` \e -> do- closeFd fd- ioError e- -- closeDirStream closes the filedescriptor- finally (_dirloop dirstream) (PosixBS.closeDirStream dirstream)---_dirloop :: DirStream -> IO [(DirType, RawFilePath)]-{-# INLINE _dirloop #-}-_dirloop dirp = do- t@(_typ,e) <- readDirEnt dirp- if BS.null e then return [] else do- es <- _dirloop dirp- return (t:es)----- | return the canonicalized absolute pathname------ like canonicalizePath, but uses @realpath(3)@-realpath :: RawFilePath -> IO RawFilePath-realpath inp =- allocaBytes pathMax $ \tmp -> do- void $ BS.useAsCString inp $ \cstr -> throwErrnoIfNull "realpath" $ c_realpath cstr tmp- BS.packCString tmp
− src/System/Posix/FD.hs
@@ -1,75 +0,0 @@--- |--- Module : System.Posix.FD--- Copyright : © 2016 Julian Ospald--- License : BSD3------ Maintainer : Julian Ospald <hasufell@posteo.de>--- Stability : experimental--- Portability : portable------ Provides an alternative for `System.Posix.IO.ByteString.openFd`--- which gives us more control on what status flags to pass to the--- low-level @open(2)@ call, in contrast to the unix package.---{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TupleSections #-}--{-# OPTIONS_GHC -Wall #-}---module System.Posix.FD (- openFd-) where---import Foreign.C.String-import Foreign.C.Types-import System.Posix.Directory.Foreign-import qualified System.Posix as Posix-import System.Posix.ByteString.FilePath---foreign import ccall unsafe "open"- c_open :: CString -> CInt -> Posix.CMode -> IO CInt---open_ :: CString- -> Posix.OpenMode- -> [Flags]- -> Maybe Posix.FileMode- -> IO Posix.Fd-open_ str how optional_flags maybe_mode = do- fd <- c_open str all_flags mode_w- return (Posix.Fd fd)- where- all_flags = unionFlags $ optional_flags ++ [open_mode] ++ creat--- (creat, mode_w) = case maybe_mode of- Nothing -> ([],0)- Just x -> ([oCreat], x)-- open_mode = case how of- Posix.ReadOnly -> oRdonly- Posix.WriteOnly -> oWronly- Posix.ReadWrite -> oRdwr----- |Open and optionally create this file. See 'System.Posix.Files'--- for information on how to use the 'FileMode' type.------ Note that passing @Just x@ as the 4th argument triggers the--- `oCreat` status flag, which must be set when you pass in `oExcl`--- to the status flags. Also see the manpage for @open(2)@.-openFd :: RawFilePath- -> Posix.OpenMode- -> [Flags] -- ^ status flags of @open(2)@- -> Maybe Posix.FileMode -- ^ @Just x@ => creates the file with the given modes, Nothing => the file must exist.- -> IO Posix.Fd-openFd name how optional_flags maybe_mode =- withFilePath name $ \str ->- throwErrnoPathIfMinus1Retry "openFd" name $- open_ str how optional_flags maybe_mode-
− src/System/Posix/FilePath.hs
@@ -1,823 +0,0 @@--- |--- Module : System.Posix.FilePath--- Copyright : © 2016 Julian Ospald--- License : BSD3------ Maintainer : Julian Ospald <hasufell@posteo.de>--- Stability : experimental--- Portability : portable------ The equivalent of "System.FilePath" on raw (byte string) file paths.------ Not all functions of "System.FilePath" are implemented yet. Feel free to contribute!---{-# LANGUAGE CPP #-}-{-# LANGUAGE TupleSections #-}--{-# OPTIONS_GHC -Wall #-}---module System.Posix.FilePath (-- -- * Separator predicates- pathSeparator-, isPathSeparator-, searchPathSeparator-, isSearchPathSeparator-, extSeparator-, isExtSeparator-- -- * $PATH methods-, splitSearchPath-, getSearchPath-- -- * Extension functions-, splitExtension-, takeExtension-, replaceExtension-, dropExtension-, addExtension-, hasExtension-, (<.>)-, splitExtensions-, dropExtensions-, takeExtensions-, stripExtension-- -- * Filename\/directory functions-, splitFileName-, takeFileName-, replaceFileName-, dropFileName-, takeBaseName-, replaceBaseName-, takeDirectory-, replaceDirectory-, combine-, (</>)-, splitPath-, joinPath-, splitDirectories-- -- * Trailing slash functions-, hasTrailingPathSeparator-, addTrailingPathSeparator-, dropTrailingPathSeparator-- -- * File name manipulations-, normalise-, makeRelative-, equalFilePath-, isRelative-, isAbsolute-, isValid-, makeValid-, isFileName-, hasParentDir-, hiddenFile--, module System.Posix.ByteString.FilePath-) where--import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import Data.String (fromString)-import System.Posix.ByteString.FilePath-import qualified System.Posix.Env.ByteString as PE--import Data.Maybe (isJust)-import Data.Word8-#if !MIN_VERSION_bytestring(0,10,8)-import qualified Data.List as L-#endif-import Control.Arrow (second)---- $setup--- >>> import Data.Char--- >>> import Data.Maybe--- >>> import Test.QuickCheck--- >>> import Control.Applicative--- >>> import qualified Data.ByteString as BS--- >>> instance Arbitrary ByteString where arbitrary = BS.pack <$> arbitrary--- >>> instance CoArbitrary ByteString where coarbitrary = coarbitrary . BS.unpack------ >>> let _chr :: Word8 -> Char; _chr = chr . fromIntegral------------------------------- Separator predicates----- | Path separator character-pathSeparator :: Word8-pathSeparator = _slash----- | Check if a character is the path separator------ prop> \n -> (_chr n == '/') == isPathSeparator n-isPathSeparator :: Word8 -> Bool-isPathSeparator = (== pathSeparator)----- | Search path separator-searchPathSeparator :: Word8-searchPathSeparator = _colon----- | Check if a character is the search path separator------ prop> \n -> (_chr n == ':') == isSearchPathSeparator n-isSearchPathSeparator :: Word8 -> Bool-isSearchPathSeparator = (== searchPathSeparator)----- | File extension separator-extSeparator :: Word8-extSeparator = _period----- | Check if a character is the file extension separator------ prop> \n -> (_chr n == '.') == isExtSeparator n-isExtSeparator :: Word8 -> Bool-isExtSeparator = (== extSeparator)------------------------------- $PATH methods----- | Take a ByteString, split it on the 'searchPathSeparator'.--- Blank items are converted to @.@.------ Follows the recommendations in--- <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>------ >>> splitSearchPath "File1:File2:File3"--- ["File1","File2","File3"]--- >>> splitSearchPath "File1::File2:File3"--- ["File1",".","File2","File3"]--- >>> splitSearchPath ""--- ["."]-splitSearchPath :: ByteString -> [RawFilePath]-splitSearchPath = f- where- f bs = let (pre, post) = BS.break isSearchPathSeparator bs- in if BS.null post- then g pre- else g pre ++ f (BS.tail post)- g x- | BS.null x = [BS.singleton _period]- | otherwise = [x]----- | Get a list of 'RawFilePath's in the $PATH variable.-getSearchPath :: IO [RawFilePath]-getSearchPath = fmap (maybe [] splitSearchPath) (PE.getEnv $ fromString "PATH")------------------------------- Extension functions---- | Split a 'RawFilePath' into a path+filename and extension------ >>> splitExtension "file.exe"--- ("file",".exe")--- >>> splitExtension "file"--- ("file","")--- >>> splitExtension "/path/file.tar.gz"--- ("/path/file.tar",".gz")------ prop> \path -> uncurry (BS.append) (splitExtension path) == path-splitExtension :: RawFilePath -> (RawFilePath, ByteString)-splitExtension x = if BS.null basename- then (x,BS.empty)- else (BS.append path (BS.init basename),BS.cons extSeparator fileExt)- where- (path,file) = splitFileNameRaw x- (basename,fileExt) = BS.breakEnd isExtSeparator file----- | Get the final extension from a 'RawFilePath'------ >>> takeExtension "file.exe"--- ".exe"--- >>> takeExtension "file"--- ""--- >>> takeExtension "/path/file.tar.gz"--- ".gz"-takeExtension :: RawFilePath -> ByteString-takeExtension = snd . splitExtension----- | Change a file's extension------ prop> \path -> let ext = takeExtension path in replaceExtension path ext == path-replaceExtension :: RawFilePath -> ByteString -> RawFilePath-replaceExtension path ext = dropExtension path <.> ext----- | Drop the final extension from a 'RawFilePath'------ >>> dropExtension "file.exe"--- "file"--- >>> dropExtension "file"--- "file"--- >>> dropExtension "/path/file.tar.gz"--- "/path/file.tar"-dropExtension :: RawFilePath -> RawFilePath-dropExtension = fst . splitExtension----- | Add an extension to a 'RawFilePath'------ >>> addExtension "file" ".exe"--- "file.exe"--- >>> addExtension "file.tar" ".gz"--- "file.tar.gz"--- >>> addExtension "/path/" ".ext"--- "/path/.ext"-addExtension :: RawFilePath -> ByteString -> RawFilePath-addExtension file ext- | BS.null ext = file- | isExtSeparator (BS.head ext) = BS.append file ext- | otherwise = BS.intercalate (BS.singleton extSeparator) [file, ext]----- | Check if a 'RawFilePath' has an extension------ >>> hasExtension "file"--- False--- >>> hasExtension "file.tar"--- True--- >>> hasExtension "/path.part1/"--- False-hasExtension :: RawFilePath -> Bool-hasExtension = isJust . BS.elemIndex extSeparator . takeFileName----- | Operator version of 'addExtension'-(<.>) :: RawFilePath -> ByteString -> RawFilePath-(<.>) = addExtension----- | Split a 'RawFilePath' on the first extension.------ >>> splitExtensions "/path/file.tar.gz"--- ("/path/file",".tar.gz")------ prop> \path -> uncurry addExtension (splitExtensions path) == path-splitExtensions :: RawFilePath -> (RawFilePath, ByteString)-splitExtensions x = if BS.null basename- then (path,fileExt)- else (BS.append path basename,fileExt)- where- (path,file) = splitFileNameRaw x- (basename,fileExt) = BS.break isExtSeparator file----- | Remove all extensions from a 'RawFilePath'------ >>> dropExtensions "/path/file.tar.gz"--- "/path/file"-dropExtensions :: RawFilePath -> RawFilePath-dropExtensions = fst . splitExtensions----- | Take all extensions from a 'RawFilePath'------ >>> takeExtensions "/path/file.tar.gz"--- ".tar.gz"-takeExtensions :: RawFilePath -> ByteString-takeExtensions = snd . splitExtensions----- | Drop the given extension from a FilePath, and the @\".\"@ preceding it.--- Returns 'Nothing' if the FilePath does not have the given extension, or--- 'Just' and the part before the extension if it does.------ This function can be more predictable than 'dropExtensions',--- especially if the filename might itself contain @.@ characters.------ >>> stripExtension "hs.o" "foo.x.hs.o"--- Just "foo.x"--- >>> stripExtension "hi.o" "foo.x.hs.o"--- Nothing--- >>> stripExtension ".c.d" "a.b.c.d"--- Just "a.b"--- >>> stripExtension ".c.d" "a.b..c.d"--- Just "a.b."--- >>> stripExtension "baz" "foo.bar"--- Nothing--- >>> stripExtension "bar" "foobar"--- Nothing------ prop> \path -> stripExtension "" path == Just path--- prop> \path -> dropExtension path == fromJust (stripExtension (takeExtension path) path)--- prop> \path -> dropExtensions path == fromJust (stripExtension (takeExtensions path) path)-stripExtension :: ByteString -> RawFilePath -> Maybe RawFilePath-stripExtension bs path- | BS.null bs = Just path- | otherwise = stripSuffix' dotExt path- where- dotExt = if isExtSeparator $ BS.head bs- then bs- else extSeparator `BS.cons` bs-#if MIN_VERSION_bytestring(0,10,8)- stripSuffix' = BS.stripSuffix-#else- stripSuffix' xs ys = fmap (BS.pack . reverse) $ L.stripPrefix (reverse $ BS.unpack xs) (reverse $ BS.unpack ys)-#endif------------------------------ Filename/directory functions----- | Split a 'RawFilePath' into (path,file). 'combine' is the inverse------ >>> splitFileName "path/file.txt"--- ("path/","file.txt")--- >>> splitFileName "path/"--- ("path/","")--- >>> splitFileName "file.txt"--- ("./","file.txt")------ prop> \path -> uncurry combine (splitFileName path) == path || fst (splitFileName path) == "./"-splitFileName :: RawFilePath -> (RawFilePath, RawFilePath)-splitFileName x = if BS.null path- then (dotSlash, file)- else (path,file)- where- (path,file) = splitFileNameRaw x- dotSlash = _period `BS.cons` (BS.singleton pathSeparator)----- | Get the file name------ >>> takeFileName "path/file.txt"--- "file.txt"--- >>> takeFileName "path/"--- ""-takeFileName :: RawFilePath -> RawFilePath-takeFileName = snd . splitFileName----- | Change the file name------ prop> \path -> replaceFileName path (takeFileName path) == path-replaceFileName :: RawFilePath -> ByteString -> RawFilePath-replaceFileName x y = fst (splitFileNameRaw x) </> y----- | Drop the file name------ >>> dropFileName "path/file.txt"--- "path/"--- >>> dropFileName "file.txt"--- "./"-dropFileName :: RawFilePath -> RawFilePath-dropFileName = fst . splitFileName----- | Get the file name, without a trailing extension------ >>> takeBaseName "path/file.tar.gz"--- "file.tar"--- >>> takeBaseName ""--- ""-takeBaseName :: RawFilePath -> ByteString-takeBaseName = dropExtension . takeFileName----- | Change the base name------ >>> replaceBaseName "path/file.tar.gz" "bob"--- "path/bob.gz"------ prop> \path -> replaceBaseName path (takeBaseName path) == path-replaceBaseName :: RawFilePath -> ByteString -> RawFilePath-replaceBaseName path name = combineRaw dir (name <.> ext)- where- (dir,file) = splitFileNameRaw path- ext = takeExtension file----- | Get the directory, moving up one level if it's already a directory------ >>> takeDirectory "path/file.txt"--- "path"--- >>> takeDirectory "file"--- "."--- >>> takeDirectory "/path/to/"--- "/path/to"--- >>> takeDirectory "/path/to"--- "/path"-takeDirectory :: RawFilePath -> RawFilePath-takeDirectory x = case () of- () | x == BS.singleton pathSeparator -> x- | BS.null res && not (BS.null file) -> file- | otherwise -> res- where- res = fst $ BS.spanEnd isPathSeparator file- file = dropFileName x----- | Change the directory component of a 'RawFilePath'------ prop> \path -> replaceDirectory path (takeDirectory path) `equalFilePath` path || takeDirectory path == "."-replaceDirectory :: RawFilePath -> ByteString -> RawFilePath-replaceDirectory file dir = combineRaw dir (takeFileName file)----- | Join two paths together------ >>> combine "/" "file"--- "/file"--- >>> combine "/path/to" "file"--- "/path/to/file"--- >>> combine "file" "/absolute/path"--- "/absolute/path"-combine :: RawFilePath -> RawFilePath -> RawFilePath-combine a b | not (BS.null b) && isPathSeparator (BS.head b) = b- | otherwise = combineRaw a b----- | Operator version of combine-(</>) :: RawFilePath -> RawFilePath -> RawFilePath-(</>) = combine---- | Split a path into a list of components:------ >>> splitPath "/path/to/file.txt"--- ["/","path/","to/","file.txt"]------ prop> \path -> BS.concat (splitPath path) == path-splitPath :: RawFilePath -> [RawFilePath]-splitPath = splitter- where- splitter x- | BS.null x = []- | otherwise = case BS.elemIndex pathSeparator x of- Nothing -> [x]- Just ix -> case BS.findIndex (not . isPathSeparator) $ BS.drop (ix+1) x of- Nothing -> [x]- Just runlen -> uncurry (:) . second splitter $ BS.splitAt (ix+1+runlen) x----- | Join a split path back together------ prop> \path -> joinPath (splitPath path) == path------ >>> joinPath ["path","to","file.txt"]--- "path/to/file.txt"-joinPath :: [RawFilePath] -> RawFilePath-joinPath = foldr (</>) BS.empty----- | Like 'splitPath', but without trailing slashes------ >>> splitDirectories "/path/to/file.txt"--- ["/","path","to","file.txt"]--- >>> splitDirectories ""--- []-splitDirectories :: RawFilePath -> [RawFilePath]-splitDirectories x- | BS.null x = []- | isPathSeparator (BS.head x) = let (root,rest) = BS.splitAt 1 x- in root : splitter rest- | otherwise = splitter x- where- splitter = filter (not . BS.null) . BS.split pathSeparator------------------------------- Trailing slash functions---- | Check if the last character of a 'RawFilePath' is '/'.------ >>> hasTrailingPathSeparator "/path/"--- True--- >>> hasTrailingPathSeparator "/"--- True--- >>> hasTrailingPathSeparator "/path"--- False-hasTrailingPathSeparator :: RawFilePath -> Bool-hasTrailingPathSeparator x- | BS.null x = False- | otherwise = isPathSeparator $ BS.last x----- | Add a trailing path separator.------ >>> addTrailingPathSeparator "/path"--- "/path/"--- >>> addTrailingPathSeparator "/path/"--- "/path/"--- >>> addTrailingPathSeparator "/"--- "/"-addTrailingPathSeparator :: RawFilePath -> RawFilePath-addTrailingPathSeparator x = if hasTrailingPathSeparator x- then x- else x `BS.snoc` pathSeparator----- | Remove a trailing path separator------ >>> dropTrailingPathSeparator "/path/"--- "/path"--- >>> dropTrailingPathSeparator "/path////"--- "/path"--- >>> dropTrailingPathSeparator "/"--- "/"--- >>> dropTrailingPathSeparator "//"--- "/"-dropTrailingPathSeparator :: RawFilePath -> RawFilePath-dropTrailingPathSeparator x- | x == BS.singleton pathSeparator = x- | otherwise = if hasTrailingPathSeparator x- then dropTrailingPathSeparator $ BS.init x- else x------------------------------- File name manipulations----- |Normalise a file.------ >>> normalise "/file/\\test////"--- "/file/\\test/"--- >>> normalise "/file/./test"--- "/file/test"--- >>> normalise "/test/file/../bob/fred/"--- "/test/file/../bob/fred/"--- >>> normalise "../bob/fred/"--- "../bob/fred/"--- >>> normalise "./bob/fred/"--- "bob/fred/"--- >>> normalise "./bob////.fred/./...///./..///#."--- "bob/.fred/.../../#."--- >>> normalise "."--- "."--- >>> normalise "./"--- "./"--- >>> normalise "./."--- "./"--- >>> normalise "/./"--- "/"--- >>> normalise "/"--- "/"--- >>> normalise "bob/fred/."--- "bob/fred/"--- >>> normalise "//home"--- "/home"-normalise :: RawFilePath -> RawFilePath-normalise filepath =- result `BS.append`- (if addPathSeparator- then BS.singleton pathSeparator- else BS.empty)- where- result = let n = f filepath- in if BS.null n- then BS.singleton _period- else n- addPathSeparator = isDirPath filepath &&- not (hasTrailingPathSeparator result)- isDirPath xs = hasTrailingPathSeparator xs- || not (BS.null xs) && BS.last xs == _period- && hasTrailingPathSeparator (BS.init xs)- f = joinPath . dropDots . propSep . splitDirectories- propSep :: [ByteString] -> [ByteString]- propSep (x:xs)- | BS.all (== pathSeparator) x = BS.singleton pathSeparator : xs- | otherwise = x : xs- propSep [] = []- dropDots :: [ByteString] -> [ByteString]- dropDots = filter (BS.singleton _period /=)------ | Contract a filename, based on a relative path. Note that the resulting--- path will never introduce @..@ paths, as the presence of symlinks--- means @..\/b@ may not reach @a\/b@ if it starts from @a\/c@. For a--- worked example see--- <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.------ >>> makeRelative "/directory" "/directory/file.ext"--- "file.ext"--- >>> makeRelative "/Home" "/home/bob"--- "/home/bob"--- >>> makeRelative "/home/" "/home/bob/foo/bar"--- "bob/foo/bar"--- >>> makeRelative "/fred" "bob"--- "bob"--- >>> makeRelative "/file/test" "/file/test/fred"--- "fred"--- >>> makeRelative "/file/test" "/file/test/fred/"--- "fred/"--- >>> makeRelative "some/path" "some/path/a/b/c"--- "a/b/c"------ prop> \p -> makeRelative p p == "."--- prop> \p -> makeRelative (takeDirectory p) p `equalFilePath` takeFileName p--- prop \x y -> equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x-makeRelative :: RawFilePath -> RawFilePath -> RawFilePath-makeRelative root path- | equalFilePath root path = BS.singleton _period- | takeAbs root /= takeAbs path = path- | otherwise = f (dropAbs root) (dropAbs path)- where- f x y- | BS.null x = BS.dropWhile isPathSeparator y- | otherwise = let (x1,x2) = g x- (y1,y2) = g y- in if equalFilePath x1 y1 then f x2 y2 else path- g x = (BS.dropWhile isPathSeparator a, BS.dropWhile isPathSeparator b)- where (a, b) = BS.break isPathSeparator $ BS.dropWhile isPathSeparator x- dropAbs x = snd $ BS.span (== _slash) x- takeAbs x = fst $ BS.span (== _slash) x----- |Equality of two filepaths. The filepaths are normalised--- and trailing path separators are dropped.------ >>> equalFilePath "foo" "foo"--- True--- >>> equalFilePath "foo" "foo/"--- True--- >>> equalFilePath "foo" "./foo"--- True--- >>> equalFilePath "" ""--- True--- >>> equalFilePath "foo" "/foo"--- False--- >>> equalFilePath "foo" "FOO"--- False--- >>> equalFilePath "foo" "../foo"--- False------ prop> \p -> equalFilePath p p-equalFilePath :: RawFilePath -> RawFilePath -> Bool-equalFilePath p1 p2 = f p1 == f p2- where- f x = dropTrailingPathSeparator $ normalise x----- | Check if a path is relative------ prop> \path -> isRelative path /= isAbsolute path-isRelative :: RawFilePath -> Bool-isRelative = not . isAbsolute----- | Check if a path is absolute------ >>> isAbsolute "/path"--- True--- >>> isAbsolute "path"--- False--- >>> isAbsolute ""--- False-isAbsolute :: RawFilePath -> Bool-isAbsolute x- | BS.length x > 0 = isPathSeparator (BS.head x)- | otherwise = False----- | Is a FilePath valid, i.e. could you create a file like it?------ >>> isValid ""--- False--- >>> isValid "\0"--- False--- >>> isValid "/random_ path:*"--- True-isValid :: RawFilePath -> Bool-isValid filepath- | BS.null filepath = False- | _nul `BS.elem` filepath = False- | otherwise = True----- | Take a FilePath and make it valid; does not change already valid FilePaths.------ >>> makeValid ""--- "_"--- >>> makeValid "file\0name"--- "file_name"------ prop> \p -> if isValid p then makeValid p == p else makeValid p /= p--- prop> \p -> isValid (makeValid p)-makeValid :: RawFilePath -> RawFilePath-makeValid path- | BS.null path = BS.singleton _underscore- | otherwise = BS.map (\x -> if x == _nul then _underscore else x) path----- | Is the given path a valid filename? This includes--- "." and "..".------ >>> isFileName "lal"--- True--- >>> isFileName "."--- True--- >>> isFileName ".."--- True--- >>> isFileName ""--- False--- >>> isFileName "\0"--- False--- >>> isFileName "/random_ path:*"--- False-isFileName :: RawFilePath -> Bool-isFileName filepath =- not (BS.singleton pathSeparator `BS.isInfixOf` filepath) &&- not (BS.null filepath) &&- not (_nul `BS.elem` filepath)----- | Check if the filepath has any parent directories in it.------ >>> hasParentDir "/.."--- True--- >>> hasParentDir "foo/bar/.."--- True--- >>> hasParentDir "foo/../bar/."--- True--- >>> hasParentDir "foo/bar"--- False--- >>> hasParentDir "foo"--- False--- >>> hasParentDir ""--- False--- >>> hasParentDir ".."--- False-hasParentDir :: RawFilePath -> Bool-hasParentDir filepath =- (pathSeparator `BS.cons` pathDoubleDot)- `BS.isSuffixOf` filepath- ||- (BS.singleton pathSeparator- `BS.append` pathDoubleDot- `BS.append` BS.singleton pathSeparator)- `BS.isInfixOf` filepath- ||- (pathDoubleDot `BS.append` BS.singleton pathSeparator)- `BS.isPrefixOf` filepath- where- pathDoubleDot = BS.pack [_period, _period]----- | Whether the file is a hidden file.------ >>> hiddenFile ".foo"--- True--- >>> hiddenFile "..foo.bar"--- True--- >>> hiddenFile "some/path/.bar"--- True--- >>> hiddenFile "..."--- True--- >>> hiddenFile "dod.bar"--- False--- >>> hiddenFile "."--- False--- >>> hiddenFile ".."--- False--- >>> hiddenFile ""--- False-hiddenFile :: RawFilePath -> Bool-hiddenFile fp- | fn == BS.pack [_period, _period] = False- | fn == BS.pack [_period] = False- | otherwise = BS.pack [extSeparator]- `BS.isPrefixOf` fn- where- fn = takeFileName fp------------------------------- internal stuff---- Just split the input FileName without adding/normalizing or changing--- anything.-splitFileNameRaw :: RawFilePath -> (RawFilePath, RawFilePath)-splitFileNameRaw = BS.breakEnd isPathSeparator---- | Combine two paths, assuming rhs is NOT absolute.-combineRaw :: RawFilePath -> RawFilePath -> RawFilePath-combineRaw a b | BS.null a = b- | BS.null b = a- | isPathSeparator (BS.last a) = BS.append a b- | otherwise = BS.intercalate (BS.singleton pathSeparator) [a, b]-
− test/HPath/IO/AppendFileSpec.hs
@@ -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)
− test/HPath/IO/CanonicalizePathSpec.hs
@@ -1,78 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.CanonicalizePathSpec where---import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils-----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CanonicalizePathSpec"- createTmpDir--setupFiles :: IO ()-setupFiles = do- createRegularFile' "file"- createDir' "dir"- createSymlink' "dirSym" "dir/"- createSymlink' "brokenSym" "nothing"- createSymlink' "fileSym" "file"--cleanupFiles :: IO ()-cleanupFiles = do- deleteFile' "file"- deleteDir' "dir"- deleteFile' "dirSym"- deleteFile' "brokenSym"- deleteFile' "fileSym"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.canonicalizePath" $ do-- -- successes --- it "canonicalizePath, all fine" $ do- path <- withTmpDir "file" return- canonicalizePath' "file"- `shouldReturn` path-- it "canonicalizePath, all fine" $ do- path <- withTmpDir "dir" return- canonicalizePath' "dir"- `shouldReturn` path-- it "canonicalizePath, all fine" $ do- path <- withTmpDir "file" return- canonicalizePath' "fileSym"- `shouldReturn` path-- it "canonicalizePath, all fine" $ do- path <- withTmpDir "dir" return- canonicalizePath' "dirSym"- `shouldReturn` path--- -- posix failures --- it "canonicalizePath, broken symlink" $- canonicalizePath' "brokenSym"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "canonicalizePath, file does not exist" $- canonicalizePath' "nothingBlah"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-
− test/HPath/IO/CopyDirRecursiveCollectFailuresSpec.hs
@@ -1,247 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}---module HPath.IO.CopyDirRecursiveCollectFailuresSpec where---import Test.Hspec-import Data.List (sort)-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import System.Exit-import System.Process-import Utils-import qualified Data.ByteString as BS-import Data.ByteString.UTF8 (toString)----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CopyDirRecursiveCollectFailuresSpec"- createTmpDir--setupFiles :: IO ()-setupFiles = do- createRegularFile' "alreadyExists"- createRegularFile' "wrongInput"- createSymlink' "wrongInputSymL" "inputDir/"- createDir' "alreadyExistsD"- createDir' "noPerms"- createDir' "noWritePerm"-- createDir' "inputDir"- createDir' "inputDir/bar"- createDir' "inputDir/foo"- createRegularFile' "inputDir/foo/inputFile1"- createRegularFile' "inputDir/inputFile2"- createRegularFile' "inputDir/bar/inputFile3"- writeFile' "inputDir/foo/inputFile1" "SDAADSdsada"- writeFile' "inputDir/inputFile2" "Blahfaselgagaga"- writeFile' "inputDir/bar/inputFile3"- "fdfdssdffsd3223sasdasdasdadasasddasdasdasasd4"-- createDir' "inputDir1"- createDir' "inputDir1/foo2"- createDir' "inputDir1/foo2/foo3"- createDir' "inputDir1/foo2/foo4"- createRegularFile' "inputDir1/foo2/inputFile1"- createRegularFile' "inputDir1/foo2/inputFile2"- createRegularFile' "inputDir1/foo2/inputFile3"- createRegularFile' "inputDir1/foo2/foo4/inputFile4"- createRegularFile' "inputDir1/foo2/foo4/inputFile6"- createRegularFile' "inputDir1/foo2/foo3/inputFile5"- noPerms "inputDir1/foo2/foo3"-- createDir' "outputDir1"- createDir' "outputDir1/foo2"- createDir' "outputDir1/foo2/foo4"- createDir' "outputDir1/foo2/foo4/inputFile4"- createRegularFile' "outputDir1/foo2/foo4/inputFile6"- noPerms "outputDir1/foo2/foo4/inputFile4"- noPerms "outputDir1/foo2/foo4"-- noPerms "noPerms"- noWritableDirPerms "noWritePerm"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"-- normalDirPerms "inputDir1/foo2/foo3"- deleteFile' "inputDir1/foo2/foo4/inputFile4"- deleteFile' "inputDir1/foo2/foo4/inputFile6"- deleteFile' "inputDir1/foo2/inputFile1"- deleteFile' "inputDir1/foo2/inputFile2"- deleteFile' "inputDir1/foo2/inputFile3"- deleteFile' "inputDir1/foo2/foo3/inputFile5"- deleteDir' "inputDir1/foo2/foo3"- deleteDir' "inputDir1/foo2/foo4"- deleteDir' "inputDir1/foo2"- deleteDir' "inputDir1"-- normalDirPerms "outputDir1/foo2/foo4"- normalDirPerms "outputDir1/foo2/foo4/inputFile4"- deleteFile' "outputDir1/foo2/foo4/inputFile6"- deleteDir' "outputDir1/foo2/foo4/inputFile4"- deleteDir' "outputDir1/foo2/foo4"- deleteDir' "outputDir1/foo2"- deleteDir' "outputDir1"-- deleteFile' "alreadyExists"- deleteFile' "wrongInput"- deleteFile' "wrongInputSymL"- deleteDir' "alreadyExistsD"- deleteDir' "noPerms"- deleteDir' "noWritePerm"- deleteFile' "inputDir/foo/inputFile1"- deleteFile' "inputDir/inputFile2"- deleteFile' "inputDir/bar/inputFile3"- deleteDir' "inputDir/foo"- deleteDir' "inputDir/bar"- deleteDir' "inputDir"-----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.copyDirRecursive" $ do-- -- successes --- it "copyDirRecursive (Strict, CollectFailures), all fine and compare" $ do- tmpDir' <- getRawTmpDir- copyDirRecursive' "inputDir"- "outputDir"- Strict- CollectFailures- (system $ "diff -r --no-dereference "- ++ toString tmpDir' ++ "inputDir" ++ " "- ++ toString tmpDir' ++ "outputDir")- `shouldReturn` ExitSuccess- removeDirIfExists "outputDir"-- -- posix failures --- it "copyDirRecursive (Strict, CollectFailures), source directory does not exist" $- copyDirRecursive' "doesNotExist"- "outputDir"- Strict- CollectFailures- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "copyDirRecursive (Strict, CollectFailures), cannot open source dir" $- copyDirRecursive' "noPerms/inputDir"- "foo"- Strict- CollectFailures- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)--- -- custom failures- it "copyDirRecursive (Overwrite, CollectFailures), various failures" $ do- copyDirRecursive' "inputDir1/foo2"- "outputDir1/foo2"- Overwrite- CollectFailures- `shouldThrow`- (\(RecursiveFailure ex@[_, _]) ->- any (\(h, e) -> ioeGetErrorType e == InappropriateType- && isCopyFileFailed h) ex &&- any (\(h, e) -> ioeGetErrorType e == PermissionDenied- && isReadContentsFailed h) ex)- normalDirPerms "outputDir1/foo2/foo4"- normalDirPerms "outputDir1/foo2/foo4/inputFile4"- c <- allDirectoryContents' "outputDir1"- tmpDir' <- getRawTmpDir- let shouldC = (fmap (\x -> tmpDir' `BS.append` x)- ["outputDir1"- ,"outputDir1/foo2"- ,"outputDir1/foo2/inputFile1"- ,"outputDir1/foo2/inputFile2"- ,"outputDir1/foo2/inputFile3"- ,"outputDir1/foo2/foo4"- ,"outputDir1/foo2/foo4/inputFile6"- ,"outputDir1/foo2/foo4/inputFile4"])- deleteFile' "outputDir1/foo2/inputFile1"- deleteFile' "outputDir1/foo2/inputFile2"- deleteFile' "outputDir1/foo2/inputFile3"- sort c `shouldBe` sort shouldC--- it "copyDirRecursive (Strict, CollectFailures), no write permission on output dir" $- copyDirRecursive' "inputDir"- "noWritePerm/foo"- Strict- CollectFailures- `shouldThrow`- (\(RecursiveFailure [(CreateDirFailed{}, e)]) -> ioeGetErrorType e == PermissionDenied)-- it "copyDirRecursive (Strict, CollectFailures), cannot open output dir" $- copyDirRecursive' "inputDir"- "noPerms/foo"- Strict- CollectFailures- `shouldThrow`- isRecursiveFailure-- it "copyDirRecursive (Strict, CollectFailures), destination dir already exists" $- copyDirRecursive' "inputDir"- "alreadyExistsD"- Strict- CollectFailures- `shouldThrow`- (\(RecursiveFailure [(CreateDirFailed{}, e)]) -> ioeGetErrorType e == AlreadyExists)-- it "copyDirRecursive (Strict, CollectFailures), destination already exists and is a file" $- copyDirRecursive' "inputDir"- "alreadyExists"- Strict- CollectFailures- `shouldThrow`- isRecursiveFailure-- it "copyDirRecursive (Strict, CollectFailures), wrong input (regular file)" $- copyDirRecursive' "wrongInput"- "outputDir"- Strict- CollectFailures- `shouldThrow`- (\(RecursiveFailure [(ReadContentsFailed{}, e)]) -> ioeGetErrorType e == InappropriateType)-- it "copyDirRecursive (Strict, CollectFailures), wrong input (symlink to directory)" $- copyDirRecursive' "wrongInputSymL"- "outputDir"- Strict- CollectFailures- `shouldThrow`- (\(RecursiveFailure [(ReadContentsFailed{}, e)]) -> ioeGetErrorType e == InvalidArgument)-- it "copyDirRecursive (Strict, CollectFailures), destination in source" $- copyDirRecursive' "inputDir"- "inputDir/foo"- Strict- CollectFailures- `shouldThrow`- isDestinationInSource-- it "copyDirRecursive (Strict, CollectFailures), destination and source same directory" $- copyDirRecursive' "inputDir"- "inputDir"- Strict- CollectFailures- `shouldThrow`- isSameFile--
− test/HPath/IO/CopyDirRecursiveOverwriteSpec.hs
@@ -1,202 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}---module HPath.IO.CopyDirRecursiveOverwriteSpec where---import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import System.Exit-import System.Process-import Utils-import Data.ByteString.UTF8 (toString)----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CopyDirRecursiveOverwriteSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "alreadyExists"- createRegularFile' "wrongInput"- createSymlink' "wrongInputSymL" "inputDir/"- createDir' "noPerms"- createDir' "noWritePerm"-- createDir' "inputDir"- createDir' "inputDir/bar"- createDir' "inputDir/foo"- createRegularFile' "inputDir/foo/inputFile1"- createRegularFile' "inputDir/inputFile2"- createRegularFile' "inputDir/bar/inputFile3"- writeFile' "inputDir/foo/inputFile1" "SDAADSdsada"- writeFile' "inputDir/inputFile2" "Blahfaselgagaga"- writeFile' "inputDir/bar/inputFile3"- "fdfdssdffsd3223sasdasdasdadasasddasdasdasasd4"-- createDir' "alreadyExistsD"- createDir' "alreadyExistsD/bar"- createDir' "alreadyExistsD/foo"- createRegularFile' "alreadyExistsD/foo/inputFile1"- createRegularFile' "alreadyExistsD/inputFile2"- createRegularFile' "alreadyExistsD/bar/inputFile3"- writeFile' "alreadyExistsD/foo/inputFile1" "DAAsada"- writeFile' "alreadyExistsD/inputFile2" "ahfaagaga"- writeFile' "alreadyExistsD/bar/inputFile3"- "f3223sasdasdaasdasdasasd4"-- noPerms "noPerms"- noWritableDirPerms "noWritePerm"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"- deleteFile' "alreadyExists"- deleteFile' "wrongInput"- deleteFile' "wrongInputSymL"- deleteDir' "noPerms"- deleteDir' "noWritePerm"- deleteFile' "inputDir/foo/inputFile1"- deleteFile' "inputDir/inputFile2"- deleteFile' "inputDir/bar/inputFile3"- deleteDir' "inputDir/foo"- deleteDir' "inputDir/bar"- deleteDir' "inputDir"- deleteFile' "alreadyExistsD/foo/inputFile1"- deleteFile' "alreadyExistsD/inputFile2"- deleteFile' "alreadyExistsD/bar/inputFile3"- deleteDir' "alreadyExistsD/foo"- deleteDir' "alreadyExistsD/bar"- deleteDir' "alreadyExistsD"----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.copyDirRecursive" $ do-- -- successes --- it "copyDirRecursive (Overwrite, FailEarly), all fine" $ do- copyDirRecursive' "inputDir"- "outputDir"- Overwrite- FailEarly- removeDirIfExists "outputDir"-- it "copyDirRecursive (Overwrite, FailEarly), all fine and compare" $ do- tmpDir' <- getRawTmpDir- copyDirRecursive' "inputDir"- "outputDir"- Overwrite- FailEarly- (system $ "diff -r --no-dereference "- ++ toString tmpDir' ++ "inputDir" ++ " "- ++ toString tmpDir' ++ "outputDir")- `shouldReturn` ExitSuccess- removeDirIfExists "outputDir"-- it "copyDirRecursive (Overwrite, FailEarly), destination dir already exists" $ do- tmpDir' <- getRawTmpDir- (system $ "diff -r --no-dereference "- ++ toString tmpDir' ++ "inputDir" ++ " "- ++ toString tmpDir' ++ "alreadyExistsD")- `shouldReturn` (ExitFailure 1)- copyDirRecursive' "inputDir"- "alreadyExistsD"- Overwrite- FailEarly- (system $ "diff -r --no-dereference "- ++ toString tmpDir' ++ "inputDir" ++ " "- ++ toString tmpDir' ++ "alreadyExistsD")- `shouldReturn` ExitSuccess- removeDirIfExists "outputDir"--- -- posix failures --- it "copyDirRecursive, source directory does not exist" $- copyDirRecursive' "doesNotExist"- "outputDir"- Overwrite- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "copyDirRecursive, no write permission on output dir" $- copyDirRecursive' "inputDir"- "noWritePerm/foo"- Overwrite- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyDirRecursive, cannot open output dir" $- copyDirRecursive' "inputDir"- "noPerms/foo"- Overwrite- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyDirRecursive, cannot open source dir" $- copyDirRecursive' "noPerms/inputDir"- "foo"- Overwrite- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyDirRecursive, destination already exists and is a file" $- copyDirRecursive' "inputDir"- "alreadyExists"- Overwrite- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "copyDirRecursive, wrong input (regular file)" $- copyDirRecursive' "wrongInput"- "outputDir"- Overwrite- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "copyDirRecursive, wrong input (symlink to directory)" $- copyDirRecursive' "wrongInputSymL"- "outputDir"- Overwrite- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- -- custom failures- it "copyDirRecursive (Overwrite, FailEarly), destination in source" $- copyDirRecursive' "inputDir"- "inputDir/foo"- Overwrite- FailEarly- `shouldThrow`- isDestinationInSource-- it "copyDirRecursive (Overwrite, FailEarly), destination and source same directory" $- copyDirRecursive' "inputDir"- "inputDir"- Overwrite- FailEarly- `shouldThrow`- isSameFile
− test/HPath/IO/CopyDirRecursiveSpec.hs
@@ -1,180 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}---module HPath.IO.CopyDirRecursiveSpec where---import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import System.Exit-import System.Process-import Utils-import Data.ByteString.UTF8 (toString)----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CopyDirRecursiveSpec"- createTmpDir--setupFiles :: IO ()-setupFiles = do- createRegularFile' "alreadyExists"- createRegularFile' "wrongInput"- createSymlink' "wrongInputSymL" "inputDir/"- createDir' "alreadyExistsD"- createDir' "noPerms"- createDir' "noWritePerm"-- createDir' "inputDir"- createDir' "inputDir/bar"- createDir' "inputDir/foo"- createRegularFile' "inputDir/foo/inputFile1"- createRegularFile' "inputDir/inputFile2"- createRegularFile' "inputDir/bar/inputFile3"- writeFile' "inputDir/foo/inputFile1" "SDAADSdsada"- writeFile' "inputDir/inputFile2" "Blahfaselgagaga"- writeFile' "inputDir/bar/inputFile3"- "fdfdssdffsd3223sasdasdasdadasasddasdasdasasd4"-- noPerms "noPerms"- noWritableDirPerms "noWritePerm"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"- deleteFile' "alreadyExists"- deleteFile' "wrongInput"- deleteFile' "wrongInputSymL"- deleteDir' "alreadyExistsD"- deleteDir' "noPerms"- deleteDir' "noWritePerm"- deleteFile' "inputDir/foo/inputFile1"- deleteFile' "inputDir/inputFile2"- deleteFile' "inputDir/bar/inputFile3"- deleteDir' "inputDir/foo"- deleteDir' "inputDir/bar"- deleteDir' "inputDir"-----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.copyDirRecursive" $ do-- -- successes --- it "copyDirRecursive (Strict, FailEarly), all fine" $ do- copyDirRecursive' "inputDir"- "outputDir"- Strict- FailEarly- removeDirIfExists "outputDir"-- it "copyDirRecursive (Strict, FailEarly), all fine and compare" $ do- tmpDir' <- getRawTmpDir- copyDirRecursive' "inputDir"- "outputDir"- Strict- FailEarly- (system $ "diff -r --no-dereference "- ++ toString tmpDir' ++ "inputDir" ++ " "- ++ toString tmpDir' ++ "outputDir")- `shouldReturn` ExitSuccess- removeDirIfExists "outputDir"-- -- posix failures --- it "copyDirRecursive (Strict, FailEarly), source directory does not exist" $- copyDirRecursive' "doesNotExist"- "outputDir"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "copyDirRecursive (Strict, FailEarly), no write permission on output dir" $- copyDirRecursive' "inputDir"- "noWritePerm/foo"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyDirRecursive (Strict, FailEarly), cannot open output dir" $- copyDirRecursive' "inputDir"- "noPerms/foo"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyDirRecursive (Strict, FailEarly), cannot open source dir" $- copyDirRecursive' "noPerms/inputDir"- "foo"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyDirRecursive (Strict, FailEarly), destination dir already exists" $- copyDirRecursive' "inputDir"- "alreadyExistsD"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "copyDirRecursive (Strict, FailEarly), destination already exists and is a file" $- copyDirRecursive' "inputDir"- "alreadyExists"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "copyDirRecursive (Strict, FailEarly), wrong input (regular file)" $- copyDirRecursive' "wrongInput"- "outputDir"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "copyDirRecursive (Strict, FailEarly), wrong input (symlink to directory)" $- copyDirRecursive' "wrongInputSymL"- "outputDir"- Strict- FailEarly- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- -- custom failures- it "copyDirRecursive (Strict, FailEarly), destination in source" $- copyDirRecursive' "inputDir"- "inputDir/foo"- Strict- FailEarly- `shouldThrow`- isDestinationInSource-- it "copyDirRecursive (Strict, FailEarly), destination and source same directory" $- copyDirRecursive' "inputDir"- "inputDir"- Strict- FailEarly- `shouldThrow`- isSameFile--
− test/HPath/IO/CopyFileOverwriteSpec.hs
@@ -1,148 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.CopyFileOverwriteSpec where---import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import System.Exit-import System.Process-import Utils-import Data.ByteString.UTF8 (toString)----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CopyFileOverwriteSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "inputFile"- createRegularFile' "alreadyExists"- createSymlink' "inputFileSymL" "inputFile"- createDir' "alreadyExistsD"- createDir' "noPerms"- createRegularFile' "noPerms/inputFile"- createDir' "outputDirNoWrite"- createDir' "wrongInput"- noPerms "noPerms"- noWritableDirPerms "outputDirNoWrite"- writeFile' "inputFile" "Blahfaselgagaga"- writeFile' "alreadyExists" "dsaldsalkaklsdlkasksdadasl"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "outputDirNoWrite"- deleteFile' "noPerms/inputFile"- deleteFile' "inputFile"- deleteFile' "alreadyExists"- deleteFile' "inputFileSymL"- deleteDir' "alreadyExistsD"- deleteDir' "noPerms"- deleteDir' "outputDirNoWrite"- deleteDir' "wrongInput"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.copyFile" $ do-- -- successes --- it "copyFile (Overwrite), everything clear" $ do- copyFile' "inputFile"- "outputFile"- Overwrite- removeFileIfExists "outputFile"-- it "copyFile (Overwrite), output file already exists, all clear" $ do- tmpDir' <- getRawTmpDir- copyFile' "alreadyExists" "alreadyExists.bak" Strict- copyFile' "inputFile" "alreadyExists" Overwrite- (system $ "cmp -s " ++ toString tmpDir' ++ "inputFile" ++ " "- ++ toString tmpDir' ++ "alreadyExists")- `shouldReturn` ExitSuccess- removeFileIfExists "alreadyExists"- copyFile' "alreadyExists.bak" "alreadyExists" Strict- removeFileIfExists "alreadyExists.bak"-- it "copyFile (Overwrite), and compare" $ do- tmpDir' <- getRawTmpDir- copyFile' "inputFile"- "outputFile"- Overwrite- (system $ "cmp -s " ++ toString tmpDir' ++ "inputFile" ++ " "- ++ toString tmpDir' ++ "outputFile")- `shouldReturn` ExitSuccess- removeFileIfExists "outputFile"--- -- posix failures --- it "copyFile (Overwrite), input file does not exist" $- copyFile' "noSuchFile"- "outputFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "copyFile (Overwrite), no permission to write to output directory" $- copyFile' "inputFile"- "outputDirNoWrite/outputFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyFile (Overwrite), cannot open output directory" $- copyFile' "inputFile"- "noPerms/outputFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyFile (Overwrite), cannot open source directory" $- copyFile' "noPerms/inputFile"- "outputFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyFile (Overwrite), wrong input type (symlink)" $- copyFile' "inputFileSymL"- "outputFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "copyFile (Overwrite), wrong input type (directory)" $- copyFile' "wrongInput"- "outputFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "copyFile (Overwrite), output file already exists and is a dir" $- copyFile' "inputFile"- "alreadyExistsD"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- -- custom failures --- it "copyFile (Overwrite), output and input are same file" $- copyFile' "inputFile"- "inputFile"- Overwrite- `shouldThrow` isSameFile
− test/HPath/IO/CopyFileSpec.hs
@@ -1,143 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}---module HPath.IO.CopyFileSpec where---import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import System.Exit-import System.Process-import Utils-import Data.ByteString.UTF8 (toString)----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CopyFileSpec"- createTmpDir--setupFiles :: IO ()-setupFiles = do- createRegularFile' "inputFile"- createRegularFile' "alreadyExists"- createSymlink' "inputFileSymL" "inputFile"- createDir' "alreadyExistsD"- createDir' "noPerms"- createRegularFile' "noPerms/inputFile"- createDir' "outputDirNoWrite"- createDir' "wrongInput"- noPerms "noPerms"- noWritableDirPerms "outputDirNoWrite"- writeFile' "inputFile" "Blahfaselgagaga"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "outputDirNoWrite"- deleteFile' "noPerms/inputFile"- deleteFile' "inputFile"- deleteFile' "alreadyExists"- deleteFile' "inputFileSymL"- deleteDir' "alreadyExistsD"- deleteDir' "noPerms"- deleteDir' "outputDirNoWrite"- deleteDir' "wrongInput"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.copyFile" $ do-- -- successes --- it "copyFile (Strict), everything clear" $ do- copyFile' "inputFile"- "outputFile"- Strict- removeFileIfExists "outputFile"-- it "copyFile (Strict), and compare" $ do- tmpDir' <- getRawTmpDir- copyFile' "inputFile"- "outputFile"- Strict- (system $ "cmp -s " ++ toString tmpDir' ++ "inputFile" ++ " "- ++ toString tmpDir' ++ "outputFile")- `shouldReturn` ExitSuccess- removeFileIfExists "outputFile"-- -- posix failures --- it "copyFile (Strict), input file does not exist" $- copyFile' "noSuchFile"- "outputFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "copyFile (Strict), no permission to write to output directory" $- copyFile' "inputFile"- "outputDirNoWrite/outputFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyFile (Strict), cannot open output directory" $- copyFile' "inputFile"- "noPerms/outputFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyFile (Strict), cannot open source directory" $- copyFile' "noPerms/inputFile"- "outputFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "copyFile (Strict), wrong input type (symlink)" $- copyFile' "inputFileSymL"- "outputFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "copyFile (Strict), wrong input type (directory)" $- copyFile' "wrongInput"- "outputFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "copyFile (Strict), output file already exists" $- copyFile' "inputFile"- "alreadyExists"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "copyFile (Strict), output file already exists and is a dir" $- copyFile' "inputFile"- "alreadyExistsD"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- -- custom failures --- it "copyFile (Strict), output and input are same file" $- copyFile' "inputFile"- "inputFile"- Strict- `shouldThrow`- isSameFile
− test/HPath/IO/CreateDirRecursiveSpec.hs
@@ -1,78 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.CreateDirRecursiveSpec where---import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CreateDirRecursiveSpec"- createTmpDir--setupFiles :: IO ()-setupFiles = do- createDir' "alreadyExists"- createRegularFile' "alreadyExistsF"- createDir' "noPerms"- createDir' "noWritePerms"- noPerms "noPerms"- noWritableDirPerms "noWritePerms"--cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerms"- deleteDir' "alreadyExists"- deleteDir' "noPerms"- deleteDir' "noWritePerms"- deleteFile' "alreadyExistsF"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.createDirRecursive" $ do-- -- successes --- it "createDirRecursive, all fine" $ do- createDirRecursive' "newDir"- deleteDir' "newDir"-- it "createDirRecursive, parent directories do not exist" $ do- createDirRecursive' "some/thing/dada"- deleteDir' "some/thing/dada"- deleteDir' "some/thing"- deleteDir' "some"-- it "createDirRecursive, destination directory already exists" $- createDirRecursive' "alreadyExists"-- -- posix failures --- it "createDirRecursive, destination already exists and is a file" $- createDirRecursive' "alreadyExistsF"- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "createDirRecursive, can't write to output directory" $- createDirRecursive' "noWritePerms/newDir"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "createDirRecursive, can't open output directory" $- createDirRecursive' "noPerms/newDir"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)---
− test/HPath/IO/CreateDirSpec.hs
@@ -1,72 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.CreateDirSpec where---import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CreateDirSpec"- createTmpDir--setupFiles :: IO ()-setupFiles = do- createDir' "alreadyExists"- createDir' "noPerms"- createDir' "noWritePerms"- noPerms "noPerms"- noWritableDirPerms "noWritePerms"----cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerms"- deleteDir' "alreadyExists"- deleteDir' "noPerms"- deleteDir' "noWritePerms"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.createDir" $ do-- -- successes --- it "createDir, all fine" $ do- createDir' "newDir"- removeDirIfExists "newDir"-- -- posix failures --- it "createDir, parent directories do not exist" $- createDir' "some/thing/dada"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "createDir, can't write to output directory" $- createDir' "noWritePerms/newDir"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "createDir, can't open output directory" $- createDir' "noPerms/newDir"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "createDir, destination directory already exists" $- createDir' "alreadyExists"- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-
− test/HPath/IO/CreateRegularFileSpec.hs
@@ -1,70 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.CreateRegularFileSpec where---import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CreateRegularFileSpec"- createTmpDir--setupFiles :: IO ()-setupFiles = do- createRegularFile' "alreadyExists"- createDir' "noPerms"- createDir' "noWritePerms"- noPerms "noPerms"- noWritableDirPerms "noWritePerms"--cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerms"- deleteFile' "alreadyExists"- deleteDir' "noPerms"- deleteDir' "noWritePerms"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.createRegularFile" $ do-- -- successes --- it "createRegularFile, all fine" $ do- createRegularFile' "newDir"- removeFileIfExists "newDir"-- -- posix failures --- it "createRegularFile, parent directories do not exist" $- createRegularFile' "some/thing/dada"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "createRegularFile, can't write to destination directory" $- createRegularFile' "noWritePerms/newDir"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "createRegularFile, can't write to destination directory" $- createRegularFile' "noPerms/newDir"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "createRegularFile, destination file already exists" $- createRegularFile' "alreadyExists"- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-
− test/HPath/IO/CreateSymlinkSpec.hs
@@ -1,71 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.CreateSymlinkSpec where---import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils---upTmpDir :: IO ()-upTmpDir = do- setTmpDir "CreateSymlinkSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "alreadyExists"- createDir' "noPerms"- createDir' "noWritePerms"- noPerms "noPerms"- noWritableDirPerms "noWritePerms"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerms"- deleteFile' "alreadyExists"- deleteDir' "noPerms"- deleteDir' "noWritePerms"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.createSymlink" $ do-- -- successes --- it "createSymlink, all fine" $ do- createSymlink' "newSymL" "alreadyExists/"- removeFileIfExists "newSymL"-- -- posix failures --- it "createSymlink, parent directories do not exist" $- createSymlink' "some/thing/dada" "lala"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "createSymlink, can't write to destination directory" $- createSymlink' "noWritePerms/newDir" "lala"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "createSymlink, can't write to destination directory" $- createSymlink' "noPerms/newDir" "lala"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "createSymlink, destination file already exists" $- createSymlink' "alreadyExists" "lala"- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-
− test/HPath/IO/DeleteDirRecursiveSpec.hs
@@ -1,116 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.DeleteDirRecursiveSpec where---import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import System.Posix.Files.ByteString- (- getSymbolicLinkStatus- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "DeleteDirRecursiveSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "file"- createDir' "dir"- createRegularFile' "dir/.keep"- createSymlink' "dirSym" "dir/"- createDir' "noPerms"- createRegularFile' "noPerms/.keep"- createDir' "noWritable"- createRegularFile' "noWritable/.keep"---cleanupFiles :: IO ()-cleanupFiles = do- deleteFile' "file"- deleteFile' "dir/.keep"- deleteDir' "dir"- deleteFile' "dirSym"- deleteFile' "noPerms/.keep"- deleteDir' "noPerms"- deleteFile' "noWritable/.keep"- deleteDir' "noWritable"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.deleteDirRecursive" $ do-- -- successes --- it "deleteDirRecursive, empty directory, all fine" $ do- createDir' "testDir"- deleteDirRecursive' "testDir"- getSymbolicLinkStatus "testDir"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "deleteDirRecursive, empty directory with null permissions, all fine" $ do- createDir' "noPerms/testDir"- noPerms "noPerms/testDir"- deleteDirRecursive' "noPerms/testDir"-- it "deleteDirRecursive, non-empty directory, all fine" $ do- createDir' "nonEmpty"- createDir' "nonEmpty/dir1"- createDir' "nonEmpty/dir2"- createDir' "nonEmpty/dir2/dir3"- createRegularFile' "nonEmpty/file1"- createRegularFile' "nonEmpty/dir1/file2"- deleteDirRecursive' "nonEmpty"- getSymbolicLinkStatus "nonEmpty"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- -- posix failures --- it "deleteDirRecursive, can't open parent directory" $ do- createDir' "noPerms/foo"- noPerms "noPerms"- (deleteDirRecursive' "noPerms/foo")- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)- normalDirPerms "noPerms"- deleteDir' "noPerms/foo"-- it "deleteDirRecursive, can't write to parent directory" $ do- createDir' "noWritable/foo"- noWritableDirPerms "noWritable"- (deleteDirRecursive' "noWritable/foo")- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)- normalDirPerms "noWritable"- deleteDir' "noWritable/foo"-- it "deleteDirRecursive, wrong file type (symlink to directory)" $- deleteDirRecursive' "dirSym"- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "deleteDirRecursive, wrong file type (regular file)" $- deleteDirRecursive' "file"- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "deleteDirRecursive, directory does not exist" $- deleteDirRecursive' "doesNotExist"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)--
− test/HPath/IO/DeleteDirSpec.hs
@@ -1,114 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.DeleteDirSpec where---import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import System.Posix.Files.ByteString- (- getSymbolicLinkStatus- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils-----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "DeleteDirSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "file"- createDir' "dir"- createRegularFile' "dir/.keep"- createSymlink' "dirSym" "dir/"- createDir' "noPerms"- createRegularFile' "noPerms/.keep"- createDir' "noWritable"- createRegularFile' "noWritable/.keep"---cleanupFiles :: IO ()-cleanupFiles = do- deleteFile' "file"- deleteFile' "dir/.keep"- deleteDir' "dir"- deleteFile' "dirSym"- deleteFile' "noPerms/.keep"- deleteDir' "noPerms"- deleteFile' "noWritable/.keep"- deleteDir' "noWritable"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.deleteDir" $ do-- -- successes --- it "deleteDir, empty directory, all fine" $ do- createDir' "testDir"- deleteDir' "testDir"- getSymbolicLinkStatus "testDir"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "deleteDir, directory with null permissions, all fine" $ do- createDir' "noPerms/testDir"- noPerms "noPerms/testDir"- deleteDir' "noPerms/testDir"- getSymbolicLinkStatus "testDir"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- -- posix failures --- it "deleteDir, wrong file type (symlink to directory)" $- deleteDir' "dirSym"- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "deleteDir, wrong file type (regular file)" $- deleteDir' "file"- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "deleteDir, directory does not exist" $- deleteDir' "doesNotExist"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "deleteDir, directory not empty" $- deleteDir' "dir"- `shouldThrow`- (\e -> ioeGetErrorType e == UnsatisfiedConstraints)-- it "deleteDir, can't open parent directory" $ do- createDir' "noPerms/foo"- noPerms "noPerms"- (deleteDir' "noPerms/foo")- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)- normalDirPerms "noPerms"- deleteDir' "noPerms/foo"-- it "deleteDir, can't write to parent directory, still fine" $ do- createDir' "noWritable/foo"- noWritableDirPerms "noWritable"- (deleteDir' "noWritable/foo")- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)- normalDirPerms "noWritable"- deleteDir' "noWritable/foo"---
− test/HPath/IO/DeleteFileSpec.hs
@@ -1,84 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.DeleteFileSpec where---import Test.Hspec-import HPath.IO-import System.IO.Error- (- ioeGetErrorType- )-import System.Posix.Files.ByteString- (- getSymbolicLinkStatus- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils---upTmpDir :: IO ()-upTmpDir = do- setTmpDir "DeleteFileSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "foo"- createSymlink' "syml" "foo"- createDir' "dir"- createDir' "noPerms"- noPerms "noPerms"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- deleteFile' "foo"- deleteFile' "syml"- deleteDir' "dir"- deleteDir' "noPerms"----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.deleteFile" $ do-- -- successes --- it "deleteFile, regular file, all fine" $ do- createRegularFile' "testFile"- deleteFile' "testFile"- getSymbolicLinkStatus "testFile"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "deleteFile, symlink, all fine" $ do- recreateSymlink' "syml"- "testFile"- Strict- deleteFile' "testFile"- getSymbolicLinkStatus "testFile"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- -- posix failures --- it "deleteFile, wrong file type (directory)" $- deleteFile' "dir"- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "deleteFile, file does not exist" $- deleteFile' "doesNotExist"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "deleteFile, can't read directory" $- deleteFile' "noPerms/blah"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-
− test/HPath/IO/GetDirsFilesSpec.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.GetDirsFilesSpec where---import Data.List- (- sort- )-import qualified HPath as P-import HPath.IO-import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils---upTmpDir :: IO ()-upTmpDir = do- setTmpDir "GetDirsFilesSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "file"- createRegularFile' "Lala"- createRegularFile' ".hidden"- createSymlink' "syml" "Lala"- createDir' "dir"- createSymlink' "dirsym" "dir"- createDir' "noPerms"- noPerms "noPerms"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- deleteFile' "file"- deleteFile' "Lala"- deleteFile' ".hidden"- deleteFile' "syml"- deleteDir' "dir"- deleteFile' "dirsym"- deleteDir' "noPerms"----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.getDirsFiles" $ do-- -- successes --- it "getDirsFiles, all fine" $- withRawTmpDir $ \p -> do- expectedFiles <- mapM P.parseRel [".hidden"- ,"Lala"- ,"dir"- ,"dirsym"- ,"file"- ,"noPerms"- ,"syml"]- (fmap sort $ getDirsFiles p)- `shouldReturn` fmap (p P.</>) expectedFiles-- -- posix failures --- it "getDirsFiles, nonexistent directory" $- getDirsFiles' "nothingHere"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "getDirsFiles, wrong file type (file)" $- getDirsFiles' "file"- `shouldThrow`- (\e -> ioeGetErrorType e == InappropriateType)-- it "getDirsFiles, wrong file type (symlink to file)" $- getDirsFiles' "syml"- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "getDirsFiles, wrong file type (symlink to dir)" $- getDirsFiles' "dirsym"- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "getDirsFiles, can't open directory" $- getDirsFiles' "noPerms"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)----
− test/HPath/IO/GetFileTypeSpec.hs
@@ -1,88 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.GetFileTypeSpec where---import HPath.IO-import Test.Hspec-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "GetFileTypeSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "regularfile"- createSymlink' "symlink" "regularfile"- createSymlink' "brokenSymlink" "broken"- createDir' "directory"- createSymlink' "symlinkD" "directory"- createDir' "noPerms"- noPerms "noPerms"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- deleteFile' "regularfile"- deleteFile' "symlink"- deleteFile' "brokenSymlink"- deleteDir' "directory"- deleteFile' "symlinkD"- deleteDir' "noPerms"----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.getFileType" $ do-- -- successes --- it "getFileType, regular file" $- getFileType' "regularfile"- `shouldReturn` RegularFile-- it "getFileType, directory" $- getFileType' "directory"- `shouldReturn` Directory-- it "getFileType, directory with null permissions" $- getFileType' "noPerms"- `shouldReturn` Directory-- it "getFileType, symlink to file" $- getFileType' "symlink"- `shouldReturn` SymbolicLink-- it "getFileType, symlink to directory" $- getFileType' "symlinkD"- `shouldReturn` SymbolicLink-- it "getFileType, broken symlink" $- getFileType' "brokenSymlink"- `shouldReturn` SymbolicLink-- -- posix failures --- it "getFileType, file does not exist" $- getFileType' "nothingHere"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "getFileType, can't open directory" $- getFileType' "noPerms/forz"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-
− test/HPath/IO/MoveFileOverwriteSpec.hs
@@ -1,126 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.MoveFileOverwriteSpec where---import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "MoveFileOverwriteSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "myFile"- createSymlink' "myFileL" "myFile"- createDir' "alreadyExistsD"- createDir' "dir"- createDir' "noPerms"- createDir' "noWritePerm"- noPerms "noPerms"- noWritableDirPerms "noWritePerm"- writeFile' "myFile" "Blahfaselgagaga"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"- deleteFile' "myFile"- deleteFile' "myFileL"- deleteDir' "alreadyExistsD"- deleteDir' "dir"- deleteDir' "noPerms"- deleteDir' "noWritePerm"----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.moveFile" $ do-- -- successes --- it "moveFile (Overwrite), all fine" $- moveFile' "myFile"- "movedFile"- Overwrite-- it "moveFile (Overwrite), all fine" $- moveFile' "myFile"- "dir/movedFile"- Overwrite-- it "moveFile (Overwrite), all fine on symlink" $- moveFile' "myFileL"- "movedFile"- Overwrite-- it "moveFile (Overwrite), all fine on directory" $- moveFile' "dir"- "movedFile"- Overwrite-- it "moveFile (Overwrite), destination file already exists" $- moveFile' "myFile"- "alreadyExists"- Overwrite-- -- posix failures --- it "moveFile (Overwrite), source file does not exist" $- moveFile' "fileDoesNotExist"- "movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "moveFile (Overwrite), can't write to destination directory" $- moveFile' "myFile"- "noWritePerm/movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "moveFile (Overwrite), can't open destination directory" $- moveFile' "myFile"- "noPerms/movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "moveFile (Overwrite), can't open source directory" $- moveFile' "noPerms/myFile"- "movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- -- custom failures ---- it "moveFile (Overwrite), move from file to dir" $- moveFile' "myFile"- "alreadyExistsD"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "moveFile (Overwrite), source and dest are same file" $- moveFile' "myFile"- "myFile"- Overwrite- `shouldThrow`- isSameFile
− test/HPath/IO/MoveFileSpec.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.MoveFileSpec where---import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "MoveFileSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "myFile"- createSymlink' "myFileL" "myFile"- createRegularFile' "alreadyExists"- createDir' "alreadyExistsD"- createDir' "dir"- createDir' "noPerms"- createDir' "noWritePerm"- noPerms "noPerms"- noWritableDirPerms "noWritePerm"- writeFile' "myFile" "Blahfaselgagaga"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"- deleteFile' "myFile"- deleteFile' "myFileL"- deleteFile' "alreadyExists"- deleteDir' "alreadyExistsD"- deleteDir' "dir"- deleteDir' "noPerms"- deleteDir' "noWritePerm"----spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.moveFile" $ do-- -- successes --- it "moveFile (Strict), all fine" $- moveFile' "myFile"- "movedFile"- Strict-- it "moveFile (Strict), all fine" $- moveFile' "myFile"- "dir/movedFile"- Strict-- it "moveFile (Strict), all fine on symlink" $- moveFile' "myFileL"- "movedFile"- Strict-- it "moveFile (Strict), all fine on directory" $- moveFile' "dir"- "movedFile"- Strict-- -- posix failures --- it "moveFile (Strict), source file does not exist" $- moveFile' "fileDoesNotExist"- "movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "moveFile (Strict), can't write to destination directory" $- moveFile' "myFile"- "noWritePerm/movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "moveFile (Strict), can't open destination directory" $- moveFile' "myFile"- "noPerms/movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "moveFile (Strict), can't open source directory" $- moveFile' "noPerms/myFile"- "movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- -- custom failures --- it "moveFile (Strict), destination file already exists" $- moveFile' "myFile"- "alreadyExists"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "moveFile (Strict), move from file to dir" $- moveFile' "myFile"- "alreadyExistsD"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "moveFile (Strict), source and dest are same file" $- moveFile' "myFile"- "myFile"- Strict- `shouldThrow`- isSameFile
− test/HPath/IO/ReadFileEOFSpec.hs
@@ -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)
− test/HPath/IO/ReadFileSpec.hs
@@ -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)
− test/HPath/IO/RecreateSymlinkOverwriteSpec.hs
@@ -1,139 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.RecreateSymlinkOverwriteSpec where----- TODO: exception if destination exists but is not a file + `OverWrite` CopyMode---import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils---upTmpDir :: IO ()-upTmpDir = do- setTmpDir "RecreateSymlinkOverwriteSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "myFile"- createSymlink' "myFileL" "myFile"- createRegularFile' "alreadyExists"- createDir' "alreadyExistsD"- createDir' "dir"- createDir' "noPerms"- createDir' "noWritePerm"- createDir' "alreadyExistsD2"- createRegularFile' "alreadyExistsD2/lala"- noPerms "noPerms"- noWritableDirPerms "noWritePerm"- writeFile' "myFile" "Blahfaselgagaga"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"- deleteFile' "myFile"- deleteFile' "myFileL"- deleteFile' "alreadyExists"- deleteFile' "alreadyExistsD2/lala"- deleteDir' "alreadyExistsD"- deleteDir' "alreadyExistsD2"- deleteDir' "dir"- deleteDir' "noPerms"- deleteDir' "noWritePerm"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.recreateSymlink" $ do-- -- successes --- it "recreateSymLink (Overwrite), all fine" $ do- recreateSymlink' "myFileL"- "movedFile"- Overwrite- removeFileIfExists "movedFile"-- it "recreateSymLink (Overwrite), all fine" $ do- recreateSymlink' "myFileL"- "dir/movedFile"- Overwrite- removeFileIfExists "dir/movedFile"-- it "recreateSymLink (Overwrite), destination file already exists" $- recreateSymlink' "myFileL"- "alreadyExists"- Overwrite-- it "recreateSymLink (Overwrite), destination already exists and is an empty dir" $ do- recreateSymlink' "myFileL"- "alreadyExistsD"- Overwrite- deleteFile' "alreadyExistsD"- createDir' "alreadyExistsD"-- -- posix failures --- it "recreateSymLink (Overwrite), destination already exists and is a non-empty dir" $- recreateSymlink' "myFileL"- "alreadyExistsD2"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == UnsatisfiedConstraints)-- it "recreateSymLink (Overwrite), wrong input type (file)" $- recreateSymlink' "myFile"- "movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "recreateSymLink (Overwrite), wrong input type (directory)" $- recreateSymlink' "dir"- "movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "recreateSymLink (Overwrite), can't write to destination directory" $- recreateSymlink' "myFileL"- "noWritePerm/movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "recreateSymLink (Overwrite), can't open destination directory" $- recreateSymlink' "myFileL"- "noPerms/movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "recreateSymLink (Overwrite), can't open source directory" $- recreateSymlink' "noPerms/myFileL"- "movedFile"- Overwrite- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- -- custom failures --- it "recreateSymLink (Overwrite), source and destination are the same file" $- recreateSymlink' "myFileL"- "myFileL"- Overwrite- `shouldThrow`- isSameFile-
− test/HPath/IO/RecreateSymlinkSpec.hs
@@ -1,130 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.RecreateSymlinkSpec where-----import Test.Hspec-import HPath.IO-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "RecreateSymlinkSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "myFile"- createSymlink' "myFileL" "myFile"- createRegularFile' "alreadyExists"- createDir' "alreadyExistsD"- createDir' "dir"- createDir' "noPerms"- createDir' "noWritePerm"- noPerms "noPerms"- noWritableDirPerms "noWritePerm"- writeFile' "myFile" "Blahfaselgagaga"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"- deleteFile' "myFile"- deleteFile' "myFileL"- deleteFile' "alreadyExists"- deleteDir' "alreadyExistsD"- deleteDir' "dir"- deleteDir' "noPerms"- deleteDir' "noWritePerm"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.recreateSymlink" $ do-- -- successes --- it "recreateSymLink (Strict), all fine" $ do- recreateSymlink' "myFileL"- "movedFile"- Strict- removeFileIfExists "movedFile"-- it "recreateSymLink (Strict), all fine" $ do- recreateSymlink' "myFileL"- "dir/movedFile"- Strict- removeFileIfExists "dir/movedFile"-- -- posix failures --- it "recreateSymLink (Strict), wrong input type (file)" $- recreateSymlink' "myFile"- "movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "recreateSymLink (Strict), wrong input type (directory)" $- recreateSymlink' "dir"- "movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == InvalidArgument)-- it "recreateSymLink (Strict), can't write to destination directory" $- recreateSymlink' "myFileL"- "noWritePerm/movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "recreateSymLink (Strict), can't open destination directory" $- recreateSymlink' "myFileL"- "noPerms/movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "recreateSymLink (Strict), can't open source directory" $- recreateSymlink' "noPerms/myFileL"- "movedFile"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "recreateSymLink (Strict), destination file already exists" $- recreateSymlink' "myFileL"- "alreadyExists"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "recreateSymLink (Strict), destination already exists and is a dir" $- recreateSymlink' "myFileL"- "alreadyExistsD"- Strict- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- -- custom failures --- it "recreateSymLink (Strict), source and destination are the same file" $- recreateSymlink' "myFileL"- "myFileL"- Strict- `shouldThrow`- isSameFile-
− test/HPath/IO/RenameFileSpec.hs
@@ -1,117 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HPath.IO.RenameFileSpec where---import Test.Hspec-import HPath.IO.Errors-import System.IO.Error- (- ioeGetErrorType- )-import GHC.IO.Exception- (- IOErrorType(..)- )-import Utils----upTmpDir :: IO ()-upTmpDir = do- setTmpDir "RenameFileSpec"- createTmpDir---setupFiles :: IO ()-setupFiles = do- createRegularFile' "myFile"- createSymlink' "myFileL" "myFile"- createRegularFile' "alreadyExists"- createDir' "alreadyExistsD"- createDir' "dir"- createDir' "noPerms"- createDir' "noWritePerm"- noPerms "noPerms"- noWritableDirPerms "noWritePerm"- writeFile' "myFile" "Blahfaselgagaga"---cleanupFiles :: IO ()-cleanupFiles = do- normalDirPerms "noPerms"- normalDirPerms "noWritePerm"- deleteFile' "myFile"- deleteFile' "myFileL"- deleteFile' "alreadyExists"- deleteDir' "alreadyExistsD"- deleteDir' "dir"- deleteDir' "noPerms"- deleteDir' "noWritePerm"---spec :: Spec-spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $- describe "HPath.IO.renameFile" $ do-- -- successes --- it "renameFile, all fine" $- renameFile' "myFile"- "renamedFile"-- it "renameFile, all fine" $- renameFile' "myFile"- "dir/renamedFile"-- it "renameFile, all fine on symlink" $- renameFile' "myFileL"- "renamedFile"-- it "renameFile, all fine on directory" $- renameFile' "dir"- "renamedFile"-- -- posix failures --- it "renameFile, source file does not exist" $- renameFile' "fileDoesNotExist"- "renamedFile"- `shouldThrow`- (\e -> ioeGetErrorType e == NoSuchThing)-- it "renameFile, can't write to output directory" $- renameFile' "myFile"- "noWritePerm/renamedFile"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "renameFile, can't open output directory" $- renameFile' "myFile"- "noPerms/renamedFile"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- it "renameFile, can't open source directory" $- renameFile' "noPerms/myFile"- "renamedFile"- `shouldThrow`- (\e -> ioeGetErrorType e == PermissionDenied)-- -- custom failures --- it "renameFile, destination file already exists" $- renameFile' "myFile"- "alreadyExists"- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "renameFile, move from file to dir" $- renameFile' "myFile"- "alreadyExistsD"- `shouldThrow`- (\e -> ioeGetErrorType e == AlreadyExists)-- it "renameFile, source and dest are same file" $- renameFile' "myFile"- "myFile"- `shouldThrow`- isSameFile-
− test/HPath/IO/ToAbsSpec.hs
@@ -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--
− test/HPath/IO/WriteFileSpec.hs
@@ -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)
− test/Main.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--import Test.Hspec-import Test.Hspec.Runner-import Test.Hspec.Formatters-import qualified Spec-import Utils----- TODO: chardev, blockdev, namedpipe, socket---main :: IO ()-main =- hspecWith- defaultConfig { configFormatter = Just progress }- $ beforeAll_ createBaseTmpDir- $ afterAll_ deleteBaseTmpDir- $ Spec.spec
− test/Spec.hs
@@ -1,2 +0,0 @@--- file test/Spec.hs-{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}
− test/Utils.hs
@@ -1,310 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PackageImports #-}---module Utils where---import Control.Applicative- (- (<$>)- )-import Control.Monad- (- forM_- , void- )-import Control.Monad.IfElse- (- whenM- )-import qualified Data.ByteString as BS-import Data.IORef- (- newIORef- , readIORef- , writeIORef- , IORef- )-import HPath.IO-import HPath.IO.Errors-import Prelude hiding (appendFile, readFile, writeFile)-import Data.Maybe- (- fromJust- )-import qualified HPath as P-import System.IO.Unsafe- (- unsafePerformIO- )-import qualified System.Posix.Directory.Traversals as DT-import System.Posix.Env.ByteString- (- getEnv- )-import Data.ByteString- (- ByteString- )-import qualified Data.ByteString.Lazy as L-import System.Posix.Files.ByteString- (- groupExecuteMode- , groupReadMode- , nullFileMode- , otherExecuteMode- , otherReadMode- , ownerExecuteMode- , ownerReadMode- , setFileMode- , unionFileModes- )--import qualified "unix" System.Posix.IO.ByteString as SPI-import qualified "unix-bytestring" System.Posix.IO.ByteString as SPB----baseTmpDir :: ByteString-baseTmpDir = "test/HPath/IO/tmp/"---tmpDir :: IORef ByteString-{-# NOINLINE tmpDir #-}-tmpDir = unsafePerformIO (newIORef baseTmpDir)---- ------------------ --[ Utilities ]--- --------------------setTmpDir :: ByteString -> IO ()-{-# NOINLINE setTmpDir #-}-setTmpDir bs = writeIORef tmpDir (baseTmpDir `BS.append` bs)---createTmpDir :: IO ()-{-# NOINLINE createTmpDir #-}-createTmpDir = do- pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs- tmp <- P.parseRel =<< readIORef tmpDir- void $ createDir newDirPerms (pwd P.</> tmp)---deleteTmpDir :: IO ()-{-# NOINLINE deleteTmpDir #-}-deleteTmpDir = do- pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs- tmp <- P.parseRel =<< readIORef tmpDir- void $ deleteDir (pwd P.</> tmp)---createBaseTmpDir :: IO ()-{-# NOINLINE createBaseTmpDir #-}-createBaseTmpDir = do- pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs- tmp <- P.parseRel baseTmpDir- void $ createDir newDirPerms (pwd P.</> tmp)---deleteBaseTmpDir :: IO ()-{-# NOINLINE deleteBaseTmpDir #-}-deleteBaseTmpDir = do- pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs- tmp <- P.parseRel baseTmpDir- contents <- getDirsFiles (pwd P.</> tmp)- forM_ contents deleteDir- void $ deleteDir (pwd P.</> tmp)---withRawTmpDir :: (P.Path P.Abs -> IO a) -> IO a-{-# NOINLINE withRawTmpDir #-}-withRawTmpDir f = do- pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs- tmp <- P.parseRel =<< readIORef tmpDir- f (pwd P.</> tmp)---getRawTmpDir :: IO ByteString-{-# NOINLINE getRawTmpDir #-}-getRawTmpDir = withRawTmpDir (return . flip BS.append "/" . P.fromAbs)---withTmpDir :: ByteString -> (P.Path P.Abs -> IO a) -> IO a-{-# NOINLINE withTmpDir #-}-withTmpDir ip f = do- pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs- tmp <- P.parseRel =<< readIORef tmpDir- p <- (pwd P.</> tmp P.</>) <$> P.parseRel ip- f p---withTmpDir' :: ByteString- -> ByteString- -> (P.Path P.Abs -> P.Path P.Abs -> IO a)- -> IO a-{-# NOINLINE withTmpDir' #-}-withTmpDir' ip1 ip2 f = do- pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs- tmp <- P.parseRel =<< readIORef tmpDir- p1 <- (pwd P.</> tmp P.</>) <$> P.parseRel ip1- p2 <- (pwd P.</> tmp P.</>) <$> P.parseRel ip2- f p1 p2---removeFileIfExists :: ByteString -> IO ()-{-# NOINLINE removeFileIfExists #-}-removeFileIfExists bs =- withTmpDir bs $ \p -> whenM (doesFileExist p) (deleteFile p)---removeDirIfExists :: ByteString -> IO ()-{-# NOINLINE removeDirIfExists #-}-removeDirIfExists bs =- withTmpDir bs $ \p -> whenM (doesDirectoryExist p) (deleteDirRecursive p)---copyFile' :: ByteString -> ByteString -> CopyMode -> IO ()-{-# NOINLINE copyFile' #-}-copyFile' inputFileP outputFileP cm =- withTmpDir' inputFileP outputFileP (\p1 p2 -> copyFile p1 p2 cm)---copyDirRecursive' :: ByteString -> ByteString- -> CopyMode -> RecursiveErrorMode -> IO ()-{-# NOINLINE copyDirRecursive' #-}-copyDirRecursive' inputDirP outputDirP cm rm =- withTmpDir' inputDirP outputDirP (\p1 p2 -> copyDirRecursive p1 p2 cm rm)---createDir' :: ByteString -> IO ()-{-# NOINLINE createDir' #-}-createDir' dest = withTmpDir dest (createDir newDirPerms)--createDirRecursive' :: ByteString -> IO ()-{-# NOINLINE createDirRecursive' #-}-createDirRecursive' dest = withTmpDir dest (createDirRecursive newDirPerms)--createRegularFile' :: ByteString -> IO ()-{-# NOINLINE createRegularFile' #-}-createRegularFile' dest = withTmpDir dest (createRegularFile newFilePerms)---createSymlink' :: ByteString -> ByteString -> IO ()-{-# NOINLINE createSymlink' #-}-createSymlink' dest sympoint = withTmpDir dest- (\x -> createSymlink x sympoint)---renameFile' :: ByteString -> ByteString -> IO ()-{-# NOINLINE renameFile' #-}-renameFile' inputFileP outputFileP =- withTmpDir' inputFileP outputFileP $ \i o -> do- renameFile i o- renameFile o i---moveFile' :: ByteString -> ByteString -> CopyMode -> IO ()-{-# NOINLINE moveFile' #-}-moveFile' inputFileP outputFileP cm =- withTmpDir' inputFileP outputFileP $ \i o -> do- moveFile i o cm- moveFile o i Strict---recreateSymlink' :: ByteString -> ByteString -> CopyMode -> IO ()-{-# NOINLINE recreateSymlink' #-}-recreateSymlink' inputFileP outputFileP cm =- withTmpDir' inputFileP outputFileP (\p1 p2 -> recreateSymlink p1 p2 cm)---noWritableDirPerms :: ByteString -> IO ()-{-# NOINLINE noWritableDirPerms #-}-noWritableDirPerms path = withTmpDir path $ \p ->- setFileMode (P.fromAbs p) perms- where- perms = ownerReadMode- `unionFileModes` ownerExecuteMode- `unionFileModes` groupReadMode- `unionFileModes` groupExecuteMode- `unionFileModes` otherReadMode- `unionFileModes` otherExecuteMode---noPerms :: ByteString -> IO ()-{-# NOINLINE noPerms #-}-noPerms path = withTmpDir path $ \p -> setFileMode (P.fromAbs p) nullFileMode---normalDirPerms :: ByteString -> IO ()-{-# NOINLINE normalDirPerms #-}-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---getDirsFiles' :: ByteString -> IO [P.Path P.Abs]-{-# NOINLINE getDirsFiles' #-}-getDirsFiles' path = withTmpDir path getDirsFiles---deleteFile' :: ByteString -> IO ()-{-# NOINLINE deleteFile' #-}-deleteFile' p = withTmpDir p deleteFile---deleteDir' :: ByteString -> IO ()-{-# NOINLINE deleteDir' #-}-deleteDir' p = withTmpDir p deleteDir---deleteDirRecursive' :: ByteString -> IO ()-{-# NOINLINE deleteDirRecursive' #-}-deleteDirRecursive' p = withTmpDir p deleteDirRecursive---canonicalizePath' :: ByteString -> IO (P.Path P.Abs)-{-# NOINLINE canonicalizePath' #-}-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---allDirectoryContents' :: ByteString -> IO [ByteString]-{-# NOINLINE allDirectoryContents' #-}-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-