diff --git a/CHANGELOG.md b/CHANGELOG.md index cce686d..44586b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ Bugfixes: Other improvements: +## [v8.2.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.2.0) - 2023-03-23 + +New features: +- Add FFI for `access`, `copyFile` and `mkdtemp` (#73 by @JordanMartinez) + ## [v8.1.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.1.1) - 2022-10-24 Other improvements: diff --git a/src/Node/FS.purs b/src/Node/FS.purs index 22f1b26..11a0c66 100644 --- a/src/Node/FS.purs +++ b/src/Node/FS.purs @@ -1,6 +1,5 @@ module Node.FS ( FileDescriptor(..) - , FileFlags(..) , FileMode(..) , SymlinkType(..) , symlinkTypeToNode @@ -8,50 +7,14 @@ module Node.FS , BufferOffset(..) , ByteCount(..) , FilePosition(..) - , fileFlagsToNode + , module Exports ) where import Prelude -foreign import data FileDescriptor :: Type - -data FileFlags = R | R_PLUS | RS | RS_PLUS - | W | WX | W_PLUS | WX_PLUS - | A | AX | A_PLUS | AX_PLUS - -instance showFileFlags :: Show FileFlags where - show R = "R" - show R_PLUS = "R_PLUS" - show RS = "RS" - show RS_PLUS = "RS_PLUS" - show W = "W" - show WX = "WX" - show W_PLUS = "W_PLUS" - show WX_PLUS = "WX_PLUS" - show A = "A" - show AX = "AX" - show A_PLUS = "A_PLUS" - show AX_PLUS = "AX_PLUS" +import Node.FS.Constants (FileFlags(..), fileFlagsToNode) as Exports -instance eqFileFlags :: Eq FileFlags where - eq x y = show x == show y - --- | Convert a `FileFlags` to a `String` in the format expected by the Node.js --- | filesystem API. -fileFlagsToNode :: FileFlags -> String -fileFlagsToNode ff = case ff of - R -> "r" - R_PLUS -> "r+" - RS -> "rs" - RS_PLUS -> "rs+" - W -> "w" - WX -> "wx" - W_PLUS -> "w+" - WX_PLUS -> "wx+" - A -> "a" - AX -> "ax" - A_PLUS -> "a+" - AX_PLUS -> "ax+" +foreign import data FileDescriptor :: Type type FileMode = Int type FilePosition = Int @@ -71,12 +34,12 @@ symlinkTypeToNode ty = case ty of JunctionLink -> "junction" instance showSymlinkType :: Show SymlinkType where - show FileLink = "FileLink" - show DirLink = "DirLink" + show FileLink = "FileLink" + show DirLink = "DirLink" show JunctionLink = "JunctionLink" instance eqSymlinkType :: Eq SymlinkType where - eq FileLink FileLink = true - eq DirLink DirLink = true + eq FileLink FileLink = true + eq DirLink DirLink = true eq JunctionLink JunctionLink = true eq _ _ = false diff --git a/src/Node/FS/Async.js b/src/Node/FS/Async.js index 3ddc7a7..cf935da 100644 --- a/src/Node/FS/Async.js +++ b/src/Node/FS/Async.js @@ -1,4 +1,7 @@ export { + access as accessImpl, + copyFile as copyFileImpl, + mkdtemp as mkdtempImpl, rename as renameImpl, truncate as truncateImpl, chown as chownImpl, @@ -22,4 +25,4 @@ export { read as readImpl, write as writeImpl, close as closeImpl -} from "fs"; +} from "node:fs"; diff --git a/src/Node/FS/Async.purs b/src/Node/FS/Async.purs index 998bde4..8fe33bd 100644 --- a/src/Node/FS/Async.purs +++ b/src/Node/FS/Async.purs @@ -1,5 +1,11 @@ module Node.FS.Async - ( Callback (..) + ( Callback(..) + , access + , access' + , copyFile + , copyFile' + , mkdtemp + , mkdtemp' , rename , truncate , chown @@ -45,10 +51,11 @@ import Data.Nullable (Nullable, toMaybe, toNullable) import Data.Time.Duration (Milliseconds(..)) import Effect (Effect) import Effect.Exception (Error) -import Effect.Uncurried (EffectFn2, EffectFn3, EffectFn4, EffectFn6, mkEffectFn2, runEffectFn2, runEffectFn3, runEffectFn4, runEffectFn6) +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, EffectFn6, mkEffectFn1, mkEffectFn2, runEffectFn2, runEffectFn3, runEffectFn4, runEffectFn6) import Node.Buffer (Buffer, size) -import Node.Encoding (Encoding) -import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, FileFlags, SymlinkType, fileFlagsToNode, symlinkTypeToNode) +import Node.Encoding (Encoding(..), encodingToNode) +import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode) +import Node.FS.Constants (FileFlags, fileFlagsToNode, AccessMode, CopyMode, defaultAccessMode, defaultCopyMode) import Node.FS.Perms (Perms, permsToString, all, mkPerms) import Node.FS.Stats (StatsObj, Stats(..)) import Node.Path (FilePath) @@ -63,6 +70,31 @@ handleCallback cb = mkEffectFn2 \err a -> case toMaybe err of -- | Type synonym for callback functions. type Callback a = Either Error a -> Effect Unit +access :: FilePath -> (Maybe Error -> Effect Unit) -> Effect Unit +access path = access' path defaultAccessMode + +access' :: FilePath -> AccessMode -> (Maybe Error -> Effect Unit) -> Effect Unit +access' path mode cb = runEffectFn3 accessImpl path mode $ mkEffectFn1 \err -> do + cb $ toMaybe err + +foreign import accessImpl :: EffectFn3 FilePath AccessMode (EffectFn1 (Nullable Error) Unit) Unit + +copyFile :: FilePath -> FilePath -> Callback Unit -> Effect Unit +copyFile src dest = copyFile' src dest defaultCopyMode + +copyFile' :: FilePath -> FilePath -> CopyMode -> Callback Unit -> Effect Unit +copyFile' src dest mode cb = runEffectFn4 copyFileImpl src dest mode (handleCallback cb) + +foreign import copyFileImpl :: EffectFn4 FilePath FilePath CopyMode (JSCallback Unit) Unit + +mkdtemp :: String -> Callback String -> Effect Unit +mkdtemp prefix = mkdtemp' prefix UTF8 + +mkdtemp' :: String -> Encoding -> Callback String -> Effect Unit +mkdtemp' prefix encoding cb = runEffectFn3 mkdtempImpl prefix (encodingToNode encoding) (handleCallback cb) + +foreign import mkdtempImpl :: EffectFn3 FilePath String (JSCallback String) Unit + foreign import renameImpl :: EffectFn3 FilePath FilePath (JSCallback Unit) Unit foreign import truncateImpl :: EffectFn3 FilePath Int (JSCallback Unit) Unit foreign import chownImpl :: EffectFn4 FilePath Int Int (JSCallback Unit) Unit @@ -87,146 +119,138 @@ foreign import readImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLe foreign import writeImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) (JSCallback ByteCount) Unit foreign import closeImpl :: EffectFn2 FileDescriptor (JSCallback Unit) Unit - -- | Renames a file. -rename :: FilePath - -> FilePath - -> Callback Unit - -> Effect Unit -rename oldFile newFile cb = runEffectFn3 - renameImpl oldFile newFile (handleCallback cb) +rename + :: FilePath + -> FilePath + -> Callback Unit + -> Effect Unit +rename oldFile newFile cb = runEffectFn3 renameImpl oldFile newFile (handleCallback cb) -- | Truncates a file to the specified length. -truncate :: FilePath - -> Int - -> Callback Unit - -> Effect Unit - -truncate file len cb = runEffectFn3 - truncateImpl file len (handleCallback cb) +truncate + :: FilePath + -> Int + -> Callback Unit + -> Effect Unit +truncate file len cb = runEffectFn3 truncateImpl file len (handleCallback cb) -- | Changes the ownership of a file. -chown :: FilePath - -> Int - -> Int - -> Callback Unit - -> Effect Unit - -chown file uid gid cb = runEffectFn4 - chownImpl file uid gid (handleCallback cb) +chown + :: FilePath + -> Int + -> Int + -> Callback Unit + -> Effect Unit +chown file uid gid cb = runEffectFn4 chownImpl file uid gid (handleCallback cb) -- | Changes the permissions of a file. -chmod :: FilePath - -> Perms - -> Callback Unit - -> Effect Unit - -chmod file perms cb = runEffectFn3 - chmodImpl file (permsToString perms) (handleCallback cb) +chmod + :: FilePath + -> Perms + -> Callback Unit + -> Effect Unit +chmod file perms cb = runEffectFn3 chmodImpl file (permsToString perms) (handleCallback cb) -- | Gets file statistics. -stat :: FilePath - -> Callback Stats - -> Effect Unit - -stat file cb = runEffectFn2 - statImpl file (handleCallback $ cb <<< map Stats) +stat + :: FilePath + -> Callback Stats + -> Effect Unit +stat file cb = runEffectFn2 statImpl file (handleCallback $ cb <<< map Stats) -- | Gets file or symlink statistics. `lstat` is identical to `stat`, except -- | that if theĀ `FilePath` is a symbolic link, then the link itself is stat-ed, -- | not the file that it refers to. -lstat :: FilePath - -> Callback Stats - -> Effect Unit -lstat file cb = runEffectFn2 - lstatImpl file (handleCallback $ cb <<< map Stats) +lstat + :: FilePath + -> Callback Stats + -> Effect Unit +lstat file cb = runEffectFn2 lstatImpl file (handleCallback $ cb <<< map Stats) -- | Creates a link to an existing file. -link :: FilePath - -> FilePath - -> Callback Unit - -> Effect Unit - -link src dst cb = runEffectFn3 - linkImpl src dst (handleCallback cb) +link + :: FilePath + -> FilePath + -> Callback Unit + -> Effect Unit +link src dst cb = runEffectFn3 linkImpl src dst (handleCallback cb) -- | Creates a symlink. -symlink :: FilePath - -> FilePath - -> SymlinkType - -> Callback Unit - -> Effect Unit - -symlink src dest ty cb = runEffectFn4 - symlinkImpl src dest (symlinkTypeToNode ty) (handleCallback cb) +symlink + :: FilePath + -> FilePath + -> SymlinkType + -> Callback Unit + -> Effect Unit +symlink src dest ty cb = runEffectFn4 symlinkImpl src dest (symlinkTypeToNode ty) (handleCallback cb) -- | Reads the value of a symlink. -readlink :: FilePath - -> Callback FilePath - -> Effect Unit - -readlink path cb = runEffectFn2 - readlinkImpl path (handleCallback cb) +readlink + :: FilePath + -> Callback FilePath + -> Effect Unit +readlink path cb = runEffectFn2 readlinkImpl path (handleCallback cb) -- | Find the canonicalized absolute location for a path. -realpath :: FilePath - -> Callback FilePath - -> Effect Unit - -realpath path cb = runEffectFn3 - realpathImpl path {} (handleCallback cb) +realpath + :: FilePath + -> Callback FilePath + -> Effect Unit +realpath path cb = runEffectFn3 realpathImpl path {} (handleCallback cb) -- | Find the canonicalized absolute location for a path using a cache object -- | for already resolved paths. -realpath' :: forall cache. FilePath - -> { | cache } - -> Callback FilePath - -> Effect Unit - -realpath' path cache cb = runEffectFn3 - realpathImpl path cache (handleCallback cb) +realpath' + :: forall cache + . FilePath + -> { | cache } + -> Callback FilePath + -> Effect Unit +realpath' path cache cb = runEffectFn3 realpathImpl path cache (handleCallback cb) -- | Deletes a file. -unlink :: FilePath - -> Callback Unit - -> Effect Unit - -unlink file cb = runEffectFn2 - unlinkImpl file (handleCallback cb) +unlink + :: FilePath + -> Callback Unit + -> Effect Unit +unlink file cb = runEffectFn2 unlinkImpl file (handleCallback cb) -- | Deletes a directory. -rmdir :: FilePath - -> Callback Unit - -> Effect Unit +rmdir + :: FilePath + -> Callback Unit + -> Effect Unit rmdir path cb = rmdir' path { maxRetries: 0, retryDelay: 100 } cb -- | Deletes a directory with options. -rmdir' :: FilePath - -> { maxRetries :: Int, retryDelay :: Int } - -> Callback Unit - -> Effect Unit -rmdir' path opts cb = runEffectFn3 - rmdirImpl path opts (handleCallback cb) +rmdir' + :: FilePath + -> { maxRetries :: Int, retryDelay :: Int } + -> Callback Unit + -> Effect Unit +rmdir' path opts cb = runEffectFn3 rmdirImpl path opts (handleCallback cb) -- | Deletes a file or directory. -rm :: FilePath - -> Callback Unit - -> Effect Unit +rm + :: FilePath + -> Callback Unit + -> Effect Unit rm path = rm' path { force: false, maxRetries: 100, recursive: false, retryDelay: 1000 } -- | Deletes a file or directory with options. -rm' :: FilePath - -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } - -> Callback Unit - -> Effect Unit -rm' path opts cb = runEffectFn3 - rmImpl path opts (handleCallback cb) - +rm' + :: FilePath + -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } + -> Callback Unit + -> Effect Unit +rm' path opts cb = runEffectFn3 rmImpl path opts (handleCallback cb) -- | Makes a new directory. -mkdir :: FilePath - -> Callback Unit - -> Effect Unit +mkdir + :: FilePath + -> Callback Unit + -> Effect Unit mkdir path = mkdir' path { recursive: false, mode: mkPerms all all all } -- | Makes a new directory with the specified permissions. @@ -235,144 +259,137 @@ mkdir' -> { recursive :: Boolean, mode :: Perms } -> Callback Unit -> Effect Unit -mkdir' file { recursive, mode: perms } cb = runEffectFn3 - mkdirImpl file { recursive, mode: permsToString perms } (handleCallback cb) +mkdir' file { recursive, mode: perms } cb = runEffectFn3 mkdirImpl file { recursive, mode: permsToString perms } (handleCallback cb) -- | Reads the contents of a directory. -readdir :: FilePath - -> Callback (Array FilePath) - -> Effect Unit - -readdir file cb = runEffectFn2 - readdirImpl file (handleCallback cb) +readdir + :: FilePath + -> Callback (Array FilePath) + -> Effect Unit +readdir file cb = runEffectFn2 readdirImpl file (handleCallback cb) -- | Sets the accessed and modified times for the specified file. -utimes :: FilePath - -> DateTime - -> DateTime - -> Callback Unit - -> Effect Unit - -utimes file atime mtime cb = runEffectFn4 - utimesImpl file - (fromDate atime) - (fromDate mtime) - (handleCallback cb) +utimes + :: FilePath + -> DateTime + -> DateTime + -> Callback Unit + -> Effect Unit +utimes file atime mtime cb = runEffectFn4 utimesImpl file (fromDate atime) (fromDate mtime) (handleCallback cb) where fromDate date = ms (toEpochMilliseconds date) / 1000 ms (Milliseconds n) = round n toEpochMilliseconds = unInstant <<< fromDateTime -- | Reads the entire contents of a file returning the result as a raw buffer. -readFile :: FilePath - -> Callback Buffer - -> Effect Unit - -readFile file cb = runEffectFn3 - readFileImpl file {} (handleCallback cb) +readFile + :: FilePath + -> Callback Buffer + -> Effect Unit +readFile file cb = runEffectFn3 readFileImpl file {} (handleCallback cb) -- | Reads the entire contents of a text file with the specified encoding. -readTextFile :: Encoding - -> FilePath - -> Callback String - -> Effect Unit - -readTextFile encoding file cb = runEffectFn3 - readFileImpl file { encoding: show encoding } (handleCallback cb) +readTextFile + :: Encoding + -> FilePath + -> Callback String + -> Effect Unit +readTextFile encoding file cb = runEffectFn3 readFileImpl file { encoding: show encoding } (handleCallback cb) -- | Writes a buffer to a file. -writeFile :: FilePath - -> Buffer - -> Callback Unit - -> Effect Unit - -writeFile file buff cb = runEffectFn4 - writeFileImpl file buff {} (handleCallback cb) +writeFile + :: FilePath + -> Buffer + -> Callback Unit + -> Effect Unit +writeFile file buff cb = runEffectFn4 writeFileImpl file buff {} (handleCallback cb) -- | Writes text to a file using the specified encoding. -writeTextFile :: Encoding - -> FilePath - -> String - -> Callback Unit - -> Effect Unit - -writeTextFile encoding file buff cb = runEffectFn4 - writeFileImpl file buff { encoding: show encoding } (handleCallback cb) +writeTextFile + :: Encoding + -> FilePath + -> String + -> Callback Unit + -> Effect Unit +writeTextFile encoding file buff cb = runEffectFn4 writeFileImpl file buff { encoding: show encoding } (handleCallback cb) -- | Appends the contents of a buffer to a file. -appendFile :: FilePath - -> Buffer - -> Callback Unit - -> Effect Unit - -appendFile file buff cb = runEffectFn4 - appendFileImpl file buff {} (handleCallback cb) +appendFile + :: FilePath + -> Buffer + -> Callback Unit + -> Effect Unit +appendFile file buff cb = runEffectFn4 appendFileImpl file buff {} (handleCallback cb) -- | Appends text to a file using the specified encoding. -appendTextFile :: Encoding - -> FilePath - -> String - -> Callback Unit - -> Effect Unit - -appendTextFile encoding file buff cb = runEffectFn4 - appendFileImpl file buff { encoding: show encoding } (handleCallback cb) - +appendTextFile + :: Encoding + -> FilePath + -> String + -> Callback Unit + -> Effect Unit +appendTextFile encoding file buff cb = runEffectFn4 appendFileImpl file buff { encoding: show encoding } (handleCallback cb) -- | Open a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback) -- | for details. -fdOpen :: FilePath - -> FileFlags - -> Maybe FileMode - -> Callback FileDescriptor - -> Effect Unit +fdOpen + :: FilePath + -> FileFlags + -> Maybe FileMode + -> Callback FileDescriptor + -> Effect Unit fdOpen file flags mode cb = runEffectFn4 openImpl file (fileFlagsToNode flags) (toNullable mode) (handleCallback cb) -- | Read from a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback) -- | for details. -fdRead :: FileDescriptor - -> Buffer - -> BufferOffset - -> BufferLength - -> Maybe FilePosition - -> Callback ByteCount - -> Effect Unit +fdRead + :: FileDescriptor + -> Buffer + -> BufferOffset + -> BufferLength + -> Maybe FilePosition + -> Callback ByteCount + -> Effect Unit fdRead fd buff off len pos cb = runEffectFn6 readImpl fd buff off len (toNullable pos) (handleCallback cb) -- | Convenience function to fill the whole buffer from the current -- | file position. -fdNext :: FileDescriptor - -> Buffer - -> Callback ByteCount - -> Effect Unit +fdNext + :: FileDescriptor + -> Buffer + -> Callback ByteCount + -> Effect Unit fdNext fd buff cb = do sz <- size buff fdRead fd buff 0 sz Nothing cb -- | Write to a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_write_fd_buffer_offset_length_position_callback) -- | for details. -fdWrite :: FileDescriptor - -> Buffer - -> BufferOffset - -> BufferLength - -> Maybe FilePosition - -> Callback ByteCount - -> Effect Unit +fdWrite + :: FileDescriptor + -> Buffer + -> BufferOffset + -> BufferLength + -> Maybe FilePosition + -> Callback ByteCount + -> Effect Unit fdWrite fd buff off len pos cb = runEffectFn6 writeImpl fd buff off len (toNullable pos) (handleCallback cb) -- | Convenience function to append the whole buffer to the current -- | file position. -fdAppend :: FileDescriptor - -> Buffer - -> Callback ByteCount - -> Effect Unit +fdAppend + :: FileDescriptor + -> Buffer + -> Callback ByteCount + -> Effect Unit fdAppend fd buff cb = do sz <- size buff fdWrite fd buff 0 sz Nothing cb -- | Close a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_close_fd_callback) -- | for details. -fdClose :: FileDescriptor - -> Callback Unit - -> Effect Unit +fdClose + :: FileDescriptor + -> Callback Unit + -> Effect Unit fdClose fd cb = runEffectFn2 closeImpl fd (handleCallback cb) diff --git a/src/Node/FS/Constants.js b/src/Node/FS/Constants.js new file mode 100644 index 0000000..b71e93c --- /dev/null +++ b/src/Node/FS/Constants.js @@ -0,0 +1,17 @@ +import { constants } from "node:fs"; + +export const f_OK = constants.F_OK; + +export const r_OK = constants.R_OK; + +export const w_OK = constants.W_OK; + +export const x_OK = constants.X_OK; + +export const copyFile_EXCL = constants.COPYFILE_EXCL; + +export const copyFile_FICLONE = constants.COPYFILE_FICLONE; + +export const copyFile_FICLONE_FORCE = constants.COPYFILE_FICLONE_FORCE; + +export const appendCopyMode = (l, r) => l | r; diff --git a/src/Node/FS/Constants.purs b/src/Node/FS/Constants.purs new file mode 100644 index 0000000..c6bbcab --- /dev/null +++ b/src/Node/FS/Constants.purs @@ -0,0 +1,90 @@ +module Node.FS.Constants where + +import Prelude + +import Data.Function.Uncurried (Fn2, runFn2) + +-- | the mode parameter passed to `access` and `accessSync`. +foreign import data AccessMode :: Type + +-- | the file is visible to the calling process. +-- | This is useful for determining if a file exists, but says nothing about rwx permissions. Default if no mode is specified. +foreign import f_OK :: AccessMode + +-- | the file can be read by the calling process. +foreign import r_OK :: AccessMode + +-- | the file can be written by the calling process. +foreign import w_OK :: AccessMode + +-- | the file can be executed by the calling process. This has no effect on Windows (will behave like fs.constants.F_OK). +foreign import x_OK :: AccessMode + +defaultAccessMode = f_OK :: AccessMode + +-- | A constant used in `copyFile`. +foreign import data CopyMode :: Type + +-- | If present, the copy operation will fail with an error if the destination path already exists. +foreign import copyFile_EXCL :: CopyMode + +-- | If present, the copy operation will attempt to create a copy-on-write reflink. If the underlying platform does not support copy-on-write, then a fallback copy mechanism is used. +foreign import copyFile_FICLONE :: CopyMode + +-- | If present, the copy operation will attempt to create a copy-on-write reflink. If the underlying platform does not support copy-on-write, then the operation will fail with an error. +foreign import copyFile_FICLONE_FORCE :: CopyMode + +defaultCopyMode = copyFile_EXCL :: CopyMode + +foreign import appendCopyMode :: Fn2 CopyMode CopyMode CopyMode + +instance Semigroup CopyMode where + append l r = runFn2 appendCopyMode l r + +data FileFlags + = R + | R_PLUS + | RS + | RS_PLUS + | W + | WX + | W_PLUS + | WX_PLUS + | A + | AX + | A_PLUS + | AX_PLUS + +instance showFileFlags :: Show FileFlags where + show R = "R" + show R_PLUS = "R_PLUS" + show RS = "RS" + show RS_PLUS = "RS_PLUS" + show W = "W" + show WX = "WX" + show W_PLUS = "W_PLUS" + show WX_PLUS = "WX_PLUS" + show A = "A" + show AX = "AX" + show A_PLUS = "A_PLUS" + show AX_PLUS = "AX_PLUS" + +instance eqFileFlags :: Eq FileFlags where + eq x y = show x == show y + +-- | Convert a `FileFlags` to a `String` in the format expected by the Node.js +-- | filesystem API. +fileFlagsToNode :: FileFlags -> String +fileFlagsToNode ff = case ff of + R -> "r" + R_PLUS -> "r+" + RS -> "rs" + RS_PLUS -> "rs+" + W -> "w" + WX -> "wx" + W_PLUS -> "w+" + WX_PLUS -> "wx+" + A -> "a" + AX -> "ax" + A_PLUS -> "a+" + AX_PLUS -> "ax+" diff --git a/src/Node/FS/Perms.purs b/src/Node/FS/Perms.purs index cd0c2e7..a00c458 100644 --- a/src/Node/FS/Perms.purs +++ b/src/Node/FS/Perms.purs @@ -1,13 +1,15 @@ module Node.FS.Perms - ( Perm() + ( Perm , mkPerm , none , read , write , execute , all - , Perms() + , Perms , mkPerms + , permsAll + , permsReadWrite , permsFromString , permsToString , permsToInt @@ -40,29 +42,30 @@ newtype Perm = Perm { r :: Boolean, w :: Boolean, x :: Boolean } instance eqPerm :: Eq Perm where eq (Perm { r: r1, w: w1, x: x1 }) (Perm { r: r2, w: w2, x: x2 }) = - r1 == r2 && w1 == w2 && x1 == x2 + r1 == r2 && w1 == w2 && x1 == x2 instance ordPerm :: Ord Perm where compare (Perm { r: r1, w: w1, x: x1 }) (Perm { r: r2, w: w2, x: x2 }) = - compare [r1, w1, x1] [r2, w2, x2] + compare [ r1, w1, x1 ] [ r2, w2, x2 ] instance showPerm :: Show Perm where show p | p == none = "none" - show p | p == all = "all" + show p | p == all = "all" show (Perm { r: r, w: w, x: x }) = joinWith " + " ps where ps = - (if r then ["read"] else []) <> - (if w then ["write"] else []) <> - (if x then ["execute"] else []) + (if r then [ "read" ] else []) + <> (if w then [ "write" ] else []) + <> + (if x then [ "execute" ] else []) instance semiringPerm :: Semiring Perm where add (Perm { r: r0, w: w0, x: x0 }) (Perm { r: r1, w: w1, x: x1 }) = - Perm { r: r0 || r1, w: w0 || w1, x: x0 || x1 } + Perm { r: r0 || r1, w: w0 || w1, x: x0 || x1 } zero = Perm { r: false, w: false, x: false } mul (Perm { r: r0, w: w0, x: x0 }) (Perm { r: r1, w: w1, x: x1 }) = - Perm { r: r0 && r1, w: w0 && w1, x: x0 && x1 } + Perm { r: r0 && r1, w: w0 && w1, x: x0 && x1 } one = Perm { r: true, w: true, x: true } -- | Create a `Perm` value. The arguments represent the readable, writable, and @@ -100,20 +103,22 @@ newtype Perms = Perms { u :: Perm, g :: Perm, o :: Perm } instance eqPerms :: Eq Perms where eq (Perms { u: u1, g: g1, o: o1 }) (Perms { u: u2, g: g2, o: o2 }) = - u1 == u2 && g1 == g2 && o1 == o2 + u1 == u2 && g1 == g2 && o1 == o2 instance ordPerms :: Ord Perms where compare (Perms { u: u1, g: g1, o: o1 }) (Perms { u: u2, g: g2, o: o2 }) = - compare [u1, g1, o1] [u2, g2, o2] + compare [ u1, g1, o1 ] [ u2, g2, o2 ] instance showPerms :: Show Perms where show (Perms { u: u, g: g, o: o }) = - "mkPerms " <> joinWith " " (f <$> [u, g, o]) + "mkPerms " <> joinWith " " (f <$> [ u, g, o ]) where - f perm = let str = show perm - in if isNothing $ indexOf (Pattern " ") str - then str - else "(" <> str <> ")" + f perm = + let + str = show perm + in + if isNothing $ indexOf (Pattern " ") str then str + else "(" <> str <> ")" -- | Attempt to parse a `Perms` value from a `String` containing an octal -- | integer. For example, @@ -121,17 +126,17 @@ instance showPerms :: Show Perms where permsFromString :: String -> Maybe Perms permsFromString = _perms <<< toCharArray <<< dropPrefix zeroChar where - zeroChar = unsafePartial $ fromJust $ toEnum 48 + zeroChar = unsafePartial $ fromJust $ toEnum 48 - dropPrefix x xs - | charAt 0 xs == Just x = drop 1 xs - | otherwise = xs + dropPrefix x xs + | charAt 0 xs == Just x = drop 1 xs + | otherwise = xs - _perms [u, g, o] = - mkPerms <$> permFromChar u - <*> permFromChar g - <*> permFromChar o - _perms _ = Nothing + _perms [ u, g, o ] = + mkPerms <$> permFromChar u + <*> permFromChar g + <*> permFromChar o + _perms _ = Nothing permFromChar :: Char -> Maybe Perm permFromChar c = case c of @@ -143,13 +148,19 @@ permFromChar c = case c of '5' -> Just $ read + execute '6' -> Just $ read + write '7' -> Just $ read + write + execute - _ -> Nothing + _ -> Nothing -- | Create a `Perms` value. The arguments represent the owner's, group's, and -- | other users' permission sets, respectively. mkPerms :: Perm -> Perm -> Perm -> Perms mkPerms u g o = Perms { u: u, g: g, o: o } +permsAll :: Perms +permsAll = mkPerms all all all + +permsReadWrite :: Perms +permsReadWrite = mkPerms all all none + -- | Convert a `Perm` to an octal digit. For example: -- | -- | * `permToInt r == 4` @@ -157,7 +168,7 @@ mkPerms u g o = Perms { u: u, g: g, o: o } -- | * `permToInt (r + w) == 6` permToInt :: Perm -> Int permToInt (Perm { r: r, w: w, x: x }) = - (if r then 4 else 0) + (if r then 4 else 0) + (if w then 2 else 0) + (if x then 1 else 0) @@ -170,10 +181,10 @@ permToString = show <<< permToInt -- | `permsToString (mkPerms (read + write) read read) == "0644"` permsToString :: Perms -> String permsToString (Perms { u: u, g: g, o: o }) = - "0" - <> permToString u - <> permToString g - <> permToString o + "0" + <> permToString u + <> permToString g + <> permToString o -- | Convert a `Perms` value to an `Int`, via `permsToString`. permsToInt :: Perms -> Int diff --git a/src/Node/FS/Stats.purs b/src/Node/FS/Stats.purs index 13bea3d..24b5ac7 100644 --- a/src/Node/FS/Stats.purs +++ b/src/Node/FS/Stats.purs @@ -1,6 +1,6 @@ module Node.FS.Stats - ( Stats (..) - , StatsObj (..) + ( Stats(..) + , StatsObj(..) , isFile , isDirectory , isBlockDevice @@ -14,6 +14,7 @@ module Node.FS.Stats ) where import Prelude + import Data.DateTime (DateTime) import Data.Function.Uncurried (Fn2, Fn0, runFn2) import Data.JSDate (JSDate, toDateTime) diff --git a/src/Node/FS/Stream.purs b/src/Node/FS/Stream.purs index f795445..5ed7563 100644 --- a/src/Node/FS/Stream.purs +++ b/src/Node/FS/Stream.purs @@ -19,13 +19,14 @@ import Data.Maybe (Maybe(..)) import Data.Nullable (Nullable, toNullable) import Effect (Effect) import Effect.Uncurried (EffectFn2, runEffectFn2) -import Node.FS (FileDescriptor, FileFlags(..), fileFlagsToNode) +import Node.FS (FileDescriptor) +import Node.FS.Constants (FileFlags(..), fileFlagsToNode) import Node.FS.Perms (Perms) import Node.FS.Perms as Perms import Node.Path (FilePath) import Node.Stream (Readable, Writable) -foreign import createReadStreamImpl :: forall opts. EffectFn2 (Nullable FilePath) { | opts } (Readable ()) +foreign import createReadStreamImpl :: forall opts. EffectFn2 (Nullable FilePath) { | opts } (Readable ()) foreign import createWriteStreamImpl :: forall opts. EffectFn2 (Nullable FilePath) { | opts } (Writable ()) readWrite :: Perms @@ -41,14 +42,16 @@ nonnull = toNullable <<< Just -- | Create a Writable stream which writes data to the specified file, using -- | the default options. -createWriteStream :: FilePath - -> Effect (Writable ()) +createWriteStream + :: FilePath + -> Effect (Writable ()) createWriteStream = createWriteStreamWith defaultWriteStreamOptions -- | Create a Writable stream which writes data to the specified file -- | descriptor, using the default options. -fdCreateWriteStream :: FileDescriptor - -> Effect (Writable ()) +fdCreateWriteStream + :: FileDescriptor + -> Effect (Writable ()) fdCreateWriteStream = fdCreateWriteStreamWith defaultWriteStreamOptions type WriteStreamOptions = @@ -63,41 +66,47 @@ defaultWriteStreamOptions = } -- | Like `createWriteStream`, but allows you to pass options. -createWriteStreamWith :: WriteStreamOptions - -> FilePath - -> Effect (Writable ()) +createWriteStreamWith + :: WriteStreamOptions + -> FilePath + -> Effect (Writable ()) createWriteStreamWith opts file = runEffectFn2 - createWriteStreamImpl (nonnull file) - { mode: Perms.permsToInt opts.perms - , flags: fileFlagsToNode opts.flags - } + createWriteStreamImpl + (nonnull file) + { mode: Perms.permsToInt opts.perms + , flags: fileFlagsToNode opts.flags + } -- | Like `fdCreateWriteStream`, but allows you to pass options. -fdCreateWriteStreamWith :: WriteStreamOptions - -> FileDescriptor - -> Effect (Writable ()) +fdCreateWriteStreamWith + :: WriteStreamOptions + -> FileDescriptor + -> Effect (Writable ()) fdCreateWriteStreamWith opts fd = runEffectFn2 - createWriteStreamImpl null - { fd - , mode: Perms.permsToInt opts.perms - , flags: fileFlagsToNode opts.flags - } + createWriteStreamImpl + null + { fd + , mode: Perms.permsToInt opts.perms + , flags: fileFlagsToNode opts.flags + } -- | Create a Readable stream which reads data to the specified file, using -- | the default options. -createReadStream :: FilePath - -> Effect (Readable ()) +createReadStream + :: FilePath + -> Effect (Readable ()) createReadStream = createReadStreamWith defaultReadStreamOptions -- | Create a Readable stream which reads data to the specified file -- | descriptor, using the default options. -fdCreateReadStream :: FileDescriptor - -> Effect (Readable ()) +fdCreateReadStream + :: FileDescriptor + -> Effect (Readable ()) fdCreateReadStream = fdCreateReadStreamWith defaultReadStreamOptions type ReadStreamOptions = - { flags :: FileFlags - , perms :: Perms + { flags :: FileFlags + , perms :: Perms , autoClose :: Boolean } @@ -109,24 +118,28 @@ defaultReadStreamOptions = } -- | Create a Readable stream which reads data from the specified file. -createReadStreamWith :: ReadStreamOptions - -> FilePath - -> Effect (Readable ()) +createReadStreamWith + :: ReadStreamOptions + -> FilePath + -> Effect (Readable ()) createReadStreamWith opts file = runEffectFn2 - createReadStreamImpl (nonnull file) - { mode: Perms.permsToInt opts.perms - , flags: fileFlagsToNode opts.flags - , autoClose: opts.autoClose - } + createReadStreamImpl + (nonnull file) + { mode: Perms.permsToInt opts.perms + , flags: fileFlagsToNode opts.flags + , autoClose: opts.autoClose + } -- | Create a Readable stream which reads data from the specified file descriptor. -fdCreateReadStreamWith :: ReadStreamOptions - -> FileDescriptor - -> Effect (Readable ()) +fdCreateReadStreamWith + :: ReadStreamOptions + -> FileDescriptor + -> Effect (Readable ()) fdCreateReadStreamWith opts fd = runEffectFn2 - createReadStreamImpl null - { fd - , mode: Perms.permsToInt opts.perms - , flags: fileFlagsToNode opts.flags - , autoClose: opts.autoClose - } + createReadStreamImpl + null + { fd + , mode: Perms.permsToInt opts.perms + , flags: fileFlagsToNode opts.flags + , autoClose: opts.autoClose + } diff --git a/src/Node/FS/Sync.js b/src/Node/FS/Sync.js index dcebaaa..acddc23 100644 --- a/src/Node/FS/Sync.js +++ b/src/Node/FS/Sync.js @@ -1,4 +1,7 @@ -export { +export { + accessSync as accessImpl, + copyFileSync as copyFileImpl, + mkdtempSync as mkdtempImpl, renameSync as renameSyncImpl, truncateSync as truncateSyncImpl, chownSync as chownSyncImpl, @@ -24,4 +27,4 @@ export { writeSync as writeSyncImpl, fsyncSync as fsyncSyncImpl, closeSync as closeSyncImpl -} from "fs"; +} from "node:fs"; diff --git a/src/Node/FS/Sync.purs b/src/Node/FS/Sync.purs index c89c2ec..9cadc4f 100644 --- a/src/Node/FS/Sync.purs +++ b/src/Node/FS/Sync.purs @@ -1,5 +1,11 @@ module Node.FS.Sync - ( rename + ( access + , access' + , copyFile + , copyFile' + , mkdtemp + , mkdtemp' + , rename , truncate , chown , chmod @@ -36,23 +42,49 @@ module Node.FS.Sync ) where import Prelude -import Effect (Effect) + import Data.DateTime (DateTime) -import Data.Time.Duration (Milliseconds(..)) import Data.DateTime.Instant (fromDateTime, unInstant) -import Data.Nullable (Nullable(), toNullable) +import Data.Either (blush) import Data.Int (round) import Data.Maybe (Maybe(..)) +import Data.Nullable (Nullable, toNullable) +import Data.Time.Duration (Milliseconds(..)) +import Effect (Effect) +import Effect.Exception (Error, try) import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn5, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn5) -import Node.Buffer (Buffer(), size) -import Node.Encoding (Encoding) - -import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, - BufferOffset, FileMode, FileFlags, SymlinkType, - fileFlagsToNode, symlinkTypeToNode) -import Node.FS.Stats (StatsObj, Stats(..)) -import Node.Path (FilePath()) +import Node.Buffer (Buffer, size) +import Node.Encoding (Encoding(..), encodingToNode) +import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode) +import Node.FS.Constants (AccessMode, CopyMode, FileFlags, defaultAccessMode, defaultCopyMode, fileFlagsToNode) import Node.FS.Perms (Perms, permsToString, all, mkPerms) +import Node.FS.Stats (StatsObj, Stats(..)) +import Node.Path (FilePath) + +access :: FilePath -> Effect (Maybe Error) +access = flip access' defaultAccessMode + +access' :: FilePath -> AccessMode -> Effect (Maybe Error) +access' path mode = do + map blush $ try $ runEffectFn2 accessImpl path mode + +foreign import accessImpl :: EffectFn2 FilePath AccessMode (Maybe Error) + +copyFile :: FilePath -> FilePath -> Effect Unit +copyFile src dest = runEffectFn3 copyFileImpl src dest defaultCopyMode + +copyFile' :: FilePath -> FilePath -> CopyMode -> Effect Unit +copyFile' src dest mode = runEffectFn3 copyFileImpl src dest mode + +foreign import copyFileImpl :: EffectFn3 FilePath FilePath CopyMode Unit + +mkdtemp :: String -> Effect String +mkdtemp prefix = mkdtemp' prefix UTF8 + +mkdtemp' :: String -> Encoding -> Effect String +mkdtemp' prefix encoding = runEffectFn2 mkdtempImpl prefix (encodingToNode encoding) + +foreign import mkdtempImpl :: EffectFn2 String String String foreign import renameSyncImpl :: EffectFn2 FilePath FilePath Unit foreign import truncateSyncImpl :: EffectFn2 FilePath Int Unit @@ -81,129 +113,117 @@ foreign import fsyncSyncImpl :: EffectFn1 FileDescriptor Unit foreign import closeSyncImpl :: EffectFn1 FileDescriptor Unit -- | Renames a file. -rename :: FilePath - -> FilePath - -> Effect Unit - -rename oldFile newFile = runEffectFn2 - renameSyncImpl oldFile newFile +rename :: FilePath -> FilePath -> Effect Unit +rename oldFile newFile = runEffectFn2 renameSyncImpl oldFile newFile -- | Truncates a file to the specified length. -truncate :: FilePath - -> Int - -> Effect Unit - -truncate file len = runEffectFn2 - truncateSyncImpl file len +truncate + :: FilePath + -> Int + -> Effect Unit +truncate file len = runEffectFn2 truncateSyncImpl file len -- | Changes the ownership of a file. -chown :: FilePath - -> Int - -> Int - -> Effect Unit - -chown file uid gid = runEffectFn3 - chownSyncImpl file uid gid +chown + :: FilePath + -> Int + -> Int + -> Effect Unit +chown file uid gid = runEffectFn3 chownSyncImpl file uid gid -- | Changes the permissions of a file. -chmod :: FilePath - -> Perms - -> Effect Unit - -chmod file perms = runEffectFn2 - chmodSyncImpl file (permsToString perms) +chmod + :: FilePath + -> Perms + -> Effect Unit +chmod file perms = runEffectFn2 chmodSyncImpl file (permsToString perms) -- | Gets file statistics. -stat :: FilePath - -> Effect Stats - -stat file = map Stats $ runEffectFn1 - statSyncImpl file +stat + :: FilePath + -> Effect Stats +stat file = map Stats $ runEffectFn1 statSyncImpl file -- | Gets file or symlink statistics. `lstat` is identical to `stat`, except -- | that if theĀ `FilePath` is a symbolic link, then the link itself is stat-ed, -- | not the file that it refers to. -lstat :: FilePath - -> Effect Stats - -lstat file = map Stats $ runEffectFn1 - lstatSyncImpl file +lstat + :: FilePath + -> Effect Stats +lstat file = map Stats $ runEffectFn1 lstatSyncImpl file -- | Creates a link to an existing file. -link :: FilePath - -> FilePath - -> Effect Unit - -link src dst = runEffectFn2 - linkSyncImpl src dst +link + :: FilePath + -> FilePath + -> Effect Unit +link src dst = runEffectFn2 linkSyncImpl src dst -- | Creates a symlink. -symlink :: FilePath - -> FilePath - -> SymlinkType - -> Effect Unit - -symlink src dst ty = runEffectFn3 - symlinkSyncImpl src dst (symlinkTypeToNode ty) +symlink + :: FilePath + -> FilePath + -> SymlinkType + -> Effect Unit +symlink src dst ty = runEffectFn3 symlinkSyncImpl src dst (symlinkTypeToNode ty) -- | Reads the value of a symlink. -readlink :: FilePath - -> Effect FilePath - -readlink path = runEffectFn1 - readlinkSyncImpl path +readlink + :: FilePath + -> Effect FilePath +readlink path = runEffectFn1 readlinkSyncImpl path -- | Find the canonicalized absolute location for a path. -realpath :: FilePath - -> Effect FilePath - -realpath path = runEffectFn2 - realpathSyncImpl path {} +realpath + :: FilePath + -> Effect FilePath +realpath path = runEffectFn2 realpathSyncImpl path {} -- | Find the canonicalized absolute location for a path using a cache object for -- | already resolved paths. -realpath' :: forall cache. FilePath - -> { | cache } - -> Effect FilePath - -realpath' path cache = runEffectFn2 - realpathSyncImpl path cache +realpath' + :: forall cache + . FilePath + -> { | cache } + -> Effect FilePath +realpath' path cache = runEffectFn2 realpathSyncImpl path cache -- | Deletes a file. -unlink :: FilePath - -> Effect Unit - -unlink file = runEffectFn1 - unlinkSyncImpl file +unlink + :: FilePath + -> Effect Unit +unlink file = runEffectFn1 unlinkSyncImpl file -- | Deletes a directory. -rmdir :: FilePath - -> Effect Unit +rmdir + :: FilePath + -> Effect Unit rmdir path = rmdir' path { maxRetries: 0, retryDelay: 100 } -- | Deletes a directory with options. -rmdir' :: FilePath - -> { maxRetries :: Int, retryDelay :: Int } - -> Effect Unit -rmdir' path opts = runEffectFn2 - rmdirSyncImpl path opts +rmdir' + :: FilePath + -> { maxRetries :: Int, retryDelay :: Int } + -> Effect Unit +rmdir' path opts = runEffectFn2 rmdirSyncImpl path opts -- | Deletes a file or directory. -rm :: FilePath - -> Effect Unit +rm + :: FilePath + -> Effect Unit rm path = rm' path { force: false, maxRetries: 100, recursive: false, retryDelay: 1000 } -- | Deletes a file or directory with options. -rm' :: FilePath - -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } - -> Effect Unit -rm' path opts = runEffectFn2 - rmSyncImpl path opts - +rm' + :: FilePath + -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } + -> Effect Unit +rm' path opts = runEffectFn2 rmSyncImpl path opts -- | Makes a new directory. -mkdir :: FilePath - -> Effect Unit +mkdir + :: FilePath + -> Effect Unit mkdir path = mkdir' path { recursive: false, mode: mkPerms all all all } -- | Makes a new directory with the specified permissions. @@ -211,142 +231,138 @@ mkdir' :: FilePath -> { recursive :: Boolean, mode :: Perms } -> Effect Unit -mkdir' file { recursive, mode: perms } = runEffectFn2 - mkdirSyncImpl file { recursive, mode: permsToString perms } +mkdir' file { recursive, mode: perms } = runEffectFn2 mkdirSyncImpl file { recursive, mode: permsToString perms } -- | Reads the contents of a directory. -readdir :: FilePath - -> Effect (Array FilePath) - -readdir file = runEffectFn1 - readdirSyncImpl file +readdir + :: FilePath + -> Effect (Array FilePath) +readdir file = runEffectFn1 readdirSyncImpl file -- | Sets the accessed and modified times for the specified file. -utimes :: FilePath - -> DateTime - -> DateTime - -> Effect Unit - -utimes file atime mtime = runEffectFn3 - utimesSyncImpl file - (fromDate atime) - (fromDate mtime) +utimes + :: FilePath + -> DateTime + -> DateTime + -> Effect Unit +utimes file atime mtime = runEffectFn3 utimesSyncImpl file (fromDate atime) (fromDate mtime) where fromDate date = ms (toEpochMilliseconds date) / 1000 ms (Milliseconds n) = round n toEpochMilliseconds = unInstant <<< fromDateTime -- | Reads the entire contents of a file returning the result as a raw buffer. -readFile :: FilePath - -> Effect Buffer - -readFile file = runEffectFn2 - readFileSyncImpl file {} +readFile + :: FilePath + -> Effect Buffer +readFile file = runEffectFn2 readFileSyncImpl file {} -- | Reads the entire contents of a text file with the specified encoding. -readTextFile :: Encoding - -> FilePath - -> Effect String - -readTextFile encoding file = runEffectFn2 - readFileSyncImpl file { encoding: show encoding } +readTextFile + :: Encoding + -> FilePath + -> Effect String +readTextFile encoding file = runEffectFn2 readFileSyncImpl file { encoding: show encoding } -- | Writes a buffer to a file. -writeFile :: FilePath - -> Buffer - -> Effect Unit - -writeFile file buff = runEffectFn3 - writeFileSyncImpl file buff {} +writeFile + :: FilePath + -> Buffer + -> Effect Unit +writeFile file buff = runEffectFn3 writeFileSyncImpl file buff {} -- | Writes text to a file using the specified encoding. -writeTextFile :: Encoding - -> FilePath - -> String - -> Effect Unit - -writeTextFile encoding file text = runEffectFn3 - writeFileSyncImpl file text { encoding: show encoding } +writeTextFile + :: Encoding + -> FilePath + -> String + -> Effect Unit +writeTextFile encoding file text = runEffectFn3 writeFileSyncImpl file text { encoding: show encoding } -- | Appends the contents of a buffer to a file. -appendFile :: FilePath - -> Buffer - -> Effect Unit - -appendFile file buff = runEffectFn3 - appendFileSyncImpl file buff {} +appendFile + :: FilePath + -> Buffer + -> Effect Unit +appendFile file buff = runEffectFn3 appendFileSyncImpl file buff {} -- | Appends text to a file using the specified encoding. -appendTextFile :: Encoding - -> FilePath - -> String - -> Effect Unit - -appendTextFile encoding file buff = runEffectFn3 - appendFileSyncImpl file buff { encoding: show encoding } +appendTextFile + :: Encoding + -> FilePath + -> String + -> Effect Unit +appendTextFile encoding file buff = runEffectFn3 appendFileSyncImpl file buff { encoding: show encoding } -- | Check if the path exists. -exists :: FilePath - -> Effect Boolean +exists + :: FilePath + -> Effect Boolean exists file = runEffectFn1 existsSyncImpl file -- | Open a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_opensync_path_flags_mode) -- | for details. -fdOpen :: FilePath - -> FileFlags - -> Maybe FileMode - -> Effect FileDescriptor -fdOpen file flags mode = runEffectFn3 - openSyncImpl file (fileFlagsToNode flags) (toNullable mode) +fdOpen + :: FilePath + -> FileFlags + -> Maybe FileMode + -> Effect FileDescriptor +fdOpen file flags mode = runEffectFn3 openSyncImpl file (fileFlagsToNode flags) (toNullable mode) -- | Read from a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_readsync_fd_buffer_offset_length_position) -- | for details. -fdRead :: FileDescriptor - -> Buffer - -> BufferOffset - -> BufferLength - -> Maybe FilePosition - -> Effect ByteCount +fdRead + :: FileDescriptor + -> Buffer + -> BufferOffset + -> BufferLength + -> Maybe FilePosition + -> Effect ByteCount fdRead fd buff off len pos = runEffectFn5 readSyncImpl fd buff off len (toNullable pos) -- | Convenience function to fill the whole buffer from the current -- | file position. -fdNext :: FileDescriptor - -> Buffer - -> Effect ByteCount +fdNext + :: FileDescriptor + -> Buffer + -> Effect ByteCount fdNext fd buff = do sz <- size buff fdRead fd buff 0 sz Nothing -- | Write to a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_writesync_fd_buffer_offset_length_position) -- | for details. -fdWrite :: FileDescriptor - -> Buffer - -> BufferOffset - -> BufferLength - -> Maybe FilePosition - -> Effect ByteCount +fdWrite + :: FileDescriptor + -> Buffer + -> BufferOffset + -> BufferLength + -> Maybe FilePosition + -> Effect ByteCount fdWrite fd buff off len pos = runEffectFn5 writeSyncImpl fd buff off len (toNullable pos) -- | Convenience function to append the whole buffer to the current -- | file position. -fdAppend :: FileDescriptor - -> Buffer - -> Effect ByteCount +fdAppend + :: FileDescriptor + -> Buffer + -> Effect ByteCount fdAppend fd buff = do sz <- size buff fdWrite fd buff 0 sz Nothing -- | Flush a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_fsyncsync_fd) -- | for details. -fdFlush :: FileDescriptor - -> Effect Unit +fdFlush + :: FileDescriptor + -> Effect Unit fdFlush fd = runEffectFn1 fsyncSyncImpl fd -- | Close a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_closesync_fd) -- | for details. -fdClose :: FileDescriptor - -> Effect Unit +fdClose + :: FileDescriptor + -> Effect Unit fdClose fd = runEffectFn1 closeSyncImpl fd diff --git a/test/Streams.purs b/test/Streams.purs index 8e75daf..231939f 100644 --- a/test/Streams.purs +++ b/test/Streams.purs @@ -1,12 +1,12 @@ module Test.Streams where import Prelude + import Effect (Effect) import Effect.Console (log) import Node.Encoding (Encoding(..)) import Node.Path as Path import Node.Stream as Stream - import Node.FS.Stream (createWriteStream, createReadStream) import Node.FS.Sync as Sync @@ -16,15 +16,14 @@ main = do _ <- log "Testing streams" - r <- createReadStream (fp ["test", "Streams.purs"]) - w <- createWriteStream (fp ["tmp", "Streams.purs"]) + r <- createReadStream (fp [ "test", "Streams.purs" ]) + w <- createWriteStream (fp [ "tmp", "Streams.purs" ]) _ <- Stream.pipe r w Stream.onEnd r do - src <- Sync.readTextFile UTF8 (fp ["test", "Streams.purs"]) - dst <- Sync.readTextFile UTF8 (fp ["tmp", "Streams.purs"]) + src <- Sync.readTextFile UTF8 (fp [ "test", "Streams.purs" ]) + dst <- Sync.readTextFile UTF8 (fp [ "tmp", "Streams.purs" ]) - if src == dst - then log "all good" - else log "not good" + if src == dst then log "all good" + else log "not good" diff --git a/test/Test.purs b/test/Test.purs index c58ff50..914cd6b 100644 --- a/test/Test.purs +++ b/test/Test.purs @@ -2,56 +2,62 @@ module Test where import Prelude -import Data.Either (Either(..), either) -import Data.Maybe (Maybe(..)) -import Data.Traversable (traverse) +import Data.Either (Either(..), either, isRight) +import Data.Maybe (Maybe(..), isNothing) +import Data.Traversable (for_, traverse) import Effect (Effect) import Effect.Console (log) -import Effect.Exception (Error, error, throwException, catchException) +import Effect.Exception (Error, catchException, error, message, throw, throwException, try) import Node.Buffer as Buffer import Node.Encoding (Encoding(..)) import Node.FS (FileFlags(..), SymlinkType(..)) import Node.FS.Async as A +import Node.FS.Constants (copyFile_EXCL, r_OK, w_OK) +import Node.FS.Perms (mkPerms, permsAll) +import Node.FS.Perms as Perms import Node.FS.Stats (statusChangedTime, accessedTime, modifiedTime, isSymbolicLink, isSocket, isFIFO, isCharacterDevice, isBlockDevice, isDirectory, isFile) +import Node.FS.Sync (chmod) import Node.FS.Sync as S import Node.Path as Path import Unsafe.Coerce (unsafeCoerce) -- Cheat to allow `main` to type check. See also issue #5 in -- purescript-exceptions. -catchException' :: - forall a. - (Error -> Effect a) - -> Effect a - -> Effect a +catchException' + :: forall a + . (Error -> Effect a) + -> Effect a + -> Effect a catchException' = unsafeCoerce catchException - main :: Effect Unit main = do let fp = Path.concat - e <- S.exists (fp ["test", "Test.purs"]) + e <- S.exists (fp [ "test", "Test.purs" ]) log $ "Test.purs exists? " <> show e - file <- S.readTextFile UTF8 (fp ["test", "Test.purs"]) + file <- S.readTextFile UTF8 (fp [ "test", "Test.purs" ]) log "\n\nreadTextFile sync result:" log $ file - _ <- catchException' (\err -> do - log $ "Caught readTextFile error:\n" <> show err - pure "") $ S.readTextFile UTF8 (fp ["test", "does not exist"]) + _ <- + catchException' + ( \err -> do + log $ "Caught readTextFile error:\n" <> show err + pure "" + ) $ S.readTextFile UTF8 (fp [ "test", "does not exist" ]) -- If an error is thrown, it's probably EEXIST, so ignore it. Should -- really check this instead. catchException' (const (pure unit)) (S.mkdir "tmp") - S.writeTextFile ASCII (fp ["tmp", "Test.js"]) "console.log('hello world')" - S.rename (fp ["tmp", "Test.js"]) (fp ["tmp", "Test1.js"]) + S.writeTextFile ASCII (fp [ "tmp", "Test.js" ]) "console.log('hello world')" + S.rename (fp [ "tmp", "Test.js" ]) (fp [ "tmp", "Test1.js" ]) - S.truncate (fp ["tmp", "Test1.js"]) 1000 + S.truncate (fp [ "tmp", "Test1.js" ]) 1000 - stats <- S.stat (fp ["tmp", "Test1.js"]) + stats <- S.stat (fp [ "tmp", "Test1.js" ]) log "\n\nS.stat:" log "isFile:" log $ show $ isFile stats @@ -74,33 +80,33 @@ main = do log "statusChangedTime:" log $ show $ statusChangedTime stats - S.symlink (fp ["tmp", "Test1.js"]) (fp ["tmp", "TestSymlink.js"]) FileLink + S.symlink (fp [ "tmp", "Test1.js" ]) (fp [ "tmp", "TestSymlink.js" ]) FileLink - lstats <- S.lstat (fp ["tmp", "TestSymlink.js"]) + lstats <- S.lstat (fp [ "tmp", "TestSymlink.js" ]) log "\n\nS.lstat:" log "isSymbolicLink:" log $ show $ isSymbolicLink lstats - S.unlink (fp ["tmp", "TestSymlink.js"]) + S.unlink (fp [ "tmp", "TestSymlink.js" ]) - A.rename (fp ["tmp", "Test1.js"]) (fp ["tmp", "Test.js"]) $ \x -> do + A.rename (fp [ "tmp", "Test1.js" ]) (fp [ "tmp", "Test.js" ]) $ \x -> do log "\n\nrename result:" either (log <<< show) (log <<< show) x - A.truncate (fp ["tmp", "Test.js"]) 10 $ \y -> do + A.truncate (fp [ "tmp", "Test.js" ]) 10 $ \y -> do log "\n\ntruncate result:" either (log <<< show) (log <<< show) y - A.readFile (fp ["test", "Test.purs"]) $ \mbuf -> do + A.readFile (fp [ "test", "Test.purs" ]) $ \mbuf -> do buf <- traverse Buffer.freeze mbuf log "\n\nreadFile result:" either (log <<< show) (log <<< show) buf - A.readTextFile UTF8 (fp ["test", "Test.purs"]) $ \x -> do + A.readTextFile UTF8 (fp [ "test", "Test.purs" ]) $ \x -> do log "\n\nreadTextFile result:" either (log <<< show) log x - A.stat (fp ["test", "Test.purs"]) $ \x -> do + A.stat (fp [ "test", "Test.purs" ]) $ \x -> do log "\n\nA.stat:" case x of Left err -> log $ "Error:" <> show err @@ -126,10 +132,10 @@ main = do log "statusChangedTime:" log $ show $ statusChangedTime x' - A.symlink (fp ["tmp", "Test.js"]) (fp ["tmp", "TestSymlink.js"]) FileLink \u -> + A.symlink (fp [ "tmp", "Test.js" ]) (fp [ "tmp", "TestSymlink.js" ]) FileLink \u -> case u of Left err -> log $ "Error:" <> show err - Right _ -> A.lstat (fp ["tmp", "TestSymlink.js"]) \s -> do + Right _ -> A.lstat (fp [ "tmp", "TestSymlink.js" ]) \s -> do log "\n\nA.lstat:" case s of Left err -> log $ "Error:" <> show err @@ -137,11 +143,11 @@ main = do log "isSymbolicLink:" log $ show $ isSymbolicLink s' - A.unlink (fp ["tmp", "TestSymlink.js"]) \result -> do + A.unlink (fp [ "tmp", "TestSymlink.js" ]) \result -> do log "\n\nA.unlink result:" either (log <<< show) (\_ -> log "Success") result - let fdFile = fp ["tmp", "FD.json"] + let fdFile = fp [ "tmp", "FD.json" ] fd0 <- S.fdOpen fdFile W (Just 420) buf0 <- Buffer.fromString "[ 42 ]" UTF8 bytes0 <- S.fdAppend fd0 buf0 @@ -154,8 +160,39 @@ main = do log "statSync on a non-existing file should be catchable" r <- catchException' - (const (pure true)) - (S.stat "this-does-not-exist" *> pure false) + (const (pure true)) + (S.stat "this-does-not-exist" *> pure false) unless r $ throwException (error "FS.Sync.stat should have thrown") + log "access tests" + mbNotExistsErr <- S.access "./test/not-exists.txt" + when (isNothing mbNotExistsErr) do + throw "`access \"./test/not-exists.txt\"` should produce error" + + let readableFixturePath = "./test/fixtures/readable.txt" + chmod readableFixturePath $ mkPerms Perms.read Perms.read Perms.read + + mbErr <- S.access' readableFixturePath r_OK + for_ mbErr \err -> do + throw $ "`access \"" <> readableFixturePath <> "\" R_OK` should not produce error.\n" <> message err + mbWriteErr <- S.access' readableFixturePath w_OK + case mbWriteErr of + Just _ -> pure unit + Nothing -> throw $ "`access \"" <> readableFixturePath <> "\" W_OK` should produce error" + + log "copy tests" + let outerTmpDir = "./test/node-fs-tests" + S.mkdir' outerTmpDir { recursive: true, mode: permsAll } + tempDir <- S.mkdtemp outerTmpDir + S.mkdir' tempDir { recursive: true, mode: permsAll } + let destReadPath = Path.concat [ tempDir, "readable.txt" ] + S.copyFile readableFixturePath destReadPath + unlessM (S.exists destReadPath) do + throw $ destReadPath <> " does not exist after copy" + + copyErr <- try $ S.copyFile' readableFixturePath destReadPath copyFile_EXCL + case copyErr of + Left _ -> pure unit + Right _ -> throw $ destReadPath <> " already exists, but copying a file to there did not throw an error with COPYFILE_EXCL option" + diff --git a/test/TestAsync.purs b/test/TestAsync.purs index 5bdbec9..d44f790 100644 --- a/test/TestAsync.purs +++ b/test/TestAsync.purs @@ -5,7 +5,6 @@ import Data.Either (Either(..)) import Data.Maybe (Maybe(..)) import Effect (Effect) import Effect.Console (log) - import Node.FS (FileFlags(..)) import Node.FS.Async as A import Node.Path as FP @@ -15,8 +14,9 @@ import Node.Buffer as B main :: Effect Unit main = do - let path1 = FP.concat( ["test", "TestAsync.purs"] ) - path2 = FP.concat( ["test", "TestAsync.purs.partial"] ) + let + path1 = FP.concat ([ "test", "TestAsync.purs" ]) + path2 = FP.concat ([ "test", "TestAsync.purs.partial" ]) buf <- B.create 1000 @@ -34,16 +34,16 @@ main = do log ("opened " <> path2) A.fdAppend fd2 buf $ \d -> case d of (Left err) -> log ("err:" <> show err) - (Right nbytes) -> do + (Right nbytes) -> do log ("wrote " <> show nbytes) A.fdClose fd2 $ \e -> case e of (Left err) -> log ("err:" <> show err) - (Right _) -> do + (Right _) -> do log ("closed " <> path2) A.fdClose fd $ \f -> case f of (Left err) -> log ("err:" <> show err) - (Right _) -> do + (Right _) -> do log ("closed " <> path1) A.unlink path2 $ \g -> case g of (Left err) -> log ("err:" <> show err) - (Right _) -> log ("unlinked " <> path2) + (Right _) -> log ("unlinked " <> path2) diff --git a/test/fixtures/readable.txt b/test/fixtures/readable.txt new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/test/fixtures/readable.txt @@ -0,0 +1 @@ +foo