Compare commits
207 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eea53e7113 | |||
| e194fdec91 | |||
| dc4e652f3a | |||
| b965635d05 | |||
| d21b7bed3b | |||
| 25083b293d | |||
| 09f25deecc | |||
| 1d7ffca4ac | |||
| 33e68590b2 | |||
| e1f302b4a6 | |||
| 4df7e02e27 | |||
| ecb52f5217 | |||
| 768443df27 | |||
| f7e2131192 | |||
| 87e452c49f | |||
| 1d00ae469d | |||
| d4402a25bb | |||
| 0c770be3a5 | |||
| f3f232e4c9 | |||
| b7cd5ba857 | |||
| 7d0ca1c230 | |||
| a6036a7aea | |||
| 0ff3808544 | |||
| 9d8b7d5bfc | |||
| d0beba227a | |||
| 607e67378c | |||
| 7b66379d49 | |||
| db23ad6b38 | |||
| f2986e60db | |||
| 1eeef0806d | |||
| 375d8ae0a3 | |||
| 3abc68cdd6 | |||
| 117641c419 | |||
| 824aff1751 | |||
| 94077aa6a6 | |||
| 22ddeeadcc | |||
| 1f4e289903 | |||
| bb24a57e36 | |||
| 9e831749c0 | |||
| dc2493427c | |||
| d898af171d | |||
| 80d60845dc | |||
| 5b9958ba23 | |||
| 9b20ce2e72 | |||
| 6a1f80bc17 | |||
| 931851c8c1 | |||
| 3b6eb46dc9 | |||
| 0ae398df39 | |||
| fe92de9abd | |||
| c49d36d1c3 | |||
| a6f902a9df | |||
| d0c3a2c9a7 | |||
| c3d2c0433e | |||
| dc131c3e6b | |||
| ca59169f02 | |||
| 09eea518b8 | |||
| 4e07fcf5b2 | |||
| a343d43b5c | |||
| fda03eb27a | |||
| a487f02894 | |||
| 8412ea96fb | |||
| 28e0d1d635 | |||
| c9013e5a2a | |||
| 1d03ec78b3 | |||
| f5c541b9cc | |||
| 3529ec2a15 | |||
| 7db7a9402f | |||
| 21dd1718c0 | |||
| 2e0fe6b698 | |||
| 200fc9b581 | |||
| 4ac3ee3e42 | |||
| 035c364b35 | |||
| 6bc5381108 | |||
| ef51863180 | |||
| 09062351f5 | |||
| ab4137572e | |||
| c03a7ec18f | |||
|
|
df298f187e | ||
| 466c72924a | |||
| 9dfb803ba8 | |||
| de46a0c568 | |||
| d9ba67b6f0 | |||
| 9342abeb7a | |||
| e8cbc632c9 | |||
| c556a3d3e4 | |||
| 3aee719130 | |||
| 10662ee803 | |||
| 672875f48f | |||
| 3e6d93182a | |||
| 1c95c9f8f9 | |||
| 0ec2cf8ca5 | |||
| 9ac10a6a7d | |||
| 1a2c77c6a6 | |||
| 3baecb7b51 | |||
| 5d5b0ae3c1 | |||
| f47c8edb42 | |||
| ef66a24f87 | |||
| f6a5cb8668 | |||
| 4dec385332 | |||
| 5b08e14b55 | |||
| ac381cbf60 | |||
| ce7fdcdcd6 | |||
| a31c9d1e88 | |||
| a5942ff026 | |||
| 4d71ad08ce | |||
| 92017ab630 | |||
| 16af98b32d | |||
| 6da01e382f | |||
| ed06543981 | |||
| d3eb2fc254 | |||
| a1eb06324f | |||
| d12ce30f57 | |||
| 7a6f0e8728 | |||
| 7ed5829d47 | |||
| d708f80a1f | |||
| f07619b7c6 | |||
| c5bcb90b65 | |||
| 4f047dbc77 | |||
| bc348c7dd5 | |||
| 5d1c5cc2ce | |||
| 8f6ca81d22 | |||
| a27d4ed55d | |||
| 64ae6db83a | |||
| 2a0a88a96d | |||
| 69dbf6714d | |||
| 2d96311b33 | |||
| 21668f12fe | |||
| 6e37e18bc8 | |||
| ae24f87c74 | |||
| 9f6734e700 | |||
| 741c510b91 | |||
| bb590a7692 | |||
| 641e23c3ef | |||
| 82ea75cc88 | |||
| abf043be14 | |||
| 10adc4be27 | |||
| a176e4970b | |||
| 08de2ebefb | |||
| d15d7761c1 | |||
| 7e924d3386 | |||
| 21fccc9ca9 | |||
| 79dbcd8b55 | |||
| b603f72407 | |||
| 98ca6c5d86 | |||
| 8d948366f9 | |||
| 86e7496917 | |||
| 1b9b8cc886 | |||
| 395621b27a | |||
| 51da8bf5c2 | |||
| bebc96fa6d | |||
| 08fa277b31 | |||
| 51609781b2 | |||
| 3cb3a822d7 | |||
| 7fa4c041a9 | |||
| e66074af1c | |||
| 4032629407 | |||
| f2fe5a3419 | |||
| 5ac7450495 | |||
| b55cf6d9f3 | |||
| ae9a806c2e | |||
| 9c199c6da2 | |||
| eb72fce33f | |||
| 65bb09d133 | |||
| 908513da2b | |||
| 47dd729e8a | |||
| 620550dab4 | |||
| ebab5355bc | |||
| 8fdf1bf956 | |||
| 39913faed6 | |||
| 5ed249f5d6 | |||
| a8ccfc2587 | |||
| 8fec862304 | |||
| 646fe7cfea | |||
| 1bf27258c1 | |||
| 797dcaf725 | |||
| 0fa66cd581 | |||
| ee3ace362b | |||
| 05fcad14f1 | |||
| 456af3b1ab | |||
| f841a53985 | |||
| eb27c368e6 | |||
| c76df7f159 | |||
| 613754c58f | |||
| d8b0b99edf | |||
| 794c3a2fc4 | |||
| 8a28a5dd0f | |||
| 78a3baeb25 | |||
| a3b1528974 | |||
| 7aac9bcb93 | |||
| 930b021a32 | |||
| 6b6c7f05c9 | |||
| a83e96259f | |||
| 14b48515a2 | |||
| 820bf8814d | |||
| e18d2dd2d6 | |||
| 117b3dc7d7 | |||
| c0ceccf716 | |||
| 010756e190 | |||
| 9a4fd00710 | |||
| f27becc4df | |||
| 3bbde22377 | |||
| 3da8533b48 | |||
| 687a113252 | |||
| 86a4b9ade2 | |||
| 6638cd8cc1 | |||
| 4ce35b9bec | |||
| 196647a383 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,5 +8,7 @@ TAGS
|
|||||||
tags
|
tags
|
||||||
*.tag
|
*.tag
|
||||||
.stack-work/
|
.stack-work/
|
||||||
|
dist/
|
||||||
|
dist-newstyle/
|
||||||
.cabal-sandbox/
|
.cabal-sandbox/
|
||||||
cabal.sandbox.config
|
cabal.sandbox.config
|
||||||
|
|||||||
85
.travis.yml
Normal file
85
.travis.yml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# See https://github.com/hvr/multi-ghc-travis for more information
|
||||||
|
|
||||||
|
language: c
|
||||||
|
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- env: CABALVER=3.0 GHCVER=7.10.3 SKIP_DOCTESTS=yes
|
||||||
|
addons: {apt: {packages: [cabal-install-3.0,ghc-7.10.3], sources: [hvr-ghc]}}
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install -y hscolour
|
||||||
|
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
|
||||||
|
- env: CABALVER=3.0 GHCVER=8.0.2 SKIP_DOCTESTS=yes
|
||||||
|
addons: {apt: {packages: [cabal-install-3.0,ghc-8.0.2], sources: [hvr-ghc]}}
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install -y hscolour
|
||||||
|
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
|
||||||
|
- env: CABALVER=3.0 GHCVER=8.2.2
|
||||||
|
addons: {apt: {packages: [cabal-install-3.0,ghc-8.2.2], sources: [hvr-ghc]}}
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install -y hscolour
|
||||||
|
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
|
||||||
|
- env: CABALVER=3.0 GHCVER=8.4.4
|
||||||
|
addons: {apt: {packages: [cabal-install-3.0,ghc-8.4.4], sources: [hvr-ghc]}}
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install -y hscolour
|
||||||
|
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
|
||||||
|
- env: CABALVER=3.0 GHCVER=8.6.5
|
||||||
|
addons: {apt: {packages: [cabal-install-3.0,ghc-8.6.5], sources: [hvr-ghc]}}
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install -y hscolour
|
||||||
|
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
|
||||||
|
- env: CABALVER=3.0 GHCVER=8.8.1
|
||||||
|
addons: {apt: {packages: [cabal-install-3.0,ghc-8.8.1], sources: [hvr-ghc]}}
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install -y hscolour
|
||||||
|
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
|
||||||
|
- env: CABALVER=head GHCVER=head
|
||||||
|
addons: {apt: {packages: [cabal-install-head,ghc-head], sources: [hvr-ghc]}}
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install -y hscolour
|
||||||
|
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode11.3
|
||||||
|
language: generic
|
||||||
|
before_install:
|
||||||
|
- mkdir -p ~/.ghcup/bin
|
||||||
|
- curl https://gitlab.haskell.org/haskell/ghcup/raw/master/ghcup > ~/.ghcup/bin/ghcup
|
||||||
|
- chmod +x ~/.ghcup/bin/ghcup
|
||||||
|
- export PATH=~/.cabal/bin:~/.ghcup/bin:$PATH
|
||||||
|
- ghcup install 8.6.5
|
||||||
|
- ghcup set 8.6.5
|
||||||
|
- ghcup install-cabal
|
||||||
|
|
||||||
|
allow_failures:
|
||||||
|
- env: CABALVER=head GHCVER=head
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- secure: HPBARvNM85ea2U0Ynq5MMe6BRlnuwqXWuSn20VY3EYCAT2njkVPYnR3O7+bGE6aq0KHAV87zz5iUfGJontd86tE0sDVjcSuRY0hqjOeJTkQq5M8WXJZOpVqlBTwDP1Q3x/fwoRa0dt9Z0tZZdKMlrf2XdcKPDdhcP1QYP4aV/jO4ZCfAQr7zVCvTae+Lp/KmwFYcBbFo/pj0duF1M4Oqx/D388b/W4jVE3lgd/TK7Ja1xWP6g+Oyvo6iQK8yJVYGdm6E+cVsNueiisnTJ/rRA53lsaC9dmWtZaFGl41wPviSU5zPq03vOuZMiyE2WtCHoo46ONXrXJ9N2soqdQVfEkr9Nw5LQl+6C5lCPEejZ575YUkuO05H3wvHMk3YY4zWXNFA9eZ47PEH8tpoUk9LPBacCKQFtp5lfRk63crba5CiFtcMyFq++0mLpNthNvtto7ffHMZrt6fvK9axI+r21VPftf/3FiFY4mnCp/Bln+ijklfZSN71VqiT20EWuqxQHw8aCpT00KA/PKGl9iJfoN4OO3XzNRTtmM+L9Im4bc1ni9YQ6N3UYg3z0nEnCLwFcTmTH/tDMHRremE0dM6B++YfcnyIhen8w+hG4bcXk7jbMUizRhUhStN7TZQuC9S4wE5whhp9c03rJZMmH5E2rlXY3lwVgeyWm1TuMp1RYWI=
|
||||||
|
|
||||||
|
install:
|
||||||
|
- cabal --version
|
||||||
|
- travis_retry cabal update
|
||||||
|
- cabal install --installdir=$HOME/.cabal/bin hspec-discover
|
||||||
|
- cabal install --installdir=$HOME/.cabal/bin doctest
|
||||||
|
|
||||||
|
script:
|
||||||
|
- (cd unix && autoreconf -fi)
|
||||||
|
- cabal build --enable-tests all
|
||||||
|
- cabal run spec
|
||||||
|
- ./hpath/run-doctests.sh
|
||||||
|
- ./hpath-filepath/run-doctests.sh
|
||||||
|
- (cd hpath && cabal check)
|
||||||
|
- (cd hpath-filepath && cabal check)
|
||||||
|
- (cd hpath-io && cabal check)
|
||||||
|
- cabal sdist all
|
||||||
|
- cabal install --lib all
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
- hasufell@posteo.de
|
||||||
|
|
||||||
33
CHANGELOG
33
CHANGELOG
@@ -1,33 +0,0 @@
|
|||||||
0.5.8:
|
|
||||||
* First version of the fork.
|
|
||||||
0.5.7:
|
|
||||||
* Fix haddock problem.
|
|
||||||
0.5.6:
|
|
||||||
* Reject only .. and .
|
|
||||||
0.5.5:
|
|
||||||
* Use filepath's isValid function for additional sanity checks
|
|
||||||
0.5.4:
|
|
||||||
* Disable parsing of path consisting only of "."
|
|
||||||
* Add NFData instance for Path
|
|
||||||
* Some typo/docs improvements
|
|
||||||
* Add standard headers to modules
|
|
||||||
0.5.3:
|
|
||||||
* Added conversion functions.
|
|
||||||
|
|
||||||
0.2.0:
|
|
||||||
|
|
||||||
* Rename parentAbs to simply parent.
|
|
||||||
* Add dirname.
|
|
||||||
|
|
||||||
0.3.0:
|
|
||||||
* Removed Generic instance.
|
|
||||||
|
|
||||||
0.4.0:
|
|
||||||
* Implemented stricter parsing, disabling use of "..".
|
|
||||||
* Made stripDir generic over MonadThrow
|
|
||||||
|
|
||||||
0.5.0:
|
|
||||||
* Fix stripDir p p /= Nothing bug.
|
|
||||||
|
|
||||||
0.5.2:
|
|
||||||
* Removed unused DeriveGeneric.
|
|
||||||
25
LICENSE
25
LICENSE
@@ -1,25 +0,0 @@
|
|||||||
Copyright (c) 2015–2016, FP Complete
|
|
||||||
Copyright (c) 2016, Julian Ospald
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of paths nor the
|
|
||||||
names of its contributors may be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
||||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
49
README.md
49
README.md
@@ -1,42 +1,19 @@
|
|||||||
# HPath
|
# HPath libraries
|
||||||
|
|
||||||
Support for well-typed paths in Haskell. Also provides ByteString based filepath
|
[](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](http://travis-ci.org/hasufell/hpath)
|
||||||
manipulation.
|
|
||||||
|
Set of libraries to deal with filepaths and files.
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
The motivation came during development of
|
* filepaths should be type-safe (absolute, relative, ...)
|
||||||
[hsfm](https://github.com/hasufell/hsfm)
|
* filepaths should be ByteString under the hood, see [Abstract FilePath Proposal (AFPP)](https://gitlab.haskell.org/ghc/ghc/wikis/proposal/abstract-file-path)
|
||||||
which has a pretty strict File type, but lacks a strict Path type, e.g.
|
* file high-level operations should be platform-specific, exception-stable, safe and as atomic as possible
|
||||||
for user input.
|
|
||||||
|
|
||||||
The library that came closest to my needs was
|
## Projects
|
||||||
[path](https://github.com/chrisdone/path),
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Differences to 'path'
|
|
||||||
|
|
||||||
* doesn't attempt to fake IO-related information into the path, so whether a path points to a file or directory is up to your IO-code to decide...
|
|
||||||
* trailing path separators will be preserved if they exist, no messing with that
|
|
||||||
* uses safe ByteString for filepaths under the hood instead of unsafe String
|
|
||||||
* fixes broken [dirname](https://github.com/chrisdone/path/issues/18)
|
|
||||||
* renames dirname/filename to basename/dirname to match the POSIX shell functions
|
|
||||||
* introduces a new `Path Fn` for safe filename guarantees and a `RelC` class
|
|
||||||
* allows pattern matching via unidirectional PatternSynonym
|
|
||||||
* 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'
|
|
||||||
|
|
||||||
* `hasTrailingPathSeparator` behaves in the same way as `System.FilePath`
|
|
||||||
* `dropTrailingPathSeparator` behaves in the same way as `System.FilePath`
|
|
||||||
* added various functions like `isValid`, `normalise` and `equalFilePath`
|
|
||||||
* uses the `word8` package for save word8 literals instead of `OverloadedStrings`
|
|
||||||
* has custom versions of `openFd` and `getDirectoryContents`
|
|
||||||
|
|
||||||
|
* [](https://hackage.haskell.org/package/hpath) [hpath](./hpath): Support for well-typed paths
|
||||||
|
* [](https://hackage.haskell.org/package/hpath-filepath) [hpath-filepath](./hpath-filepath): ByteString based filepath manipulation (can be used without hpath)
|
||||||
|
* [](https://hackage.haskell.org/package/hpath-directory) [hpath-directory](./hpath-directory): High-level IO operations for files/directories on raw ByteString filepaths (use hpath-io for the type-safe path version)
|
||||||
|
* [](https://hackage.haskell.org/package/hpath-io) [hpath-io](./hpath-io): High-level IO operations for files/directories utilizing type-safe Path
|
||||||
|
* [](https://hackage.haskell.org/package/hpath-posix) [hpath-posix](./hpath-posix): Some low-level POSIX glue code that is not in 'unix'
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
{-# LANGUAGE ForeignFunctionInterface #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE TupleSections #-}
|
|
||||||
{-# LANGUAGE ViewPatterns #-}
|
|
||||||
|
|
||||||
{-# OPTIONS_GHC -Wall #-}
|
|
||||||
import Control.Applicative
|
|
||||||
import Control.Monad
|
|
||||||
import System.Directory
|
|
||||||
import System.FilePath ((</>))
|
|
||||||
import System.Posix.ByteString.FilePath
|
|
||||||
import System.Posix.Directory.ByteString as PosixBS
|
|
||||||
import System.Posix.Directory.Traversals
|
|
||||||
import qualified System.Posix.FilePath as PosixBS
|
|
||||||
import System.Posix.Files.ByteString
|
|
||||||
|
|
||||||
import Control.Exception
|
|
||||||
import qualified Data.ByteString.Char8 as BS
|
|
||||||
|
|
||||||
import System.Environment (getArgs, withArgs)
|
|
||||||
import System.IO.Error
|
|
||||||
import System.IO.Unsafe
|
|
||||||
import System.Process (system)
|
|
||||||
import Criterion.Main
|
|
||||||
|
|
||||||
|
|
||||||
-- | Based on code from 'Real World Haskell', at
|
|
||||||
-- http://book.realworldhaskell.org/read/io-case-study-a-library-for-searching-the-filesystem.html#id620419
|
|
||||||
listFilesRecursive :: FilePath -> IO [FilePath]
|
|
||||||
listFilesRecursive topdir = do
|
|
||||||
names <- System.Directory.getDirectoryContents topdir
|
|
||||||
let properNames = filter (`notElem` [".", ".."]) names
|
|
||||||
paths <- forM properNames $ \name -> do
|
|
||||||
let path = topdir </> name
|
|
||||||
isDir <- doesDirectoryExist path
|
|
||||||
if isDir
|
|
||||||
then listFilesRecursive path
|
|
||||||
else return [path]
|
|
||||||
return (topdir : concat paths)
|
|
||||||
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
getDirectoryContentsBS :: RawFilePath -> IO [RawFilePath]
|
|
||||||
getDirectoryContentsBS path =
|
|
||||||
modifyIOError ((`ioeSetFileName` (BS.unpack path)) .
|
|
||||||
(`ioeSetLocation` "getDirectoryContentsBS")) $ do
|
|
||||||
bracket
|
|
||||||
(PosixBS.openDirStream path)
|
|
||||||
PosixBS.closeDirStream
|
|
||||||
loop
|
|
||||||
where
|
|
||||||
loop dirp = do
|
|
||||||
e <- PosixBS.readDirStream dirp
|
|
||||||
if BS.null e then return [] else do
|
|
||||||
es <- loop dirp
|
|
||||||
return (e:es)
|
|
||||||
|
|
||||||
|
|
||||||
-- | similar to 'listFilesRecursive, but uses RawFilePaths
|
|
||||||
listFilesRecursiveBS :: RawFilePath -> IO [RawFilePath]
|
|
||||||
listFilesRecursiveBS topdir = do
|
|
||||||
names <- getDirectoryContentsBS topdir
|
|
||||||
let properNames = filter (`notElem` [".", ".."]) names
|
|
||||||
paths <- forM properNames $ \name -> unsafeInterleaveIO $ do
|
|
||||||
let path = PosixBS.combine topdir name
|
|
||||||
isDir <- isDirectory <$> getFileStatus path
|
|
||||||
if isDir
|
|
||||||
then listFilesRecursiveBS path
|
|
||||||
else return [path]
|
|
||||||
return (topdir : concat paths)
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
benchTraverse :: RawFilePath -> IO ()
|
|
||||||
benchTraverse = traverseDirectory (\() p -> BS.putStrLn p) ()
|
|
||||||
|
|
||||||
main :: IO ()
|
|
||||||
main = do
|
|
||||||
args <- getArgs
|
|
||||||
let (d,otherArgs) = case args of
|
|
||||||
[] -> ("/usr/local",[])
|
|
||||||
x:xs -> (x,xs)
|
|
||||||
withArgs otherArgs $ defaultMain
|
|
||||||
[ bench "traverse (FilePath)" $ nfIO $ listFilesRecursive d >>= mapM_ putStrLn
|
|
||||||
, bench "traverse (RawFilePath)" $ nfIO $ listFilesRecursiveBS (BS.pack d) >>= mapM_ BS.putStrLn
|
|
||||||
, bench "allDirectoryContents" $ nfIO $ allDirectoryContents (BS.pack d) >>= mapM_ BS.putStrLn
|
|
||||||
, bench "allDirectoryContents'" $ nfIO $ allDirectoryContents' (BS.pack d) >>= mapM_ BS.putStrLn
|
|
||||||
, bench "traverseDirectory" $ nfIO $ benchTraverse (BS.pack d)
|
|
||||||
, bench "unix find" $ nfIO $ void $ system ("find " ++ d)
|
|
||||||
]
|
|
||||||
13
cabal.project
Normal file
13
cabal.project
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
packages: ./hpath
|
||||||
|
./hpath-directory
|
||||||
|
./hpath-filepath
|
||||||
|
./hpath-io
|
||||||
|
./hpath-posix
|
||||||
|
./unix
|
||||||
|
|
||||||
|
package hpath-io
|
||||||
|
ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16
|
||||||
|
|
||||||
|
-- https://github.com/composewell/streamly/blob/master/docs/Build.md
|
||||||
|
package streamly
|
||||||
|
ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
module Main where
|
|
||||||
|
|
||||||
import Control.Applicative
|
|
||||||
|
|
||||||
import Test.DocTest
|
|
||||||
import Test.HUnit
|
|
||||||
|
|
||||||
main =
|
|
||||||
doctest
|
|
||||||
["-isrc"
|
|
||||||
, "-XOverloadedStrings"
|
|
||||||
, "src/HPath.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
|
|
||||||
]
|
|
||||||
9
hpath-directory/CHANGELOG.md
Normal file
9
hpath-directory/CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Revision history for hpath-directory
|
||||||
|
|
||||||
|
## 0.13.1 -- 2020-01-29
|
||||||
|
|
||||||
|
* Split some functionality out into 'hpath-posix'
|
||||||
|
|
||||||
|
## 0.1.0.0 -- 2020-01-26
|
||||||
|
|
||||||
|
* First version. Released on an unsuspecting world.
|
||||||
30
hpath-directory/LICENSE
Normal file
30
hpath-directory/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2020, Julian Ospald
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Julian Ospald nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
21
hpath-directory/README.md
Normal file
21
hpath-directory/README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# HPath-filepath
|
||||||
|
|
||||||
|
[](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hackage.haskell.org/package/hpath-directory) [](http://travis-ci.org/hasufell/hpath) [](http://packdeps.haskellers.com/feed?needle=hpath-directory)
|
||||||
|
|
||||||
|
Support high-level IO operations on files/directories, utilizing ByteString
|
||||||
|
as FilePaths.
|
||||||
|
|
||||||
|
This package is part of the HPath suite, also check out:
|
||||||
|
|
||||||
|
* [hpath](https://hackage.haskell.org/package/hpath)
|
||||||
|
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
|
||||||
|
* [hpath-io](https://hackage.haskell.org/package/hpath-io)
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
This is basically a fork of [directory](https://hackage.haskell.org/package/directory), but is a complete rewrite and the API doesn't follow the directory package.
|
||||||
|
|
||||||
|
## Differences to 'posix-paths'
|
||||||
|
|
||||||
|
* 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
|
||||||
114
hpath-directory/hpath-directory.cabal
Normal file
114
hpath-directory/hpath-directory.cabal
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
cabal-version: >=1.10
|
||||||
|
|
||||||
|
name: hpath-directory
|
||||||
|
version: 0.13.1
|
||||||
|
synopsis: Alternative to 'directory' package with ByteString based filepaths
|
||||||
|
description: This provides a safer alternative to the 'directory'
|
||||||
|
package. FilePaths are ByteString based, so this
|
||||||
|
package only works on POSIX systems.
|
||||||
|
|
||||||
|
For a more high-level version of this with
|
||||||
|
proper Path type, use 'hpath-io', which makes
|
||||||
|
use of this package.
|
||||||
|
homepage: https://github.com/hasufell/hpath
|
||||||
|
bug-reports: https://github.com/hasufell/hpath/issues
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Julian Ospald <hasufell@posteo.de>
|
||||||
|
maintainer: Julian Ospald <hasufell@posteo.de>
|
||||||
|
copyright: Julian Ospald <hasufell@posteo.de> 2020
|
||||||
|
category: Filesystem
|
||||||
|
build-type: Simple
|
||||||
|
extra-source-files: CHANGELOG.md
|
||||||
|
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
|
||||||
|
|
||||||
|
library
|
||||||
|
if os(windows)
|
||||||
|
build-depends: unbuildable<0
|
||||||
|
buildable: False
|
||||||
|
exposed-modules: System.Posix.RawFilePath.Directory
|
||||||
|
System.Posix.RawFilePath.Directory.Errors
|
||||||
|
-- other-modules:
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends: base >= 4.8 && <5
|
||||||
|
, IfElse
|
||||||
|
, bytestring >= 0.10
|
||||||
|
, exceptions >= 0.10
|
||||||
|
, hpath-filepath >= 0.10.3
|
||||||
|
, hpath-posix >= 0.13
|
||||||
|
, safe-exceptions >= 0.1
|
||||||
|
, streamly >= 0.7
|
||||||
|
, streamly-bytestring >= 0.1.0.1
|
||||||
|
, time >= 1.8
|
||||||
|
, unix >= 2.5
|
||||||
|
, unix-bytestring >= 0.3
|
||||||
|
, utf8-string
|
||||||
|
if impl(ghc < 8.0)
|
||||||
|
build-depends:
|
||||||
|
fail >= 4.9
|
||||||
|
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
||||||
|
default-extensions: PackageImports
|
||||||
|
|
||||||
|
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:
|
||||||
|
System.Posix.RawFilePath.Directory.AppendFileSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CanonicalizePathSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CopyDirRecursiveCollectFailuresSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CopyDirRecursiveOverwriteSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CopyDirRecursiveSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CopyFileOverwriteSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CopyFileSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CreateDirIfMissingSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CreateDirRecursiveSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CreateDirSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CreateRegularFileSpec
|
||||||
|
System.Posix.RawFilePath.Directory.CreateSymlinkSpec
|
||||||
|
System.Posix.RawFilePath.Directory.DeleteDirRecursiveSpec
|
||||||
|
System.Posix.RawFilePath.Directory.DeleteDirSpec
|
||||||
|
System.Posix.RawFilePath.Directory.DeleteFileSpec
|
||||||
|
System.Posix.RawFilePath.Directory.GetDirsFilesSpec
|
||||||
|
System.Posix.RawFilePath.Directory.GetFileTypeSpec
|
||||||
|
System.Posix.RawFilePath.Directory.MoveFileOverwriteSpec
|
||||||
|
System.Posix.RawFilePath.Directory.MoveFileSpec
|
||||||
|
System.Posix.RawFilePath.Directory.ReadFileSpec
|
||||||
|
System.Posix.RawFilePath.Directory.RecreateSymlinkOverwriteSpec
|
||||||
|
System.Posix.RawFilePath.Directory.RecreateSymlinkSpec
|
||||||
|
System.Posix.RawFilePath.Directory.RenameFileSpec
|
||||||
|
System.Posix.RawFilePath.Directory.ToAbsSpec
|
||||||
|
System.Posix.RawFilePath.Directory.WriteFileLSpec
|
||||||
|
System.Posix.RawFilePath.Directory.WriteFileSpec
|
||||||
|
Spec
|
||||||
|
Utils
|
||||||
|
GHC-Options: -Wall
|
||||||
|
Build-Depends: base
|
||||||
|
, HUnit
|
||||||
|
, IfElse
|
||||||
|
, bytestring >= 0.10.0.0
|
||||||
|
, hpath-directory
|
||||||
|
, hpath-filepath >= 0.10
|
||||||
|
, hpath-posix >= 0.13
|
||||||
|
, hspec >= 1.3
|
||||||
|
, process
|
||||||
|
, time >= 1.8
|
||||||
|
, unix
|
||||||
|
, unix-bytestring
|
||||||
|
, utf8-string
|
||||||
|
default-extensions: PackageImports
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/hasufell/hpath
|
||||||
1215
hpath-directory/src/System/Posix/RawFilePath/Directory.hs
Normal file
1215
hpath-directory/src/System/Posix/RawFilePath/Directory.hs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
|||||||
|
module System.Posix.RawFilePath.Directory where
|
||||||
|
|
||||||
|
import System.Posix.ByteString.FilePath (RawFilePath)
|
||||||
|
|
||||||
|
canonicalizePath :: RawFilePath -> IO RawFilePath
|
||||||
|
|
||||||
|
toAbs :: RawFilePath -> IO RawFilePath
|
||||||
|
|
||||||
|
doesFileExist :: RawFilePath -> IO Bool
|
||||||
|
|
||||||
|
doesDirectoryExist :: RawFilePath -> IO Bool
|
||||||
|
|
||||||
|
isWritable :: RawFilePath -> IO Bool
|
||||||
|
|
||||||
|
canOpenDirectory :: RawFilePath -> IO Bool
|
||||||
327
hpath-directory/src/System/Posix/RawFilePath/Directory/Errors.hs
Normal file
327
hpath-directory/src/System/Posix/RawFilePath/Directory/Errors.hs
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
-- |
|
||||||
|
-- Module : System.Posix.RawFilePath.Directory.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 System.Posix.RawFilePath.Directory.Errors
|
||||||
|
(
|
||||||
|
-- * Types
|
||||||
|
HPathIOException(..)
|
||||||
|
, RecursiveFailureHint(..)
|
||||||
|
|
||||||
|
-- * Exception identifiers
|
||||||
|
, isSameFile
|
||||||
|
, isDestinationInSource
|
||||||
|
, isRecursiveFailure
|
||||||
|
, isReadContentsFailed
|
||||||
|
, isCreateDirFailed
|
||||||
|
, isCopyFileFailed
|
||||||
|
, isRecreateSymlinkFailed
|
||||||
|
|
||||||
|
-- * Path based functions
|
||||||
|
, throwFileDoesExist
|
||||||
|
, throwDirDoesExist
|
||||||
|
, throwSameFile
|
||||||
|
, sameFile
|
||||||
|
, throwDestinationInSource
|
||||||
|
|
||||||
|
-- * Error handling functions
|
||||||
|
, catchErrno
|
||||||
|
, rethrowErrnoAs
|
||||||
|
, handleIOError
|
||||||
|
, hideError
|
||||||
|
, bracketeer
|
||||||
|
, reactOnError
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
|
||||||
|
import Control.Applicative
|
||||||
|
(
|
||||||
|
(<$>)
|
||||||
|
)
|
||||||
|
import Control.Exception.Safe hiding (handleIOError)
|
||||||
|
import Control.Monad
|
||||||
|
(
|
||||||
|
forM
|
||||||
|
, when
|
||||||
|
)
|
||||||
|
import Control.Monad.IfElse
|
||||||
|
(
|
||||||
|
whenM
|
||||||
|
)
|
||||||
|
import Data.ByteString
|
||||||
|
(
|
||||||
|
ByteString
|
||||||
|
)
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import Data.ByteString.UTF8
|
||||||
|
(
|
||||||
|
toString
|
||||||
|
)
|
||||||
|
import Data.Typeable
|
||||||
|
(
|
||||||
|
Typeable
|
||||||
|
)
|
||||||
|
import Foreign.C.Error
|
||||||
|
(
|
||||||
|
getErrno
|
||||||
|
, Errno
|
||||||
|
)
|
||||||
|
import GHC.IO.Exception
|
||||||
|
(
|
||||||
|
IOErrorType
|
||||||
|
)
|
||||||
|
import {-# SOURCE #-} System.Posix.RawFilePath.Directory
|
||||||
|
(
|
||||||
|
canonicalizePath
|
||||||
|
, toAbs
|
||||||
|
, doesFileExist
|
||||||
|
, doesDirectoryExist
|
||||||
|
, isWritable
|
||||||
|
, canOpenDirectory
|
||||||
|
)
|
||||||
|
import System.IO.Error
|
||||||
|
(
|
||||||
|
alreadyExistsErrorType
|
||||||
|
, ioeGetErrorType
|
||||||
|
, mkIOError
|
||||||
|
)
|
||||||
|
import System.Posix.FilePath
|
||||||
|
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 mempty mempty)
|
||||||
|
isDestinationInSource ex = toConstr (ex :: HPathIOException) == (toConstr $ DestinationInSource mempty mempty)
|
||||||
|
isRecursiveFailure ex = toConstr (ex :: HPathIOException) == (toConstr $ RecursiveFailure mempty)
|
||||||
|
|
||||||
|
|
||||||
|
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 :: RawFilePath -> IO ()
|
||||||
|
throwFileDoesExist bs =
|
||||||
|
whenM (doesFileExist bs)
|
||||||
|
(ioError . mkIOError
|
||||||
|
alreadyExistsErrorType
|
||||||
|
"File already exists"
|
||||||
|
Nothing
|
||||||
|
$ (Just (toString $ bs))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
-- |Throws `AlreadyExists` `IOError` if directory exists.
|
||||||
|
throwDirDoesExist :: RawFilePath -> IO ()
|
||||||
|
throwDirDoesExist bs =
|
||||||
|
whenM (doesDirectoryExist bs)
|
||||||
|
(ioError . mkIOError
|
||||||
|
alreadyExistsErrorType
|
||||||
|
"Directory already exists"
|
||||||
|
Nothing
|
||||||
|
$ (Just (toString $ bs))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
-- |Uses `isSameFile` and throws `SameFile` if it returns True.
|
||||||
|
throwSameFile :: RawFilePath
|
||||||
|
-> RawFilePath
|
||||||
|
-> IO ()
|
||||||
|
throwSameFile bs1 bs2 =
|
||||||
|
whenM (sameFile bs1 bs2)
|
||||||
|
(throwIO $ SameFile bs1 bs2)
|
||||||
|
|
||||||
|
|
||||||
|
-- |Check if the files are the same by examining device and file id.
|
||||||
|
-- This follows symbolic links.
|
||||||
|
sameFile :: RawFilePath -> RawFilePath -> IO Bool
|
||||||
|
sameFile fp1 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 :: RawFilePath -- ^ source dir
|
||||||
|
-> RawFilePath -- ^ full destination, @dirname dest@
|
||||||
|
-- must exist
|
||||||
|
-> IO ()
|
||||||
|
throwDestinationInSource sbs dbs = do
|
||||||
|
destAbs <- toAbs dbs
|
||||||
|
dest' <- (\x -> maybe x (\y -> x </> y) $ basename dbs)
|
||||||
|
<$> (canonicalizePath $ takeDirectory destAbs)
|
||||||
|
dids <- forM (takeAllParents dest') $ \p -> do
|
||||||
|
fs <- PF.getSymbolicLinkStatus 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)
|
||||||
|
where
|
||||||
|
basename x = let b = takeBaseName x
|
||||||
|
in if BS.null b then Nothing else Just b
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
--[ 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
|
||||||
|
|
||||||
|
|
||||||
|
hideError :: IOErrorType -> IO () -> IO ()
|
||||||
|
hideError err = handleIO (\e -> if err == ioeGetErrorType e then pure () else ioError e)
|
||||||
|
|
||||||
|
|
||||||
|
-- |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
|
||||||
|
|
||||||
|
|
||||||
24
hpath-directory/test/Main.hs
Normal file
24
hpath-directory/test/Main.hs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import Data.IORef
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.Hspec.Runner
|
||||||
|
import Test.Hspec.Formatters
|
||||||
|
import qualified Spec
|
||||||
|
import Utils
|
||||||
|
import System.Posix.Temp.ByteString (mkdtemp)
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO: chardev, blockdev, namedpipe, socket
|
||||||
|
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
tmpBase <- mkdtemp "/tmp/"
|
||||||
|
writeIORef baseTmpDir (Just (tmpBase `BS.append` "/"))
|
||||||
|
putStrLn $ ("Temporary test directory at: " ++ show tmpBase)
|
||||||
|
hspecWith
|
||||||
|
defaultConfig { configFormatter = Just progress }
|
||||||
|
$ afterAll_ deleteBaseTmpDir
|
||||||
|
$ Spec.spec
|
||||||
2
hpath-directory/test/Spec.hs
Normal file
2
hpath-directory/test/Spec.hs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- file test/Spec.hs
|
||||||
|
{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.AppendFileSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import System.IO.Error
|
||||||
|
(
|
||||||
|
ioeGetErrorType
|
||||||
|
)
|
||||||
|
import GHC.IO.Exception
|
||||||
|
(
|
||||||
|
IOErrorType(..)
|
||||||
|
)
|
||||||
|
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 "System.Posix.RawFilePath.Directory.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)
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.CopyDirRecursiveCollectFailuresSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import Data.List (sort)
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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"
|
||||||
|
++ " >/dev/null")
|
||||||
|
`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
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.CopyDirRecursiveOverwriteSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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"
|
||||||
|
++ " >/dev/null")
|
||||||
|
`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"
|
||||||
|
++ " >/dev/null")
|
||||||
|
`shouldReturn` (ExitFailure 1)
|
||||||
|
copyDirRecursive' "inputDir"
|
||||||
|
"alreadyExistsD"
|
||||||
|
Overwrite
|
||||||
|
FailEarly
|
||||||
|
(system $ "diff -r --no-dereference "
|
||||||
|
++ toString tmpDir' ++ "inputDir" ++ " "
|
||||||
|
++ toString tmpDir' ++ "alreadyExistsD"
|
||||||
|
++ " >/dev/null")
|
||||||
|
`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
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.CopyDirRecursiveSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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"
|
||||||
|
++ " >/dev/null")
|
||||||
|
`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
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.CopyFileOverwriteSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.CopyFileSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.CreateDirIfMissingSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import System.IO.Error
|
||||||
|
(
|
||||||
|
ioeGetErrorType
|
||||||
|
)
|
||||||
|
import GHC.IO.Exception
|
||||||
|
(
|
||||||
|
IOErrorType(..)
|
||||||
|
)
|
||||||
|
import Utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
upTmpDir :: IO ()
|
||||||
|
upTmpDir = do
|
||||||
|
setTmpDir "CreateDirIfMissingSpec"
|
||||||
|
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 "System.Posix.RawFilePath.Directory.CreateDirIfMissing" $ do
|
||||||
|
|
||||||
|
-- successes --
|
||||||
|
it "createDirIfMissing, all fine" $ do
|
||||||
|
createDirIfMissing' "newDir"
|
||||||
|
removeDirIfExists "newDir"
|
||||||
|
|
||||||
|
it "createDirIfMissing, destination directory already exists" $
|
||||||
|
createDirIfMissing' "alreadyExists"
|
||||||
|
|
||||||
|
-- posix failures --
|
||||||
|
it "createDirIfMissing, parent directories do not exist" $
|
||||||
|
createDirIfMissing' "some/thing/dada"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == NoSuchThing)
|
||||||
|
|
||||||
|
it "createDirIfMissing, can't write to output directory" $
|
||||||
|
createDirIfMissing' "noWritePerms/newDir"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == PermissionDenied)
|
||||||
|
|
||||||
|
it "createDirIfMissing, can't open output directory" $
|
||||||
|
createDirIfMissing' "noPerms/newDir"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == PermissionDenied)
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.DeleteFileSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.GetDirsFilesSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
(
|
||||||
|
sort
|
||||||
|
)
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory hiding (getDirsFiles')
|
||||||
|
import System.Posix.FilePath
|
||||||
|
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 "System.Posix.RawFilePath.Directory.getDirsFiles" $ do
|
||||||
|
|
||||||
|
-- successes --
|
||||||
|
it "getDirsFiles, all fine" $
|
||||||
|
withRawTmpDir $ \p -> do
|
||||||
|
let expectedFiles = [".hidden"
|
||||||
|
,"Lala"
|
||||||
|
,"dir"
|
||||||
|
,"dirsym"
|
||||||
|
,"file"
|
||||||
|
,"noPerms"
|
||||||
|
,"syml"]
|
||||||
|
(fmap sort $ getDirsFiles p)
|
||||||
|
`shouldReturn` fmap (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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.GetFileTypeSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
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 "System.Posix.RawFilePath.Directory.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)
|
||||||
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.MoveFileOverwriteSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.MoveFileSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.ReadFileSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import System.IO.Error
|
||||||
|
(
|
||||||
|
ioeGetErrorType
|
||||||
|
)
|
||||||
|
import GHC.IO.Exception
|
||||||
|
(
|
||||||
|
IOErrorType(..)
|
||||||
|
)
|
||||||
|
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 "System.Posix.RawFilePath.Directory.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)
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.RecreateSymlinkOverwriteSpec where
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO: exception if destination exists but is not a file + `OverWrite` CopyMode
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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
|
||||||
|
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.RecreateSymlinkSpec where
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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
|
||||||
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.RenameFileSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import System.Posix.RawFilePath.Directory.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 "System.Posix.RawFilePath.Directory.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
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.ToAbsSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = describe "System.Posix.RawFilePath.Directory.toAbs" $ do
|
||||||
|
|
||||||
|
-- successes --
|
||||||
|
it "toAbs returns absolute paths unchanged" $ do
|
||||||
|
let p1 = "/a/b/c/d"
|
||||||
|
to <- toAbs p1
|
||||||
|
p1 `shouldBe` to
|
||||||
|
|
||||||
|
it "toAbs returns even existing absolute paths unchanged" $ do
|
||||||
|
let p1 = "/home"
|
||||||
|
to <- toAbs p1
|
||||||
|
p1 `shouldBe` to
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.WriteFileLSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import System.IO.Error
|
||||||
|
(
|
||||||
|
ioeGetErrorType
|
||||||
|
)
|
||||||
|
import GHC.IO.Exception
|
||||||
|
(
|
||||||
|
IOErrorType(..)
|
||||||
|
)
|
||||||
|
import Utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
upTmpDir :: IO ()
|
||||||
|
upTmpDir = do
|
||||||
|
setTmpDir "WriteFileLSpec"
|
||||||
|
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 "System.Posix.RawFilePath.Directory.WriteFileL" $ do
|
||||||
|
|
||||||
|
-- successes --
|
||||||
|
it "WriteFileL file with content, everything clear" $ do
|
||||||
|
writeFileL' "fileWithContent" "blahfaselllll"
|
||||||
|
out <- readFile' "fileWithContent"
|
||||||
|
out `shouldBe` "blahfaselllll"
|
||||||
|
|
||||||
|
it "WriteFileL file with content, everything clear" $ do
|
||||||
|
writeFileL' "fileWithContent" "gagagaga"
|
||||||
|
out <- readFile' "fileWithContent"
|
||||||
|
out `shouldBe` "gagagaga"
|
||||||
|
|
||||||
|
it "WriteFileL file with content, everything clear" $ do
|
||||||
|
writeFileL' "fileWithContent" ""
|
||||||
|
out <- readFile' "fileWithContent"
|
||||||
|
out `shouldBe` ""
|
||||||
|
|
||||||
|
it "WriteFileL file without content, everything clear" $ do
|
||||||
|
writeFileL' "fileWithoutContent" "blahfaselllll"
|
||||||
|
out <- readFile' "fileWithoutContent"
|
||||||
|
out `shouldBe` "blahfaselllll"
|
||||||
|
|
||||||
|
it "WriteFileL, everything clear" $ do
|
||||||
|
writeFileL' "fileWithoutContent" "gagagaga"
|
||||||
|
out <- readFile' "fileWithoutContent"
|
||||||
|
out `shouldBe` "gagagaga"
|
||||||
|
|
||||||
|
it "WriteFileL symlink, everything clear" $ do
|
||||||
|
writeFileL' "inputFileSymL" "blahfaselllll"
|
||||||
|
out <- readFile' "inputFileSymL"
|
||||||
|
out `shouldBe` "blahfaselllll"
|
||||||
|
|
||||||
|
it "WriteFileL symlink, everything clear" $ do
|
||||||
|
writeFileL' "inputFileSymL" "gagagaga"
|
||||||
|
out <- readFile' "inputFileSymL"
|
||||||
|
out `shouldBe` "gagagaga"
|
||||||
|
|
||||||
|
|
||||||
|
-- posix failures --
|
||||||
|
it "WriteFileL to dir, inappropriate type" $ do
|
||||||
|
writeFileL' "alreadyExistsD" ""
|
||||||
|
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
|
||||||
|
|
||||||
|
it "WriteFileL, no permissions to file" $ do
|
||||||
|
writeFileL' "noPerms" ""
|
||||||
|
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||||
|
|
||||||
|
it "WriteFileL, no permissions to file" $ do
|
||||||
|
writeFileL' "noPermsD/inputFile" ""
|
||||||
|
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||||
|
|
||||||
|
it "WriteFileL, file does not exist" $ do
|
||||||
|
writeFileL' "gaga" ""
|
||||||
|
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.WriteFileSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import System.IO.Error
|
||||||
|
(
|
||||||
|
ioeGetErrorType
|
||||||
|
)
|
||||||
|
import GHC.IO.Exception
|
||||||
|
(
|
||||||
|
IOErrorType(..)
|
||||||
|
)
|
||||||
|
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 "System.Posix.RawFilePath.Directory.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)
|
||||||
293
hpath-directory/test/Utils.hs
Normal file
293
hpath-directory/test/Utils.hs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
|
||||||
|
module Utils where
|
||||||
|
|
||||||
|
|
||||||
|
import Control.Applicative
|
||||||
|
(
|
||||||
|
(<$>)
|
||||||
|
)
|
||||||
|
import Control.Monad
|
||||||
|
(
|
||||||
|
forM_
|
||||||
|
, void
|
||||||
|
)
|
||||||
|
import Control.Monad.IfElse
|
||||||
|
(
|
||||||
|
whenM
|
||||||
|
)
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import qualified Data.ByteString.Lazy as BSL
|
||||||
|
import Data.IORef
|
||||||
|
(
|
||||||
|
newIORef
|
||||||
|
, readIORef
|
||||||
|
, writeIORef
|
||||||
|
, IORef
|
||||||
|
)
|
||||||
|
import "hpath-directory" System.Posix.RawFilePath.Directory
|
||||||
|
import Prelude hiding (appendFile, readFile, writeFile)
|
||||||
|
import Data.Maybe
|
||||||
|
(
|
||||||
|
fromJust
|
||||||
|
)
|
||||||
|
import System.IO.Unsafe
|
||||||
|
(
|
||||||
|
unsafePerformIO
|
||||||
|
)
|
||||||
|
import qualified System.Posix.RawFilePath.Directory.Traversals as DT
|
||||||
|
import Data.ByteString
|
||||||
|
(
|
||||||
|
ByteString
|
||||||
|
)
|
||||||
|
import qualified Data.ByteString.Lazy as L
|
||||||
|
import System.Posix.FilePath
|
||||||
|
import System.Posix.Files.ByteString
|
||||||
|
(
|
||||||
|
groupExecuteMode
|
||||||
|
, groupReadMode
|
||||||
|
, nullFileMode
|
||||||
|
, otherExecuteMode
|
||||||
|
, otherReadMode
|
||||||
|
, ownerExecuteMode
|
||||||
|
, ownerReadMode
|
||||||
|
, setFileMode
|
||||||
|
, unionFileModes
|
||||||
|
)
|
||||||
|
|
||||||
|
baseTmpDir :: IORef (Maybe ByteString)
|
||||||
|
{-# NOINLINE baseTmpDir #-}
|
||||||
|
baseTmpDir = unsafePerformIO (newIORef Nothing)
|
||||||
|
|
||||||
|
|
||||||
|
tmpDir :: IORef (Maybe ByteString)
|
||||||
|
{-# NOINLINE tmpDir #-}
|
||||||
|
tmpDir = unsafePerformIO (newIORef Nothing)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
--[ Utilities ]--
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
|
||||||
|
setTmpDir :: ByteString -> IO ()
|
||||||
|
{-# NOINLINE setTmpDir #-}
|
||||||
|
setTmpDir bs = do
|
||||||
|
tmp <- fromJust <$> readIORef baseTmpDir
|
||||||
|
writeIORef tmpDir (Just (tmp `BS.append` bs))
|
||||||
|
|
||||||
|
|
||||||
|
createTmpDir :: IO ()
|
||||||
|
{-# NOINLINE createTmpDir #-}
|
||||||
|
createTmpDir = do
|
||||||
|
tmp <- fromJust <$> readIORef tmpDir
|
||||||
|
void $ createDir newDirPerms tmp
|
||||||
|
|
||||||
|
|
||||||
|
deleteTmpDir :: IO ()
|
||||||
|
{-# NOINLINE deleteTmpDir #-}
|
||||||
|
deleteTmpDir = do
|
||||||
|
tmp <- fromJust <$> readIORef tmpDir
|
||||||
|
void $ deleteDir tmp
|
||||||
|
|
||||||
|
|
||||||
|
deleteBaseTmpDir :: IO ()
|
||||||
|
{-# NOINLINE deleteBaseTmpDir #-}
|
||||||
|
deleteBaseTmpDir = do
|
||||||
|
tmp <- fromJust <$> readIORef baseTmpDir
|
||||||
|
contents <- getDirsFiles tmp
|
||||||
|
forM_ contents deleteDir
|
||||||
|
void $ deleteDir tmp
|
||||||
|
|
||||||
|
|
||||||
|
withRawTmpDir :: (ByteString -> IO a) -> IO a
|
||||||
|
{-# NOINLINE withRawTmpDir #-}
|
||||||
|
withRawTmpDir f = do
|
||||||
|
tmp <- fromJust <$> readIORef tmpDir
|
||||||
|
f tmp
|
||||||
|
|
||||||
|
|
||||||
|
getRawTmpDir :: IO ByteString
|
||||||
|
{-# NOINLINE getRawTmpDir #-}
|
||||||
|
getRawTmpDir = withRawTmpDir (return . flip BS.append "/")
|
||||||
|
|
||||||
|
|
||||||
|
withTmpDir :: ByteString -> (ByteString -> IO a) -> IO a
|
||||||
|
{-# NOINLINE withTmpDir #-}
|
||||||
|
withTmpDir ip f = do
|
||||||
|
tmp <- fromJust <$> readIORef tmpDir
|
||||||
|
let p = tmp </> ip
|
||||||
|
f p
|
||||||
|
|
||||||
|
|
||||||
|
withTmpDir' :: ByteString
|
||||||
|
-> ByteString
|
||||||
|
-> (ByteString -> ByteString -> IO a)
|
||||||
|
-> IO a
|
||||||
|
{-# NOINLINE withTmpDir' #-}
|
||||||
|
withTmpDir' ip1 ip2 f = do
|
||||||
|
tmp <- fromJust <$> readIORef tmpDir
|
||||||
|
let p1 = tmp </> ip1
|
||||||
|
let p2 = tmp </> 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)
|
||||||
|
|
||||||
|
createDirIfMissing' :: ByteString -> IO ()
|
||||||
|
{-# NOINLINE createDirIfMissing' #-}
|
||||||
|
createDirIfMissing' dest = withTmpDir dest (createDirIfMissing 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 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 nullFileMode
|
||||||
|
|
||||||
|
|
||||||
|
normalDirPerms :: ByteString -> IO ()
|
||||||
|
{-# NOINLINE normalDirPerms #-}
|
||||||
|
normalDirPerms path =
|
||||||
|
withTmpDir path $ \p -> setFileMode p newDirPerms
|
||||||
|
|
||||||
|
|
||||||
|
normalFilePerms :: ByteString -> IO ()
|
||||||
|
{-# NOINLINE normalFilePerms #-}
|
||||||
|
normalFilePerms path =
|
||||||
|
withTmpDir path $ \p -> setFileMode p newFilePerms
|
||||||
|
|
||||||
|
|
||||||
|
getFileType' :: ByteString -> IO FileType
|
||||||
|
{-# NOINLINE getFileType' #-}
|
||||||
|
getFileType' path = withTmpDir path getFileType
|
||||||
|
|
||||||
|
|
||||||
|
getDirsFiles' :: ByteString -> IO [ByteString]
|
||||||
|
{-# 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 ByteString
|
||||||
|
{-# NOINLINE canonicalizePath' #-}
|
||||||
|
canonicalizePath' p = withTmpDir p canonicalizePath
|
||||||
|
|
||||||
|
|
||||||
|
writeFile' :: ByteString -> ByteString -> IO ()
|
||||||
|
{-# NOINLINE writeFile' #-}
|
||||||
|
writeFile' ip bs =
|
||||||
|
withTmpDir ip $ \p -> writeFile p Nothing bs
|
||||||
|
|
||||||
|
writeFileL' :: ByteString -> BSL.ByteString -> IO ()
|
||||||
|
{-# NOINLINE writeFileL' #-}
|
||||||
|
writeFileL' ip bs =
|
||||||
|
withTmpDir ip $ \p -> writeFileL p Nothing 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
|
||||||
|
|
||||||
|
|
||||||
|
readFile' :: ByteString -> IO ByteString
|
||||||
|
{-# NOINLINE readFile' #-}
|
||||||
|
readFile' p = withTmpDir p (fmap L.toStrict . readFile)
|
||||||
|
|
||||||
14
hpath-filepath/CHANGELOG.md
Normal file
14
hpath-filepath/CHANGELOG.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Revision history for hpath-filepath
|
||||||
|
|
||||||
|
## 0.10.4 -- 2020-01-26
|
||||||
|
|
||||||
|
* Add `takeAllParents`
|
||||||
|
|
||||||
|
|
||||||
|
## 0.10.2 -- 2020-01-18
|
||||||
|
|
||||||
|
* Add `isSpecialDirectoryEntry`
|
||||||
|
|
||||||
|
## 0.10.0 -- 2020-01-04
|
||||||
|
|
||||||
|
* First version. Split from 'hpath', contains only the filepath ByteString manipulation parts.
|
||||||
30
hpath-filepath/LICENSE
Normal file
30
hpath-filepath/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2020, Julian Ospald
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Julian Ospald nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
29
hpath-filepath/README.md
Normal file
29
hpath-filepath/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# HPath-filepath
|
||||||
|
|
||||||
|
[](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hackage.haskell.org/package/hpath-filepath) [](http://travis-ci.org/hasufell/hpath) [](http://packdeps.haskellers.com/feed?needle=hpath-filepath)
|
||||||
|
|
||||||
|
Support for bytestring based filepath manipulation, similar to 'filepath'.
|
||||||
|
|
||||||
|
This package is part of the HPath suite, also check out:
|
||||||
|
|
||||||
|
* [hpath](https://hackage.haskell.org/package/hpath)
|
||||||
|
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
|
||||||
|
* [hpath-io](https://hackage.haskell.org/package/hpath-io)
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
This is basically a fork of [posix-paths](https://github.com/JohnLato/posix-paths), which seemed to have stalled development.
|
||||||
|
|
||||||
|
There is also a similar library [filepath-bytestring](https://hackage.haskell.org/package/filepath-bytestring), but it doesn't follow an open development model and is cross-platform, which this library is not interested in.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
* has some additional functions
|
||||||
|
|
||||||
|
## Differences to 'filepath-bytestring'
|
||||||
|
|
||||||
|
* uses the `word8` package for save word8 literals instead of `OverloadedStrings`
|
||||||
|
* is not cross-platform (less odd code to maintain)
|
||||||
|
* has some additional functions
|
||||||
2
hpath-filepath/Setup.hs
Normal file
2
hpath-filepath/Setup.hs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
||||||
39
hpath-filepath/hpath-filepath.cabal
Normal file
39
hpath-filepath/hpath-filepath.cabal
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: hpath-filepath
|
||||||
|
version: 0.10.4
|
||||||
|
synopsis: ByteString based filepath manipulation
|
||||||
|
description: ByteString based filepath manipulation, similar to 'filepath' package. This is POSIX only.
|
||||||
|
-- bug-reports:
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Julian Ospald <hasufell@posteo.de>
|
||||||
|
maintainer: Julian Ospald <hasufell@posteo.de>
|
||||||
|
copyright: Julian Ospald 2016
|
||||||
|
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.md
|
||||||
|
|
||||||
|
library
|
||||||
|
if os(windows)
|
||||||
|
build-depends: unbuildable<0
|
||||||
|
buildable: False
|
||||||
|
exposed-modules: System.Posix.FilePath
|
||||||
|
-- other-modules:
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends: base >=4.8 && <5
|
||||||
|
, bytestring >= 0.10.0.0
|
||||||
|
, unix >= 2.5
|
||||||
|
, word8
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/hasufell/hpath
|
||||||
23
hpath-filepath/run-doctests.sh
Executable file
23
hpath-filepath/run-doctests.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -n "${SKIP_DOCTESTS}" ] ; then
|
||||||
|
echo "Skipping doctests"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v doctest >/dev/null ; then
|
||||||
|
tempdir="$(mktemp -d)"
|
||||||
|
(
|
||||||
|
cd "${tempdir}"
|
||||||
|
cabal install --installdir="${tempdir}" doctest
|
||||||
|
)
|
||||||
|
export PATH="${tempdir}:$PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cd "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
|
||||||
|
|
||||||
|
cabal exec doctest -- -isrc -XOverloadedStrings System.Posix.FilePath
|
||||||
@@ -1,12 +1,26 @@
|
|||||||
|
-- |
|
||||||
|
-- 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 #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
|
||||||
{-# OPTIONS_GHC -Wall #-}
|
{-# OPTIONS_GHC -Wall #-}
|
||||||
|
|
||||||
-- | The equivalent of "System.FilePath" on raw (byte string) file paths.
|
|
||||||
--
|
|
||||||
-- Not all functions of "System.FilePath" are implemented yet. Feel free to contribute!
|
|
||||||
module System.Posix.FilePath (
|
module System.Posix.FilePath (
|
||||||
|
|
||||||
|
-- * Separator predicates
|
||||||
pathSeparator
|
pathSeparator
|
||||||
, isPathSeparator
|
, isPathSeparator
|
||||||
, searchPathSeparator
|
, searchPathSeparator
|
||||||
@@ -14,6 +28,11 @@ module System.Posix.FilePath (
|
|||||||
, extSeparator
|
, extSeparator
|
||||||
, isExtSeparator
|
, isExtSeparator
|
||||||
|
|
||||||
|
-- * $PATH methods
|
||||||
|
, splitSearchPath
|
||||||
|
, getSearchPath
|
||||||
|
|
||||||
|
-- * Extension functions
|
||||||
, splitExtension
|
, splitExtension
|
||||||
, takeExtension
|
, takeExtension
|
||||||
, replaceExtension
|
, replaceExtension
|
||||||
@@ -24,7 +43,9 @@ module System.Posix.FilePath (
|
|||||||
, splitExtensions
|
, splitExtensions
|
||||||
, dropExtensions
|
, dropExtensions
|
||||||
, takeExtensions
|
, takeExtensions
|
||||||
|
, stripExtension
|
||||||
|
|
||||||
|
-- * Filename\/directory functions
|
||||||
, splitFileName
|
, splitFileName
|
||||||
, takeFileName
|
, takeFileName
|
||||||
, replaceFileName
|
, replaceFileName
|
||||||
@@ -37,74 +58,134 @@ module System.Posix.FilePath (
|
|||||||
, (</>)
|
, (</>)
|
||||||
, splitPath
|
, splitPath
|
||||||
, joinPath
|
, joinPath
|
||||||
, normalise
|
|
||||||
, splitDirectories
|
, splitDirectories
|
||||||
|
, takeAllParents
|
||||||
|
|
||||||
|
-- * Trailing slash functions
|
||||||
, hasTrailingPathSeparator
|
, hasTrailingPathSeparator
|
||||||
, addTrailingPathSeparator
|
, addTrailingPathSeparator
|
||||||
, dropTrailingPathSeparator
|
, dropTrailingPathSeparator
|
||||||
|
|
||||||
|
-- * File name manipulations
|
||||||
|
, normalise
|
||||||
|
, makeRelative
|
||||||
|
, equalFilePath
|
||||||
, isRelative
|
, isRelative
|
||||||
, isAbsolute
|
, isAbsolute
|
||||||
, isValid
|
, isValid
|
||||||
, equalFilePath
|
, makeValid
|
||||||
|
, isSpecialDirectoryEntry
|
||||||
|
, isFileName
|
||||||
|
, hasParentDir
|
||||||
|
, hiddenFile
|
||||||
|
|
||||||
, module System.Posix.ByteString.FilePath
|
, module System.Posix.ByteString.FilePath
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
|
import Data.String (fromString)
|
||||||
import System.Posix.ByteString.FilePath
|
import System.Posix.ByteString.FilePath
|
||||||
|
import qualified System.Posix.Env.ByteString as PE
|
||||||
|
|
||||||
import Data.Maybe (isJust)
|
import Data.Maybe (isJust)
|
||||||
import Data.Word8
|
import Data.Word8
|
||||||
|
#if !MIN_VERSION_bytestring(0,10,8)
|
||||||
|
import qualified Data.List as L
|
||||||
|
#endif
|
||||||
import Control.Arrow (second)
|
import Control.Arrow (second)
|
||||||
|
|
||||||
-- $setup
|
-- $setup
|
||||||
-- >>> import Data.Char
|
-- >>> import Data.Char
|
||||||
|
-- >>> import Data.Maybe
|
||||||
|
-- >>> import Data.Word8
|
||||||
-- >>> import Test.QuickCheck
|
-- >>> import Test.QuickCheck
|
||||||
-- >>> import Control.Applicative
|
-- >>> import Control.Applicative
|
||||||
-- >>> import qualified Data.ByteString as BS
|
-- >>> import qualified Data.ByteString as BS
|
||||||
-- >>> import Data.ByteString (ByteString)
|
|
||||||
-- >>> instance Arbitrary ByteString where arbitrary = BS.pack <$> arbitrary
|
-- >>> instance Arbitrary ByteString where arbitrary = BS.pack <$> arbitrary
|
||||||
-- >>> instance CoArbitrary ByteString where coarbitrary = coarbitrary . BS.unpack
|
-- >>> instance CoArbitrary ByteString where coarbitrary = coarbitrary . BS.unpack
|
||||||
--
|
--
|
||||||
-- >>> let _chr :: Word8 -> Char; _chr = chr . fromIntegral
|
-- >>> let _chr :: Word8 -> Char; _chr = chr . fromIntegral
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
-- Separator predicates
|
||||||
|
|
||||||
|
|
||||||
-- | Path separator character
|
-- | Path separator character
|
||||||
pathSeparator :: Word8
|
pathSeparator :: Word8
|
||||||
pathSeparator = _slash
|
pathSeparator = _slash
|
||||||
|
|
||||||
|
|
||||||
-- | Check if a character is the path separator
|
-- | Check if a character is the path separator
|
||||||
--
|
--
|
||||||
-- prop> \n -> (_chr n == '/') == isPathSeparator n
|
-- prop> \n -> (_chr n == '/') == isPathSeparator n
|
||||||
isPathSeparator :: Word8 -> Bool
|
isPathSeparator :: Word8 -> Bool
|
||||||
isPathSeparator = (== pathSeparator)
|
isPathSeparator = (== pathSeparator)
|
||||||
|
|
||||||
|
|
||||||
-- | Search path separator
|
-- | Search path separator
|
||||||
searchPathSeparator :: Word8
|
searchPathSeparator :: Word8
|
||||||
searchPathSeparator = _colon
|
searchPathSeparator = _colon
|
||||||
|
|
||||||
|
|
||||||
-- | Check if a character is the search path separator
|
-- | Check if a character is the search path separator
|
||||||
--
|
--
|
||||||
-- prop> \n -> (_chr n == ':') == isSearchPathSeparator n
|
-- prop> \n -> (_chr n == ':') == isSearchPathSeparator n
|
||||||
isSearchPathSeparator :: Word8 -> Bool
|
isSearchPathSeparator :: Word8 -> Bool
|
||||||
isSearchPathSeparator = (== searchPathSeparator)
|
isSearchPathSeparator = (== searchPathSeparator)
|
||||||
|
|
||||||
|
|
||||||
-- | File extension separator
|
-- | File extension separator
|
||||||
extSeparator :: Word8
|
extSeparator :: Word8
|
||||||
extSeparator = _period
|
extSeparator = _period
|
||||||
|
|
||||||
|
|
||||||
-- | Check if a character is the file extension separator
|
-- | Check if a character is the file extension separator
|
||||||
--
|
--
|
||||||
-- prop> \n -> (_chr n == '.') == isExtSeparator n
|
-- prop> \n -> (_chr n == '.') == isExtSeparator n
|
||||||
isExtSeparator :: Word8 -> Bool
|
isExtSeparator :: Word8 -> Bool
|
||||||
isExtSeparator = (== extSeparator)
|
isExtSeparator = (== extSeparator)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
-- extension stuff
|
-- $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
|
-- | Split a 'RawFilePath' into a path+filename and extension
|
||||||
--
|
--
|
||||||
@@ -124,6 +205,7 @@ splitExtension x = if BS.null basename
|
|||||||
(path,file) = splitFileNameRaw x
|
(path,file) = splitFileNameRaw x
|
||||||
(basename,fileExt) = BS.breakEnd isExtSeparator file
|
(basename,fileExt) = BS.breakEnd isExtSeparator file
|
||||||
|
|
||||||
|
|
||||||
-- | Get the final extension from a 'RawFilePath'
|
-- | Get the final extension from a 'RawFilePath'
|
||||||
--
|
--
|
||||||
-- >>> takeExtension "file.exe"
|
-- >>> takeExtension "file.exe"
|
||||||
@@ -135,12 +217,14 @@ splitExtension x = if BS.null basename
|
|||||||
takeExtension :: RawFilePath -> ByteString
|
takeExtension :: RawFilePath -> ByteString
|
||||||
takeExtension = snd . splitExtension
|
takeExtension = snd . splitExtension
|
||||||
|
|
||||||
|
|
||||||
-- | Change a file's extension
|
-- | Change a file's extension
|
||||||
--
|
--
|
||||||
-- prop> \path -> let ext = takeExtension path in replaceExtension path ext == path
|
-- prop> \path -> let ext = takeExtension path in replaceExtension path ext == path
|
||||||
replaceExtension :: RawFilePath -> ByteString -> RawFilePath
|
replaceExtension :: RawFilePath -> ByteString -> RawFilePath
|
||||||
replaceExtension path ext = dropExtension path <.> ext
|
replaceExtension path ext = dropExtension path <.> ext
|
||||||
|
|
||||||
|
|
||||||
-- | Drop the final extension from a 'RawFilePath'
|
-- | Drop the final extension from a 'RawFilePath'
|
||||||
--
|
--
|
||||||
-- >>> dropExtension "file.exe"
|
-- >>> dropExtension "file.exe"
|
||||||
@@ -152,6 +236,7 @@ replaceExtension path ext = dropExtension path <.> ext
|
|||||||
dropExtension :: RawFilePath -> RawFilePath
|
dropExtension :: RawFilePath -> RawFilePath
|
||||||
dropExtension = fst . splitExtension
|
dropExtension = fst . splitExtension
|
||||||
|
|
||||||
|
|
||||||
-- | Add an extension to a 'RawFilePath'
|
-- | Add an extension to a 'RawFilePath'
|
||||||
--
|
--
|
||||||
-- >>> addExtension "file" ".exe"
|
-- >>> addExtension "file" ".exe"
|
||||||
@@ -167,10 +252,6 @@ addExtension file ext
|
|||||||
| otherwise = BS.intercalate (BS.singleton extSeparator) [file, ext]
|
| otherwise = BS.intercalate (BS.singleton extSeparator) [file, ext]
|
||||||
|
|
||||||
|
|
||||||
-- | Operator version of 'addExtension'
|
|
||||||
(<.>) :: RawFilePath -> ByteString -> RawFilePath
|
|
||||||
(<.>) = addExtension
|
|
||||||
|
|
||||||
-- | Check if a 'RawFilePath' has an extension
|
-- | Check if a 'RawFilePath' has an extension
|
||||||
--
|
--
|
||||||
-- >>> hasExtension "file"
|
-- >>> hasExtension "file"
|
||||||
@@ -182,7 +263,13 @@ addExtension file ext
|
|||||||
hasExtension :: RawFilePath -> Bool
|
hasExtension :: RawFilePath -> Bool
|
||||||
hasExtension = isJust . BS.elemIndex extSeparator . takeFileName
|
hasExtension = isJust . BS.elemIndex extSeparator . takeFileName
|
||||||
|
|
||||||
-- | Split a 'RawFilePath' on the first extension
|
|
||||||
|
-- | Operator version of 'addExtension'
|
||||||
|
(<.>) :: RawFilePath -> ByteString -> RawFilePath
|
||||||
|
(<.>) = addExtension
|
||||||
|
|
||||||
|
|
||||||
|
-- | Split a 'RawFilePath' on the first extension.
|
||||||
--
|
--
|
||||||
-- >>> splitExtensions "/path/file.tar.gz"
|
-- >>> splitExtensions "/path/file.tar.gz"
|
||||||
-- ("/path/file",".tar.gz")
|
-- ("/path/file",".tar.gz")
|
||||||
@@ -196,6 +283,7 @@ splitExtensions x = if BS.null basename
|
|||||||
(path,file) = splitFileNameRaw x
|
(path,file) = splitFileNameRaw x
|
||||||
(basename,fileExt) = BS.break isExtSeparator file
|
(basename,fileExt) = BS.break isExtSeparator file
|
||||||
|
|
||||||
|
|
||||||
-- | Remove all extensions from a 'RawFilePath'
|
-- | Remove all extensions from a 'RawFilePath'
|
||||||
--
|
--
|
||||||
-- >>> dropExtensions "/path/file.tar.gz"
|
-- >>> dropExtensions "/path/file.tar.gz"
|
||||||
@@ -203,6 +291,7 @@ splitExtensions x = if BS.null basename
|
|||||||
dropExtensions :: RawFilePath -> RawFilePath
|
dropExtensions :: RawFilePath -> RawFilePath
|
||||||
dropExtensions = fst . splitExtensions
|
dropExtensions = fst . splitExtensions
|
||||||
|
|
||||||
|
|
||||||
-- | Take all extensions from a 'RawFilePath'
|
-- | Take all extensions from a 'RawFilePath'
|
||||||
--
|
--
|
||||||
-- >>> takeExtensions "/path/file.tar.gz"
|
-- >>> takeExtensions "/path/file.tar.gz"
|
||||||
@@ -210,8 +299,48 @@ dropExtensions = fst . splitExtensions
|
|||||||
takeExtensions :: RawFilePath -> ByteString
|
takeExtensions :: RawFilePath -> ByteString
|
||||||
takeExtensions = snd . splitExtensions
|
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
|
||||||
|
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
-- more stuff
|
-- Filename/directory functions
|
||||||
|
|
||||||
|
|
||||||
-- | Split a 'RawFilePath' into (path,file). 'combine' is the inverse
|
-- | Split a 'RawFilePath' into (path,file). 'combine' is the inverse
|
||||||
--
|
--
|
||||||
@@ -241,12 +370,14 @@ splitFileName x = if BS.null path
|
|||||||
takeFileName :: RawFilePath -> RawFilePath
|
takeFileName :: RawFilePath -> RawFilePath
|
||||||
takeFileName = snd . splitFileName
|
takeFileName = snd . splitFileName
|
||||||
|
|
||||||
|
|
||||||
-- | Change the file name
|
-- | Change the file name
|
||||||
--
|
--
|
||||||
-- prop> \path -> replaceFileName path (takeFileName path) == path
|
-- prop> \path -> replaceFileName path (takeFileName path) == path
|
||||||
replaceFileName :: RawFilePath -> ByteString -> RawFilePath
|
replaceFileName :: RawFilePath -> ByteString -> RawFilePath
|
||||||
replaceFileName x y = fst (splitFileNameRaw x) </> y
|
replaceFileName x y = fst (splitFileNameRaw x) </> y
|
||||||
|
|
||||||
|
|
||||||
-- | Drop the file name
|
-- | Drop the file name
|
||||||
--
|
--
|
||||||
-- >>> dropFileName "path/file.txt"
|
-- >>> dropFileName "path/file.txt"
|
||||||
@@ -256,6 +387,7 @@ replaceFileName x y = fst (splitFileNameRaw x) </> y
|
|||||||
dropFileName :: RawFilePath -> RawFilePath
|
dropFileName :: RawFilePath -> RawFilePath
|
||||||
dropFileName = fst . splitFileName
|
dropFileName = fst . splitFileName
|
||||||
|
|
||||||
|
|
||||||
-- | Get the file name, without a trailing extension
|
-- | Get the file name, without a trailing extension
|
||||||
--
|
--
|
||||||
-- >>> takeBaseName "path/file.tar.gz"
|
-- >>> takeBaseName "path/file.tar.gz"
|
||||||
@@ -265,6 +397,7 @@ dropFileName = fst . splitFileName
|
|||||||
takeBaseName :: RawFilePath -> ByteString
|
takeBaseName :: RawFilePath -> ByteString
|
||||||
takeBaseName = dropExtension . takeFileName
|
takeBaseName = dropExtension . takeFileName
|
||||||
|
|
||||||
|
|
||||||
-- | Change the base name
|
-- | Change the base name
|
||||||
--
|
--
|
||||||
-- >>> replaceBaseName "path/file.tar.gz" "bob"
|
-- >>> replaceBaseName "path/file.tar.gz" "bob"
|
||||||
@@ -277,6 +410,7 @@ replaceBaseName path name = combineRaw dir (name <.> ext)
|
|||||||
(dir,file) = splitFileNameRaw path
|
(dir,file) = splitFileNameRaw path
|
||||||
ext = takeExtension file
|
ext = takeExtension file
|
||||||
|
|
||||||
|
|
||||||
-- | Get the directory, moving up one level if it's already a directory
|
-- | Get the directory, moving up one level if it's already a directory
|
||||||
--
|
--
|
||||||
-- >>> takeDirectory "path/file.txt"
|
-- >>> takeDirectory "path/file.txt"
|
||||||
@@ -296,12 +430,14 @@ takeDirectory x = case () of
|
|||||||
res = fst $ BS.spanEnd isPathSeparator file
|
res = fst $ BS.spanEnd isPathSeparator file
|
||||||
file = dropFileName x
|
file = dropFileName x
|
||||||
|
|
||||||
|
|
||||||
-- | Change the directory component of a 'RawFilePath'
|
-- | Change the directory component of a 'RawFilePath'
|
||||||
--
|
--
|
||||||
-- prop> \path -> replaceDirectory path (takeDirectory path) `equalFilePath` path || takeDirectory path == "."
|
-- prop> \path -> replaceDirectory path (takeDirectory path) `equalFilePath` path || takeDirectory path == "."
|
||||||
replaceDirectory :: RawFilePath -> ByteString -> RawFilePath
|
replaceDirectory :: RawFilePath -> ByteString -> RawFilePath
|
||||||
replaceDirectory file dir = combineRaw dir (takeFileName file)
|
replaceDirectory file dir = combineRaw dir (takeFileName file)
|
||||||
|
|
||||||
|
|
||||||
-- | Join two paths together
|
-- | Join two paths together
|
||||||
--
|
--
|
||||||
-- >>> combine "/" "file"
|
-- >>> combine "/" "file"
|
||||||
@@ -314,6 +450,7 @@ combine :: RawFilePath -> RawFilePath -> RawFilePath
|
|||||||
combine a b | not (BS.null b) && isPathSeparator (BS.head b) = b
|
combine a b | not (BS.null b) && isPathSeparator (BS.head b) = b
|
||||||
| otherwise = combineRaw a b
|
| otherwise = combineRaw a b
|
||||||
|
|
||||||
|
|
||||||
-- | Operator version of combine
|
-- | Operator version of combine
|
||||||
(</>) :: RawFilePath -> RawFilePath -> RawFilePath
|
(</>) :: RawFilePath -> RawFilePath -> RawFilePath
|
||||||
(</>) = combine
|
(</>) = combine
|
||||||
@@ -335,10 +472,23 @@ splitPath = splitter
|
|||||||
Nothing -> [x]
|
Nothing -> [x]
|
||||||
Just runlen -> uncurry (:) . second splitter $ BS.splitAt (ix+1+runlen) 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
|
-- | Like 'splitPath', but without trailing slashes
|
||||||
--
|
--
|
||||||
-- >>> splitDirectories "/path/to/file.txt"
|
-- >>> splitDirectories "/path/to/file.txt"
|
||||||
-- ["/","path","to","file.txt"]
|
-- ["/","path","to","file.txt"]
|
||||||
|
-- >>> splitDirectories "path/to/file.txt"
|
||||||
|
-- ["path","to","file.txt"]
|
||||||
-- >>> splitDirectories ""
|
-- >>> splitDirectories ""
|
||||||
-- []
|
-- []
|
||||||
splitDirectories :: RawFilePath -> [RawFilePath]
|
splitDirectories :: RawFilePath -> [RawFilePath]
|
||||||
@@ -350,14 +500,75 @@ splitDirectories x
|
|||||||
where
|
where
|
||||||
splitter = filter (not . BS.null) . BS.split pathSeparator
|
splitter = filter (not . BS.null) . BS.split pathSeparator
|
||||||
|
|
||||||
-- | Join a split path back together
|
|
||||||
|
-- |Get all parents of a path.
|
||||||
--
|
--
|
||||||
-- prop> \path -> joinPath (splitPath path) == path
|
-- >>> takeAllParents "/abs/def/dod"
|
||||||
|
-- ["/abs/def","/abs","/"]
|
||||||
|
-- >>> takeAllParents "/foo"
|
||||||
|
-- ["/"]
|
||||||
|
-- >>> takeAllParents "/"
|
||||||
|
-- []
|
||||||
|
takeAllParents :: RawFilePath -> [RawFilePath]
|
||||||
|
takeAllParents p
|
||||||
|
| np == BS.singleton pathSeparator = []
|
||||||
|
| otherwise = takeDirectory np : takeAllParents (takeDirectory np)
|
||||||
|
where
|
||||||
|
np = normalise p
|
||||||
|
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
-- Trailing slash functions
|
||||||
|
|
||||||
|
-- | Check if the last character of a 'RawFilePath' is '/'.
|
||||||
--
|
--
|
||||||
-- >>> joinPath ["path","to","file.txt"]
|
-- >>> hasTrailingPathSeparator "/path/"
|
||||||
-- "path/to/file.txt"
|
-- True
|
||||||
joinPath :: [RawFilePath] -> RawFilePath
|
-- >>> hasTrailingPathSeparator "/"
|
||||||
joinPath = foldr (</>) BS.empty
|
-- 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 a file.
|
||||||
@@ -414,54 +625,79 @@ normalise filepath =
|
|||||||
dropDots = filter (BS.singleton _period /=)
|
dropDots = filter (BS.singleton _period /=)
|
||||||
|
|
||||||
|
|
||||||
------------------------
|
|
||||||
-- trailing path separators
|
|
||||||
|
|
||||||
-- | Check if the last character of a 'RawFilePath' is '/'.
|
-- | 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>.
|
||||||
--
|
--
|
||||||
-- >>> hasTrailingPathSeparator "/path/"
|
-- >>> 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
|
-- True
|
||||||
-- >>> hasTrailingPathSeparator "/"
|
-- >>> equalFilePath "foo" "foo/"
|
||||||
-- True
|
-- True
|
||||||
-- >>> hasTrailingPathSeparator "/path"
|
-- >>> equalFilePath "foo" "./foo"
|
||||||
|
-- True
|
||||||
|
-- >>> equalFilePath "" ""
|
||||||
|
-- True
|
||||||
|
-- >>> equalFilePath "foo" "/foo"
|
||||||
|
-- False
|
||||||
|
-- >>> equalFilePath "foo" "FOO"
|
||||||
|
-- False
|
||||||
|
-- >>> equalFilePath "foo" "../foo"
|
||||||
-- False
|
-- False
|
||||||
hasTrailingPathSeparator :: RawFilePath -> Bool
|
|
||||||
hasTrailingPathSeparator x
|
|
||||||
| BS.null x = False
|
|
||||||
| otherwise = isPathSeparator $ BS.last x
|
|
||||||
|
|
||||||
-- | Add a trailing path separator.
|
|
||||||
--
|
--
|
||||||
-- >>> addTrailingPathSeparator "/path"
|
-- prop> \p -> equalFilePath p p
|
||||||
-- "/path/"
|
equalFilePath :: RawFilePath -> RawFilePath -> Bool
|
||||||
-- >>> addTrailingPathSeparator "/path/"
|
equalFilePath p1 p2 = f p1 == f p2
|
||||||
-- "/path/"
|
where
|
||||||
-- >>> addTrailingPathSeparator "/"
|
f x = dropTrailingPathSeparator $ normalise x
|
||||||
-- "/"
|
|
||||||
addTrailingPathSeparator :: RawFilePath -> RawFilePath
|
|
||||||
addTrailingPathSeparator x = if hasTrailingPathSeparator x
|
|
||||||
then x
|
|
||||||
else x `BS.snoc` pathSeparator
|
|
||||||
|
|
||||||
-- | Remove a trailing path separator
|
|
||||||
|
-- | Check if a path is relative
|
||||||
--
|
--
|
||||||
-- >>> dropTrailingPathSeparator "/path/"
|
-- prop> \path -> isRelative path /= isAbsolute path
|
||||||
-- "/path"
|
isRelative :: RawFilePath -> Bool
|
||||||
-- >>> dropTrailingPathSeparator "/path////"
|
isRelative = not . isAbsolute
|
||||||
-- "/path"
|
|
||||||
-- >>> dropTrailingPathSeparator "/"
|
|
||||||
-- "/"
|
|
||||||
-- >>> dropTrailingPathSeparator "//"
|
|
||||||
-- "/"
|
|
||||||
dropTrailingPathSeparator :: RawFilePath -> RawFilePath
|
|
||||||
dropTrailingPathSeparator x
|
|
||||||
| x == BS.singleton pathSeparator = x
|
|
||||||
| otherwise = if hasTrailingPathSeparator x
|
|
||||||
then dropTrailingPathSeparator $ BS.init x
|
|
||||||
else x
|
|
||||||
|
|
||||||
------------------------
|
|
||||||
-- Filename/system stuff
|
|
||||||
|
|
||||||
-- | Check if a path is absolute
|
-- | Check if a path is absolute
|
||||||
--
|
--
|
||||||
@@ -476,11 +712,6 @@ isAbsolute x
|
|||||||
| BS.length x > 0 = isPathSeparator (BS.head x)
|
| BS.length x > 0 = isPathSeparator (BS.head x)
|
||||||
| otherwise = False
|
| otherwise = False
|
||||||
|
|
||||||
-- | Check if a path is relative
|
|
||||||
--
|
|
||||||
-- prop> \path -> isRelative path /= isAbsolute path
|
|
||||||
isRelative :: RawFilePath -> Bool
|
|
||||||
isRelative = not . isAbsolute
|
|
||||||
|
|
||||||
-- | Is a FilePath valid, i.e. could you create a file like it?
|
-- | Is a FilePath valid, i.e. could you create a file like it?
|
||||||
--
|
--
|
||||||
@@ -496,27 +727,120 @@ isValid filepath
|
|||||||
| _nul `BS.elem` filepath = False
|
| _nul `BS.elem` filepath = False
|
||||||
| otherwise = True
|
| otherwise = True
|
||||||
|
|
||||||
-- |Equality of two filepaths. The filepaths are normalised
|
|
||||||
-- and trailing path separators are dropped.
|
-- | Take a FilePath and make it valid; does not change already valid FilePaths.
|
||||||
--
|
--
|
||||||
-- >>> equalFilePath "foo" "foo"
|
-- >>> makeValid ""
|
||||||
-- True
|
-- "_"
|
||||||
-- >>> equalFilePath "foo" "foo/"
|
-- >>> makeValid "file\0name"
|
||||||
-- True
|
-- "file_name"
|
||||||
-- >>> equalFilePath "foo" "./foo"
|
|
||||||
-- True
|
|
||||||
-- >>> equalFilePath "foo" "/foo"
|
|
||||||
-- False
|
|
||||||
-- >>> equalFilePath "foo" "FOO"
|
|
||||||
-- False
|
|
||||||
-- >>> equalFilePath "foo" "../foo"
|
|
||||||
-- False
|
|
||||||
--
|
--
|
||||||
-- prop> \p -> equalFilePath p p
|
-- prop> \p -> if isValid p then makeValid p == p else makeValid p /= p
|
||||||
equalFilePath :: RawFilePath -> RawFilePath -> Bool
|
-- prop> \p -> isValid (makeValid p)
|
||||||
equalFilePath p1 p2 = f p1 == f p2
|
makeValid :: RawFilePath -> RawFilePath
|
||||||
|
makeValid path
|
||||||
|
| BS.null path = BS.singleton _underscore
|
||||||
|
| otherwise = BS.map (\x -> if x == _nul then _underscore else x) path
|
||||||
|
|
||||||
|
|
||||||
|
-- | Whether the filename is a special directory entry
|
||||||
|
-- (. and ..). Does not normalise filepaths.
|
||||||
|
--
|
||||||
|
-- >>> isSpecialDirectoryEntry "."
|
||||||
|
-- True
|
||||||
|
-- >>> isSpecialDirectoryEntry ".."
|
||||||
|
-- True
|
||||||
|
-- >>> isSpecialDirectoryEntry "/random_ path:*"
|
||||||
|
-- False
|
||||||
|
isSpecialDirectoryEntry :: RawFilePath -> Bool
|
||||||
|
isSpecialDirectoryEntry filepath
|
||||||
|
| BS.pack [_period, _period] == filepath = True
|
||||||
|
| BS.pack [_period] == filepath = True
|
||||||
|
| otherwise = False
|
||||||
|
|
||||||
|
|
||||||
|
-- | 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
|
where
|
||||||
f x = dropTrailingPathSeparator $ normalise x
|
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
|
-- internal stuff
|
||||||
@@ -524,7 +848,7 @@ equalFilePath p1 p2 = f p1 == f p2
|
|||||||
-- Just split the input FileName without adding/normalizing or changing
|
-- Just split the input FileName without adding/normalizing or changing
|
||||||
-- anything.
|
-- anything.
|
||||||
splitFileNameRaw :: RawFilePath -> (RawFilePath, RawFilePath)
|
splitFileNameRaw :: RawFilePath -> (RawFilePath, RawFilePath)
|
||||||
splitFileNameRaw x = BS.breakEnd isPathSeparator x
|
splitFileNameRaw = BS.breakEnd isPathSeparator
|
||||||
|
|
||||||
-- | Combine two paths, assuming rhs is NOT absolute.
|
-- | Combine two paths, assuming rhs is NOT absolute.
|
||||||
combineRaw :: RawFilePath -> RawFilePath -> RawFilePath
|
combineRaw :: RawFilePath -> RawFilePath -> RawFilePath
|
||||||
43
hpath-io/CHANGELOG.md
Normal file
43
hpath-io/CHANGELOG.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Revision history for hpath-io
|
||||||
|
|
||||||
|
## 0.13.0 -- 2020-01-26
|
||||||
|
|
||||||
|
* switch to using 'hpath-bytestring' for the implementation (this is now just a wrapper module, mostly)
|
||||||
|
|
||||||
|
## 0.12.0 -- 2020-01-20
|
||||||
|
|
||||||
|
* breaking API changes
|
||||||
|
* RelC and Fn were removed from `hpath`
|
||||||
|
* further changes to `parseAny`
|
||||||
|
|
||||||
|
|
||||||
|
## 0.11.0 -- 2020-01-18
|
||||||
|
|
||||||
|
* `writeFile` not allows to set file mode and create file if it does not exist (this broke API)
|
||||||
|
* added various new functions:
|
||||||
|
* createDirIfMissing
|
||||||
|
* writeFileL (for lazy bytestring)
|
||||||
|
* isReadable
|
||||||
|
* isExecutable
|
||||||
|
* getModificationTime
|
||||||
|
* setModificationTime
|
||||||
|
* setModificationTimeHiRes
|
||||||
|
* getDirsFiles' (returns filenames instead of paths)
|
||||||
|
* withRawFilePath
|
||||||
|
* withHandle
|
||||||
|
|
||||||
|
## 0.10.1 -- 2020-01-13
|
||||||
|
|
||||||
|
* Move file check functions to HPath.IO
|
||||||
|
* Add 'doesExist'
|
||||||
|
* Exception handling of `doesExist`, `doesFileExist`, `doesDirectoryExist` has changed: only eNOENT is catched
|
||||||
|
* Exception handling of `isWritable` has changed: just a wrapper around `access` now
|
||||||
|
* switch exception handling to `safe-exceptions`
|
||||||
|
* Redo file reading API (readFileEOF dropped and now using streamly under the hood, added `readFileStream`)
|
||||||
|
|
||||||
|
|
||||||
|
## 0.10.0 -- 2020-01-04
|
||||||
|
|
||||||
|
* First version. Split from 'hpath', contains only the IO parts.
|
||||||
|
* Now uses streamly for 'copyFile'
|
||||||
|
* Fixed tmpdir in hspec
|
||||||
30
hpath-io/LICENSE
Normal file
30
hpath-io/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2020, Julian Ospald
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Julian Ospald nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
27
hpath-io/README.md
Normal file
27
hpath-io/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# HPath-IO
|
||||||
|
|
||||||
|
[](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hackage.haskell.org/package/hpath-io) [](http://travis-ci.org/hasufell/hpath) [](http://packdeps.haskellers.com/feed?needle=hpath-io)
|
||||||
|
|
||||||
|
High-level IO operations on files/directories, utilizing type-safe Paths. This uses [hpath-directory](https://hackage.haskell.org/package/hpath-directory) under the hood.
|
||||||
|
|
||||||
|
This package is part of the HPath suite, also check out:
|
||||||
|
|
||||||
|
* [hpath](https://hackage.haskell.org/package/hpath)
|
||||||
|
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
|
||||||
|
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
The motivation came during development of
|
||||||
|
[hsfm](https://github.com/hasufell/hsfm)
|
||||||
|
in order to have a proper high-level API of file related operations,
|
||||||
|
while utilizing type-safe Paths.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
* high-level API to file operations like recursive directory copy
|
||||||
|
* still allowing sufficient control to interact with the underlying low-level calls
|
||||||
|
* unit-testing exceptions (because yes, people may rely on them)
|
||||||
|
|
||||||
|
Note: this library was written for __posix__ systems and it will probably not support other systems.
|
||||||
|
|
||||||
2
hpath-io/Setup.hs
Normal file
2
hpath-io/Setup.hs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
||||||
6
hpath-io/TODO.md
Normal file
6
hpath-io/TODO.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
* `doesExist` not tested
|
||||||
|
* `readFileStream` only implicitly tested by `readFile`
|
||||||
46
hpath-io/hpath-io.cabal
Normal file
46
hpath-io/hpath-io.cabal
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: hpath-io
|
||||||
|
version: 0.13.1
|
||||||
|
synopsis: High-level IO operations on files/directories
|
||||||
|
description: High-level IO operations on files/directories, utilizing type-safe Paths
|
||||||
|
-- bug-reports:
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Julian Ospald <hasufell@posteo.de>
|
||||||
|
maintainer: Julian Ospald <hasufell@posteo.de>
|
||||||
|
copyright: Julian Ospald 2016
|
||||||
|
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.md
|
||||||
|
|
||||||
|
library
|
||||||
|
if os(windows)
|
||||||
|
build-depends: unbuildable<0
|
||||||
|
buildable: False
|
||||||
|
exposed-modules: HPath.IO
|
||||||
|
build-depends: base >= 4.8 && <5
|
||||||
|
, bytestring >= 0.10.0.0
|
||||||
|
, exceptions
|
||||||
|
, hpath >= 0.11 && < 0.12
|
||||||
|
, hpath-directory >= 0.13 && < 0.14
|
||||||
|
, hpath-posix >= 0.13 && < 0.14
|
||||||
|
, safe-exceptions >= 0.1
|
||||||
|
, streamly >= 0.7
|
||||||
|
, time >= 1.8
|
||||||
|
, unix >= 2.5
|
||||||
|
if !impl(ghc>=7.11)
|
||||||
|
build-depends: transformers
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/hasufell/hpath
|
||||||
852
hpath-io/src/HPath/IO.hs
Normal file
852
hpath-io/src/HPath/IO.hs
Normal file
@@ -0,0 +1,852 @@
|
|||||||
|
-- |
|
||||||
|
-- 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. This is a thin wrapper over
|
||||||
|
-- System.Posix.RawFilePath.Directory in 'hpath-directory'. It's
|
||||||
|
-- encouraged to use this module.
|
||||||
|
--
|
||||||
|
-- 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 PackageImports #-}
|
||||||
|
|
||||||
|
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
|
||||||
|
, createDirIfMissing
|
||||||
|
, createDirRecursive
|
||||||
|
, createSymlink
|
||||||
|
-- * File renaming/moving
|
||||||
|
, renameFile
|
||||||
|
, moveFile
|
||||||
|
-- * File reading
|
||||||
|
, readFile
|
||||||
|
, readFileStream
|
||||||
|
-- * File writing
|
||||||
|
, writeFile
|
||||||
|
, writeFileL
|
||||||
|
, appendFile
|
||||||
|
-- * File permissions
|
||||||
|
, RD.newFilePerms
|
||||||
|
, RD.newDirPerms
|
||||||
|
-- * File checks
|
||||||
|
, doesExist
|
||||||
|
, doesFileExist
|
||||||
|
, doesDirectoryExist
|
||||||
|
, isReadable
|
||||||
|
, isWritable
|
||||||
|
, isExecutable
|
||||||
|
, canOpenDirectory
|
||||||
|
-- * File times
|
||||||
|
, getModificationTime
|
||||||
|
, setModificationTime
|
||||||
|
, setModificationTimeHiRes
|
||||||
|
-- * Directory reading
|
||||||
|
, getDirsFiles
|
||||||
|
, getDirsFiles'
|
||||||
|
-- * Filetype operations
|
||||||
|
, getFileType
|
||||||
|
-- * Others
|
||||||
|
, canonicalizePath
|
||||||
|
, toAbs
|
||||||
|
, withRawFilePath
|
||||||
|
, withHandle
|
||||||
|
, module System.Posix.RawFilePath.Directory.Errors
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
|
||||||
|
import Control.Exception.Safe ( bracketOnError
|
||||||
|
, finally
|
||||||
|
)
|
||||||
|
import Control.Monad.Catch ( MonadThrow(..) )
|
||||||
|
|
||||||
|
import Data.ByteString ( ByteString )
|
||||||
|
import Data.Traversable ( for )
|
||||||
|
import qualified Data.ByteString.Lazy as L
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Data.Time.Clock.POSIX ( POSIXTime )
|
||||||
|
import HPath
|
||||||
|
import Prelude hiding ( appendFile
|
||||||
|
, readFile
|
||||||
|
, writeFile
|
||||||
|
)
|
||||||
|
import Streamly
|
||||||
|
import qualified System.IO as SIO
|
||||||
|
import System.Posix.Directory.ByteString
|
||||||
|
( getWorkingDirectory )
|
||||||
|
import qualified "unix" System.Posix.IO.ByteString
|
||||||
|
as SPI
|
||||||
|
import System.Posix.FD ( openFd )
|
||||||
|
import System.Posix.RawFilePath.Directory.Errors
|
||||||
|
import System.Posix.Types ( FileMode
|
||||||
|
, ProcessID
|
||||||
|
, EpochTime
|
||||||
|
)
|
||||||
|
import qualified System.Posix.RawFilePath.Directory
|
||||||
|
as RD
|
||||||
|
import System.Posix.RawFilePath.Directory
|
||||||
|
( FileType
|
||||||
|
, RecursiveErrorMode
|
||||||
|
, CopyMode
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
--[ 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
|
||||||
|
--
|
||||||
|
-- Note: may call `getcwd` (only if destination is a relative path)
|
||||||
|
copyDirRecursive :: Path b1 -- ^ source dir
|
||||||
|
-> Path b2 -- ^ destination (parent dirs
|
||||||
|
-- are not automatically created)
|
||||||
|
-> CopyMode
|
||||||
|
-> RecursiveErrorMode
|
||||||
|
-> IO ()
|
||||||
|
copyDirRecursive (Path fromp) (Path destdirp) cm rm =
|
||||||
|
RD.copyDirRecursive fromp destdirp cm rm
|
||||||
|
|
||||||
|
|
||||||
|
-- |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
|
||||||
|
--
|
||||||
|
-- Notes:
|
||||||
|
--
|
||||||
|
-- - calls `symlink`
|
||||||
|
-- - calls `getcwd` in Overwrite mode (if destination is a relative path)
|
||||||
|
recreateSymlink :: Path b1 -- ^ the old symlink file
|
||||||
|
-> Path b2 -- ^ destination file
|
||||||
|
-> CopyMode
|
||||||
|
-> IO ()
|
||||||
|
recreateSymlink (Path symsourceBS) (Path newsymBS) cm =
|
||||||
|
RD.recreateSymlink symsourceBS newsymBS cm
|
||||||
|
|
||||||
|
|
||||||
|
-- |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.
|
||||||
|
-- In `Strict` mode the destination file must not exist.
|
||||||
|
--
|
||||||
|
-- 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
|
||||||
|
--
|
||||||
|
-- Notes:
|
||||||
|
--
|
||||||
|
-- - may call `getcwd` in Overwrite mode (if destination is a relative path)
|
||||||
|
copyFile :: Path b1 -- ^ source file
|
||||||
|
-> Path b2 -- ^ destination file
|
||||||
|
-> CopyMode
|
||||||
|
-> IO ()
|
||||||
|
copyFile (Path from) (Path to) cm = RD.copyFile from to cm
|
||||||
|
|
||||||
|
-- |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
|
||||||
|
--
|
||||||
|
-- Note: may call `getcwd` in Overwrite mode (if destination is a relative path)
|
||||||
|
easyCopy :: Path b1 -> Path b2 -> CopyMode -> RecursiveErrorMode -> IO ()
|
||||||
|
easyCopy (Path from) (Path to) cm rm = RD.easyCopy from to cm rm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
--[ 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 (Path p) = RD.deleteFile 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 (Path p) = RD.deleteDir 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 (Path p) = RD.deleteDirRecursive 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 (Path p) = RD.easyDelete p
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
--[ 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 (Path fp) = RD.openFile fp
|
||||||
|
|
||||||
|
|
||||||
|
-- |Executes a program with the given arguments. This forks a process.
|
||||||
|
executeFile :: Path b -- ^ program
|
||||||
|
-> [ByteString] -- ^ arguments
|
||||||
|
-> IO ProcessID
|
||||||
|
executeFile (Path fp) args = RD.executeFile fp args
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
--[ 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 (Path destBS) = RD.createRegularFile fm destBS
|
||||||
|
|
||||||
|
|
||||||
|
-- |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 (Path destBS) = RD.createDir fm destBS
|
||||||
|
|
||||||
|
-- |Create an empty directory at the given directory with the given filename.
|
||||||
|
--
|
||||||
|
-- Throws:
|
||||||
|
--
|
||||||
|
-- - `PermissionDenied` if output directory cannot be written to
|
||||||
|
-- - `NoSuchThing` if any of the parent components of the path
|
||||||
|
-- do not exist
|
||||||
|
createDirIfMissing :: FileMode -> Path b -> IO ()
|
||||||
|
createDirIfMissing fm (Path destBS) = RD.createDirIfMissing fm destBS
|
||||||
|
|
||||||
|
|
||||||
|
-- |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
|
||||||
|
--
|
||||||
|
-- Note: calls `getcwd` if the input path is a relative path
|
||||||
|
createDirRecursive :: FileMode -> Path b -> IO ()
|
||||||
|
createDirRecursive fm (Path p) = RD.createDirRecursive fm p
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- |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 (Path destBS) sympoint = RD.createSymlink destBS sympoint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------
|
||||||
|
--[ 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 (Path from) (Path to) = RD.renameFile from to
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- |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
|
||||||
|
--
|
||||||
|
-- Notes:
|
||||||
|
--
|
||||||
|
-- - calls `rename` (but does not allow to rename over existing files)
|
||||||
|
-- - calls `getcwd` in Overwrite mode if destination is a relative path
|
||||||
|
moveFile :: Path b1 -- ^ file to move
|
||||||
|
-> Path b2 -- ^ destination
|
||||||
|
-> CopyMode
|
||||||
|
-> IO ()
|
||||||
|
moveFile (Path from) (Path to) cm = RD.moveFile from to cm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
--[ File Reading ]--
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
-- |Read the given file *at once* into memory as a lazy ByteString.
|
||||||
|
-- Symbolic links are followed, no sanity checks on file size
|
||||||
|
-- or file type. File must exist. Uses Builders under the hood
|
||||||
|
-- (hence lazy ByteString).
|
||||||
|
--
|
||||||
|
-- Safety/reliability concerns:
|
||||||
|
--
|
||||||
|
-- * the whole file is read into memory, this doesn't read lazily
|
||||||
|
--
|
||||||
|
-- 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 L.ByteString
|
||||||
|
readFile (Path path) = RD.readFile path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- | Open the given file as a filestream. Once the filestream is
|
||||||
|
-- exits, the filehandle is cleaned up.
|
||||||
|
--
|
||||||
|
-- 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
|
||||||
|
readFileStream :: Path b -> IO (SerialT IO ByteString)
|
||||||
|
readFileStream (Path fp) = RD.readFileStream fp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
--[ File Writing ]--
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
-- |Write a given ByteString to a file, truncating the file beforehand.
|
||||||
|
-- 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
|
||||||
|
-> Maybe FileMode -- ^ if Nothing, file must exist
|
||||||
|
-> ByteString
|
||||||
|
-> IO ()
|
||||||
|
writeFile (Path fp) fmode bs = RD.writeFile fp fmode bs
|
||||||
|
|
||||||
|
|
||||||
|
-- |Write a given lazy ByteString to a file, truncating the file beforehand.
|
||||||
|
-- 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
|
||||||
|
--
|
||||||
|
-- Note: uses streamly under the hood
|
||||||
|
writeFileL :: Path b
|
||||||
|
-> Maybe FileMode -- ^ if Nothing, file must exist
|
||||||
|
-> L.ByteString
|
||||||
|
-> IO ()
|
||||||
|
writeFileL (Path fp) fmode lbs = RD.writeFileL fp fmode lbs
|
||||||
|
|
||||||
|
|
||||||
|
-- |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 (Path fp) bs = RD.appendFile fp bs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-------------------
|
||||||
|
--[ File checks ]--
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
|
||||||
|
-- |Checks if the given file exists.
|
||||||
|
-- Does not follow symlinks.
|
||||||
|
--
|
||||||
|
-- Only eNOENT is catched (and returns False).
|
||||||
|
doesExist :: Path b -> IO Bool
|
||||||
|
doesExist (Path bs) = RD.doesExist bs
|
||||||
|
|
||||||
|
|
||||||
|
-- |Checks if the given file exists and is not a directory.
|
||||||
|
-- Does not follow symlinks.
|
||||||
|
--
|
||||||
|
-- Only eNOENT is catched (and returns False).
|
||||||
|
doesFileExist :: Path b -> IO Bool
|
||||||
|
doesFileExist (Path bs) = RD.doesFileExist bs
|
||||||
|
|
||||||
|
|
||||||
|
-- |Checks if the given file exists and is a directory.
|
||||||
|
-- Does not follow symlinks.
|
||||||
|
--
|
||||||
|
-- Only eNOENT is catched (and returns False).
|
||||||
|
doesDirectoryExist :: Path b -> IO Bool
|
||||||
|
doesDirectoryExist (Path bs) = RD.doesDirectoryExist bs
|
||||||
|
|
||||||
|
|
||||||
|
-- |Checks whether a file or folder is readable.
|
||||||
|
--
|
||||||
|
-- Only eACCES, eROFS, eTXTBSY, ePERM are catched (and return False).
|
||||||
|
--
|
||||||
|
-- Throws:
|
||||||
|
--
|
||||||
|
-- - `NoSuchThing` if the file does not exist
|
||||||
|
isReadable :: Path b -> IO Bool
|
||||||
|
isReadable (Path bs) = RD.isReadable bs
|
||||||
|
|
||||||
|
-- |Checks whether a file or folder is writable.
|
||||||
|
--
|
||||||
|
-- Only eACCES, eROFS, eTXTBSY, ePERM are catched (and return False).
|
||||||
|
--
|
||||||
|
-- Throws:
|
||||||
|
--
|
||||||
|
-- - `NoSuchThing` if the file does not exist
|
||||||
|
isWritable :: Path b -> IO Bool
|
||||||
|
isWritable (Path bs) = RD.isWritable bs
|
||||||
|
|
||||||
|
|
||||||
|
-- |Checks whether a file or folder is executable.
|
||||||
|
--
|
||||||
|
-- Only eACCES, eROFS, eTXTBSY, ePERM are catched (and return False).
|
||||||
|
--
|
||||||
|
-- Throws:
|
||||||
|
--
|
||||||
|
-- - `NoSuchThing` if the file does not exist
|
||||||
|
isExecutable :: Path b -> IO Bool
|
||||||
|
isExecutable (Path bs) = RD.isExecutable bs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- |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 (Path bs) = RD.canOpenDirectory bs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------------------
|
||||||
|
--[ File times ]--
|
||||||
|
------------------
|
||||||
|
|
||||||
|
|
||||||
|
getModificationTime :: Path b -> IO UTCTime
|
||||||
|
getModificationTime (Path bs) = RD.getModificationTime bs
|
||||||
|
|
||||||
|
setModificationTime :: Path b -> EpochTime -> IO ()
|
||||||
|
setModificationTime (Path bs) t = RD.setModificationTime bs t
|
||||||
|
|
||||||
|
setModificationTimeHiRes :: Path b -> POSIXTime -> IO ()
|
||||||
|
setModificationTimeHiRes (Path bs) t = RD.setModificationTimeHiRes bs t
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------
|
||||||
|
--[ 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
|
||||||
|
-- - `PathParseException` if a filename could not be parsed (should never happen)
|
||||||
|
getDirsFiles :: Path b -- ^ dir to read
|
||||||
|
-> IO [Path b]
|
||||||
|
getDirsFiles p = do
|
||||||
|
contents <- getDirsFiles' p
|
||||||
|
pure $ fmap (p </>) contents
|
||||||
|
|
||||||
|
|
||||||
|
-- | Like 'getDirsFiles', but returns the filename only, instead
|
||||||
|
-- of prepending the base path.
|
||||||
|
getDirsFiles' :: Path b -- ^ dir to read
|
||||||
|
-> IO [Path Rel]
|
||||||
|
getDirsFiles' (Path fp) = do
|
||||||
|
rawContents <- RD.getDirsFiles' fp
|
||||||
|
for rawContents $ \r -> parseRel r
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
--[ 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 (Path fp) = RD.getFileType fp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------
|
||||||
|
--[ 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
|
||||||
|
-- - `PathParseException` if realpath does not return an absolute path
|
||||||
|
canonicalizePath :: Path b -> IO (Path Abs)
|
||||||
|
canonicalizePath (Path l) = do
|
||||||
|
nl <- RD.canonicalizePath l
|
||||||
|
parseAbs 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 (Path bs) = do
|
||||||
|
let mabs = parseAbs bs :: Maybe (Path Abs)
|
||||||
|
case mabs of
|
||||||
|
Just a -> return a
|
||||||
|
Nothing -> do
|
||||||
|
cwd <- getWorkingDirectory >>= parseAbs
|
||||||
|
r <- parseRel bs -- we know it must be relative now
|
||||||
|
return $ cwd </> r
|
||||||
|
|
||||||
|
|
||||||
|
-- | Helper function to use the Path library without
|
||||||
|
-- buying into the Path type too much. This uses 'parseAny'
|
||||||
|
-- under the hood and may throw `PathParseException`.
|
||||||
|
--
|
||||||
|
-- Throws:
|
||||||
|
--
|
||||||
|
-- - `PathParseException` if the bytestring could neither be parsed as
|
||||||
|
-- relative or absolute Path
|
||||||
|
withRawFilePath :: MonadThrow m
|
||||||
|
=> ByteString
|
||||||
|
-> (Either (Path Abs) (Path Rel) -> m b)
|
||||||
|
-> m b
|
||||||
|
withRawFilePath bs action = do
|
||||||
|
path <- parseAny bs
|
||||||
|
action path
|
||||||
|
|
||||||
|
|
||||||
|
-- | Convenience function to open the path as a handle.
|
||||||
|
--
|
||||||
|
-- If the file does not exist, it will be created with 'newFilePerms'.
|
||||||
|
--
|
||||||
|
-- Throws:
|
||||||
|
--
|
||||||
|
-- - `PathParseException` if the bytestring could neither be parsed as
|
||||||
|
-- relative or absolute Path
|
||||||
|
withHandle :: ByteString
|
||||||
|
-> SPI.OpenMode
|
||||||
|
-> ((SIO.Handle, Either (Path Abs) (Path Rel)) -> IO a)
|
||||||
|
-> IO a
|
||||||
|
withHandle bs mode action = do
|
||||||
|
path <- parseAny bs
|
||||||
|
handle <-
|
||||||
|
bracketOnError (openFd bs mode [] (Just RD.newFilePerms)) (SPI.closeFd)
|
||||||
|
$ SPI.fdToHandle
|
||||||
|
finally (action (handle, path)) (SIO.hClose handle)
|
||||||
5
hpath-posix/CHANGELOG.md
Normal file
5
hpath-posix/CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Revision history for hpath-posix
|
||||||
|
|
||||||
|
## 0.1.0.0 -- 2020-01-29
|
||||||
|
|
||||||
|
* First version. Released on an unsuspecting world.
|
||||||
30
hpath-posix/LICENSE
Normal file
30
hpath-posix/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2020, Julian Ospald
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Julian Ospald nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
13
hpath-posix/README.md
Normal file
13
hpath-posix/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# HPath-filepath
|
||||||
|
|
||||||
|
[](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hackage.haskell.org/package/hpath-posix) [](http://travis-ci.org/hasufell/hpath) [](http://packdeps.haskellers.com/feed?needle=hpath-posix)
|
||||||
|
|
||||||
|
Some low-level POSIX glue code, that is not in 'unix'.
|
||||||
|
|
||||||
|
This package is part of the HPath suite, also check out:
|
||||||
|
|
||||||
|
* [hpath](https://hackage.haskell.org/package/hpath)
|
||||||
|
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
|
||||||
|
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
|
||||||
|
* [hpath-io](https://hackage.haskell.org/package/hpath-io)
|
||||||
|
|
||||||
2
hpath-posix/Setup.hs
Normal file
2
hpath-posix/Setup.hs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
||||||
57
hpath-posix/hpath-posix.cabal
Normal file
57
hpath-posix/hpath-posix.cabal
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
cabal-version: >=1.10
|
||||||
|
|
||||||
|
name: hpath-posix
|
||||||
|
version: 0.13.0
|
||||||
|
synopsis: Some low-level POSIX glue code, that is not in 'unix'
|
||||||
|
homepage: https://github.com/hasufell/hpath
|
||||||
|
bug-reports: https://github.com/hasufell/hpath/issues
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Julian Ospald <hasufell@posteo.de>
|
||||||
|
maintainer: Julian Ospald <hasufell@posteo.de>
|
||||||
|
copyright: Julian Ospald <hasufell@posteo.de> 2020
|
||||||
|
category: Filesystem
|
||||||
|
build-type: Simple
|
||||||
|
extra-source-files: CHANGELOG.md
|
||||||
|
cbits/dirutils.h
|
||||||
|
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
|
||||||
|
|
||||||
|
library
|
||||||
|
if os(windows)
|
||||||
|
build-depends: unbuildable<0
|
||||||
|
buildable: False
|
||||||
|
exposed-modules: System.Posix.RawFilePath.Directory.Traversals
|
||||||
|
System.Posix.Foreign
|
||||||
|
System.Posix.FD
|
||||||
|
-- other-modules:
|
||||||
|
-- other-extensions:
|
||||||
|
c-sources: cbits/dirutils.c
|
||||||
|
build-depends: base >= 4.8 && <5
|
||||||
|
, IfElse
|
||||||
|
, bytestring >= 0.10
|
||||||
|
, deepseq
|
||||||
|
, exceptions >= 0.10
|
||||||
|
, hpath-filepath >= 0.10.3
|
||||||
|
, safe-exceptions >= 0.1
|
||||||
|
, streamly >= 0.7
|
||||||
|
, streamly-bytestring >= 0.1.0.1
|
||||||
|
, time >= 1.8
|
||||||
|
, unix >= 2.5
|
||||||
|
, unix-bytestring >= 0.3
|
||||||
|
, utf8-string
|
||||||
|
if impl(ghc < 8.0)
|
||||||
|
build-depends:
|
||||||
|
fail >= 4.9
|
||||||
|
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
||||||
|
default-extensions: PackageImports
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/hasufell/hpath
|
||||||
75
hpath-posix/src/System/Posix/FD.hs
Normal file
75
hpath-posix/src/System/Posix/FD.hs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
-- |
|
||||||
|
-- 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.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
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
module System.Posix.Directory.Foreign where
|
module System.Posix.Foreign where
|
||||||
|
|
||||||
import Data.Bits
|
import Data.Bits
|
||||||
import Data.List (foldl')
|
import Data.List (foldl')
|
||||||
@@ -1,10 +1,25 @@
|
|||||||
|
-- |
|
||||||
|
-- Module : System.Posix.RawFilePath.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 ForeignFunctionInterface #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
{-# LANGUAGE ViewPatterns #-}
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
|
|
||||||
{-# OPTIONS_GHC -Wall #-}
|
{-# OPTIONS_GHC -Wall #-}
|
||||||
module System.Posix.Directory.Traversals (
|
|
||||||
|
|
||||||
|
module System.Posix.RawFilePath.Directory.Traversals (
|
||||||
|
|
||||||
getDirectoryContents
|
getDirectoryContents
|
||||||
, getDirectoryContents'
|
, getDirectoryContents'
|
||||||
@@ -15,17 +30,19 @@ module System.Posix.Directory.Traversals (
|
|||||||
|
|
||||||
-- lower-level stuff
|
-- lower-level stuff
|
||||||
, readDirEnt
|
, readDirEnt
|
||||||
, packDirStream
|
, fdOpendir
|
||||||
, unpackDirStream
|
|
||||||
, openFd
|
|
||||||
|
|
||||||
, realpath
|
, realpath
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Applicative
|
|
||||||
|
#if __GLASGOW_HASKELL__ < 710
|
||||||
|
import Control.Applicative ((<$>))
|
||||||
|
#endif
|
||||||
|
import Control.DeepSeq
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import System.Posix.FilePath ((</>))
|
import System.Posix.FilePath ((</>))
|
||||||
import System.Posix.Directory.Foreign
|
import System.Posix.Foreign
|
||||||
|
|
||||||
import qualified System.Posix as Posix
|
import qualified System.Posix as Posix
|
||||||
import System.IO.Error
|
import System.IO.Error
|
||||||
@@ -33,10 +50,11 @@ import Control.Exception
|
|||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import System.Posix.ByteString.FilePath
|
import System.Posix.ByteString.FilePath
|
||||||
import System.Posix.Directory.ByteString as PosixBS
|
import System.Posix.Directory.ByteString as PosixBS
|
||||||
|
import System.Posix.Directory.Common
|
||||||
import System.Posix.Files.ByteString
|
import System.Posix.Files.ByteString
|
||||||
|
|
||||||
import System.IO.Unsafe
|
import System.IO.Unsafe
|
||||||
import Unsafe.Coerce (unsafeCoerce)
|
import "unix" System.Posix.IO.ByteString (closeFd)
|
||||||
import Foreign.C.Error
|
import Foreign.C.Error
|
||||||
import Foreign.C.String
|
import Foreign.C.String
|
||||||
import Foreign.C.Types
|
import Foreign.C.Types
|
||||||
@@ -54,6 +72,8 @@ import Foreign.Storable
|
|||||||
-- Upon entering a directory, 'allDirectoryContents' will get all entries
|
-- Upon entering a directory, 'allDirectoryContents' will get all entries
|
||||||
-- strictly. However the returned list is lazy in that directories will only
|
-- strictly. However the returned list is lazy in that directories will only
|
||||||
-- be accessed on demand.
|
-- be accessed on demand.
|
||||||
|
--
|
||||||
|
-- Follows symbolic links for the input dir.
|
||||||
allDirectoryContents :: RawFilePath -> IO [RawFilePath]
|
allDirectoryContents :: RawFilePath -> IO [RawFilePath]
|
||||||
allDirectoryContents topdir = do
|
allDirectoryContents topdir = do
|
||||||
namesAndTypes <- getDirectoryContents topdir
|
namesAndTypes <- getDirectoryContents topdir
|
||||||
@@ -71,6 +91,8 @@ allDirectoryContents topdir = do
|
|||||||
return (topdir : concat paths)
|
return (topdir : concat paths)
|
||||||
|
|
||||||
-- | Get all files from a directory and its subdirectories strictly.
|
-- | Get all files from a directory and its subdirectories strictly.
|
||||||
|
--
|
||||||
|
-- Follows symbolic links for the input dir.
|
||||||
allDirectoryContents' :: RawFilePath -> IO [RawFilePath]
|
allDirectoryContents' :: RawFilePath -> IO [RawFilePath]
|
||||||
allDirectoryContents' = fmap reverse . traverseDirectory (\acc fp -> return (fp:acc)) []
|
allDirectoryContents' = fmap reverse . traverseDirectory (\acc fp -> return (fp:acc)) []
|
||||||
-- this uses traverseDirectory because it's more efficient than forcing the
|
-- this uses traverseDirectory because it's more efficient than forcing the
|
||||||
@@ -80,6 +102,8 @@ allDirectoryContents' = fmap reverse . traverseDirectory (\acc fp -> return (fp:
|
|||||||
-- files/subdirectories.
|
-- files/subdirectories.
|
||||||
--
|
--
|
||||||
-- This function allows for memory-efficient traversals.
|
-- This function allows for memory-efficient traversals.
|
||||||
|
--
|
||||||
|
-- Follows symbolic links for the input dir.
|
||||||
traverseDirectory :: (s -> RawFilePath -> IO s) -> s -> RawFilePath -> IO s
|
traverseDirectory :: (s -> RawFilePath -> IO s) -> s -> RawFilePath -> IO s
|
||||||
traverseDirectory act s0 topdir = toploop
|
traverseDirectory act s0 topdir = toploop
|
||||||
where
|
where
|
||||||
@@ -103,17 +127,17 @@ actOnDirContents :: RawFilePath
|
|||||||
-> IO b
|
-> IO b
|
||||||
actOnDirContents pathRelToTop b f =
|
actOnDirContents pathRelToTop b f =
|
||||||
modifyIOError ((`ioeSetFileName` (BS.unpack pathRelToTop)) .
|
modifyIOError ((`ioeSetFileName` (BS.unpack pathRelToTop)) .
|
||||||
(`ioeSetLocation` "findBSTypRel")) $ do
|
(`ioeSetLocation` "findBSTypRel")) $
|
||||||
bracket
|
bracket
|
||||||
(openDirStream pathRelToTop)
|
(openDirStream pathRelToTop)
|
||||||
(Posix.closeDirStream)
|
Posix.closeDirStream
|
||||||
(\dirp -> loop dirp b)
|
(\dirp -> loop dirp b)
|
||||||
where
|
where
|
||||||
loop dirp b' = do
|
loop dirp b' = do
|
||||||
(typ,e) <- readDirEnt dirp
|
(typ,e) <- readDirEnt dirp
|
||||||
if (e == "")
|
if (e == "")
|
||||||
then return b'
|
then return b'
|
||||||
else do
|
else
|
||||||
if (e == "." || e == "..")
|
if (e == "." || e == "..")
|
||||||
then loop dirp b'
|
then loop dirp b'
|
||||||
else f typ (pathRelToTop </> e) b' >>= loop dirp
|
else f typ (pathRelToTop </> e) b' >>= loop dirp
|
||||||
@@ -122,18 +146,6 @@ actOnDirContents pathRelToTop b f =
|
|||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
-- dodgy stuff
|
-- 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 __hscore_* functions are defined in the unix package. We can import them and let
|
||||||
-- the linker figure it out.
|
-- the linker figure it out.
|
||||||
foreign import ccall unsafe "__hscore_readdir"
|
foreign import ccall unsafe "__hscore_readdir"
|
||||||
@@ -152,17 +164,14 @@ foreign import ccall "realpath"
|
|||||||
c_realpath :: CString -> CString -> IO CString
|
c_realpath :: CString -> CString -> IO CString
|
||||||
|
|
||||||
foreign import ccall unsafe "fdopendir"
|
foreign import ccall unsafe "fdopendir"
|
||||||
c_fdopendir :: Posix.Fd -> IO (Ptr ())
|
c_fdopendir :: Posix.Fd -> IO (Ptr CDir)
|
||||||
|
|
||||||
foreign import ccall unsafe "open"
|
|
||||||
c_open :: CString -> CInt -> Posix.CMode -> IO CInt
|
|
||||||
|
|
||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
-- less dodgy but still lower-level
|
-- less dodgy but still lower-level
|
||||||
|
|
||||||
|
|
||||||
readDirEnt :: DirStream -> IO (DirType, RawFilePath)
|
readDirEnt :: DirStream -> IO (DirType, RawFilePath)
|
||||||
readDirEnt (unpackDirStream -> dirp) =
|
readDirEnt (DirStream dirp) =
|
||||||
alloca $ \ptr_dEnt -> loop ptr_dEnt
|
alloca $ \ptr_dEnt -> loop ptr_dEnt
|
||||||
where
|
where
|
||||||
loop ptr_dEnt = do
|
loop ptr_dEnt = do
|
||||||
@@ -171,12 +180,14 @@ readDirEnt (unpackDirStream -> dirp) =
|
|||||||
if (r == 0)
|
if (r == 0)
|
||||||
then do
|
then do
|
||||||
dEnt <- peek ptr_dEnt
|
dEnt <- peek ptr_dEnt
|
||||||
|
putStrLn $ "readDirEnt dEnt " ++ (show dEnt)
|
||||||
if (dEnt == nullPtr)
|
if (dEnt == nullPtr)
|
||||||
then return (dtUnknown,BS.empty)
|
then return (dtUnknown,BS.empty)
|
||||||
else do
|
else do
|
||||||
dName <- c_name dEnt >>= peekFilePath
|
dName <- c_name dEnt >>= peekFilePath >>= evaluate . force
|
||||||
dType <- c_type dEnt
|
dType <- c_type dEnt
|
||||||
c_freeDirEnt dEnt
|
c_freeDirEnt dEnt
|
||||||
|
putStrLn $ "readDirEnt" ++ (show dName)
|
||||||
return (dType, dName)
|
return (dType, dName)
|
||||||
else do
|
else do
|
||||||
errno <- getErrno
|
errno <- getErrno
|
||||||
@@ -189,81 +200,53 @@ readDirEnt (unpackDirStream -> dirp) =
|
|||||||
else throwErrno "readDirEnt"
|
else throwErrno "readDirEnt"
|
||||||
|
|
||||||
|
|
||||||
|
-- |Gets all directory contents (not recursively).
|
||||||
getDirectoryContents :: RawFilePath -> IO [(DirType, RawFilePath)]
|
getDirectoryContents :: RawFilePath -> IO [(DirType, RawFilePath)]
|
||||||
getDirectoryContents path =
|
getDirectoryContents path =
|
||||||
modifyIOError ((`ioeSetFileName` (BS.unpack path)) .
|
modifyIOError ((`ioeSetFileName` (BS.unpack path)) .
|
||||||
(`ioeSetLocation` "System.Posix.Directory.Traversals.getDirectoryContents")) $ do
|
(`ioeSetLocation` "System.Posix.RawFilePath.Directory.Traversals.getDirectoryContents")) $
|
||||||
bracket
|
bracket
|
||||||
(PosixBS.openDirStream path)
|
(PosixBS.openDirStream path)
|
||||||
PosixBS.closeDirStream
|
PosixBS.closeDirStream
|
||||||
loop
|
_dirloop
|
||||||
where
|
|
||||||
loop dirp = do
|
|
||||||
t@(_typ,e) <- readDirEnt dirp
|
|
||||||
if BS.null e then return [] else do
|
|
||||||
es <- loop dirp
|
|
||||||
return (t:es)
|
|
||||||
|
|
||||||
|
|
||||||
|
-- |Binding to @fdopendir(3)@.
|
||||||
fdOpendir :: Posix.Fd -> IO DirStream
|
fdOpendir :: Posix.Fd -> IO DirStream
|
||||||
fdOpendir fd =
|
fdOpendir fd =
|
||||||
packDirStream <$> throwErrnoIfNull "fdOpendir" (c_fdopendir fd)
|
DirStream <$> 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' :: Posix.Fd -> IO [(DirType, RawFilePath)]
|
||||||
getDirectoryContents' fd =
|
getDirectoryContents' fd = do
|
||||||
bracket
|
dirstream <- fdOpendir fd `catchIOError` \e -> do
|
||||||
(fdOpendir fd)
|
closeFd fd
|
||||||
PosixBS.closeDirStream
|
ioError e
|
||||||
loop
|
-- closeDirStream closes the filedescriptor
|
||||||
where
|
finally (_dirloop dirstream) (PosixBS.closeDirStream dirstream)
|
||||||
loop dirp = do
|
|
||||||
t@(_typ,e) <- readDirEnt dirp
|
|
||||||
if BS.null e then return [] else do
|
|
||||||
es <- loop dirp
|
|
||||||
return (t:es)
|
|
||||||
|
|
||||||
|
|
||||||
open_ :: CString
|
_dirloop :: DirStream -> IO [(DirType, RawFilePath)]
|
||||||
-> Posix.OpenMode
|
{-# INLINE _dirloop #-}
|
||||||
-> [Flags]
|
_dirloop dirp = do
|
||||||
-> Maybe Posix.FileMode
|
t@(_typ,e) <- readDirEnt dirp
|
||||||
-> IO Posix.Fd
|
if BS.null e then return [] else do
|
||||||
open_ str how optional_flags maybe_mode = do
|
es <- _dirloop dirp
|
||||||
fd <- c_open str all_flags mode_w
|
return (t:es)
|
||||||
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.
|
|
||||||
openFd :: RawFilePath
|
|
||||||
-> Posix.OpenMode
|
|
||||||
-> [Flags]
|
|
||||||
-> Maybe Posix.FileMode
|
|
||||||
-> IO Posix.Fd
|
|
||||||
openFd name how optional_flags maybe_mode =
|
|
||||||
withFilePath name $ \str ->
|
|
||||||
throwErrnoPathIfMinus1Retry "openFd" name $
|
|
||||||
open_ str how optional_flags maybe_mode
|
|
||||||
|
|
||||||
|
|
||||||
-- | return the canonicalized absolute pathname
|
-- | return the canonicalized absolute pathname
|
||||||
--
|
--
|
||||||
-- like canonicalizePath, but uses realpath(3)
|
-- like canonicalizePath, but uses @realpath(3)@
|
||||||
realpath :: RawFilePath -> IO RawFilePath
|
realpath :: RawFilePath -> IO RawFilePath
|
||||||
realpath inp = do
|
realpath inp =
|
||||||
allocaBytes pathMax $ \tmp -> do
|
allocaBytes pathMax $ \tmp -> do
|
||||||
void $ BS.useAsCString inp $ \cstr -> throwErrnoIfNull "realpath" $ c_realpath cstr tmp
|
void $ BS.useAsCString inp $ \cstr -> throwErrnoIfNull "realpath" $ c_realpath cstr tmp
|
||||||
BS.packCString tmp
|
BS.packCString tmp
|
||||||
83
hpath.cabal
83
hpath.cabal
@@ -1,83 +0,0 @@
|
|||||||
name: hpath
|
|
||||||
version: 0.5.8
|
|
||||||
synopsis: Support for well-typed paths
|
|
||||||
description: Support for will-typed paths.
|
|
||||||
license: BSD3
|
|
||||||
license-file: LICENSE
|
|
||||||
author: Julian Ospald <hasufell@posteo.de>
|
|
||||||
maintainer: Julian Ospald <hasufell@posteo.de>
|
|
||||||
copyright: 2015–2016 FP Complete, Julian Ospald 2016
|
|
||||||
category: Filesystem
|
|
||||||
build-type: Simple
|
|
||||||
cabal-version: >=1.14
|
|
||||||
extra-source-files: README.md
|
|
||||||
CHANGELOG
|
|
||||||
cbits/dirutils.h
|
|
||||||
doctests.hs
|
|
||||||
benchmarks/*.hs
|
|
||||||
|
|
||||||
library
|
|
||||||
hs-source-dirs: src/
|
|
||||||
default-language: Haskell2010
|
|
||||||
ghc-options: -Wall -O2
|
|
||||||
c-sources: cbits/dirutils.c
|
|
||||||
exposed-modules: HPath,
|
|
||||||
HPath.Internal,
|
|
||||||
System.Posix.Directory.Foreign,
|
|
||||||
System.Posix.Directory.Traversals,
|
|
||||||
System.Posix.FilePath
|
|
||||||
build-depends: base >= 4.2 && <5
|
|
||||||
, bytestring >= 0.9.2.0
|
|
||||||
, deepseq
|
|
||||||
, exceptions
|
|
||||||
, hspec
|
|
||||||
, unix >= 2.5
|
|
||||||
, utf8-string
|
|
||||||
, word8
|
|
||||||
|
|
||||||
|
|
||||||
test-suite doctests-hpath
|
|
||||||
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
|
|
||||||
default-language: Haskell2010
|
|
||||||
type: exitcode-stdio-1.0
|
|
||||||
ghc-options: -threaded
|
|
||||||
main-is: doctests-posix.hs
|
|
||||||
build-depends: base,
|
|
||||||
bytestring,
|
|
||||||
unix,
|
|
||||||
hpath,
|
|
||||||
doctest >= 0.8,
|
|
||||||
HUnit,
|
|
||||||
QuickCheck
|
|
||||||
|
|
||||||
benchmark bench.hs
|
|
||||||
default-language: Haskell2010
|
|
||||||
type: exitcode-stdio-1.0
|
|
||||||
hs-source-dirs: benchmarks
|
|
||||||
main-is: Bench.hs
|
|
||||||
|
|
||||||
build-depends:
|
|
||||||
base,
|
|
||||||
hpath,
|
|
||||||
bytestring,
|
|
||||||
unix,
|
|
||||||
directory >= 1.1 && < 1.3,
|
|
||||||
filepath >= 1.2 && < 1.4,
|
|
||||||
process >= 1.0 && < 1.3,
|
|
||||||
criterion >= 0.6 && < 0.9
|
|
||||||
ghc-options: -O2
|
|
||||||
|
|
||||||
source-repository head
|
|
||||||
type: git
|
|
||||||
location: https://github.com/hasufell/hpath
|
|
||||||
|
|
||||||
89
hpath/CHANGELOG
Normal file
89
hpath/CHANGELOG
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
0.11.0
|
||||||
|
* Many API breaking changes
|
||||||
|
* Remove RelC and Fn, because they complicate API/break semantics (see #29)
|
||||||
|
* Redo 'parseAny'
|
||||||
|
* Unexpose HPath.Internal
|
||||||
|
* Don't preserve trailing path separators (if you need to pass something to a C function that way, do it manually)
|
||||||
|
* Added `rooPath`, `isRootPath`, `getAllComponents`, `getAllComponentsAfterRoot`
|
||||||
|
0.10.2
|
||||||
|
* Add `parseAny` and the related QuasiQuoter
|
||||||
|
0.10.1
|
||||||
|
* Add quasi quoters for hpath
|
||||||
|
0.10.0
|
||||||
|
* split packages, this one now just contains the type-safe Path wrappers
|
||||||
|
0.9.2
|
||||||
|
* fix build with ghc-7.6
|
||||||
|
* raise required bytestring version
|
||||||
|
* Tighten base bound to prevent building before GHC 7.6 (by George Wilson)
|
||||||
|
0.9.1
|
||||||
|
* fix build with ghc-7.8 and 7.10
|
||||||
|
0.9.0
|
||||||
|
* don't force "Path Abs" anymore in IO module, abstract more over Path types
|
||||||
|
* add 'toAbs'
|
||||||
|
0.8.1
|
||||||
|
* add 'readFile', 'readFileEOF', 'writeFile' and 'appendFile'
|
||||||
|
0.8.0
|
||||||
|
* 'copyDirRecursiveOverwrite', 'copyFileOverwrite', 'easyCopyOverwrite' and 'moveFileOverwrite' have been removed, instead use the versions without the *Overwrite suffix and pass in 'Strict' (for default behavior) or 'Overwrite' as the CopyMode argument
|
||||||
|
* introduced a new 'RecursiveErrorMode' type to allow controlling recursive behavior of 'copyDirRecursive' (use 'FailEarly' for default behavior)
|
||||||
|
* 'createRegularFile' and 'createDir' now take FileMode as a parameter (also see 'newFilePerms' and 'newDirPerms')
|
||||||
|
* various documentation fixes
|
||||||
|
* improved reliability of tests
|
||||||
|
0.7.5:
|
||||||
|
* relicense to BSD3
|
||||||
|
0.7.3:
|
||||||
|
* don't expose HPath.Internal
|
||||||
|
0.7.2:
|
||||||
|
* fix tests, so they work with the sdist tarball too
|
||||||
|
* added the following function to HPath.IO: createSymlink
|
||||||
|
0.7.1:
|
||||||
|
* various cleanups and documentation improvements
|
||||||
|
* added the following functions to System.Posix.FilePath: splitSearchPath, getSearchPath, stripExtension, makeRelative, makeValid
|
||||||
|
0.7.0:
|
||||||
|
* use 'sendfile' from 'simple-sendfile' in _copyFile and do read/write as a fallback only
|
||||||
|
* add isFileName, hasParentDir, hiddenFile to System.Posix.FilePath
|
||||||
|
* add our own openFd version for more control
|
||||||
|
* small documentation improvements
|
||||||
|
* add a getDirectoryContents' version that works on Fd
|
||||||
|
* lift version constraints in benchmark
|
||||||
|
* remove fpToString and userStringToFP, use Data.ByteString.UTF8 directly instead
|
||||||
|
0.6.0:
|
||||||
|
* fixes 'throwDestinationInSource' to be more reliable.
|
||||||
|
* removes some unused HPathIOException constructors
|
||||||
|
* consistently provide exception constructor identifiers
|
||||||
|
* be less harsh when non-supported file types get passed to our functions, possibly ignoring them
|
||||||
|
* minor cleanups
|
||||||
|
0.5.9:
|
||||||
|
* Adds our posix-paths fork and a lot of IO operations.
|
||||||
|
0.5.8:
|
||||||
|
* First version of the fork.
|
||||||
|
0.5.7:
|
||||||
|
* Fix haddock problem.
|
||||||
|
0.5.6:
|
||||||
|
* Reject only .. and .
|
||||||
|
0.5.5:
|
||||||
|
* Use filepath's isValid function for additional sanity checks
|
||||||
|
0.5.4:
|
||||||
|
* Disable parsing of path consisting only of "."
|
||||||
|
* Add NFData instance for Path
|
||||||
|
* Some typo/docs improvements
|
||||||
|
* Add standard headers to modules
|
||||||
|
0.5.3:
|
||||||
|
* Added conversion functions.
|
||||||
|
|
||||||
|
0.2.0:
|
||||||
|
|
||||||
|
* Rename parentAbs to simply parent.
|
||||||
|
* Add dirname.
|
||||||
|
|
||||||
|
0.3.0:
|
||||||
|
* Removed Generic instance.
|
||||||
|
|
||||||
|
0.4.0:
|
||||||
|
* Implemented stricter parsing, disabling use of "..".
|
||||||
|
* Made stripDir generic over MonadThrow
|
||||||
|
|
||||||
|
0.5.0:
|
||||||
|
* Fix stripDir p p /= Nothing bug.
|
||||||
|
|
||||||
|
0.5.2:
|
||||||
|
* Removed unused DeriveGeneric.
|
||||||
30
hpath/LICENSE
Normal file
30
hpath/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) Julian Ospald
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the author nor the names of his contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
|
||||||
|
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
40
hpath/README.md
Normal file
40
hpath/README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
This package is part of the HPath suite, also check out:
|
||||||
|
|
||||||
|
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
|
||||||
|
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
|
||||||
|
* [hpath-io](https://hackage.haskell.org/package/hpath-io)
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
The motivation came during development of
|
||||||
|
[hsfm](https://github.com/hasufell/hsfm)
|
||||||
|
which has a pretty strict File type, but lacks a strict Path type, e.g.
|
||||||
|
for user input.
|
||||||
|
|
||||||
|
The library that came closest to my needs was
|
||||||
|
[path](https://github.com/chrisdone/path),
|
||||||
|
but the API turned out to be oddly complicated for my use case, so I
|
||||||
|
decided to fork it.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
* well-typed paths
|
||||||
|
* safe filepath manipulation, never using String as filepath, but ByteString
|
||||||
|
|
||||||
|
Note: this library was written for __posix__ systems and it will probably not support other systems.
|
||||||
|
|
||||||
|
## Differences to 'path'
|
||||||
|
|
||||||
|
* doesn't attempt to fake IO-related information into the path, so whether a path points to a file or directory is up to your IO-code to decide...
|
||||||
|
* uses safe ByteString for filepaths under the hood instead of unsafe String
|
||||||
|
* fixes broken [dirname](https://github.com/chrisdone/path/issues/18)
|
||||||
|
* renames dirname/filename to basename/dirname to match the POSIX shell functions
|
||||||
|
* allows pattern matching via unidirectional PatternSynonym
|
||||||
|
* uses simple doctest for testing
|
||||||
|
* allows `~/` as relative path, because on posix level `~` is just a regular filename that does _NOT_ point to `$HOME`
|
||||||
2
hpath/Setup.hs
Normal file
2
hpath/Setup.hs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
||||||
46
hpath/hpath.cabal
Normal file
46
hpath/hpath.cabal
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: hpath
|
||||||
|
version: 0.11.0
|
||||||
|
synopsis: Support for well-typed paths
|
||||||
|
description: Support for well-typed paths, utilizing ByteString under the hood.
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Julian Ospald <hasufell@posteo.de>
|
||||||
|
maintainer: Julian Ospald <hasufell@posteo.de>
|
||||||
|
copyright: Julian Ospald 2016
|
||||||
|
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
|
||||||
|
|
||||||
|
library
|
||||||
|
if os(windows)
|
||||||
|
build-depends: unbuildable<0
|
||||||
|
buildable: False
|
||||||
|
hs-source-dirs: src/
|
||||||
|
default-language: Haskell2010
|
||||||
|
if impl(ghc >= 8.0)
|
||||||
|
ghc-options: -Wall -Wno-redundant-constraints
|
||||||
|
else
|
||||||
|
ghc-options: -Wall
|
||||||
|
exposed-modules: HPath
|
||||||
|
other-modules: HPath.Internal
|
||||||
|
build-depends: base >= 4.8 && <5
|
||||||
|
, bytestring >= 0.10.0.0
|
||||||
|
, deepseq
|
||||||
|
, exceptions
|
||||||
|
, hpath-filepath >= 0.10 && < 0.11
|
||||||
|
, template-haskell
|
||||||
|
, utf8-string
|
||||||
|
, word8
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/hasufell/hpath
|
||||||
|
|
||||||
23
hpath/run-doctests.sh
Executable file
23
hpath/run-doctests.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -n "${SKIP_DOCTESTS}" ] ; then
|
||||||
|
echo "Skipping doctests"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v doctest >/dev/null ; then
|
||||||
|
tempdir="$(mktemp -d)"
|
||||||
|
(
|
||||||
|
cd "${tempdir}"
|
||||||
|
cabal install --installdir="${tempdir}" doctest
|
||||||
|
)
|
||||||
|
export PATH="${tempdir}:$PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cd "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
|
||||||
|
|
||||||
|
cabal exec doctest -- -isrc -XOverloadedStrings -XQuasiQuotes HPath
|
||||||
472
hpath/src/HPath.hs
Normal file
472
hpath/src/HPath.hs
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
-- |
|
||||||
|
-- Module : HPath
|
||||||
|
-- Copyright : © 2015–2016 FP Complete, 2016 Julian Ospald
|
||||||
|
-- License : BSD 3 clause
|
||||||
|
--
|
||||||
|
-- Maintainer : Julian Ospald <hasufell@posteo.de>
|
||||||
|
-- Stability : experimental
|
||||||
|
-- Portability : portable
|
||||||
|
--
|
||||||
|
-- Support for well-typed paths.
|
||||||
|
|
||||||
|
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
|
{-# LANGUAGE EmptyDataDecls #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 708
|
||||||
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
|
#endif
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
|
module HPath
|
||||||
|
(
|
||||||
|
-- * Types
|
||||||
|
Abs
|
||||||
|
,Path
|
||||||
|
,Rel
|
||||||
|
,PathParseException
|
||||||
|
,PathException
|
||||||
|
#if __GLASGOW_HASKELL__ >= 708
|
||||||
|
-- * PatternSynonyms/ViewPatterns
|
||||||
|
,pattern Path
|
||||||
|
#endif
|
||||||
|
-- * Path Construction
|
||||||
|
,parseAbs
|
||||||
|
,parseRel
|
||||||
|
,parseAny
|
||||||
|
,rootPath
|
||||||
|
-- * Path Conversion
|
||||||
|
,fromAbs
|
||||||
|
,fromRel
|
||||||
|
,toFilePath
|
||||||
|
,fromAny
|
||||||
|
-- * Path Operations
|
||||||
|
,(</>)
|
||||||
|
,basename
|
||||||
|
,dirname
|
||||||
|
,getAllParents
|
||||||
|
,getAllComponents
|
||||||
|
,getAllComponentsAfterRoot
|
||||||
|
,stripDir
|
||||||
|
-- * Path Examination
|
||||||
|
,isParentOf
|
||||||
|
,isRootPath
|
||||||
|
-- * Path IO helpers
|
||||||
|
,withAbsPath
|
||||||
|
,withRelPath
|
||||||
|
-- * Quasiquoters
|
||||||
|
,abs
|
||||||
|
,rel
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Exception (Exception)
|
||||||
|
import Control.Monad.Catch (MonadThrow(..))
|
||||||
|
#if MIN_VERSION_bytestring(0,10,8)
|
||||||
|
import Data.ByteString(ByteString, stripPrefix)
|
||||||
|
#else
|
||||||
|
import Data.ByteString(ByteString)
|
||||||
|
import qualified Data.List as L
|
||||||
|
#endif
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import Data.ByteString.UTF8
|
||||||
|
import Data.Data
|
||||||
|
import Data.Maybe
|
||||||
|
import Data.Word8
|
||||||
|
import HPath.Internal
|
||||||
|
import Language.Haskell.TH
|
||||||
|
import Language.Haskell.TH.Syntax (Exp(..), Lift(..), lift)
|
||||||
|
import Language.Haskell.TH.Quote (QuasiQuoter(..))
|
||||||
|
import Prelude hiding (abs, any)
|
||||||
|
import System.Posix.FilePath hiding ((</>))
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Types
|
||||||
|
|
||||||
|
-- | An absolute path.
|
||||||
|
data Abs deriving (Typeable)
|
||||||
|
|
||||||
|
-- | A relative path; one without a root.
|
||||||
|
data Rel deriving (Typeable)
|
||||||
|
|
||||||
|
-- | Exception when parsing a location.
|
||||||
|
data PathParseException
|
||||||
|
= InvalidAbs ByteString
|
||||||
|
| InvalidRel ByteString
|
||||||
|
| Couldn'tStripPrefixTPS ByteString ByteString
|
||||||
|
deriving (Show,Typeable)
|
||||||
|
instance Exception PathParseException
|
||||||
|
|
||||||
|
data PathException = RootDirHasNoBasename
|
||||||
|
deriving (Show,Typeable)
|
||||||
|
instance Exception PathException
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- PatternSynonyms
|
||||||
|
|
||||||
|
#if __GLASGOW_HASKELL__ >= 710
|
||||||
|
pattern Path :: ByteString -> Path a
|
||||||
|
#endif
|
||||||
|
#if __GLASGOW_HASKELL__ >= 708
|
||||||
|
pattern Path x <- (MkPath x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Path Parsers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- | Get a location for an absolute path. Produces a normalised path.
|
||||||
|
--
|
||||||
|
-- Throws: 'PathParseException'
|
||||||
|
--
|
||||||
|
-- >>> parseAbs "/abc" :: Maybe (Path Abs)
|
||||||
|
-- Just "/abc"
|
||||||
|
-- >>> parseAbs "/" :: Maybe (Path Abs)
|
||||||
|
-- Just "/"
|
||||||
|
-- >>> parseAbs "/abc/def" :: Maybe (Path Abs)
|
||||||
|
-- Just "/abc/def"
|
||||||
|
-- >>> parseAbs "/abc/def/.///" :: Maybe (Path Abs)
|
||||||
|
-- Just "/abc/def"
|
||||||
|
-- >>> parseAbs "abc" :: Maybe (Path Abs)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseAbs "" :: Maybe (Path Abs)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseAbs "/abc/../foo" :: Maybe (Path Abs)
|
||||||
|
-- Nothing
|
||||||
|
parseAbs :: MonadThrow m
|
||||||
|
=> ByteString -> m (Path Abs)
|
||||||
|
parseAbs filepath =
|
||||||
|
if isAbsolute filepath &&
|
||||||
|
isValid filepath &&
|
||||||
|
not (hasParentDir filepath)
|
||||||
|
then return (MkPath . dropTrailingPathSeparator . normalise $ filepath)
|
||||||
|
else throwM (InvalidAbs filepath)
|
||||||
|
|
||||||
|
|
||||||
|
-- | Get a location for a relative path. Produces a normalised
|
||||||
|
-- path.
|
||||||
|
--
|
||||||
|
-- Note that @filepath@ may contain any number of @./@ but may not consist
|
||||||
|
-- solely of @./@. It also may not contain a single @..@ anywhere.
|
||||||
|
--
|
||||||
|
-- Throws: 'PathParseException'
|
||||||
|
--
|
||||||
|
-- >>> parseRel "abc" :: Maybe (Path Rel)
|
||||||
|
-- Just "abc"
|
||||||
|
-- >>> parseRel "def/" :: Maybe (Path Rel)
|
||||||
|
-- Just "def"
|
||||||
|
-- >>> parseRel "abc/def" :: Maybe (Path Rel)
|
||||||
|
-- Just "abc/def"
|
||||||
|
-- >>> parseRel "abc/def/." :: Maybe (Path Rel)
|
||||||
|
-- Just "abc/def"
|
||||||
|
-- >>> parseRel "/abc" :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseRel "" :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseRel "abc/../foo" :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseRel "." :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseRel ".." :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
parseRel :: MonadThrow m
|
||||||
|
=> ByteString -> m (Path Rel)
|
||||||
|
parseRel filepath =
|
||||||
|
if not (isAbsolute filepath) &&
|
||||||
|
filepath /= BS.singleton _period &&
|
||||||
|
filepath /= BS.pack [_period, _period] &&
|
||||||
|
not (hasParentDir filepath) &&
|
||||||
|
isValid filepath
|
||||||
|
then return (MkPath . dropTrailingPathSeparator . normalise $ filepath)
|
||||||
|
else throwM (InvalidRel filepath)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- | Parses a path, whether it's relative or absolute. Will lose
|
||||||
|
-- information on whether it's relative or absolute. If you need to know,
|
||||||
|
-- reparse it.
|
||||||
|
--
|
||||||
|
-- Filenames must not contain slashes.
|
||||||
|
-- Excludes '.' and '..'.
|
||||||
|
--
|
||||||
|
-- Throws: 'PathParseException'
|
||||||
|
--
|
||||||
|
-- >>> parseAny "/abc" :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Just (Left "/abc")
|
||||||
|
-- >>> parseAny "..." :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Just (Right "...")
|
||||||
|
-- >>> parseAny "abc/def" :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Just (Right "abc/def")
|
||||||
|
-- >>> parseAny "abc/def/." :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Just (Right "abc/def")
|
||||||
|
-- >>> parseAny "/abc" :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Just (Left "/abc")
|
||||||
|
-- >>> parseAny "" :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseAny "abc/../foo" :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseAny "." :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Nothing
|
||||||
|
-- >>> parseAny ".." :: Maybe (Either (Path Abs) (Path Rel))
|
||||||
|
-- Nothing
|
||||||
|
parseAny :: MonadThrow m => ByteString -> m (Either (Path Abs) (Path Rel))
|
||||||
|
parseAny filepath = case parseAbs filepath of
|
||||||
|
Just p -> pure $ Left p
|
||||||
|
Nothing -> case parseRel filepath of
|
||||||
|
Just p -> pure $ Right p
|
||||||
|
Nothing -> throwM (InvalidRel filepath)
|
||||||
|
|
||||||
|
|
||||||
|
rootPath :: Path Abs
|
||||||
|
rootPath = (MkPath (BS.singleton _slash))
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Path Conversion
|
||||||
|
|
||||||
|
-- | Convert any Path to a ByteString type.
|
||||||
|
toFilePath :: Path b -> ByteString
|
||||||
|
toFilePath (MkPath l) = l
|
||||||
|
|
||||||
|
-- | Convert an absolute Path to a ByteString type.
|
||||||
|
fromAbs :: Path Abs -> ByteString
|
||||||
|
fromAbs = toFilePath
|
||||||
|
|
||||||
|
-- | Convert a relative Path to a ByteString type.
|
||||||
|
fromRel :: Path Rel -> ByteString
|
||||||
|
fromRel = toFilePath
|
||||||
|
|
||||||
|
fromAny :: Either (Path Abs) (Path Rel) -> ByteString
|
||||||
|
fromAny = either toFilePath toFilePath
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Path Operations
|
||||||
|
|
||||||
|
-- | Append two paths.
|
||||||
|
--
|
||||||
|
-- The second argument must always be a relative path, which ensures
|
||||||
|
-- that undefinable things like `"/abc" </> "/def"` cannot happen.
|
||||||
|
--
|
||||||
|
-- Technically, the first argument can be a path that points to a non-directory,
|
||||||
|
-- because this library is IO-agnostic and makes no assumptions about
|
||||||
|
-- file types.
|
||||||
|
--
|
||||||
|
-- >>> (MkPath "/") </> (MkPath "file" :: Path Rel)
|
||||||
|
-- "/file"
|
||||||
|
-- >>> (MkPath "/path/to") </> (MkPath "file" :: Path Rel)
|
||||||
|
-- "/path/to/file"
|
||||||
|
-- >>> (MkPath "/") </> (MkPath "file/lal" :: Path Rel)
|
||||||
|
-- "/file/lal"
|
||||||
|
-- >>> (MkPath "/") </> (MkPath "file" :: Path Rel)
|
||||||
|
-- "/file"
|
||||||
|
(</>) :: Path b -> Path Rel -> Path b
|
||||||
|
(</>) (MkPath a) (MkPath b) = MkPath (a' `BS.append` b)
|
||||||
|
where
|
||||||
|
a' = if hasTrailingPathSeparator a
|
||||||
|
then a
|
||||||
|
else addTrailingPathSeparator a
|
||||||
|
|
||||||
|
|
||||||
|
-- | Strip directory from path, making it relative to that directory.
|
||||||
|
-- Throws 'Couldn'tStripPrefixDir' if directory is not a parent of the path.
|
||||||
|
--
|
||||||
|
-- The bases must match.
|
||||||
|
--
|
||||||
|
-- >>> (MkPath "/lal/lad") `stripDir` (MkPath "/lal/lad/fad") :: Maybe (Path Rel)
|
||||||
|
-- Just "fad"
|
||||||
|
-- >>> (MkPath "lal/lad") `stripDir` (MkPath "lal/lad/fad") :: Maybe (Path Rel)
|
||||||
|
-- Just "fad"
|
||||||
|
-- >>> (MkPath "/") `stripDir` (MkPath "/") :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> (MkPath "/lal/lad/fad") `stripDir` (MkPath "/lal/lad") :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
-- >>> (MkPath "fad") `stripDir` (MkPath "fad") :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
stripDir :: MonadThrow m
|
||||||
|
=> Path b -> Path b -> m (Path Rel)
|
||||||
|
stripDir (MkPath p) (MkPath l) =
|
||||||
|
case stripPrefix p' l of
|
||||||
|
Nothing -> throwM (Couldn'tStripPrefixTPS p' l)
|
||||||
|
Just ok -> if BS.null ok
|
||||||
|
then throwM (Couldn'tStripPrefixTPS p' l)
|
||||||
|
else return (MkPath ok)
|
||||||
|
where
|
||||||
|
p' = addTrailingPathSeparator p
|
||||||
|
|
||||||
|
|
||||||
|
-- |Get all parents of a path.
|
||||||
|
--
|
||||||
|
-- >>> getAllParents (MkPath "/abs/def/dod")
|
||||||
|
-- ["/abs/def","/abs","/"]
|
||||||
|
-- >>> getAllParents (MkPath "/foo")
|
||||||
|
-- ["/"]
|
||||||
|
-- >>> getAllParents (MkPath "/")
|
||||||
|
-- []
|
||||||
|
getAllParents :: Path Abs -> [Path Abs]
|
||||||
|
getAllParents (MkPath p)
|
||||||
|
| np == BS.singleton pathSeparator = []
|
||||||
|
| otherwise = dirname (MkPath np) : getAllParents (dirname $ MkPath np)
|
||||||
|
where
|
||||||
|
np = normalise p
|
||||||
|
|
||||||
|
|
||||||
|
-- | Gets all path components.
|
||||||
|
--
|
||||||
|
-- >>> getAllComponents (MkPath "abs/def/dod")
|
||||||
|
-- ["abs","def","dod"]
|
||||||
|
-- >>> getAllComponents (MkPath "abs")
|
||||||
|
-- ["abs"]
|
||||||
|
getAllComponents :: Path Rel -> [Path Rel]
|
||||||
|
getAllComponents (MkPath p) = fmap MkPath . splitDirectories $ p
|
||||||
|
|
||||||
|
|
||||||
|
-- | Gets all path components after the "/" root directory.
|
||||||
|
--
|
||||||
|
-- >>> getAllComponentsAfterRoot (MkPath "/abs/def/dod")
|
||||||
|
-- ["abs","def","dod"]
|
||||||
|
-- >>> getAllComponentsAfterRoot (MkPath "/abs")
|
||||||
|
-- ["abs"]
|
||||||
|
getAllComponentsAfterRoot :: Path Abs -> [Path Rel]
|
||||||
|
getAllComponentsAfterRoot p = getAllComponents (fromJust $ stripDir rootPath p)
|
||||||
|
|
||||||
|
|
||||||
|
-- | Extract the directory name of a path.
|
||||||
|
--
|
||||||
|
-- >>> dirname (MkPath "/abc/def/dod")
|
||||||
|
-- "/abc/def"
|
||||||
|
-- >>> dirname (MkPath "/")
|
||||||
|
-- "/"
|
||||||
|
dirname :: Path Abs -> Path Abs
|
||||||
|
dirname (MkPath fp) = MkPath (takeDirectory fp)
|
||||||
|
|
||||||
|
-- | Extract the file part of a path.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- The following properties hold:
|
||||||
|
--
|
||||||
|
-- @basename (p \<\/> a) == basename a@
|
||||||
|
--
|
||||||
|
-- Throws: `PathException` if given the root path "/"
|
||||||
|
--
|
||||||
|
-- >>> basename (MkPath "/abc/def/dod") :: Maybe (Path Rel)
|
||||||
|
-- Just "dod"
|
||||||
|
-- >>> basename (MkPath "abc/def/dod") :: Maybe (Path Rel)
|
||||||
|
-- Just "dod"
|
||||||
|
-- >>> basename (MkPath "dod") :: Maybe (Path Rel)
|
||||||
|
-- Just "dod"
|
||||||
|
-- >>> basename (MkPath "/") :: Maybe (Path Rel)
|
||||||
|
-- Nothing
|
||||||
|
basename :: MonadThrow m => Path b -> m (Path Rel)
|
||||||
|
basename (MkPath l)
|
||||||
|
| not (isAbsolute rl) = return $ MkPath rl
|
||||||
|
| otherwise = throwM RootDirHasNoBasename
|
||||||
|
where
|
||||||
|
rl = last . splitPath $ l
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Path Examination
|
||||||
|
|
||||||
|
-- | Is p a parent of the given location? Implemented in terms of
|
||||||
|
-- 'stripDir'. The bases must match.
|
||||||
|
--
|
||||||
|
-- >>> (MkPath "/lal/lad") `isParentOf` (MkPath "/lal/lad/fad")
|
||||||
|
-- True
|
||||||
|
-- >>> (MkPath "lal/lad") `isParentOf` (MkPath "lal/lad/fad")
|
||||||
|
-- True
|
||||||
|
-- >>> (MkPath "/") `isParentOf` (MkPath "/")
|
||||||
|
-- False
|
||||||
|
-- >>> (MkPath "/lal/lad/fad") `isParentOf` (MkPath "/lal/lad")
|
||||||
|
-- False
|
||||||
|
-- >>> (MkPath "fad") `isParentOf` (MkPath "fad")
|
||||||
|
-- False
|
||||||
|
isParentOf :: Path b -> Path b -> Bool
|
||||||
|
isParentOf p l = isJust (stripDir p l :: Maybe (Path Rel))
|
||||||
|
|
||||||
|
|
||||||
|
-- | Check whether the given Path is the root "/" path.
|
||||||
|
--
|
||||||
|
-- >>> isRootPath (MkPath "/lal/lad")
|
||||||
|
-- False
|
||||||
|
-- >>> isRootPath (MkPath "/")
|
||||||
|
-- True
|
||||||
|
isRootPath :: Path Abs -> Bool
|
||||||
|
isRootPath = (== rootPath)
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Path IO helpers
|
||||||
|
|
||||||
|
|
||||||
|
withAbsPath :: Path Abs -> (ByteString -> IO a) -> IO a
|
||||||
|
withAbsPath (MkPath p) action = action p
|
||||||
|
|
||||||
|
|
||||||
|
withRelPath :: Path Rel -> (ByteString -> IO a) -> IO a
|
||||||
|
withRelPath (MkPath p) action = action p
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
-- ByteString helpers
|
||||||
|
|
||||||
|
#if MIN_VERSION_bytestring(0,10,8)
|
||||||
|
#else
|
||||||
|
stripPrefix :: ByteString -> ByteString -> Maybe ByteString
|
||||||
|
stripPrefix a b = BS.pack `fmap` L.stripPrefix (BS.unpack a) (BS.unpack b)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
-- QuasiQuoters
|
||||||
|
|
||||||
|
instance Lift (Path a) where
|
||||||
|
lift (MkPath bs) = AppE <$> [| MkPath . BS.pack |] <*> lift (BS.unpack bs)
|
||||||
|
|
||||||
|
|
||||||
|
qq :: (ByteString -> Q Exp) -> QuasiQuoter
|
||||||
|
qq quoteExp' =
|
||||||
|
QuasiQuoter
|
||||||
|
{ quoteExp = (\s -> quoteExp' . fromString $ s)
|
||||||
|
, quotePat = \_ ->
|
||||||
|
fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"
|
||||||
|
, quoteType = \_ ->
|
||||||
|
fail "illegal QuasiQuote (allowed as expression only, used as a type)"
|
||||||
|
, quoteDec = \_ ->
|
||||||
|
fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkAbs :: ByteString -> Q Exp
|
||||||
|
mkAbs = either (error . show) lift . parseAbs
|
||||||
|
|
||||||
|
mkRel :: ByteString -> Q Exp
|
||||||
|
mkRel = either (error . show) lift . parseRel
|
||||||
|
|
||||||
|
-- | Quasiquote an absolute Path. This accepts Unicode Chars and will encode as UTF-8.
|
||||||
|
--
|
||||||
|
-- >>> [abs|/etc/profile|] :: Path Abs
|
||||||
|
-- "/etc/profile"
|
||||||
|
-- >>> [abs|/|] :: Path Abs
|
||||||
|
-- "/"
|
||||||
|
-- >>> [abs|/|] :: Path Abs
|
||||||
|
-- "/\239\131\144"
|
||||||
|
abs :: QuasiQuoter
|
||||||
|
abs = qq mkAbs
|
||||||
|
|
||||||
|
-- | Quasiquote a relative Path. This accepts Unicode Chars and will encode as UTF-8.
|
||||||
|
--
|
||||||
|
-- >>> [rel|etc|] :: Path Rel
|
||||||
|
-- "etc"
|
||||||
|
-- >>> [rel|bar/baz|] :: Path Rel
|
||||||
|
-- "bar/baz"
|
||||||
|
-- >>> [rel||] :: Path Rel
|
||||||
|
-- "\239\131\144"
|
||||||
|
rel :: QuasiQuoter
|
||||||
|
rel = qq mkRel
|
||||||
|
|
||||||
@@ -3,27 +3,30 @@
|
|||||||
-- | Internal types and functions.
|
-- | Internal types and functions.
|
||||||
|
|
||||||
module HPath.Internal
|
module HPath.Internal
|
||||||
(Path(..)
|
(Path(..))
|
||||||
,RelC)
|
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.DeepSeq (NFData (..))
|
import Control.DeepSeq (NFData (..))
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import Data.Data
|
import Data.Data
|
||||||
|
|
||||||
-- | Path of some base and type.
|
-- | The main Path type.
|
||||||
--
|
--
|
||||||
-- Internally is a string. The string can be of two formats only:
|
-- The type variable 'b' is either:
|
||||||
--
|
--
|
||||||
-- 1. without trailing path separator: @file.txt@, @foo\/bar.txt@, @\/foo\/bar.txt@
|
-- * Abs -- absolute path
|
||||||
-- 2. with trailing path separator: @foo\/@, @\/foo\/bar\/@
|
-- * Rel -- relative path
|
||||||
--
|
--
|
||||||
-- There are no duplicate
|
-- Internally is a ByteString. The path is guaranteed to
|
||||||
-- path separators @\/\/@, no @..@, no @.\/@, no @~\/@, etc.
|
-- be normalised and contain no trailing Path separators,
|
||||||
|
-- except for the '/' root path.
|
||||||
|
--
|
||||||
|
-- There are no duplicate path separators
|
||||||
|
-- @\/\/@, no @..@, no @.\/@, no @~\/@, etc.
|
||||||
data Path b = MkPath ByteString
|
data Path b = MkPath ByteString
|
||||||
deriving (Typeable)
|
deriving (Typeable)
|
||||||
|
|
||||||
-- | String equality.
|
-- | ByteString equality.
|
||||||
--
|
--
|
||||||
-- The following property holds:
|
-- The following property holds:
|
||||||
--
|
--
|
||||||
@@ -31,7 +34,7 @@ data Path b = MkPath ByteString
|
|||||||
instance Eq (Path b) where
|
instance Eq (Path b) where
|
||||||
(==) (MkPath x) (MkPath y) = x == y
|
(==) (MkPath x) (MkPath y) = x == y
|
||||||
|
|
||||||
-- | String ordering.
|
-- | ByteString ordering.
|
||||||
--
|
--
|
||||||
-- The following property holds:
|
-- The following property holds:
|
||||||
--
|
--
|
||||||
@@ -39,7 +42,7 @@ instance Eq (Path b) where
|
|||||||
instance Ord (Path b) where
|
instance Ord (Path b) where
|
||||||
compare (MkPath x) (MkPath y) = compare x y
|
compare (MkPath x) (MkPath y) = compare x y
|
||||||
|
|
||||||
-- | Same as 'Path.toFilePath'.
|
-- | Same as 'HPath.toFilePath'.
|
||||||
--
|
--
|
||||||
-- The following property holds:
|
-- The following property holds:
|
||||||
--
|
--
|
||||||
@@ -50,6 +53,3 @@ instance Show (Path b) where
|
|||||||
instance NFData (Path b) where
|
instance NFData (Path b) where
|
||||||
rnf (MkPath x) = rnf x
|
rnf (MkPath x) = rnf x
|
||||||
|
|
||||||
|
|
||||||
class RelC m
|
|
||||||
|
|
||||||
492
src/HPath.hs
492
src/HPath.hs
@@ -1,492 +0,0 @@
|
|||||||
-- |
|
|
||||||
-- Module : HPath
|
|
||||||
-- Copyright : © 2015–2016 FP Complete, 2016 Julian Ospald
|
|
||||||
-- License : BSD 3 clause
|
|
||||||
--
|
|
||||||
-- Maintainer : Julian Ospald <hasufell@posteo.de>
|
|
||||||
-- Stability : experimental
|
|
||||||
-- Portability : portable
|
|
||||||
--
|
|
||||||
-- Support for well-typed paths.
|
|
||||||
|
|
||||||
|
|
||||||
{-# LANGUAGE CPP #-}
|
|
||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
|
||||||
{-# LANGUAGE EmptyDataDecls #-}
|
|
||||||
{-# LANGUAGE PatternSynonyms #-}
|
|
||||||
{-# OPTIONS_HADDOCK ignore-exports #-}
|
|
||||||
|
|
||||||
module HPath
|
|
||||||
(
|
|
||||||
-- * Types
|
|
||||||
Abs
|
|
||||||
,Path
|
|
||||||
,Rel
|
|
||||||
,Fn
|
|
||||||
,PathParseException
|
|
||||||
-- * PatternSynonyms/ViewPatterns
|
|
||||||
,pattern Path
|
|
||||||
-- * Path Parsing
|
|
||||||
,parseAbs
|
|
||||||
,parseFn
|
|
||||||
,parseRel
|
|
||||||
-- * Path Conversion
|
|
||||||
,canonicalizePath
|
|
||||||
,fromAbs
|
|
||||||
,fromRel
|
|
||||||
,normalize
|
|
||||||
,toFilePath
|
|
||||||
-- * Path Operations
|
|
||||||
,(</>)
|
|
||||||
,basename
|
|
||||||
,dirname
|
|
||||||
,isParentOf
|
|
||||||
,getAllParents
|
|
||||||
,stripDir
|
|
||||||
-- * Path IO helpers
|
|
||||||
,withAbsPath
|
|
||||||
,withRelPath
|
|
||||||
,withFnPath
|
|
||||||
-- * ByteString/Word8 constants
|
|
||||||
,nullByte
|
|
||||||
,pathDot
|
|
||||||
,pathDot'
|
|
||||||
,pathSeparator'
|
|
||||||
-- * ByteString operations
|
|
||||||
,fpToString
|
|
||||||
,userStringToFP
|
|
||||||
-- * ByteString Query functions
|
|
||||||
,hiddenFile
|
|
||||||
-- * Queries
|
|
||||||
,hasParentDir
|
|
||||||
,isFileName
|
|
||||||
-- * String based functions
|
|
||||||
)
|
|
||||||
where
|
|
||||||
|
|
||||||
import Control.Exception (Exception)
|
|
||||||
import Control.Monad.Catch (MonadThrow(..))
|
|
||||||
import Data.ByteString(ByteString)
|
|
||||||
import qualified Data.ByteString as BS
|
|
||||||
import Data.ByteString.UTF8 (fromString, toString)
|
|
||||||
import Data.Data
|
|
||||||
import qualified Data.List as L
|
|
||||||
import Data.Maybe
|
|
||||||
import Data.Word8
|
|
||||||
import HPath.Internal
|
|
||||||
import System.Posix.FilePath hiding ((</>))
|
|
||||||
import System.Posix.Directory.Traversals(realpath)
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Types
|
|
||||||
|
|
||||||
-- | An absolute path.
|
|
||||||
data Abs deriving (Typeable)
|
|
||||||
|
|
||||||
-- | A relative path; one without a root.
|
|
||||||
data Rel deriving (Typeable)
|
|
||||||
|
|
||||||
-- | A filename, without any '/'.
|
|
||||||
data Fn deriving (Typeable)
|
|
||||||
|
|
||||||
-- | Exception when parsing a location.
|
|
||||||
data PathParseException
|
|
||||||
= InvalidAbs ByteString
|
|
||||||
| InvalidRel ByteString
|
|
||||||
| InvalidFn ByteString
|
|
||||||
| Couldn'tStripPrefixTPS ByteString ByteString
|
|
||||||
deriving (Show,Typeable)
|
|
||||||
instance Exception PathParseException
|
|
||||||
|
|
||||||
data PathException = RootDirHasNoBasename
|
|
||||||
deriving (Show,Typeable)
|
|
||||||
instance Exception PathException
|
|
||||||
|
|
||||||
instance RelC Rel
|
|
||||||
instance RelC Fn
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- PatternSynonyms
|
|
||||||
|
|
||||||
pattern Path x <- (MkPath x)
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Path Parsers
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- | Get a location for an absolute path. Produces a normalised path.
|
|
||||||
--
|
|
||||||
-- Throws: 'PathParseException'
|
|
||||||
--
|
|
||||||
-- >>> parseAbs "/abc" :: Maybe (Path Abs)
|
|
||||||
-- Just "/abc"
|
|
||||||
-- >>> parseAbs "/" :: Maybe (Path Abs)
|
|
||||||
-- Just "/"
|
|
||||||
-- >>> parseAbs "/abc/def" :: Maybe (Path Abs)
|
|
||||||
-- Just "/abc/def"
|
|
||||||
-- >>> parseAbs "/abc/def/.///" :: Maybe (Path Abs)
|
|
||||||
-- Just "/abc/def/"
|
|
||||||
-- >>> parseAbs "abc" :: Maybe (Path Abs)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseAbs "" :: Maybe (Path Abs)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseAbs "/abc/../foo" :: Maybe (Path Abs)
|
|
||||||
-- Nothing
|
|
||||||
parseAbs :: MonadThrow m
|
|
||||||
=> ByteString -> m (Path Abs)
|
|
||||||
parseAbs filepath =
|
|
||||||
if isAbsolute filepath &&
|
|
||||||
isValid filepath &&
|
|
||||||
not (hasParentDir filepath)
|
|
||||||
then return (MkPath $ normalise filepath)
|
|
||||||
else throwM (InvalidAbs filepath)
|
|
||||||
|
|
||||||
|
|
||||||
-- | Get a location for a relative path. Produces a normalised
|
|
||||||
-- path.
|
|
||||||
--
|
|
||||||
-- Note that @filepath@ may contain any number of @./@ but may not consist
|
|
||||||
-- solely of @./@. It also may not contain a single @..@ anywhere.
|
|
||||||
--
|
|
||||||
-- Throws: 'PathParseException'
|
|
||||||
--
|
|
||||||
-- >>> parseRel "abc" :: Maybe (Path Rel)
|
|
||||||
-- Just "abc"
|
|
||||||
-- >>> parseRel "def/" :: Maybe (Path Rel)
|
|
||||||
-- Just "def/"
|
|
||||||
-- >>> parseRel "abc/def" :: Maybe (Path Rel)
|
|
||||||
-- Just "abc/def"
|
|
||||||
-- >>> parseRel "abc/def/." :: Maybe (Path Rel)
|
|
||||||
-- Just "abc/def/"
|
|
||||||
-- >>> parseRel "/abc" :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseRel "" :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseRel "abc/../foo" :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseRel "." :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseRel ".." :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
parseRel :: MonadThrow m
|
|
||||||
=> ByteString -> m (Path Rel)
|
|
||||||
parseRel filepath =
|
|
||||||
if not (isAbsolute filepath) &&
|
|
||||||
filepath /= pathDot' && filepath /= pathDoubleDot &&
|
|
||||||
not (hasParentDir filepath) &&
|
|
||||||
isValid filepath
|
|
||||||
then return (MkPath $ normalise filepath)
|
|
||||||
else throwM (InvalidRel filepath)
|
|
||||||
|
|
||||||
|
|
||||||
-- | Parses a filename. Filenames must not contain slashes.
|
|
||||||
-- Excludes '.' and '..'.
|
|
||||||
--
|
|
||||||
-- Throws: 'PathParseException'
|
|
||||||
--
|
|
||||||
-- >>> parseFn "abc" :: Maybe (Path Fn)
|
|
||||||
-- Just "abc"
|
|
||||||
-- >>> parseFn "..." :: Maybe (Path Fn)
|
|
||||||
-- Just "..."
|
|
||||||
-- >>> parseFn "def/" :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseFn "abc/def" :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseFn "abc/def/." :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseFn "/abc" :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseFn "" :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseFn "abc/../foo" :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseFn "." :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> parseFn ".." :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
parseFn :: MonadThrow m
|
|
||||||
=> ByteString -> m (Path Fn)
|
|
||||||
parseFn filepath =
|
|
||||||
if isFileName filepath &&
|
|
||||||
filepath /= pathDot' && filepath /= pathDoubleDot &&
|
|
||||||
isValid filepath
|
|
||||||
then return (MkPath filepath)
|
|
||||||
else throwM (InvalidFn filepath)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Path Conversion
|
|
||||||
|
|
||||||
-- | Convert to a ByteString type.
|
|
||||||
toFilePath :: Path b -> ByteString
|
|
||||||
toFilePath (MkPath l) = l
|
|
||||||
|
|
||||||
fromAbs :: Path Abs -> ByteString
|
|
||||||
fromAbs = toFilePath
|
|
||||||
|
|
||||||
fromRel :: RelC r => Path r -> ByteString
|
|
||||||
fromRel = toFilePath
|
|
||||||
|
|
||||||
normalize :: Path t -> Path t
|
|
||||||
normalize (MkPath l) = MkPath $ normalise l
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Path Operations
|
|
||||||
|
|
||||||
-- | Append two paths.
|
|
||||||
--
|
|
||||||
-- The second argument must always be a relative path, which ensures
|
|
||||||
-- that undefinable things like `"/abc" </> "/def"` cannot happen.
|
|
||||||
--
|
|
||||||
-- Technically, the first argument can be a path that points to a non-directory,
|
|
||||||
-- because this library is IO-agnostic and makes no assumptions about
|
|
||||||
-- file types.
|
|
||||||
--
|
|
||||||
-- >>> (MkPath "/") </> (MkPath "file" :: Path Rel)
|
|
||||||
-- "/file"
|
|
||||||
-- >>> (MkPath "/path/to") </> (MkPath "file" :: Path Rel)
|
|
||||||
-- "/path/to/file"
|
|
||||||
-- >>> (MkPath "/") </> (MkPath "file/lal" :: Path Rel)
|
|
||||||
-- "/file/lal"
|
|
||||||
-- >>> (MkPath "/") </> (MkPath "file/" :: Path Rel)
|
|
||||||
-- "/file/"
|
|
||||||
(</>) :: RelC r => Path b -> Path r -> Path b
|
|
||||||
(</>) (MkPath a) (MkPath b) = MkPath (a' `BS.append` b)
|
|
||||||
where
|
|
||||||
a' = if BS.last a == pathSeparator
|
|
||||||
then a
|
|
||||||
else addTrailingPathSeparator a
|
|
||||||
|
|
||||||
-- | Strip directory from path, making it relative to that directory.
|
|
||||||
-- Throws 'Couldn'tStripPrefixDir' if directory is not a parent of the path.
|
|
||||||
--
|
|
||||||
-- The bases must match.
|
|
||||||
--
|
|
||||||
-- >>> (MkPath "/lal/lad") `stripDir` (MkPath "/lal/lad/fad") :: Maybe (Path Rel)
|
|
||||||
-- Just "fad"
|
|
||||||
-- >>> (MkPath "lal/lad") `stripDir` (MkPath "lal/lad/fad") :: Maybe (Path Rel)
|
|
||||||
-- Just "fad"
|
|
||||||
-- >>> (MkPath "/") `stripDir` (MkPath "/") :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> (MkPath "/lal/lad/fad") `stripDir` (MkPath "/lal/lad") :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
-- >>> (MkPath "fad") `stripDir` (MkPath "fad") :: Maybe (Path Rel)
|
|
||||||
-- Nothing
|
|
||||||
stripDir :: MonadThrow m
|
|
||||||
=> Path b -> Path b -> m (Path Rel)
|
|
||||||
stripDir (MkPath p) (MkPath l) =
|
|
||||||
case stripPrefix p' l of
|
|
||||||
Nothing -> throwM (Couldn'tStripPrefixTPS p' l)
|
|
||||||
Just ok -> if BS.null ok
|
|
||||||
then throwM (Couldn'tStripPrefixTPS p' l)
|
|
||||||
else return (MkPath ok)
|
|
||||||
where
|
|
||||||
p' = addTrailingPathSeparator p
|
|
||||||
|
|
||||||
-- | Is p a parent of the given location? Implemented in terms of
|
|
||||||
-- 'stripDir'. The bases must match.
|
|
||||||
--
|
|
||||||
-- >>> (MkPath "/lal/lad") `isParentOf` (MkPath "/lal/lad/fad")
|
|
||||||
-- True
|
|
||||||
-- >>> (MkPath "lal/lad") `isParentOf` (MkPath "lal/lad/fad")
|
|
||||||
-- True
|
|
||||||
-- >>> (MkPath "/") `isParentOf` (MkPath "/")
|
|
||||||
-- False
|
|
||||||
-- >>> (MkPath "/lal/lad/fad") `isParentOf` (MkPath "/lal/lad")
|
|
||||||
-- False
|
|
||||||
-- >>> (MkPath "fad") `isParentOf` (MkPath "fad")
|
|
||||||
-- False
|
|
||||||
isParentOf :: Path b -> Path b -> Bool
|
|
||||||
isParentOf p l = isJust (stripDir p l :: Maybe (Path Rel))
|
|
||||||
|
|
||||||
|
|
||||||
-- |Get all parents of a path.
|
|
||||||
--
|
|
||||||
-- >>> getAllParents (MkPath "/abs/def/dod")
|
|
||||||
-- ["/abs/def","/abs","/"]
|
|
||||||
-- >>> getAllParents (MkPath "/")
|
|
||||||
-- []
|
|
||||||
getAllParents :: Path Abs -> [Path Abs]
|
|
||||||
getAllParents (MkPath p)
|
|
||||||
| np == pathSeparator' = []
|
|
||||||
| otherwise = dirname (MkPath np) : getAllParents (dirname $ MkPath np)
|
|
||||||
where
|
|
||||||
np = dropTrailingPathSeparator . normalise $ p
|
|
||||||
|
|
||||||
|
|
||||||
-- | Extract the directory name of a path.
|
|
||||||
--
|
|
||||||
-- The following properties hold:
|
|
||||||
--
|
|
||||||
-- @dirname (p \<\/> a) == dirname p@
|
|
||||||
--
|
|
||||||
-- >>> dirname (MkPath "/abc/def/dod")
|
|
||||||
-- "/abc/def"
|
|
||||||
-- >>> dirname (MkPath "/")
|
|
||||||
-- "/"
|
|
||||||
dirname :: Path Abs -> Path Abs
|
|
||||||
dirname (MkPath fp) = MkPath (takeDirectory $ dropTrailingPathSeparator fp)
|
|
||||||
|
|
||||||
-- | Extract the file part of a path.
|
|
||||||
--
|
|
||||||
--
|
|
||||||
-- The following properties hold:
|
|
||||||
--
|
|
||||||
-- @basename (p \<\/> a) == basename a@
|
|
||||||
--
|
|
||||||
-- Throws: `PathException` if given the root path "/"
|
|
||||||
--
|
|
||||||
-- >>> basename (MkPath "/abc/def/dod") :: Maybe (Path Fn)
|
|
||||||
-- Just "dod"
|
|
||||||
-- >>> basename (MkPath "/") :: Maybe (Path Fn)
|
|
||||||
-- Nothing
|
|
||||||
basename :: MonadThrow m => Path b -> m (Path Fn)
|
|
||||||
basename (MkPath l)
|
|
||||||
| not (isAbsolute rl) = return $ MkPath rl
|
|
||||||
| otherwise = throwM RootDirHasNoBasename
|
|
||||||
where
|
|
||||||
rl = last . splitPath . dropTrailingPathSeparator $ l
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Path IO helpers
|
|
||||||
|
|
||||||
|
|
||||||
-- | May fail on `realpath`.
|
|
||||||
canonicalizePath :: Path Abs -> IO (Path Abs)
|
|
||||||
canonicalizePath (MkPath l) = do
|
|
||||||
nl <- realpath l
|
|
||||||
return $ MkPath nl
|
|
||||||
|
|
||||||
|
|
||||||
withAbsPath :: Path Abs -> (ByteString -> IO a) -> IO a
|
|
||||||
withAbsPath (MkPath p) action = action p
|
|
||||||
|
|
||||||
|
|
||||||
withRelPath :: Path Rel -> (ByteString -> IO a) -> IO a
|
|
||||||
withRelPath (MkPath p) action = action p
|
|
||||||
|
|
||||||
|
|
||||||
withFnPath :: Path Fn -> (ByteString -> IO a) -> IO a
|
|
||||||
withFnPath (MkPath p) action = action p
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- ByteString Query functions
|
|
||||||
|
|
||||||
|
|
||||||
-- | Whether the file is a hidden file.
|
|
||||||
--
|
|
||||||
-- >>> hiddenFile (MkPath ".foo")
|
|
||||||
-- True
|
|
||||||
-- >>> hiddenFile (MkPath "..foo.bar")
|
|
||||||
-- True
|
|
||||||
-- >>> hiddenFile (MkPath "...")
|
|
||||||
-- True
|
|
||||||
-- >>> hiddenFile (MkPath "dod")
|
|
||||||
-- False
|
|
||||||
-- >>> hiddenFile (MkPath "dod.bar")
|
|
||||||
-- False
|
|
||||||
hiddenFile :: Path Fn -> Bool
|
|
||||||
hiddenFile (MkPath fp)
|
|
||||||
| fp == pathDoubleDot = False
|
|
||||||
| fp == pathDot' = False
|
|
||||||
| otherwise = pathDot' `BS.isPrefixOf` fp
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- ByteString/Word8 constants
|
|
||||||
|
|
||||||
pathSeparator' :: ByteString
|
|
||||||
pathSeparator' = BS.singleton pathSeparator
|
|
||||||
|
|
||||||
|
|
||||||
pathDot :: Word8
|
|
||||||
pathDot = _period
|
|
||||||
|
|
||||||
|
|
||||||
pathDot' :: ByteString
|
|
||||||
pathDot' = BS.singleton pathDot
|
|
||||||
|
|
||||||
|
|
||||||
pathDoubleDot :: ByteString
|
|
||||||
pathDoubleDot = pathDot `BS.cons` pathDot'
|
|
||||||
|
|
||||||
|
|
||||||
nullByte :: Word8
|
|
||||||
nullByte = _nul
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- ByteString Operations
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- |Uses UTF-8 decoding to convert the bytestring into a String.
|
|
||||||
fpToString :: ByteString -> String
|
|
||||||
fpToString = toString
|
|
||||||
|
|
||||||
|
|
||||||
-- |Uses UTF-8 encoding to convert a user provided String into
|
|
||||||
-- a ByteString, which represents a filepath.
|
|
||||||
userStringToFP :: String -> ByteString
|
|
||||||
userStringToFP = fromString
|
|
||||||
|
|
||||||
|
|
||||||
#if MIN_VERSION_bytestring(0,10,8)
|
|
||||||
#else
|
|
||||||
stripPrefix :: ByteString -> ByteString -> Maybe ByteString
|
|
||||||
stripPrefix a b = BS.pack `fmap` L.stripPrefix (BS.unpack a) (BS.unpack b)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- ByteString Query functions
|
|
||||||
|
|
||||||
-- | Helper function: 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 :: ByteString -> Bool
|
|
||||||
hasParentDir filepath =
|
|
||||||
((pathSeparator `BS.cons` pathDoubleDot) `BS.isSuffixOf` filepath) ||
|
|
||||||
((pathSeparator' `BS.append` pathDoubleDot `BS.append` pathSeparator')
|
|
||||||
`BS.isInfixOf` filepath) ||
|
|
||||||
((pathDoubleDot `BS.append` pathSeparator') `BS.isPrefixOf` filepath)
|
|
||||||
|
|
||||||
|
|
||||||
-- | Is the given filename a valid filename?
|
|
||||||
--
|
|
||||||
-- >>> isFileName "lal"
|
|
||||||
-- True
|
|
||||||
-- >>> isFileName "."
|
|
||||||
-- True
|
|
||||||
-- >>> isFileName ".."
|
|
||||||
-- True
|
|
||||||
-- >>> isFileName ""
|
|
||||||
-- False
|
|
||||||
-- >>> isFileName "\0"
|
|
||||||
-- False
|
|
||||||
-- >>> isFileName "/random_ path:*"
|
|
||||||
-- False
|
|
||||||
isFileName :: ByteString -> Bool
|
|
||||||
isFileName filepath =
|
|
||||||
not (pathSeparator' `BS.isInfixOf` filepath) &&
|
|
||||||
not (BS.null filepath) &&
|
|
||||||
not (nullByte `BS.elem` filepath)
|
|
||||||
|
|
||||||
31
unix/LICENSE
Normal file
31
unix/LICENSE
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
The Glasgow Haskell Compiler License
|
||||||
|
|
||||||
|
Copyright 2004, The University Court of the University of Glasgow.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
- Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
- Neither name of the University nor the names of its contributors may be
|
||||||
|
used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
|
||||||
|
GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGE.
|
||||||
15
unix/README.md
Normal file
15
unix/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
The `unix` Package [](https://hackage.haskell.org/package/unix) [](https://travis-ci.org/haskell/unix)
|
||||||
|
==================
|
||||||
|
|
||||||
|
See [`unix` on Hackage](http://hackage.haskell.org/package/unix) for
|
||||||
|
more information.
|
||||||
|
|
||||||
|
Installing from Git
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
To build this package using Cabal directly from Git, you must run
|
||||||
|
`autoreconf -i` before the usual Cabal build steps (`cabal
|
||||||
|
{configure,build,install}`). The program `autoreconf` is part of
|
||||||
|
[GNU autoconf](http://www.gnu.org/software/autoconf/). There is no
|
||||||
|
need to run the `configure` script: `cabal configure` will do this for
|
||||||
|
you.
|
||||||
6
unix/Setup.hs
Normal file
6
unix/Setup.hs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module Main (main) where
|
||||||
|
|
||||||
|
import Distribution.Simple
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = defaultMainWithHooks autoconfUserHooks
|
||||||
189
unix/System/Posix.hs
Normal file
189
unix/System/Posix.hs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- <http://pubs.opengroup.org/onlinepubs/9699919799/ POSIX.1-2008> support
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix (
|
||||||
|
module System.Posix.Types,
|
||||||
|
module System.Posix.Signals,
|
||||||
|
module System.Posix.Directory,
|
||||||
|
module System.Posix.Files,
|
||||||
|
module System.Posix.Unistd,
|
||||||
|
module System.Posix.IO,
|
||||||
|
module System.Posix.Env,
|
||||||
|
module System.Posix.Process,
|
||||||
|
module System.Posix.Temp,
|
||||||
|
module System.Posix.Terminal,
|
||||||
|
module System.Posix.Time,
|
||||||
|
module System.Posix.User,
|
||||||
|
module System.Posix.Resource,
|
||||||
|
module System.Posix.Semaphore,
|
||||||
|
module System.Posix.SharedMem,
|
||||||
|
module System.Posix.DynamicLinker,
|
||||||
|
-- XXX 'Module' type clashes with GHC
|
||||||
|
-- module System.Posix.DynamicLinker.Module
|
||||||
|
) where
|
||||||
|
|
||||||
|
import System.Posix.Types
|
||||||
|
import System.Posix.Signals
|
||||||
|
import System.Posix.Directory
|
||||||
|
import System.Posix.Files
|
||||||
|
import System.Posix.Unistd
|
||||||
|
import System.Posix.Process
|
||||||
|
import System.Posix.IO
|
||||||
|
import System.Posix.Env
|
||||||
|
import System.Posix.Temp
|
||||||
|
import System.Posix.Terminal
|
||||||
|
import System.Posix.Time
|
||||||
|
import System.Posix.User
|
||||||
|
import System.Posix.Resource
|
||||||
|
import System.Posix.Semaphore
|
||||||
|
import System.Posix.SharedMem
|
||||||
|
-- XXX: bad planning, we have two constructors called "Default"
|
||||||
|
import System.Posix.DynamicLinker hiding (Default)
|
||||||
|
--import System.Posix.DynamicLinker.Module
|
||||||
|
|
||||||
|
{- TODO
|
||||||
|
|
||||||
|
Here we detail our support for the IEEE Std 1003.1-2001 standard. For
|
||||||
|
each header file defined by the standard, we categorise its
|
||||||
|
functionality as
|
||||||
|
|
||||||
|
- "supported"
|
||||||
|
|
||||||
|
Full equivalent functionality is provided by the specified Haskell
|
||||||
|
module.
|
||||||
|
|
||||||
|
- "unsupported" (functionality not provided by a Haskell module)
|
||||||
|
|
||||||
|
The functionality is not currently provided.
|
||||||
|
|
||||||
|
- "to be supported"
|
||||||
|
|
||||||
|
Currently unsupported, but support is planned for the future.
|
||||||
|
|
||||||
|
Exceptions are listed where appropriate.
|
||||||
|
|
||||||
|
Interfaces supported
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
unix package:
|
||||||
|
|
||||||
|
dirent.h System.Posix.Directory
|
||||||
|
dlfcn.h System.Posix.DynamicLinker
|
||||||
|
errno.h Foreign.C.Error
|
||||||
|
fcntl.h System.Posix.IO
|
||||||
|
signal.h System.Posix.Signals
|
||||||
|
sys/stat.h System.Posix.Files
|
||||||
|
sys/times.h System.Posix.Process
|
||||||
|
sys/types.h System.Posix.Types (with exceptions...)
|
||||||
|
sys/utsname.h System.Posix.Unistd
|
||||||
|
sys/wait.h System.Posix.Process
|
||||||
|
termios.h System.Posix.Terminal (check exceptions)
|
||||||
|
unistd.h System.Posix.*
|
||||||
|
utime.h System.Posix.Files
|
||||||
|
pwd.h System.Posix.User
|
||||||
|
grp.h System.Posix.User
|
||||||
|
stdlib.h: System.Posix.Env (getenv()/setenv()/unsetenv())
|
||||||
|
System.Posix.Temp (mkstemp())
|
||||||
|
sys/resource.h: System.Posix.Resource (get/setrlimit() only)
|
||||||
|
|
||||||
|
regex-posix package:
|
||||||
|
|
||||||
|
regex.h Text.Regex.Posix
|
||||||
|
|
||||||
|
network package:
|
||||||
|
|
||||||
|
arpa/inet.h
|
||||||
|
net/if.h
|
||||||
|
netinet/in.h
|
||||||
|
netinet/tcp.h
|
||||||
|
sys/socket.h
|
||||||
|
sys/un.h
|
||||||
|
|
||||||
|
To be supported
|
||||||
|
---------------
|
||||||
|
|
||||||
|
limits.h (pathconf()/fpathconf() already done)
|
||||||
|
poll.h
|
||||||
|
sys/resource.h (getrusage(): use instead of times() for getProcessTimes?)
|
||||||
|
sys/select.h
|
||||||
|
sys/statvfs.h (?)
|
||||||
|
sys/time.h (but maybe not the itimer?)
|
||||||
|
time.h (System.Posix.Time)
|
||||||
|
stdio.h (popen only: System.Posix.IO)
|
||||||
|
sys/mman.h
|
||||||
|
|
||||||
|
Unsupported interfaces
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
aio.h
|
||||||
|
assert.h
|
||||||
|
complex.h
|
||||||
|
cpio.h
|
||||||
|
ctype.h
|
||||||
|
fenv.h
|
||||||
|
float.h
|
||||||
|
fmtmsg.h
|
||||||
|
fnmatch.h
|
||||||
|
ftw.h
|
||||||
|
glob.h
|
||||||
|
iconv.h
|
||||||
|
inttypes.h
|
||||||
|
iso646.h
|
||||||
|
langinfo.h
|
||||||
|
libgen.h
|
||||||
|
locale.h (see System.Locale)
|
||||||
|
math.h
|
||||||
|
monetary.h
|
||||||
|
mqueue.h
|
||||||
|
ndbm.h
|
||||||
|
netdb.h
|
||||||
|
nl_types.h
|
||||||
|
pthread.h
|
||||||
|
sched.h
|
||||||
|
search.h
|
||||||
|
semaphore.h
|
||||||
|
setjmp.h
|
||||||
|
spawn.h
|
||||||
|
stdarg.h
|
||||||
|
stdbool.h
|
||||||
|
stddef.h
|
||||||
|
stdint.h
|
||||||
|
stdio.h except: popen()
|
||||||
|
stdlib.h except: exit(): System.Posix.Process
|
||||||
|
free()/malloc(): Foreign.Marshal.Alloc
|
||||||
|
getenv()/setenv(): ?? System.Environment
|
||||||
|
rand() etc.: System.Random
|
||||||
|
string.h
|
||||||
|
strings.h
|
||||||
|
stropts.h
|
||||||
|
sys/ipc.h
|
||||||
|
sys/msg.h
|
||||||
|
sys/sem.h
|
||||||
|
sys/shm.h
|
||||||
|
sys/timeb.h
|
||||||
|
sys/uio.h
|
||||||
|
syslog.h
|
||||||
|
tar.h
|
||||||
|
tgmath.h
|
||||||
|
trace.h
|
||||||
|
ucontext.h
|
||||||
|
ulimit.h
|
||||||
|
utmpx.h
|
||||||
|
wchar.h
|
||||||
|
wctype.h
|
||||||
|
wordexp.h
|
||||||
|
|
||||||
|
-}
|
||||||
69
unix/System/Posix/ByteString.hs
Normal file
69
unix/System/Posix/ByteString.hs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.ByteString
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- <http://pubs.opengroup.org/onlinepubs/9699919799/ POSIX.1-2008>
|
||||||
|
-- support with 'ByteString' file paths and environment strings.
|
||||||
|
--
|
||||||
|
-- This module exports exactly the same API as "System.Posix", except
|
||||||
|
-- that all file paths and environment strings are represented by
|
||||||
|
-- 'ByteString' instead of 'String'. The "System.Posix" API
|
||||||
|
-- implicitly translates all file paths and environment strings using
|
||||||
|
-- the locale encoding, whereas this version of the API does no
|
||||||
|
-- encoding or decoding and works directly in terms of raw bytes.
|
||||||
|
--
|
||||||
|
-- Note that if you do need to interpret file paths or environment
|
||||||
|
-- strings as text, then some Unicode encoding or decoding should be
|
||||||
|
-- applied first.
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.ByteString (
|
||||||
|
System.Posix.ByteString.FilePath.RawFilePath,
|
||||||
|
module System.Posix.Types,
|
||||||
|
module System.Posix.Signals,
|
||||||
|
module System.Posix.Directory.ByteString,
|
||||||
|
module System.Posix.Files.ByteString,
|
||||||
|
module System.Posix.Unistd,
|
||||||
|
module System.Posix.IO.ByteString,
|
||||||
|
module System.Posix.Env.ByteString,
|
||||||
|
module System.Posix.Process.ByteString,
|
||||||
|
module System.Posix.Temp.ByteString,
|
||||||
|
module System.Posix.Terminal.ByteString,
|
||||||
|
module System.Posix.Time,
|
||||||
|
module System.Posix.User,
|
||||||
|
module System.Posix.Resource,
|
||||||
|
module System.Posix.Semaphore,
|
||||||
|
module System.Posix.SharedMem,
|
||||||
|
module System.Posix.DynamicLinker.ByteString,
|
||||||
|
-- XXX 'Module' type clashes with GHC
|
||||||
|
-- module System.Posix.DynamicLinker.Module.ByteString
|
||||||
|
) where
|
||||||
|
|
||||||
|
import System.Posix.ByteString.FilePath
|
||||||
|
import System.Posix.Types
|
||||||
|
import System.Posix.Signals
|
||||||
|
import System.Posix.Directory.ByteString
|
||||||
|
import System.Posix.Files.ByteString
|
||||||
|
import System.Posix.Unistd
|
||||||
|
import System.Posix.Process.ByteString
|
||||||
|
import System.Posix.IO.ByteString
|
||||||
|
import System.Posix.Env.ByteString
|
||||||
|
import System.Posix.Temp.ByteString
|
||||||
|
import System.Posix.Terminal.ByteString
|
||||||
|
import System.Posix.Time
|
||||||
|
import System.Posix.User
|
||||||
|
import System.Posix.Resource
|
||||||
|
import System.Posix.Semaphore
|
||||||
|
import System.Posix.SharedMem
|
||||||
|
-- XXX: bad planning, we have two constructors called "Default"
|
||||||
|
import System.Posix.DynamicLinker.ByteString hiding (Default)
|
||||||
|
--import System.Posix.DynamicLinker.Module.ByteString
|
||||||
127
unix/System/Posix/ByteString/FilePath.hsc
Normal file
127
unix/System/Posix/ByteString/FilePath.hsc
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.ByteString.FilePath
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- Internal stuff: support for ByteString FilePaths
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.ByteString.FilePath (
|
||||||
|
RawFilePath, withFilePath, peekFilePath, peekFilePathLen,
|
||||||
|
throwErrnoPathIfMinus1Retry,
|
||||||
|
throwErrnoPathIfMinus1Retry_,
|
||||||
|
throwErrnoPathIfNullRetry,
|
||||||
|
throwErrnoPathIfRetry,
|
||||||
|
throwErrnoPath,
|
||||||
|
throwErrnoPathIf,
|
||||||
|
throwErrnoPathIf_,
|
||||||
|
throwErrnoPathIfNull,
|
||||||
|
throwErrnoPathIfMinus1,
|
||||||
|
throwErrnoPathIfMinus1_
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Foreign hiding ( void )
|
||||||
|
import Foreign.C hiding (
|
||||||
|
throwErrnoPath,
|
||||||
|
throwErrnoPathIf,
|
||||||
|
throwErrnoPathIf_,
|
||||||
|
throwErrnoPathIfNull,
|
||||||
|
throwErrnoPathIfMinus1,
|
||||||
|
throwErrnoPathIfMinus1_ )
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Data.ByteString
|
||||||
|
import Data.ByteString.Char8 as BC
|
||||||
|
import Prelude hiding (FilePath)
|
||||||
|
|
||||||
|
-- | A literal POSIX file path
|
||||||
|
type RawFilePath = ByteString
|
||||||
|
|
||||||
|
withFilePath :: RawFilePath -> (CString -> IO a) -> IO a
|
||||||
|
withFilePath = useAsCString
|
||||||
|
|
||||||
|
peekFilePath :: CString -> IO RawFilePath
|
||||||
|
peekFilePath = packCString
|
||||||
|
|
||||||
|
peekFilePathLen :: CStringLen -> IO RawFilePath
|
||||||
|
peekFilePathLen = packCStringLen
|
||||||
|
|
||||||
|
|
||||||
|
throwErrnoPathIfMinus1Retry :: (Eq a, Num a)
|
||||||
|
=> String -> RawFilePath -> IO a -> IO a
|
||||||
|
throwErrnoPathIfMinus1Retry loc path f = do
|
||||||
|
throwErrnoPathIfRetry (== -1) loc path f
|
||||||
|
|
||||||
|
throwErrnoPathIfMinus1Retry_ :: (Eq a, Num a)
|
||||||
|
=> String -> RawFilePath -> IO a -> IO ()
|
||||||
|
throwErrnoPathIfMinus1Retry_ loc path f =
|
||||||
|
void $ throwErrnoPathIfRetry (== -1) loc path f
|
||||||
|
|
||||||
|
throwErrnoPathIfNullRetry :: String -> RawFilePath -> IO (Ptr a) -> IO (Ptr a)
|
||||||
|
throwErrnoPathIfNullRetry loc path f =
|
||||||
|
throwErrnoPathIfRetry (== nullPtr) loc path f
|
||||||
|
|
||||||
|
throwErrnoPathIfRetry :: (a -> Bool) -> String -> RawFilePath -> IO a -> IO a
|
||||||
|
throwErrnoPathIfRetry pr loc rpath f =
|
||||||
|
do
|
||||||
|
res <- f
|
||||||
|
if pr res
|
||||||
|
then do
|
||||||
|
err <- getErrno
|
||||||
|
if err == eINTR
|
||||||
|
then throwErrnoPathIfRetry pr loc rpath f
|
||||||
|
else throwErrnoPath loc rpath
|
||||||
|
else return res
|
||||||
|
|
||||||
|
-- | as 'throwErrno', but exceptions include the given path when appropriate.
|
||||||
|
--
|
||||||
|
throwErrnoPath :: String -> RawFilePath -> IO a
|
||||||
|
throwErrnoPath loc path =
|
||||||
|
do
|
||||||
|
errno <- getErrno
|
||||||
|
ioError (errnoToIOError loc errno Nothing (Just (BC.unpack path)))
|
||||||
|
|
||||||
|
-- | as 'throwErrnoIf', but exceptions include the given path when
|
||||||
|
-- appropriate.
|
||||||
|
--
|
||||||
|
throwErrnoPathIf :: (a -> Bool) -> String -> RawFilePath -> IO a -> IO a
|
||||||
|
throwErrnoPathIf cond loc path f =
|
||||||
|
do
|
||||||
|
res <- f
|
||||||
|
if cond res then throwErrnoPath loc path else return res
|
||||||
|
|
||||||
|
-- | as 'throwErrnoIf_', but exceptions include the given path when
|
||||||
|
-- appropriate.
|
||||||
|
--
|
||||||
|
throwErrnoPathIf_ :: (a -> Bool) -> String -> RawFilePath -> IO a -> IO ()
|
||||||
|
throwErrnoPathIf_ cond loc path f = void $ throwErrnoPathIf cond loc path f
|
||||||
|
|
||||||
|
-- | as 'throwErrnoIfNull', but exceptions include the given path when
|
||||||
|
-- appropriate.
|
||||||
|
--
|
||||||
|
throwErrnoPathIfNull :: String -> RawFilePath -> IO (Ptr a) -> IO (Ptr a)
|
||||||
|
throwErrnoPathIfNull = throwErrnoPathIf (== nullPtr)
|
||||||
|
|
||||||
|
-- | as 'throwErrnoIfMinus1', but exceptions include the given path when
|
||||||
|
-- appropriate.
|
||||||
|
--
|
||||||
|
throwErrnoPathIfMinus1 :: (Eq a, Num a) => String -> RawFilePath -> IO a -> IO a
|
||||||
|
throwErrnoPathIfMinus1 = throwErrnoPathIf (== -1)
|
||||||
|
|
||||||
|
-- | as 'throwErrnoIfMinus1_', but exceptions include the given path when
|
||||||
|
-- appropriate.
|
||||||
|
--
|
||||||
|
throwErrnoPathIfMinus1_ :: (Eq a, Num a) => String -> RawFilePath -> IO a -> IO ()
|
||||||
|
throwErrnoPathIfMinus1_ = throwErrnoPathIf_ (== -1)
|
||||||
164
unix/System/Posix/Directory.hsc
Normal file
164
unix/System/Posix/Directory.hsc
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
{-# LANGUAGE CApiFFI #-}
|
||||||
|
{-# LANGUAGE NondecreasingIndentation #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.Directory
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- String-based POSIX directory support
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
-- hack copied from System.Posix.Files
|
||||||
|
#if !defined(PATH_MAX)
|
||||||
|
# define PATH_MAX 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
|
module System.Posix.Directory (
|
||||||
|
-- * Creating and removing directories
|
||||||
|
createDirectory, removeDirectory,
|
||||||
|
|
||||||
|
-- * Reading directories
|
||||||
|
DirStream,
|
||||||
|
openDirStream,
|
||||||
|
readDirStream,
|
||||||
|
rewindDirStream,
|
||||||
|
closeDirStream,
|
||||||
|
DirStreamOffset,
|
||||||
|
#ifdef HAVE_TELLDIR
|
||||||
|
tellDirStream,
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SEEKDIR
|
||||||
|
seekDirStream,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- * The working dirctory
|
||||||
|
getWorkingDirectory,
|
||||||
|
changeWorkingDirectory,
|
||||||
|
changeWorkingDirectoryFd,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import System.IO.Error
|
||||||
|
import System.Posix.Error
|
||||||
|
import System.Posix.Types
|
||||||
|
import Foreign
|
||||||
|
import Foreign.C
|
||||||
|
|
||||||
|
import System.Posix.Directory.Common
|
||||||
|
import System.Posix.Internals (withFilePath, peekFilePath)
|
||||||
|
|
||||||
|
-- | @createDirectory dir mode@ calls @mkdir@ to
|
||||||
|
-- create a new directory, @dir@, with permissions based on
|
||||||
|
-- @mode@.
|
||||||
|
createDirectory :: FilePath -> FileMode -> IO ()
|
||||||
|
createDirectory name mode =
|
||||||
|
withFilePath name $ \s ->
|
||||||
|
throwErrnoPathIfMinus1Retry_ "createDirectory" name (c_mkdir s mode)
|
||||||
|
-- POSIX doesn't allow mkdir() to return EINTR, but it does on
|
||||||
|
-- OS X (#5184), so we need the Retry variant here.
|
||||||
|
|
||||||
|
foreign import ccall unsafe "mkdir"
|
||||||
|
c_mkdir :: CString -> CMode -> IO CInt
|
||||||
|
|
||||||
|
-- | @openDirStream dir@ calls @opendir@ to obtain a
|
||||||
|
-- directory stream for @dir@.
|
||||||
|
openDirStream :: FilePath -> IO DirStream
|
||||||
|
openDirStream name =
|
||||||
|
withFilePath name $ \s -> do
|
||||||
|
dirp <- throwErrnoPathIfNullRetry "openDirStream" name $ c_opendir s
|
||||||
|
return (DirStream dirp)
|
||||||
|
|
||||||
|
foreign import capi unsafe "HsUnix.h opendir"
|
||||||
|
c_opendir :: CString -> IO (Ptr CDir)
|
||||||
|
|
||||||
|
-- | @readDirStream dp@ calls @readdir@ to obtain the
|
||||||
|
-- next directory entry (@struct dirent@) for the open directory
|
||||||
|
-- stream @dp@, and returns the @d_name@ member of that
|
||||||
|
-- structure.
|
||||||
|
readDirStream :: DirStream -> IO FilePath
|
||||||
|
readDirStream (DirStream 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 []
|
||||||
|
else do
|
||||||
|
entry <- (d_name dEnt >>= peekFilePath)
|
||||||
|
c_freeDirEnt dEnt
|
||||||
|
return entry
|
||||||
|
else do errno <- getErrno
|
||||||
|
if (errno == eINTR) then loop ptr_dEnt else do
|
||||||
|
let (Errno eo) = errno
|
||||||
|
if (eo == 0)
|
||||||
|
then return []
|
||||||
|
else throwErrno "readDirStream"
|
||||||
|
|
||||||
|
-- traversing directories
|
||||||
|
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"
|
||||||
|
d_name :: Ptr CDirent -> IO CString
|
||||||
|
|
||||||
|
|
||||||
|
-- | @getWorkingDirectory@ calls @getcwd@ to obtain the name
|
||||||
|
-- of the current working directory.
|
||||||
|
getWorkingDirectory :: IO FilePath
|
||||||
|
getWorkingDirectory = go (#const PATH_MAX)
|
||||||
|
where
|
||||||
|
go bytes = do
|
||||||
|
r <- allocaBytes bytes $ \buf -> do
|
||||||
|
buf' <- c_getcwd buf (fromIntegral bytes)
|
||||||
|
if buf' /= nullPtr
|
||||||
|
then do s <- peekFilePath buf
|
||||||
|
return (Just s)
|
||||||
|
else do errno <- getErrno
|
||||||
|
if errno == eRANGE
|
||||||
|
-- we use Nothing to indicate that we should
|
||||||
|
-- try again with a bigger buffer
|
||||||
|
then return Nothing
|
||||||
|
else throwErrno "getWorkingDirectory"
|
||||||
|
maybe (go (2 * bytes)) return r
|
||||||
|
|
||||||
|
foreign import ccall unsafe "getcwd"
|
||||||
|
c_getcwd :: Ptr CChar -> CSize -> IO (Ptr CChar)
|
||||||
|
|
||||||
|
-- | @changeWorkingDirectory dir@ calls @chdir@ to change
|
||||||
|
-- the current working directory to @dir@.
|
||||||
|
changeWorkingDirectory :: FilePath -> IO ()
|
||||||
|
changeWorkingDirectory path =
|
||||||
|
modifyIOError (`ioeSetFileName` path) $
|
||||||
|
withFilePath path $ \s ->
|
||||||
|
throwErrnoIfMinus1Retry_ "changeWorkingDirectory" (c_chdir s)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "chdir"
|
||||||
|
c_chdir :: CString -> IO CInt
|
||||||
|
|
||||||
|
removeDirectory :: FilePath -> IO ()
|
||||||
|
removeDirectory path =
|
||||||
|
modifyIOError (`ioeSetFileName` path) $
|
||||||
|
withFilePath path $ \s ->
|
||||||
|
throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "rmdir"
|
||||||
|
c_rmdir :: CString -> IO CInt
|
||||||
165
unix/System/Posix/Directory/ByteString.hsc
Normal file
165
unix/System/Posix/Directory/ByteString.hsc
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
{-# LANGUAGE CApiFFI #-}
|
||||||
|
{-# LANGUAGE NondecreasingIndentation #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.Directory.ByteString
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- String-based POSIX directory support
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
-- hack copied from System.Posix.Files
|
||||||
|
#if !defined(PATH_MAX)
|
||||||
|
# define PATH_MAX 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
|
module System.Posix.Directory.ByteString (
|
||||||
|
-- * Creating and removing directories
|
||||||
|
createDirectory, removeDirectory,
|
||||||
|
|
||||||
|
-- * Reading directories
|
||||||
|
DirStream,
|
||||||
|
openDirStream,
|
||||||
|
readDirStream,
|
||||||
|
rewindDirStream,
|
||||||
|
closeDirStream,
|
||||||
|
DirStreamOffset,
|
||||||
|
#ifdef HAVE_TELLDIR
|
||||||
|
tellDirStream,
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SEEKDIR
|
||||||
|
seekDirStream,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- * The working directory
|
||||||
|
getWorkingDirectory,
|
||||||
|
changeWorkingDirectory,
|
||||||
|
changeWorkingDirectoryFd,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import System.IO.Error
|
||||||
|
import System.Posix.Types
|
||||||
|
import Foreign
|
||||||
|
import Foreign.C
|
||||||
|
|
||||||
|
import Data.ByteString.Char8 as BC
|
||||||
|
|
||||||
|
import System.Posix.Directory.Common
|
||||||
|
import System.Posix.ByteString.FilePath
|
||||||
|
|
||||||
|
-- | @createDirectory dir mode@ calls @mkdir@ to
|
||||||
|
-- create a new directory, @dir@, with permissions based on
|
||||||
|
-- @mode@.
|
||||||
|
createDirectory :: RawFilePath -> FileMode -> IO ()
|
||||||
|
createDirectory name mode =
|
||||||
|
withFilePath name $ \s ->
|
||||||
|
throwErrnoPathIfMinus1Retry_ "createDirectory" name (c_mkdir s mode)
|
||||||
|
-- POSIX doesn't allow mkdir() to return EINTR, but it does on
|
||||||
|
-- OS X (#5184), so we need the Retry variant here.
|
||||||
|
|
||||||
|
foreign import ccall unsafe "mkdir"
|
||||||
|
c_mkdir :: CString -> CMode -> IO CInt
|
||||||
|
|
||||||
|
-- | @openDirStream dir@ calls @opendir@ to obtain a
|
||||||
|
-- directory stream for @dir@.
|
||||||
|
openDirStream :: RawFilePath -> IO DirStream
|
||||||
|
openDirStream name =
|
||||||
|
withFilePath name $ \s -> do
|
||||||
|
dirp <- throwErrnoPathIfNullRetry "openDirStream" name $ c_opendir s
|
||||||
|
return (DirStream dirp)
|
||||||
|
|
||||||
|
foreign import capi unsafe "HsUnix.h opendir"
|
||||||
|
c_opendir :: CString -> IO (Ptr CDir)
|
||||||
|
|
||||||
|
-- | @readDirStream dp@ calls @readdir@ to obtain the
|
||||||
|
-- next directory entry (@struct dirent@) for the open directory
|
||||||
|
-- stream @dp@, and returns the @d_name@ member of that
|
||||||
|
-- structure.
|
||||||
|
readDirStream :: DirStream -> IO RawFilePath
|
||||||
|
readDirStream (DirStream 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 BC.empty
|
||||||
|
else do
|
||||||
|
entry <- (d_name dEnt >>= peekFilePath)
|
||||||
|
c_freeDirEnt dEnt
|
||||||
|
return entry
|
||||||
|
else do errno <- getErrno
|
||||||
|
if (errno == eINTR) then loop ptr_dEnt else do
|
||||||
|
let (Errno eo) = errno
|
||||||
|
if (eo == 0)
|
||||||
|
then return BC.empty
|
||||||
|
else throwErrno "readDirStream"
|
||||||
|
|
||||||
|
-- traversing directories
|
||||||
|
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"
|
||||||
|
d_name :: Ptr CDirent -> IO CString
|
||||||
|
|
||||||
|
|
||||||
|
-- | @getWorkingDirectory@ calls @getcwd@ to obtain the name
|
||||||
|
-- of the current working directory.
|
||||||
|
getWorkingDirectory :: IO RawFilePath
|
||||||
|
getWorkingDirectory = go (#const PATH_MAX)
|
||||||
|
where
|
||||||
|
go bytes = do
|
||||||
|
r <- allocaBytes bytes $ \buf -> do
|
||||||
|
buf' <- c_getcwd buf (fromIntegral bytes)
|
||||||
|
if buf' /= nullPtr
|
||||||
|
then do s <- peekFilePath buf
|
||||||
|
return (Just s)
|
||||||
|
else do errno <- getErrno
|
||||||
|
if errno == eRANGE
|
||||||
|
-- we use Nothing to indicate that we should
|
||||||
|
-- try again with a bigger buffer
|
||||||
|
then return Nothing
|
||||||
|
else throwErrno "getWorkingDirectory"
|
||||||
|
maybe (go (2 * bytes)) return r
|
||||||
|
|
||||||
|
foreign import ccall unsafe "getcwd"
|
||||||
|
c_getcwd :: Ptr CChar -> CSize -> IO (Ptr CChar)
|
||||||
|
|
||||||
|
-- | @changeWorkingDirectory dir@ calls @chdir@ to change
|
||||||
|
-- the current working directory to @dir@.
|
||||||
|
changeWorkingDirectory :: RawFilePath -> IO ()
|
||||||
|
changeWorkingDirectory path =
|
||||||
|
modifyIOError (`ioeSetFileName` (BC.unpack path)) $
|
||||||
|
withFilePath path $ \s ->
|
||||||
|
throwErrnoIfMinus1Retry_ "changeWorkingDirectory" (c_chdir s)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "chdir"
|
||||||
|
c_chdir :: CString -> IO CInt
|
||||||
|
|
||||||
|
removeDirectory :: RawFilePath -> IO ()
|
||||||
|
removeDirectory path =
|
||||||
|
modifyIOError (`ioeSetFileName` BC.unpack path) $
|
||||||
|
withFilePath path $ \s ->
|
||||||
|
throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "rmdir"
|
||||||
|
c_rmdir :: CString -> IO CInt
|
||||||
88
unix/System/Posix/Directory/Common.hsc
Normal file
88
unix/System/Posix/Directory/Common.hsc
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.Directory.Common
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- POSIX directory support
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
module System.Posix.Directory.Common (
|
||||||
|
DirStream(..), CDir, CDirent, DirStreamOffset(..),
|
||||||
|
rewindDirStream,
|
||||||
|
closeDirStream,
|
||||||
|
#ifdef HAVE_SEEKDIR
|
||||||
|
seekDirStream,
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_TELLDIR
|
||||||
|
tellDirStream,
|
||||||
|
#endif
|
||||||
|
changeWorkingDirectoryFd,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import System.Posix.Types
|
||||||
|
import Foreign
|
||||||
|
import Foreign.C
|
||||||
|
|
||||||
|
newtype DirStream = DirStream (Ptr CDir)
|
||||||
|
|
||||||
|
data {-# CTYPE "DIR" #-} CDir
|
||||||
|
data {-# CTYPE "struct dirent" #-} CDirent
|
||||||
|
|
||||||
|
-- | @rewindDirStream dp@ calls @rewinddir@ to reposition
|
||||||
|
-- the directory stream @dp@ at the beginning of the directory.
|
||||||
|
rewindDirStream :: DirStream -> IO ()
|
||||||
|
rewindDirStream (DirStream dirp) = c_rewinddir dirp
|
||||||
|
|
||||||
|
foreign import ccall unsafe "rewinddir"
|
||||||
|
c_rewinddir :: Ptr CDir -> IO ()
|
||||||
|
|
||||||
|
-- | @closeDirStream dp@ calls @closedir@ to close
|
||||||
|
-- the directory stream @dp@.
|
||||||
|
closeDirStream :: DirStream -> IO ()
|
||||||
|
closeDirStream (DirStream dirp) = do
|
||||||
|
throwErrnoIfMinus1Retry_ "closeDirStream" (c_closedir dirp)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "closedir"
|
||||||
|
c_closedir :: Ptr CDir -> IO CInt
|
||||||
|
|
||||||
|
newtype DirStreamOffset = DirStreamOffset COff
|
||||||
|
|
||||||
|
#ifdef HAVE_SEEKDIR
|
||||||
|
seekDirStream :: DirStream -> DirStreamOffset -> IO ()
|
||||||
|
seekDirStream (DirStream dirp) (DirStreamOffset off) =
|
||||||
|
c_seekdir dirp (fromIntegral off) -- TODO: check for CLong/COff overflow
|
||||||
|
|
||||||
|
foreign import ccall unsafe "seekdir"
|
||||||
|
c_seekdir :: Ptr CDir -> CLong -> IO ()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_TELLDIR
|
||||||
|
tellDirStream :: DirStream -> IO DirStreamOffset
|
||||||
|
tellDirStream (DirStream dirp) = do
|
||||||
|
off <- c_telldir dirp
|
||||||
|
return (DirStreamOffset (fromIntegral off)) -- TODO: check for overflow
|
||||||
|
|
||||||
|
foreign import ccall unsafe "telldir"
|
||||||
|
c_telldir :: Ptr CDir -> IO CLong
|
||||||
|
#endif
|
||||||
|
|
||||||
|
changeWorkingDirectoryFd :: Fd -> IO ()
|
||||||
|
changeWorkingDirectoryFd (Fd fd) =
|
||||||
|
throwErrnoIfMinus1Retry_ "changeWorkingDirectoryFd" (c_fchdir fd)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "fchdir"
|
||||||
|
c_fchdir :: CInt -> IO CInt
|
||||||
72
unix/System/Posix/DynamicLinker.hsc
Normal file
72
unix/System/Posix/DynamicLinker.hsc
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.DynamicLinker
|
||||||
|
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : vs@foldr.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- Dynamic linker support through dlopen()
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker (
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.Prim,
|
||||||
|
dlopen,
|
||||||
|
dlsym,
|
||||||
|
dlerror,
|
||||||
|
dlclose,
|
||||||
|
withDL, withDL_,
|
||||||
|
undl,
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Usage:
|
||||||
|
-- ******
|
||||||
|
--
|
||||||
|
-- Let's assume you want to open a local shared library \'foo\' (.\/libfoo.so)
|
||||||
|
-- offering a function
|
||||||
|
-- @char \* mogrify (char\*,int)@
|
||||||
|
-- and invoke @str = mogrify("test",1)@:
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- type Fun = CString -> Int -> IO CString
|
||||||
|
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
|
||||||
|
--
|
||||||
|
-- withDL "libfoo.so" [RTLD_NOW] \$ \\ mod -> do
|
||||||
|
-- funptr <- dlsym mod "mogrify"
|
||||||
|
-- let fun = fun__ funptr
|
||||||
|
-- withCString "test" \$ \\ str -> do
|
||||||
|
-- strptr <- fun str 1
|
||||||
|
-- strstr <- peekCString strptr
|
||||||
|
-- ...
|
||||||
|
--
|
||||||
|
|
||||||
|
where
|
||||||
|
|
||||||
|
import System.Posix.DynamicLinker.Common
|
||||||
|
import System.Posix.DynamicLinker.Prim
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import Control.Exception ( bracket )
|
||||||
|
import Control.Monad ( liftM )
|
||||||
|
import Foreign
|
||||||
|
import System.Posix.Internals ( withFilePath )
|
||||||
|
|
||||||
|
dlopen :: FilePath -> [RTLDFlags] -> IO DL
|
||||||
|
dlopen path flags = do
|
||||||
|
withFilePath path $ \ p -> do
|
||||||
|
liftM DLHandle $ throwDLErrorIf "dlopen" (== nullPtr) $ c_dlopen p (packRTLDFlags flags)
|
||||||
|
|
||||||
|
withDL :: FilePath -> [RTLDFlags] -> (DL -> IO a) -> IO a
|
||||||
|
withDL file flags f = bracket (dlopen file flags) (dlclose) f
|
||||||
|
|
||||||
|
withDL_ :: FilePath -> [RTLDFlags] -> (DL -> IO a) -> IO ()
|
||||||
|
withDL_ file flags f = withDL file flags f >> return ()
|
||||||
73
unix/System/Posix/DynamicLinker/ByteString.hsc
Normal file
73
unix/System/Posix/DynamicLinker/ByteString.hsc
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.DynamicLinker.ByteString
|
||||||
|
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : vs@foldr.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- Dynamic linker support through dlopen()
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.ByteString (
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.Prim,
|
||||||
|
dlopen,
|
||||||
|
dlsym,
|
||||||
|
dlerror,
|
||||||
|
dlclose,
|
||||||
|
withDL, withDL_,
|
||||||
|
undl,
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Usage:
|
||||||
|
-- ******
|
||||||
|
--
|
||||||
|
-- Let's assume you want to open a local shared library \'foo\' (.\/libfoo.so)
|
||||||
|
-- offering a function
|
||||||
|
-- @char \* mogrify (char\*,int)@
|
||||||
|
-- and invoke @str = mogrify("test",1)@:
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- type Fun = CString -> Int -> IO CString
|
||||||
|
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
|
||||||
|
--
|
||||||
|
-- withDL "libfoo.so" [RTLD_NOW] \$ \\ mod -> do
|
||||||
|
-- funptr <- dlsym mod "mogrify"
|
||||||
|
-- let fun = fun__ funptr
|
||||||
|
-- withCString "test" \$ \\ str -> do
|
||||||
|
-- strptr <- fun str 1
|
||||||
|
-- strstr <- peekCString strptr
|
||||||
|
-- ...
|
||||||
|
--
|
||||||
|
|
||||||
|
where
|
||||||
|
|
||||||
|
import System.Posix.DynamicLinker.Common
|
||||||
|
import System.Posix.DynamicLinker.Prim
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import Control.Exception ( bracket )
|
||||||
|
import Control.Monad ( liftM )
|
||||||
|
import Foreign
|
||||||
|
import System.Posix.ByteString.FilePath
|
||||||
|
|
||||||
|
dlopen :: RawFilePath -> [RTLDFlags] -> IO DL
|
||||||
|
dlopen path flags = do
|
||||||
|
withFilePath path $ \ p -> do
|
||||||
|
liftM DLHandle $ throwDLErrorIf "dlopen" (== nullPtr) $ c_dlopen p (packRTLDFlags flags)
|
||||||
|
|
||||||
|
withDL :: RawFilePath -> [RTLDFlags] -> (DL -> IO a) -> IO a
|
||||||
|
withDL file flags f = bracket (dlopen file flags) (dlclose) f
|
||||||
|
|
||||||
|
withDL_ :: RawFilePath -> [RTLDFlags] -> (DL -> IO a) -> IO ()
|
||||||
|
withDL_ file flags f = withDL file flags f >> return ()
|
||||||
92
unix/System/Posix/DynamicLinker/Common.hsc
Normal file
92
unix/System/Posix/DynamicLinker/Common.hsc
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.DynamicLinker.Common
|
||||||
|
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : vs@foldr.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- Dynamic linker support through dlopen()
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.Common (
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.Prim,
|
||||||
|
dlsym,
|
||||||
|
dlerror,
|
||||||
|
dlclose,
|
||||||
|
undl,
|
||||||
|
throwDLErrorIf,
|
||||||
|
Module(..)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Usage:
|
||||||
|
-- ******
|
||||||
|
--
|
||||||
|
-- Let's assume you want to open a local shared library \'foo\' (.\/libfoo.so)
|
||||||
|
-- offering a function
|
||||||
|
-- @char \* mogrify (char\*,int)@
|
||||||
|
-- and invoke @str = mogrify("test",1)@:
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- type Fun = CString -> Int -> IO CString
|
||||||
|
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
|
||||||
|
--
|
||||||
|
-- withDL "libfoo.so" [RTLD_NOW] \$ \\ mod -> do
|
||||||
|
-- funptr <- dlsym mod "mogrify"
|
||||||
|
-- let fun = fun__ funptr
|
||||||
|
-- withCString "test" \$ \\ str -> do
|
||||||
|
-- strptr <- fun str 1
|
||||||
|
-- strstr <- peekCString strptr
|
||||||
|
-- ...
|
||||||
|
--
|
||||||
|
|
||||||
|
where
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import System.Posix.DynamicLinker.Prim
|
||||||
|
import Foreign
|
||||||
|
import Foreign.C
|
||||||
|
|
||||||
|
dlclose :: DL -> IO ()
|
||||||
|
dlclose (DLHandle h) = throwDLErrorIf_ "dlclose" (/= 0) $ c_dlclose h
|
||||||
|
dlclose h = error $ "dlclose: invalid argument" ++ (show h)
|
||||||
|
|
||||||
|
dlerror :: IO String
|
||||||
|
dlerror = c_dlerror >>= peekCString
|
||||||
|
|
||||||
|
-- |'dlsym' returns the address binding of the symbol described in @symbol@,
|
||||||
|
-- as it occurs in the shared object identified by @source@.
|
||||||
|
|
||||||
|
dlsym :: DL -> String -> IO (FunPtr a)
|
||||||
|
dlsym source symbol = do
|
||||||
|
withCAString symbol $ \ s -> do
|
||||||
|
throwDLErrorIf "dlsym" (== nullFunPtr) $ c_dlsym (packDL source) s
|
||||||
|
|
||||||
|
-- |'undl' obtains the raw handle. You mustn't do something like
|
||||||
|
-- @withDL mod flags $ liftM undl >>= \ p -> use p@
|
||||||
|
|
||||||
|
undl :: DL -> Ptr ()
|
||||||
|
undl = packDL
|
||||||
|
|
||||||
|
throwDLErrorIf :: String -> (a -> Bool) -> IO a -> IO a
|
||||||
|
throwDLErrorIf s p f = do
|
||||||
|
r <- f
|
||||||
|
if (p r)
|
||||||
|
then dlerror >>= \ err -> ioError (userError ( s ++ ": " ++ err))
|
||||||
|
else return r
|
||||||
|
|
||||||
|
throwDLErrorIf_ :: String -> (a -> Bool) -> IO a -> IO ()
|
||||||
|
throwDLErrorIf_ s p f = throwDLErrorIf s p f >> return ()
|
||||||
|
|
||||||
|
-- abstract handle for dynamically loaded module (EXPORTED)
|
||||||
|
--
|
||||||
|
newtype Module = Module (Ptr ())
|
||||||
121
unix/System/Posix/DynamicLinker/Module.hsc
Normal file
121
unix/System/Posix/DynamicLinker/Module.hsc
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.DynamicLinker.Module
|
||||||
|
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : vs@foldr.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- DLOpen support, old API
|
||||||
|
-- Derived from GModule.chs by M.Weber & M.Chakravarty which is part of c2hs
|
||||||
|
-- I left the API more or less the same, mostly the flags are different.
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.Module (
|
||||||
|
|
||||||
|
-- Usage:
|
||||||
|
-- ******
|
||||||
|
--
|
||||||
|
-- Let's assume you want to open a local shared library 'foo' (./libfoo.so)
|
||||||
|
-- offering a function
|
||||||
|
-- char * mogrify (char*,int)
|
||||||
|
-- and invoke str = mogrify("test",1):
|
||||||
|
--
|
||||||
|
-- type Fun = CString -> Int -> IO CString
|
||||||
|
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
|
||||||
|
--
|
||||||
|
-- withModule (Just ".") ("libfoo.so") [RTLD_NOW] $ \ mod -> do
|
||||||
|
-- funptr <- moduleSymbol mod "mogrify"
|
||||||
|
-- let fun = fun__ funptr
|
||||||
|
-- withCString "test" $ \ str -> do
|
||||||
|
-- strptr <- fun str 1
|
||||||
|
-- strstr <- peekCString strptr
|
||||||
|
-- ...
|
||||||
|
|
||||||
|
Module
|
||||||
|
, moduleOpen -- :: String -> ModuleFlags -> IO Module
|
||||||
|
, moduleSymbol -- :: Source -> String -> IO (FunPtr a)
|
||||||
|
, moduleClose -- :: Module -> IO Bool
|
||||||
|
, moduleError -- :: IO String
|
||||||
|
, withModule -- :: Maybe String
|
||||||
|
-- -> String
|
||||||
|
-- -> [ModuleFlags ]
|
||||||
|
-- -> (Module -> IO a)
|
||||||
|
-- -> IO a
|
||||||
|
, withModule_ -- :: Maybe String
|
||||||
|
-- -> String
|
||||||
|
-- -> [ModuleFlags]
|
||||||
|
-- -> (Module -> IO a)
|
||||||
|
-- -> IO ()
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import System.Posix.DynamicLinker
|
||||||
|
import System.Posix.DynamicLinker.Common
|
||||||
|
import Foreign.Ptr ( Ptr, nullPtr, FunPtr )
|
||||||
|
import System.Posix.Internals ( withFilePath )
|
||||||
|
|
||||||
|
unModule :: Module -> (Ptr ())
|
||||||
|
unModule (Module adr) = adr
|
||||||
|
|
||||||
|
-- Opens a module (EXPORTED)
|
||||||
|
--
|
||||||
|
|
||||||
|
moduleOpen :: String -> [RTLDFlags] -> IO Module
|
||||||
|
moduleOpen file flags = do
|
||||||
|
modPtr <- withFilePath file $ \ modAddr -> c_dlopen modAddr (packRTLDFlags flags)
|
||||||
|
if (modPtr == nullPtr)
|
||||||
|
then moduleError >>= \ err -> ioError (userError ("dlopen: " ++ err))
|
||||||
|
else return $ Module modPtr
|
||||||
|
|
||||||
|
-- Gets a symbol pointer from a module (EXPORTED)
|
||||||
|
--
|
||||||
|
moduleSymbol :: Module -> String -> IO (FunPtr a)
|
||||||
|
moduleSymbol file sym = dlsym (DLHandle (unModule file)) sym
|
||||||
|
|
||||||
|
-- Closes a module (EXPORTED)
|
||||||
|
--
|
||||||
|
moduleClose :: Module -> IO ()
|
||||||
|
moduleClose file = dlclose (DLHandle (unModule file))
|
||||||
|
|
||||||
|
-- Gets a string describing the last module error (EXPORTED)
|
||||||
|
--
|
||||||
|
moduleError :: IO String
|
||||||
|
moduleError = dlerror
|
||||||
|
|
||||||
|
|
||||||
|
-- Convenience function, cares for module open- & closing
|
||||||
|
-- additionally returns status of `moduleClose' (EXPORTED)
|
||||||
|
--
|
||||||
|
withModule :: Maybe String
|
||||||
|
-> String
|
||||||
|
-> [RTLDFlags]
|
||||||
|
-> (Module -> IO a)
|
||||||
|
-> IO a
|
||||||
|
withModule mdir file flags p = do
|
||||||
|
let modPath = case mdir of
|
||||||
|
Nothing -> file
|
||||||
|
Just dir -> dir ++ if ((head (reverse dir)) == '/')
|
||||||
|
then file
|
||||||
|
else ('/':file)
|
||||||
|
modu <- moduleOpen modPath flags
|
||||||
|
result <- p modu
|
||||||
|
moduleClose modu
|
||||||
|
return result
|
||||||
|
|
||||||
|
withModule_ :: Maybe String
|
||||||
|
-> String
|
||||||
|
-> [RTLDFlags]
|
||||||
|
-> (Module -> IO a)
|
||||||
|
-> IO ()
|
||||||
|
withModule_ dir file flags p = withModule dir file flags p >>= \ _ -> return ()
|
||||||
79
unix/System/Posix/DynamicLinker/Module/ByteString.hsc
Normal file
79
unix/System/Posix/DynamicLinker/Module/ByteString.hsc
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.DynamicLinker.Module.ByteString
|
||||||
|
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : vs@foldr.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- DLOpen support, old API
|
||||||
|
-- Derived from GModule.chs by M.Weber & M.Chakravarty which is part of c2hs
|
||||||
|
-- I left the API more or less the same, mostly the flags are different.
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.Module.ByteString (
|
||||||
|
|
||||||
|
-- Usage:
|
||||||
|
-- ******
|
||||||
|
--
|
||||||
|
-- Let's assume you want to open a local shared library 'foo' (./libfoo.so)
|
||||||
|
-- offering a function
|
||||||
|
-- char * mogrify (char*,int)
|
||||||
|
-- and invoke str = mogrify("test",1):
|
||||||
|
--
|
||||||
|
-- type Fun = CString -> Int -> IO CString
|
||||||
|
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
|
||||||
|
--
|
||||||
|
-- withModule (Just ".") ("libfoo.so") [RTLD_NOW] $ \ mod -> do
|
||||||
|
-- funptr <- moduleSymbol mod "mogrify"
|
||||||
|
-- let fun = fun__ funptr
|
||||||
|
-- withCString "test" $ \ str -> do
|
||||||
|
-- strptr <- fun str 1
|
||||||
|
-- strstr <- peekCString strptr
|
||||||
|
-- ...
|
||||||
|
|
||||||
|
Module
|
||||||
|
, moduleOpen -- :: String -> ModuleFlags -> IO Module
|
||||||
|
, moduleSymbol -- :: Source -> String -> IO (FunPtr a)
|
||||||
|
, moduleClose -- :: Module -> IO Bool
|
||||||
|
, moduleError -- :: IO String
|
||||||
|
, withModule -- :: Maybe String
|
||||||
|
-- -> String
|
||||||
|
-- -> [ModuleFlags ]
|
||||||
|
-- -> (Module -> IO a)
|
||||||
|
-- -> IO a
|
||||||
|
, withModule_ -- :: Maybe String
|
||||||
|
-- -> String
|
||||||
|
-- -> [ModuleFlags]
|
||||||
|
-- -> (Module -> IO a)
|
||||||
|
-- -> IO ()
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import System.Posix.DynamicLinker.Module hiding (moduleOpen)
|
||||||
|
import System.Posix.DynamicLinker.Prim
|
||||||
|
import System.Posix.DynamicLinker.Common
|
||||||
|
|
||||||
|
import Foreign
|
||||||
|
import System.Posix.ByteString.FilePath
|
||||||
|
|
||||||
|
-- Opens a module (EXPORTED)
|
||||||
|
--
|
||||||
|
|
||||||
|
moduleOpen :: RawFilePath -> [RTLDFlags] -> IO Module
|
||||||
|
moduleOpen file flags = do
|
||||||
|
modPtr <- withFilePath file $ \ modAddr -> c_dlopen modAddr (packRTLDFlags flags)
|
||||||
|
if (modPtr == nullPtr)
|
||||||
|
then moduleError >>= \ err -> ioError (userError ("dlopen: " ++ err))
|
||||||
|
else return $ Module modPtr
|
||||||
123
unix/System/Posix/DynamicLinker/Prim.hsc
Normal file
123
unix/System/Posix/DynamicLinker/Prim.hsc
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# OPTIONS_GHC -fno-warn-trustworthy-safe #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.DynamicLinker.Prim
|
||||||
|
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : vs@foldr.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- @dlopen(3)@ and friends
|
||||||
|
-- Derived from @GModule.chs@ by M.Weber & M.Chakravarty which is part of c2hs.
|
||||||
|
-- I left the API more or less the same, mostly the flags are different.
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.DynamicLinker.Prim (
|
||||||
|
-- * low level API
|
||||||
|
c_dlopen,
|
||||||
|
c_dlsym,
|
||||||
|
c_dlerror,
|
||||||
|
c_dlclose,
|
||||||
|
-- dlAddr, -- XXX NYI
|
||||||
|
haveRtldNext,
|
||||||
|
haveRtldLocal,
|
||||||
|
packRTLDFlags,
|
||||||
|
RTLDFlags(..),
|
||||||
|
packDL,
|
||||||
|
DL(..),
|
||||||
|
)
|
||||||
|
|
||||||
|
where
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import Data.Bits ( (.|.) )
|
||||||
|
import Foreign.Ptr ( Ptr, FunPtr, nullPtr )
|
||||||
|
import Foreign.C.Types
|
||||||
|
import Foreign.C.String ( CString )
|
||||||
|
|
||||||
|
|
||||||
|
-- |On some hosts (e.g. SuSe and Ubuntu Linux) @RTLD_NEXT@ (and
|
||||||
|
-- @RTLD_DEFAULT@) are not visible without setting the macro
|
||||||
|
-- @_GNU_SOURCE@. Since we don\'t want to define this macro, you can use
|
||||||
|
-- the function 'haveRtldNext' to check wether the flag `Next` is
|
||||||
|
-- available. Ideally, this will be optimized by the compiler so that it
|
||||||
|
-- should be as efficient as an @#ifdef@.
|
||||||
|
--
|
||||||
|
-- If you fail to test the flag and use it although it is undefined,
|
||||||
|
-- 'packDL' will throw an error.
|
||||||
|
|
||||||
|
haveRtldNext :: Bool
|
||||||
|
|
||||||
|
#ifdef HAVE_RTLDNEXT
|
||||||
|
haveRtldNext = True
|
||||||
|
foreign import ccall unsafe "__hsunix_rtldNext" rtldNext :: Ptr a
|
||||||
|
#else /* HAVE_RTLDNEXT */
|
||||||
|
haveRtldNext = False
|
||||||
|
#endif /* HAVE_RTLDNEXT */
|
||||||
|
|
||||||
|
#ifdef HAVE_RTLDDEFAULT
|
||||||
|
foreign import ccall unsafe "__hsunix_rtldDefault" rtldDefault :: Ptr a
|
||||||
|
#endif /* HAVE_RTLDDEFAULT */
|
||||||
|
|
||||||
|
haveRtldLocal :: Bool
|
||||||
|
haveRtldLocal = True
|
||||||
|
{-# DEPRECATED haveRtldLocal "defaults to True" #-}
|
||||||
|
|
||||||
|
|
||||||
|
-- |Flags for 'System.Posix.DynamicLinker.dlopen'.
|
||||||
|
|
||||||
|
data RTLDFlags
|
||||||
|
= RTLD_LAZY
|
||||||
|
| RTLD_NOW
|
||||||
|
| RTLD_GLOBAL
|
||||||
|
| RTLD_LOCAL
|
||||||
|
deriving (Show, Read)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "dlopen" c_dlopen :: CString -> CInt -> IO (Ptr ())
|
||||||
|
foreign import ccall unsafe "dlsym" c_dlsym :: Ptr () -> CString -> IO (FunPtr a)
|
||||||
|
foreign import ccall unsafe "dlerror" c_dlerror :: IO CString
|
||||||
|
foreign import ccall unsafe "dlclose" c_dlclose :: (Ptr ()) -> IO CInt
|
||||||
|
|
||||||
|
packRTLDFlags :: [RTLDFlags] -> CInt
|
||||||
|
packRTLDFlags flags = foldl (\ s f -> (packRTLDFlag f) .|. s) 0 flags
|
||||||
|
|
||||||
|
packRTLDFlag :: RTLDFlags -> CInt
|
||||||
|
packRTLDFlag RTLD_LAZY = #const RTLD_LAZY
|
||||||
|
packRTLDFlag RTLD_NOW = #const RTLD_NOW
|
||||||
|
packRTLDFlag RTLD_GLOBAL = #const RTLD_GLOBAL
|
||||||
|
packRTLDFlag RTLD_LOCAL = #const RTLD_LOCAL
|
||||||
|
|
||||||
|
|
||||||
|
-- |Flags for 'System.Posix.DynamicLinker.dlsym'. Notice that 'Next'
|
||||||
|
-- might not be available on your particular platform! Use
|
||||||
|
-- 'haveRtldNext'.
|
||||||
|
--
|
||||||
|
-- If 'RTLD_DEFAULT' is not defined on your platform, 'packDL' 'Default'
|
||||||
|
-- reduces to 'nullPtr'.
|
||||||
|
|
||||||
|
data DL = Null | Next | Default | DLHandle (Ptr ()) deriving (Show)
|
||||||
|
|
||||||
|
packDL :: DL -> Ptr ()
|
||||||
|
packDL Null = nullPtr
|
||||||
|
|
||||||
|
#ifdef HAVE_RTLDNEXT
|
||||||
|
packDL Next = rtldNext
|
||||||
|
#else
|
||||||
|
packDL Next = error "RTLD_NEXT not available"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_RTLDDEFAULT
|
||||||
|
packDL Default = rtldDefault
|
||||||
|
#else
|
||||||
|
packDL Default = nullPtr
|
||||||
|
#endif
|
||||||
|
|
||||||
|
packDL (DLHandle h) = h
|
||||||
205
unix/System/Posix/Env.hsc
Normal file
205
unix/System/Posix/Env.hsc
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
{-# LANGUAGE CApiFFI #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.Env
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- POSIX environment support
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.Env (
|
||||||
|
getEnv
|
||||||
|
, getEnvDefault
|
||||||
|
, getEnvironmentPrim
|
||||||
|
, getEnvironment
|
||||||
|
, setEnvironment
|
||||||
|
, putEnv
|
||||||
|
, setEnv
|
||||||
|
, unsetEnv
|
||||||
|
, clearEnv
|
||||||
|
) where
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import Foreign.C.Error (throwErrnoIfMinus1_)
|
||||||
|
import Foreign.C.Types
|
||||||
|
import Foreign.C.String
|
||||||
|
import Foreign.Marshal.Array
|
||||||
|
import Foreign.Ptr
|
||||||
|
import Foreign.Storable
|
||||||
|
import Control.Monad
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import System.Posix.Internals
|
||||||
|
|
||||||
|
#if !MIN_VERSION_base(4,7,0)
|
||||||
|
-- needed for backported local 'newFilePath' binding in 'putEnv'
|
||||||
|
import GHC.IO.Encoding (getFileSystemEncoding)
|
||||||
|
import qualified GHC.Foreign as GHC (newCString)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- |'getEnv' looks up a variable in the environment.
|
||||||
|
|
||||||
|
getEnv ::
|
||||||
|
String {- ^ variable name -} ->
|
||||||
|
IO (Maybe String) {- ^ variable value -}
|
||||||
|
getEnv name = do
|
||||||
|
litstring <- withFilePath name c_getenv
|
||||||
|
if litstring /= nullPtr
|
||||||
|
then liftM Just $ peekFilePath litstring
|
||||||
|
else return Nothing
|
||||||
|
|
||||||
|
-- |'getEnvDefault' is a wrapper around 'getEnv' where the
|
||||||
|
-- programmer can specify a fallback if the variable is not found
|
||||||
|
-- in the environment.
|
||||||
|
|
||||||
|
getEnvDefault ::
|
||||||
|
String {- ^ variable name -} ->
|
||||||
|
String {- ^ fallback value -} ->
|
||||||
|
IO String {- ^ variable value or fallback value -}
|
||||||
|
getEnvDefault name fallback = liftM (fromMaybe fallback) (getEnv name)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "getenv"
|
||||||
|
c_getenv :: CString -> IO CString
|
||||||
|
|
||||||
|
getEnvironmentPrim :: IO [String]
|
||||||
|
getEnvironmentPrim = do
|
||||||
|
c_environ <- getCEnviron
|
||||||
|
-- environ can be NULL
|
||||||
|
if c_environ == nullPtr
|
||||||
|
then return []
|
||||||
|
else do
|
||||||
|
arr <- peekArray0 nullPtr c_environ
|
||||||
|
mapM peekFilePath arr
|
||||||
|
|
||||||
|
getCEnviron :: IO (Ptr CString)
|
||||||
|
#if HAVE__NSGETENVIRON
|
||||||
|
-- You should not access @char **environ@ directly on Darwin in a bundle/shared library.
|
||||||
|
-- See #2458 and http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man7/environ.7.html
|
||||||
|
getCEnviron = nsGetEnviron >>= peek
|
||||||
|
|
||||||
|
foreign import ccall unsafe "_NSGetEnviron"
|
||||||
|
nsGetEnviron :: IO (Ptr (Ptr CString))
|
||||||
|
#else
|
||||||
|
getCEnviron = peek c_environ_p
|
||||||
|
foreign import ccall unsafe "&environ"
|
||||||
|
c_environ_p :: Ptr (Ptr CString)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- |'getEnvironment' retrieves the entire environment as a
|
||||||
|
-- list of @(key,value)@ pairs.
|
||||||
|
|
||||||
|
getEnvironment :: IO [(String,String)] {- ^ @[(key,value)]@ -}
|
||||||
|
getEnvironment = do
|
||||||
|
env <- getEnvironmentPrim
|
||||||
|
return $ map (dropEq.(break ((==) '='))) env
|
||||||
|
where
|
||||||
|
dropEq (x,'=':ys) = (x,ys)
|
||||||
|
dropEq (x,_) = error $ "getEnvironment: insane variable " ++ x
|
||||||
|
|
||||||
|
-- |'setEnvironment' resets the entire environment to the given list of
|
||||||
|
-- @(key,value)@ pairs.
|
||||||
|
|
||||||
|
setEnvironment ::
|
||||||
|
[(String,String)] {- ^ @[(key,value)]@ -} ->
|
||||||
|
IO ()
|
||||||
|
setEnvironment env = do
|
||||||
|
clearEnv
|
||||||
|
forM_ env $ \(key,value) ->
|
||||||
|
setEnv key value True {-overwrite-}
|
||||||
|
|
||||||
|
-- |The 'unsetEnv' function deletes all instances of the variable name
|
||||||
|
-- from the environment.
|
||||||
|
|
||||||
|
unsetEnv :: String {- ^ variable name -} -> IO ()
|
||||||
|
#if HAVE_UNSETENV
|
||||||
|
# if !UNSETENV_RETURNS_VOID
|
||||||
|
unsetEnv name = withFilePath name $ \ s ->
|
||||||
|
throwErrnoIfMinus1_ "unsetenv" (c_unsetenv s)
|
||||||
|
|
||||||
|
-- POSIX.1-2001 compliant unsetenv(3)
|
||||||
|
foreign import capi unsafe "HsUnix.h unsetenv"
|
||||||
|
c_unsetenv :: CString -> IO CInt
|
||||||
|
# else
|
||||||
|
unsetEnv name = withFilePath name c_unsetenv
|
||||||
|
|
||||||
|
-- pre-POSIX unsetenv(3) returning @void@
|
||||||
|
foreign import capi unsafe "HsUnix.h unsetenv"
|
||||||
|
c_unsetenv :: CString -> IO ()
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
unsetEnv name = putEnv (name ++ "=")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- |'putEnv' function takes an argument of the form @name=value@
|
||||||
|
-- and is equivalent to @setEnv(key,value,True{-overwrite-})@.
|
||||||
|
|
||||||
|
putEnv :: String {- ^ "key=value" -} -> IO ()
|
||||||
|
putEnv keyvalue = do s <- newFilePath keyvalue
|
||||||
|
-- Do not free `s` after calling putenv.
|
||||||
|
-- According to SUSv2, the string passed to putenv
|
||||||
|
-- becomes part of the environment. #7342
|
||||||
|
throwErrnoIfMinus1_ "putenv" (c_putenv s)
|
||||||
|
#if !MIN_VERSION_base(4,7,0)
|
||||||
|
where
|
||||||
|
newFilePath :: FilePath -> IO CString
|
||||||
|
newFilePath fp = getFileSystemEncoding >>= \enc -> GHC.newCString enc fp
|
||||||
|
#endif
|
||||||
|
|
||||||
|
foreign import ccall unsafe "putenv"
|
||||||
|
c_putenv :: CString -> IO CInt
|
||||||
|
|
||||||
|
{- |The 'setEnv' function inserts or resets the environment variable name in
|
||||||
|
the current environment list. If the variable @name@ does not exist in the
|
||||||
|
list, it is inserted with the given value. If the variable does exist,
|
||||||
|
the argument @overwrite@ is tested; if @overwrite@ is @False@, the variable is
|
||||||
|
not reset, otherwise it is reset to the given value.
|
||||||
|
-}
|
||||||
|
|
||||||
|
setEnv ::
|
||||||
|
String {- ^ variable name -} ->
|
||||||
|
String {- ^ variable value -} ->
|
||||||
|
Bool {- ^ overwrite -} ->
|
||||||
|
IO ()
|
||||||
|
#ifdef HAVE_SETENV
|
||||||
|
setEnv key value ovrwrt = do
|
||||||
|
withFilePath key $ \ keyP ->
|
||||||
|
withFilePath value $ \ valueP ->
|
||||||
|
throwErrnoIfMinus1_ "setenv" $
|
||||||
|
c_setenv keyP valueP (fromIntegral (fromEnum ovrwrt))
|
||||||
|
|
||||||
|
foreign import ccall unsafe "setenv"
|
||||||
|
c_setenv :: CString -> CString -> CInt -> IO CInt
|
||||||
|
#else
|
||||||
|
setEnv key value True = putEnv (key++"="++value)
|
||||||
|
setEnv key value False = do
|
||||||
|
res <- getEnv key
|
||||||
|
case res of
|
||||||
|
Just _ -> return ()
|
||||||
|
Nothing -> putEnv (key++"="++value)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- |The 'clearEnv' function clears the environment of all name-value pairs.
|
||||||
|
clearEnv :: IO ()
|
||||||
|
#if HAVE_CLEARENV
|
||||||
|
clearEnv = void c_clearenv
|
||||||
|
|
||||||
|
foreign import ccall unsafe "clearenv"
|
||||||
|
c_clearenv :: IO Int
|
||||||
|
#else
|
||||||
|
-- Fallback to 'environ[0] = NULL'.
|
||||||
|
clearEnv = do
|
||||||
|
c_environ <- getCEnviron
|
||||||
|
unless (c_environ == nullPtr) $
|
||||||
|
poke c_environ nullPtr
|
||||||
|
#endif
|
||||||
184
unix/System/Posix/Env/ByteString.hsc
Normal file
184
unix/System/Posix/Env/ByteString.hsc
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
{-# LANGUAGE CApiFFI #-}
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# OPTIONS_GHC -fno-warn-trustworthy-safe #-}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.Env.ByteString
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- POSIX environment support
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.Env.ByteString (
|
||||||
|
-- * Environment Variables
|
||||||
|
getEnv
|
||||||
|
, getEnvDefault
|
||||||
|
, getEnvironmentPrim
|
||||||
|
, getEnvironment
|
||||||
|
, putEnv
|
||||||
|
, setEnv
|
||||||
|
, unsetEnv
|
||||||
|
|
||||||
|
-- * Program arguments
|
||||||
|
, getArgs
|
||||||
|
) where
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
import Foreign
|
||||||
|
import Foreign.C
|
||||||
|
import Control.Monad ( liftM )
|
||||||
|
import Data.Maybe ( fromMaybe )
|
||||||
|
|
||||||
|
import qualified Data.ByteString as B
|
||||||
|
import qualified Data.ByteString.Char8 as BC
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
|
||||||
|
-- |'getEnv' looks up a variable in the environment.
|
||||||
|
|
||||||
|
getEnv ::
|
||||||
|
ByteString {- ^ variable name -} ->
|
||||||
|
IO (Maybe ByteString) {- ^ variable value -}
|
||||||
|
getEnv name = do
|
||||||
|
litstring <- B.useAsCString name c_getenv
|
||||||
|
if litstring /= nullPtr
|
||||||
|
then liftM Just $ B.packCString litstring
|
||||||
|
else return Nothing
|
||||||
|
|
||||||
|
-- |'getEnvDefault' is a wrapper around 'getEnv' where the
|
||||||
|
-- programmer can specify a fallback if the variable is not found
|
||||||
|
-- in the environment.
|
||||||
|
|
||||||
|
getEnvDefault ::
|
||||||
|
ByteString {- ^ variable name -} ->
|
||||||
|
ByteString {- ^ fallback value -} ->
|
||||||
|
IO ByteString {- ^ variable value or fallback value -}
|
||||||
|
getEnvDefault name fallback = liftM (fromMaybe fallback) (getEnv name)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "getenv"
|
||||||
|
c_getenv :: CString -> IO CString
|
||||||
|
|
||||||
|
getEnvironmentPrim :: IO [ByteString]
|
||||||
|
getEnvironmentPrim = do
|
||||||
|
c_environ <- getCEnviron
|
||||||
|
arr <- peekArray0 nullPtr c_environ
|
||||||
|
mapM B.packCString arr
|
||||||
|
|
||||||
|
getCEnviron :: IO (Ptr CString)
|
||||||
|
#if HAVE__NSGETENVIRON
|
||||||
|
-- You should not access @char **environ@ directly on Darwin in a bundle/shared library.
|
||||||
|
-- See #2458 and http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man7/environ.7.html
|
||||||
|
getCEnviron = nsGetEnviron >>= peek
|
||||||
|
|
||||||
|
foreign import ccall unsafe "_NSGetEnviron"
|
||||||
|
nsGetEnviron :: IO (Ptr (Ptr CString))
|
||||||
|
#else
|
||||||
|
getCEnviron = peek c_environ_p
|
||||||
|
|
||||||
|
foreign import ccall unsafe "&environ"
|
||||||
|
c_environ_p :: Ptr (Ptr CString)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- |'getEnvironment' retrieves the entire environment as a
|
||||||
|
-- list of @(key,value)@ pairs.
|
||||||
|
|
||||||
|
getEnvironment :: IO [(ByteString,ByteString)] {- ^ @[(key,value)]@ -}
|
||||||
|
getEnvironment = do
|
||||||
|
env <- getEnvironmentPrim
|
||||||
|
return $ map (dropEq.(BC.break ((==) '='))) env
|
||||||
|
where
|
||||||
|
dropEq (x,y)
|
||||||
|
| BC.head y == '=' = (x,B.tail y)
|
||||||
|
| otherwise = error $ "getEnvironment: insane variable " ++ BC.unpack x
|
||||||
|
|
||||||
|
-- |The 'unsetEnv' function deletes all instances of the variable name
|
||||||
|
-- from the environment.
|
||||||
|
|
||||||
|
unsetEnv :: ByteString {- ^ variable name -} -> IO ()
|
||||||
|
#if HAVE_UNSETENV
|
||||||
|
# if !UNSETENV_RETURNS_VOID
|
||||||
|
unsetEnv name = B.useAsCString name $ \ s ->
|
||||||
|
throwErrnoIfMinus1_ "unsetenv" (c_unsetenv s)
|
||||||
|
|
||||||
|
-- POSIX.1-2001 compliant unsetenv(3)
|
||||||
|
foreign import capi unsafe "HsUnix.h unsetenv"
|
||||||
|
c_unsetenv :: CString -> IO CInt
|
||||||
|
# else
|
||||||
|
unsetEnv name = B.useAsCString name c_unsetenv
|
||||||
|
|
||||||
|
-- pre-POSIX unsetenv(3) returning @void@
|
||||||
|
foreign import capi unsafe "HsUnix.h unsetenv"
|
||||||
|
c_unsetenv :: CString -> IO ()
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
unsetEnv name = putEnv (name ++ "=")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- |'putEnv' function takes an argument of the form @name=value@
|
||||||
|
-- and is equivalent to @setEnv(key,value,True{-overwrite-})@.
|
||||||
|
|
||||||
|
putEnv :: ByteString {- ^ "key=value" -} -> IO ()
|
||||||
|
putEnv keyvalue = B.useAsCString keyvalue $ \s ->
|
||||||
|
throwErrnoIfMinus1_ "putenv" (c_putenv s)
|
||||||
|
|
||||||
|
foreign import ccall unsafe "putenv"
|
||||||
|
c_putenv :: CString -> IO CInt
|
||||||
|
|
||||||
|
{- |The 'setEnv' function inserts or resets the environment variable name in
|
||||||
|
the current environment list. If the variable @name@ does not exist in the
|
||||||
|
list, it is inserted with the given value. If the variable does exist,
|
||||||
|
the argument @overwrite@ is tested; if @overwrite@ is @False@, the variable is
|
||||||
|
not reset, otherwise it is reset to the given value.
|
||||||
|
-}
|
||||||
|
|
||||||
|
setEnv ::
|
||||||
|
ByteString {- ^ variable name -} ->
|
||||||
|
ByteString {- ^ variable value -} ->
|
||||||
|
Bool {- ^ overwrite -} ->
|
||||||
|
IO ()
|
||||||
|
#ifdef HAVE_SETENV
|
||||||
|
setEnv key value ovrwrt = do
|
||||||
|
B.useAsCString key $ \ keyP ->
|
||||||
|
B.useAsCString value $ \ valueP ->
|
||||||
|
throwErrnoIfMinus1_ "setenv" $
|
||||||
|
c_setenv keyP valueP (fromIntegral (fromEnum ovrwrt))
|
||||||
|
|
||||||
|
foreign import ccall unsafe "setenv"
|
||||||
|
c_setenv :: CString -> CString -> CInt -> IO CInt
|
||||||
|
#else
|
||||||
|
setEnv key value True = putEnv (key++"="++value)
|
||||||
|
setEnv key value False = do
|
||||||
|
res <- getEnv key
|
||||||
|
case res of
|
||||||
|
Just _ -> return ()
|
||||||
|
Nothing -> putEnv (key++"="++value)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- | Computation 'getArgs' returns a list of the program's command
|
||||||
|
-- line arguments (not including the program name), as 'ByteString's.
|
||||||
|
--
|
||||||
|
-- Unlike 'System.Environment.getArgs', this function does no Unicode
|
||||||
|
-- decoding of the arguments; you get the exact bytes that were passed
|
||||||
|
-- to the program by the OS. To interpret the arguments as text, some
|
||||||
|
-- Unicode decoding should be applied.
|
||||||
|
--
|
||||||
|
getArgs :: IO [ByteString]
|
||||||
|
getArgs =
|
||||||
|
alloca $ \ p_argc ->
|
||||||
|
alloca $ \ p_argv -> do
|
||||||
|
getProgArgv p_argc p_argv
|
||||||
|
p <- fromIntegral `liftM` peek p_argc
|
||||||
|
argv <- peek p_argv
|
||||||
|
peekArray (p - 1) (advancePtr argv 1) >>= mapM B.packCString
|
||||||
|
|
||||||
|
foreign import ccall unsafe "getProgArgv"
|
||||||
|
getProgArgv :: Ptr CInt -> Ptr (Ptr CString) -> IO ()
|
||||||
63
unix/System/Posix/Error.hs
Normal file
63
unix/System/Posix/Error.hs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.Error
|
||||||
|
-- Copyright : (c) The University of Glasgow 2002
|
||||||
|
-- License : BSD-style (see the file libraries/base/LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- POSIX error support
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module System.Posix.Error (
|
||||||
|
throwErrnoPath,
|
||||||
|
throwErrnoPathIf,
|
||||||
|
throwErrnoPathIf_,
|
||||||
|
throwErrnoPathIfRetry,
|
||||||
|
throwErrnoPathIfNull,
|
||||||
|
throwErrnoPathIfNullRetry,
|
||||||
|
throwErrnoPathIfMinus1,
|
||||||
|
throwErrnoPathIfMinus1_,
|
||||||
|
throwErrnoPathIfMinus1Retry,
|
||||||
|
throwErrnoPathIfMinus1Retry_
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Foreign hiding (void)
|
||||||
|
import Foreign.C
|
||||||
|
import Control.Monad
|
||||||
|
|
||||||
|
throwErrnoPathIfMinus1Retry :: (Eq a, Num a)
|
||||||
|
=> String -> FilePath -> IO a -> IO a
|
||||||
|
throwErrnoPathIfMinus1Retry loc path f =
|
||||||
|
throwErrnoPathIfRetry (== -1) loc path f
|
||||||
|
|
||||||
|
throwErrnoPathIfMinus1Retry_ :: (Eq a, Num a)
|
||||||
|
=> String -> FilePath -> IO a -> IO ()
|
||||||
|
throwErrnoPathIfMinus1Retry_ loc path f =
|
||||||
|
void $ throwErrnoPathIfRetry (== -1) loc path f
|
||||||
|
|
||||||
|
throwErrnoPathIfNullRetry :: String -> FilePath -> IO (Ptr a) -> IO (Ptr a)
|
||||||
|
throwErrnoPathIfNullRetry loc path f =
|
||||||
|
throwErrnoPathIfRetry (== nullPtr) loc path f
|
||||||
|
|
||||||
|
throwErrnoPathIfRetry :: (a -> Bool) -> String -> FilePath -> IO a -> IO a
|
||||||
|
throwErrnoPathIfRetry pr loc path f =
|
||||||
|
do
|
||||||
|
res <- f
|
||||||
|
if pr res
|
||||||
|
then do
|
||||||
|
err <- getErrno
|
||||||
|
if err == eINTR
|
||||||
|
then throwErrnoPathIfRetry pr loc path f
|
||||||
|
else throwErrnoPath loc path
|
||||||
|
else return res
|
||||||
|
|
||||||
104
unix/System/Posix/Fcntl.hsc
Normal file
104
unix/System/Posix/Fcntl.hsc
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
{-# LANGUAGE CApiFFI #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 709
|
||||||
|
{-# LANGUAGE Safe #-}
|
||||||
|
#else
|
||||||
|
{-# LANGUAGE Trustworthy #-}
|
||||||
|
#endif
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : System.Posix.Fcntl
|
||||||
|
-- Copyright : (c) The University of Glasgow 2014
|
||||||
|
-- License : BSD-style (see the file LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : libraries@haskell.org
|
||||||
|
-- Stability : provisional
|
||||||
|
-- Portability : non-portable (requires POSIX)
|
||||||
|
--
|
||||||
|
-- POSIX file control support
|
||||||
|
--
|
||||||
|
-- @since 2.7.1.0
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "HsUnix.h"
|
||||||
|
|
||||||
|
module System.Posix.Fcntl (
|
||||||
|
-- * File allocation
|
||||||
|
Advice(..), fileAdvise,
|
||||||
|
fileAllocate,
|
||||||
|
) where
|
||||||
|
|
||||||
|
#if HAVE_POSIX_FALLOCATE || HAVE_POSIX_FADVISE
|
||||||
|
import Foreign.C
|
||||||
|
#endif
|
||||||
|
import System.Posix.Types
|
||||||
|
|
||||||
|
#if !HAVE_POSIX_FALLOCATE
|
||||||
|
import System.IO.Error ( ioeSetLocation )
|
||||||
|
import GHC.IO.Exception ( unsupportedOperation )
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- File control
|
||||||
|
|
||||||
|
-- | Advice parameter for 'fileAdvise' operation.
|
||||||
|
--
|
||||||
|
-- For more details, see documentation of @posix_fadvise(2)@.
|
||||||
|
--
|
||||||
|
-- @since 2.7.1.0
|
||||||
|
data Advice
|
||||||
|
= AdviceNormal
|
||||||
|
| AdviceRandom
|
||||||
|
| AdviceSequential
|
||||||
|
| AdviceWillNeed
|
||||||
|
| AdviceDontNeed
|
||||||
|
| AdviceNoReuse
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
-- | Performs @posix_fadvise(2)@ operation on file-descriptor.
|
||||||
|
--
|
||||||
|
-- If platform does not provide @posix_fadvise(2)@ 'fileAdvise'
|
||||||
|
-- becomes a no-op.
|
||||||
|
--
|
||||||
|
-- (use @#if HAVE_POSIX_FADVISE@ CPP guard to detect availability)
|
||||||
|
--
|
||||||
|
-- @since 2.7.1.0
|
||||||
|
fileAdvise :: Fd -> FileOffset -> FileOffset -> Advice -> IO ()
|
||||||
|
#if HAVE_POSIX_FADVISE
|
||||||
|
fileAdvise fd off len adv = do
|
||||||
|
throwErrnoIfMinus1_ "fileAdvise" (c_posix_fadvise (fromIntegral fd) (fromIntegral off) (fromIntegral len) (packAdvice adv))
|
||||||
|
|
||||||
|
foreign import capi safe "fcntl.h posix_fadvise"
|
||||||
|
c_posix_fadvise :: CInt -> COff -> COff -> CInt -> IO CInt
|
||||||
|
|
||||||
|
packAdvice :: Advice -> CInt
|
||||||
|
packAdvice AdviceNormal = (#const POSIX_FADV_NORMAL)
|
||||||
|
packAdvice AdviceRandom = (#const POSIX_FADV_RANDOM)
|
||||||
|
packAdvice AdviceSequential = (#const POSIX_FADV_SEQUENTIAL)
|
||||||
|
packAdvice AdviceWillNeed = (#const POSIX_FADV_WILLNEED)
|
||||||
|
packAdvice AdviceDontNeed = (#const POSIX_FADV_DONTNEED)
|
||||||
|
packAdvice AdviceNoReuse = (#const POSIX_FADV_NOREUSE)
|
||||||
|
#else
|
||||||
|
fileAdvise _ _ _ _ = return ()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- | Performs @posix_fallocate(2)@ operation on file-descriptor.
|
||||||
|
--
|
||||||
|
-- Throws 'IOError' (\"unsupported operation\") if platform does not
|
||||||
|
-- provide @posix_fallocate(2)@.
|
||||||
|
--
|
||||||
|
-- (use @#if HAVE_POSIX_FALLOCATE@ CPP guard to detect availability).
|
||||||
|
--
|
||||||
|
-- @since 2.7.1.0
|
||||||
|
fileAllocate :: Fd -> FileOffset -> FileOffset -> IO ()
|
||||||
|
#if HAVE_POSIX_FALLOCATE
|
||||||
|
fileAllocate fd off len = do
|
||||||
|
throwErrnoIfMinus1_ "fileAllocate" (c_posix_fallocate (fromIntegral fd) (fromIntegral off) (fromIntegral len))
|
||||||
|
|
||||||
|
foreign import capi safe "fcntl.h posix_fallocate"
|
||||||
|
c_posix_fallocate :: CInt -> COff -> COff -> IO CInt
|
||||||
|
#else
|
||||||
|
{-# WARNING fileAllocate
|
||||||
|
"operation will throw 'IOError' \"unsupported operation\" (CPP guard: @#if HAVE_POSIX_FALLOCATE@)" #-}
|
||||||
|
fileAllocate _ _ _ = ioError (ioeSetLocation unsupportedOperation
|
||||||
|
"fileAllocate")
|
||||||
|
#endif
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user