packages feed

rawfilepath (empty) → 0.1.0.0

raw patch · 7 files changed

+434/−0 lines, 7 filesdep +basedep +bytestringdep +rawfilepathsetup-changed

Dependencies added: base, bytestring, rawfilepath, unix

Files

+ LICENSE view
@@ -0,0 +1,22 @@+Copyright XT (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following condition is met:++    * Neither the name of XT 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.
+ README.md view
@@ -0,0 +1,41 @@+# rawfilepath++A Haskell library for the mid-level system functions for the `RawFilePath` data type.++## Background++Traditional `String` is notorious:++- Up to 40 bytes (five words) required for one character (the List constructor, the pointer to the Char constructor, the Char constructor, the actual Char value, and the pointer to the next List constructor)+- Heap fragmentation causing malloc/free overhead+- A lot of pointer chasing for reading, devastating the cache hit rate+- A lot of pointer chasing plus a lot of heap object allocation for manipulation (appending, slicing, etc.)+- Completely unnecessary but mandatory conversions and memory allocation when the data is sent to or received from the outside world++Transition to `Text` and `ByteString` began, but even after a dazzling community effort, `FilePath`, a key data type for programming anything useful, remained to be a type synonym of `String`.++To put a cherry on top of creaking, fuming, dragging, and littering pointers all over the heap space, `String` had another fantastic nature to serve as a file path data type: Encoding blindness. All functions that return `FilePath` would actually take a series of bytes returned by a syscall and somehow magically "decode" it into a `String` which is surprising because no encoding information was given. Of course there is no magic and it's an abject fail. `FilePath` just wouldn't work.++In June 2015, three bright Haskell programmers came up with an elegant solution called the [Abstract FilePath Proposal] and met an immediate thunderous applause. Inspired by this enthusiasm, they further pursued the career of professional Haskell programming and focused on more interesting things. 16 months later, a programmer under the pseudonym XT got so fed up with the situation and released a package called `rawfilepath` out of pure evil intent.++## So what is this?++`RawFilePath` is a data type provided by the `unix` package. It has no performance issues because it is `ByteString` which is packed. It has no encoding issues because it is `ByteString` which is a sequence of bytes instead of characters. However, the functions in `unix` are low-level, and the higher-level packages such as `process` and `directory` are strictly tied to `FilePath`.++So I decided to start writing the `RawFilePath` version of those functions.++## Advantages++- High performance+- No round-trip encoding issue+- Minimal dependencies (`bytestring`, `unix`, and `base`)+- Lightweight library (under 400 total lines of code)+- Available now++## To do++`rawfilepath` is in an early stage, although major backwards-incompatible changes are unlikely to happen. We can probably port more system functions that are present in `process` or `directory`++Patches will be highly appreciated.++[Abstract FilePath Proposal]: https://ghc.haskell.org/trac/ghc/wiki/Proposal/AbstractFilePath
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ rawfilepath.cabal view
@@ -0,0 +1,41 @@+name:                rawfilepath+version:             0.1.0.0+synopsis:            Use RawFilePath instead of FilePath+description:         Please see README.md+homepage:            https://github.com/xtendo-org/rawfilepath#readme+license:             BSD3+license-file:        LICENSE+author:              XT+maintainer:          xtendo@protonmail.com+copyright:           2016 XT+category:            System+build-type:          Simple+cabal-version:       >=1.10+extra-source-files:+  README.md++library+  hs-source-dirs:      src+  exposed-modules:+    Data.ByteString.RawFilePath,+    System.RawFilePath+  build-depends:+    bytestring,+    unix,+    base >= 4.7 && < 5+  default-language:    Haskell2010+  default-extensions:+    OverloadedStrings++test-suite RawFilePath-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  build-depends:       base+                     , rawfilepath+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/xtendo-org/rawfilepath
+ src/Data/ByteString/RawFilePath.hs view
@@ -0,0 +1,63 @@+-- |+-- Module      : Data.ByteString.RawFilePath+-- Copyright   : (c) XT 2016+-- License     : BSD-style+--+-- Maintainer  : e@xtendo.org+-- Stability   : experimental+-- Portability : POSIX+--+-- A variant of @Data.ByteString@ from the @bytestring@ package that provides+-- file I/O functions with 'RawFilePath' instead of 'FilePath'.++module Data.ByteString.RawFilePath+    ( module B+    , RawFilePath+    , readFile+    , writeFile+    , appendFile+    , withFile++    ) where++-- base modules++import Prelude hiding (readFile, writeFile, appendFile)+import Control.Exception (bracket)+import System.IO (IOMode(..), Handle, hClose)++-- extra modules++import System.Posix.ByteString+import Data.ByteString as B hiding (readFile, writeFile, appendFile)++-- | Read an entire file at the 'RawFilePath' strictly into a 'ByteString'.+readFile :: RawFilePath -> IO ByteString+readFile path = withFile path ReadMode B.hGetContents++-- | Write a 'ByteString' to a file at the 'RawFilePath'.+writeFile :: RawFilePath -> ByteString -> IO ()+writeFile path content = withFile path WriteMode (`B.hPut` content)++-- | Append a 'ByteString' to a file at the 'RawFilePath'.+appendFile :: RawFilePath -> ByteString -> IO ()+appendFile path content = withFile path AppendMode (`B.hPut` content)++-- | Acquire a file handle and perform an I/O action. The file will be closed+-- on exit or when this I/O action throws an exception.+withFile :: RawFilePath -> IOMode -> (Handle -> IO r) -> IO r+withFile path ioMode = bracket (open >>= fdToHandle) hClose+  where+    open = case ioMode of+        ReadMode -> openFd path ReadOnly Nothing defaultFlags+        WriteMode -> createFile path stdFileMode+        AppendMode -> openFd path WriteOnly (Just stdFileMode) appendFlags+        ReadWriteMode -> openFd path ReadWrite (Just stdFileMode) defaultFlags+    defaultFlags = OpenFileFlags+        { System.Posix.ByteString.append = False+        , exclusive = False+        , noctty = True+        , nonBlock = False+        , trunc = False+        }+    appendFlags = defaultFlags { System.Posix.ByteString.append = True }
+ src/System/RawFilePath.hs view
@@ -0,0 +1,263 @@+{-# language LambdaCase #-}++-- |+-- Module      : System.RawFilePath+-- Copyright   : (c) XT 2016+-- License     : BSD-style+--+-- Maintainer  : e@xtendo.org+-- Stability   : experimental+-- Portability : POSIX+--+-- Higher-level API for the 'RawFilePath'-variants of functions in the 'unix'+-- module.++module System.RawFilePath+    ( RawFilePath+    -- * Process+    , callProcess+    , callProcessSilent+    , readProcess+    , readProcessEither+    -- * Directory+    , listDirectory+    , getDirectoryFiles+    , getDirectoryFilesRecursive+    , copyFile+    , getHomeDirectory+    , doesFileExist+    , doesDirectoryExist+    , setCurrentDirectory+    , tryRemoveFile+    ) where++import Data.Monoid+import Control.Monad+import Control.Exception++import Data.ByteString (ByteString)+import qualified Data.ByteString as B++import System.IO+import System.IO.Error+import System.Exit (ExitCode(..))++import Foreign.Marshal.Alloc (allocaBytes)+import System.Posix.ByteString++processError :: RawFilePath -> IOError+processError cmd = mkIOError userErrorType+    ("calling process " <> show cmd) Nothing (Just $ show cmd)++-- | Creates a new process to run the specified command with the given+-- arguments, and waits for it to finish. Throws an exception if the process+-- returns a nonzero exit code.+--+-- > *System.RawFilePath> callProcess "ls" ["-a", "src"]+-- > .  ..  System+callProcess+    :: RawFilePath -- ^ Command to run+    -> [ByteString] -- ^ Command arguments+    -> IO ()+callProcess cmd args = do+    pid <- forkProcess $ executeFile cmd True args Nothing+    getProcessStatus True False pid >>= \case+        Just status -> case status of+            Exited exitCode -> case exitCode of+                ExitSuccess -> return ()+                ExitFailure _ -> failure+            _ -> failure+        Nothing -> failure+  where+    failure = ioError (processError cmd)++-- | Same as 'callProcess' except the child process will share the parent\'s+-- stdout and stderr, meaning it won\'t print anything.+callProcessSilent+    :: RawFilePath -- ^ Command to run+    -> [ByteString] -- ^ Command arguments+    -> IO ExitCode+callProcessSilent cmd args = do+    pid <- forkProcess $ do+        closeFd stdOutput+        closeFd stdError+        executeFile cmd True args Nothing+    getProcessStatus True False pid >>= \case+        Just status -> case status of+            Exited exitCode -> return exitCode+            _ -> failure+        Nothing -> failure+  where+    failure = ioError (processError cmd)++getContentsAndClose :: Handle -> IO ByteString+getContentsAndClose h = B.hGetContents h <* hClose h++-- | Runs a command, reads its standard output strictly, blocking until the process terminates, and returns the output as 'ByteString'.+--+-- > *System.RawFilePath> readProcess "date" ["+%s"]+-- > "1469999314\n"+readProcess+    :: RawFilePath -- ^ Command to run+    -> [ByteString] -- ^ Command arguments+    -> IO ByteString -- ^ The output from the command+readProcess cmd args = do+    (fd0, fd1) <- createPipe+    pid <- forkProcess $ do+        closeFd fd0+        closeFd stdOutput+        closeFd stdError+        void $ dupTo fd1 stdOutput+        executeFile cmd True args Nothing+    closeFd fd1+    (fdToHandle fd0 >>= getContentsAndClose) <*+        getProcessStatus True False pid++-- | A \'safer\' approach to 'readProcess'. Depending on the exit status of+-- the process, this function will return output either from stderr or stdout.+--+-- > *System.RawFilePath> readProcessEither "date" ["%s"]+-- > Left "date: invalid date \226\128\152%s\226\128\153\n"+-- > *System.RawFilePath> readProcessEither "date" ["+%s"]+-- > Right "1469999817\n"+readProcessEither+    :: RawFilePath -- ^ Command to run+    -> [ByteString] -- ^ Command arguments+    -> IO (Either ByteString ByteString) -- ^ Either the stedrr output from+    -- the command if it finished with a nonzero exit code, or the stdout data+    -- if it finished normally.+readProcessEither cmd args = do+    (fd0, fd1) <- createPipe+    (efd0, efd1) <- createPipe+    pid <- forkProcess $ do+        closeFd fd0+        closeFd stdOutput+        void $ dupTo fd1 stdOutput+        closeFd efd0+        closeFd stdError+        void $ dupTo efd1 stdError+        executeFile cmd True args Nothing+    closeFd fd1+    closeFd efd1+    content <- fdToHandle fd0 >>= getContentsAndClose+    err <- fdToHandle efd0 >>= getContentsAndClose+    getProcessStatus True False pid >>= \case+        Just status -> case status of+            Exited exitCode -> case exitCode of+                ExitSuccess -> return $ Right content+                ExitFailure _ -> return $ Left err+            _ -> return $ Left err+        Nothing -> return $ Left err++-- | Get a list of files in the specified directory, excluding "." and ".."+--+-- > *System.RawFilePath> listDirectory "src"+-- > ["..","System","."]+listDirectory+    :: RawFilePath -- ^ The path of directory to inspect+    -> IO [RawFilePath] -- ^ A list of files in the directory+listDirectory dirPath = filter f <$> getDirectoryFiles dirPath+  where+    f p = p /= "." && p /= ".."++-- | Get a list of files in the specified directory, including "." and ".."+--+-- > *System.RawFilePath> getDirectoryFiles "src"+-- > ["..","System","."]+getDirectoryFiles+    :: RawFilePath -- ^ The path of directory to inspect+    -> IO [RawFilePath] -- ^ A list of files in the directory+getDirectoryFiles dirPath = bracket open close repeatRead+  where+    open = openDirStream dirPath+    close = closeDirStream+    repeatRead stream = do+        d <- readDirStream stream+        if B.length d == 0 then return [] else do+            rest <- repeatRead stream+            return $ d : rest++-- | Recursively get all files in all subdirectories of the specified+-- directory.+--+-- > *System.RawFilePath> getDirectoryFilesRecursive "src"+-- > ["src/System/RawFilePath.hs"]+getDirectoryFilesRecursive+    :: RawFilePath -- ^ The path of directory to inspect+    -> IO [RawFilePath] -- ^ A list of relative paths+getDirectoryFilesRecursive path = do+    names <- map (path </>) . filter (\x -> x /= ".." && x /= ".") <$>+        getDirectoryFiles path+    inspectedNames <- mapM inspect names+    return $ concat inspectedNames+  where+    inspect :: RawFilePath -> IO [RawFilePath]+    inspect p = fmap isDirectory (getFileStatus p) >>= \i -> if i+        then getDirectoryFilesRecursive p else return [p]++defaultFlags :: OpenFileFlags+defaultFlags = OpenFileFlags+    { append = False+    , exclusive = False+    , noctty = True+    , nonBlock = False+    , trunc = False+    }++-- Buffer size for file copy+bufferSize :: Int+bufferSize = 4096++-- | Copy a file from the source path to the destination path.+copyFile+    :: RawFilePath -- ^ The source path+    -> RawFilePath -- ^ The destination path+    -> IO ()+copyFile srcPath tgtPath = do+    bracket ropen hClose $ \hi ->+        bracket topen hClose $ \ho ->+            allocaBytes bufferSize $ copyContents hi ho+    rename tmpPath tgtPath+  where+    ropen = openFd srcPath ReadOnly Nothing defaultFlags >>= fdToHandle+    topen = createFile tmpPath stdFileMode >>= fdToHandle+    tmpPath = tgtPath <> ".copyFile.tmp"+    copyContents hi ho buffer = do+        count <- hGetBuf hi buffer bufferSize+        when (count > 0) $ do+            hPutBuf ho buffer count+            copyContents hi ho buffer++-- | A function that "tries" to remove a file. If the file does not exist,+-- nothing happens.+tryRemoveFile :: RawFilePath -> IO ()+tryRemoveFile path = catchIOError (removeLink path) $+    \e -> unless (isDoesNotExistError e) $ ioError e++-- | Returns the current user\'s home directory.+getHomeDirectory :: IO RawFilePath+getHomeDirectory = getEnv "HOME" >>= maybe err return+  where+    err = ioError $ mkIOError doesNotExistErrorType errMsg Nothing Nothing+    errMsg = "Environment variable $HOME"++-- | Returns 'True' if the argument file exists and is not a directory.+doesFileExist :: RawFilePath -> IO Bool+doesFileExist path = fileExist path >>= \i -> if i+    then not . isDirectory <$> getFileStatus path+    else return False++-- | Returns 'True' if the argument file exists and is a directory.+doesDirectoryExist :: RawFilePath -> IO Bool+doesDirectoryExist path = fileExist path >>= \i -> if i+    then isDirectory <$> getFileStatus path+    else return False++-- | Change the working directory to the given path.+setCurrentDirectory :: RawFilePath -> IO ()+setCurrentDirectory = changeWorkingDirectory++-- An extremely simplistic approach for path concatenation.+infixr 5  </>+(</>) :: RawFilePath -> RawFilePath -> RawFilePath+a </> b = mconcat [a, "/", b]
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"