diff --git a/src/Node/FS/Aff.purs b/src/Node/FS/Aff.purs index af722e3..78322e6 100644 --- a/src/Node/FS/Aff.purs +++ b/src/Node/FS/Aff.purs @@ -53,8 +53,10 @@ module Node.FS.Aff , fdWriteString , fdAppend , fdClose - , cp - , cp' + , cpFile + , cpFile' + , cpDir + , cpDir' , fchmod , fchown , fdatasync @@ -91,7 +93,7 @@ import Node.Buffer (Buffer) import Node.Encoding (Encoding) import Node.FS.Types (BufferLength, BufferOffset, ByteCount, FileDescriptor, FileMode, FilePosition, SymlinkType) import Node.FS.Internal.AffUtils (toAff1, toAff2, toAff3, toAff4, toAff5) -import Node.FS.Options (AppendFileBufferOptions, CpOptions, FdReadOptions, FdWriteOptions, GlobDirentOptions, GlobFilePathOptions, MkdirOptions, OpendirOptions, ReadFileBufferOptions, ReadFileStringOptions, ReaddirBufferOptions, ReaddirDirentBufferOptions, ReaddirDirentOptions, ReaddirFilePathOptions, RealpathOptions, RmOptions, RmdirOptions, WriteFileBufferOptions, WriteFileStringOptions) +import Node.FS.Options import Node.FS.Constants (AccessMode, CopyMode, FileFlags) import Node.FS.Async as A import Node.FS.Dir (Dir) @@ -406,13 +408,23 @@ fdAppend = toAff2 A.fdAppend fdClose :: FileDescriptor -> Aff Unit fdClose = toAff1 A.fdClose --- | Copy a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fspromises_copyfile_src_dest_mode) +-- | Copy a file asynchronously using a `cp` command. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) -- | for details. -cp :: FilePath -> FilePath -> Aff Unit -cp = toAff2 A.cp +cpFile :: FilePath -> FilePath -> Aff Unit +cpFile = toAff2 A.cpFile -cp' :: FilePath -> FilePath -> CpOptions -> Aff Unit -cp' = toAff3 A.cp' +cpFile' :: FilePath -> FilePath -> CpFileOptions -> Aff Unit +cpFile' = toAff3 A.cpFile' + +-- | Copy a directory asynchronously using a `cp` command with option `recursive = true`. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) +-- | for details. +cpDir :: FilePath -> FilePath -> Aff Unit +cpDir = toAff2 A.cpDir + +cpDir' :: FilePath -> FilePath -> CpDirOptions -> Aff Unit +cpDir' = toAff3 A.cpDir' -- | Change permissions on a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchmod_fd_mode_callback) -- | for details. diff --git a/src/Node/FS/Async.purs b/src/Node/FS/Async.purs index 0f32438..c3ac5c7 100644 --- a/src/Node/FS/Async.purs +++ b/src/Node/FS/Async.purs @@ -53,8 +53,10 @@ module Node.FS.Async , fdWriteString , fdAppend , fdClose - , cp - , cp' + , cpFile + , cpFile' + , cpDir + , cpDir' , fchmod , fchown , fdatasync @@ -96,7 +98,7 @@ import Node.FS.Constants (AccessMode, CopyMode, FileFlags, defaultAccessMode, de import Node.FS.Dir (Dir) import Node.FS.Dirent (Dirent, DirentNameTypeBuffer, DirentNameTypeString) import Node.FS.Internal.Utils (Callback0, Callback1, JSCallback0, JSCallback1, JSCallback2, datetimeToUnixEpochTimeInSeconds, handleCallback0, handleCallback1, handleCallback1Tuple) -import Node.FS.Options (AppendFileBufferOptions, AppendFileOptionsInternal, AppendFileStringOptions, CpOptions, CpOptionsInternal, FdReadOptions, FdReadOptionsInternal, FdWriteOptions, FdWriteOptionsInternal, GlobDirentOptions, GlobFilePathOptions, GlobOptionsInternal, MkdirOptions, MkdirOptionsInternal, OpendirOptions, OpendirOptionsInternal, ReadFileBufferOptions, ReadFileOptionsInternal, ReadFileStringOptions, ReaddirBufferOptions, ReaddirDirentBufferOptions, ReaddirDirentOptions, ReaddirFilePathOptions, ReaddirOptionsInternal, RealpathOptions, RealpathOptionsInternal, RmOptions, RmdirOptions, WriteFileBufferOptions, WriteFileOptionsInternal, WriteFileStringOptions, appendFileBufferOptionsDefault, appendFileBufferOptionsToInternal, appendFileStringOptionsDefault, appendFileStringOptionsToInternal, cpOptionsDefault, cpOptionsToCpOptionsInternal, fdReadOptionsToInternal, fdWriteOptionsToInternal, globDirentOptionsDefault, globDirentOptionsToInternal, globFilePathOptionsDefault, globFilePathOptionsToInternal, mkdirOptionsDefault, mkdirOptionsToInternal, opendirOptionsDefault, opendirOptionsToInternal, readFileBufferOptionsDefault, readFileBufferOptionsToInternal, readFileStringOptionsDefault, readFileStringOptionsToInternal, readdirBufferOptionsDefault, readdirBufferOptionsToInternal, readdirDirentBufferOptionsDefault, readdirDirentBufferOptionsToInternal, readdirDirentOptionsDefault, readdirDirentOptionsToInternal, readdirFilePathOptionsDefault, readdirFilePathOptionsToInternal, realpathOptionsDefault, realpathOptionsToInternal, rmOptionsDefault, rmdirOptionsDefault, writeFileBufferOptionsDefault, writeFileBufferOptionsToInternal, writeFileStringOptionsDefault, writeFileStringOptionsToInternal) +import Node.FS.Options import Node.FS.Perms (Perms, permsToString) import Node.FS.Stats (Stats) import Node.FS.Types (EncodingString) @@ -591,13 +593,23 @@ fdClose -> Effect Unit fdClose fd cb = runEffectFn2 closeImpl fd (handleCallback0 cb) --- | Copy a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fspromises_copyfile_src_dest_mode) +-- | Copy a file asynchronously using a `cp` command. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) -- | for details. -cp :: FilePath -> FilePath -> Callback0 -> Effect Unit -cp src dest = cp' src dest cpOptionsDefault +cpFile :: FilePath -> FilePath -> Callback0 -> Effect Unit +cpFile src dest = cpFile' src dest cpFileOptionsDefault -cp' :: FilePath -> FilePath -> CpOptions -> Callback0 -> Effect Unit -cp' src dest opts cb = runEffectFn4 cpImpl src dest (cpOptionsToCpOptionsInternal opts) (handleCallback0 cb) +cpFile' :: FilePath -> FilePath -> CpFileOptions -> Callback0 -> Effect Unit +cpFile' src dest opts cb = runEffectFn4 cpImpl src dest (cpFileOptionsToCpOptionsInternal opts) (handleCallback0 cb) + +-- | Copy a directory asynchronously using a `cp` command with option `recursive = true`. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) +-- | for details. +cpDir :: FilePath -> FilePath -> Callback0 -> Effect Unit +cpDir src dest = cpDir' src dest cpDirOptionsDefault + +cpDir' :: FilePath -> FilePath -> CpDirOptions -> Callback0 -> Effect Unit +cpDir' src dest opts cb = runEffectFn4 cpImpl src dest (cpDirOptionsToCpOptionsInternal opts) (handleCallback0 cb) -- | Change permissions on a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchmod_fd_mode_callback) -- | for details. diff --git a/src/Node/FS/Options.purs b/src/Node/FS/Options.purs index 4f88659..8b6864f 100644 --- a/src/Node/FS/Options.purs +++ b/src/Node/FS/Options.purs @@ -250,41 +250,56 @@ fdWriteOptionsToInternal { offset, length, position } = { offset, length: Nullab type CpOptionsInternal = { dereference :: Boolean - , errorOnExist :: Boolean + , errorOnExist :: Boolean -- Whether to dereference symlinks -- if null - will throw "TypeError [ERR_INVALID_ARG_TYPE]: The "options.filter" property must be of type function. Received null" , filter :: Undefinable (Fn2 FilePath FilePath Boolean) , force :: Boolean - , mode :: CopyMode - , preserveTimestamps :: Boolean - , recursive :: Boolean - , verbatimSymlinks :: Boolean + , mode :: CopyMode -- Modifiers for copy operation + , preserveTimestamps :: Boolean -- Preserve timestamps from source + , recursive :: Boolean -- Copy directories recursively + , verbatimSymlinks :: Boolean -- Skip path resolution for symlinks } data CpForce = CpForce_False | CpForce_TrueWithoutErrorOnExit | CpForce_TrueWithErrorOnExit -type CpOptions = - { dereference :: Boolean -- Whether to dereference symlinks +type CpDirOptions = + { dereference :: Boolean , filter :: Maybe (FilePath -> FilePath -> Boolean) , force :: CpForce - , mode :: CopyMode -- Modifiers for copy operation - , preserveTimestamps :: Boolean -- Preserve timestamps from source - , recursive :: Boolean -- Copy directories recursively - , verbatimSymlinks :: Boolean -- Skip path resolution for symlinks + , mode :: CopyMode + , preserveTimestamps :: Boolean + , verbatimSymlinks :: Boolean + } + +type CpFileOptions = + { dereference :: Boolean + , force :: CpForce + , mode :: CopyMode + , preserveTimestamps :: Boolean + , verbatimSymlinks :: Boolean } -cpOptionsDefault :: CpOptions -cpOptionsDefault = +cpDirOptionsDefault :: CpDirOptions +cpDirOptionsDefault = { dereference: false , filter: Nothing , force: CpForce_TrueWithoutErrorOnExit , mode: copyFile_NO_FLAGS , preserveTimestamps: false - , recursive: false , verbatimSymlinks: false } -cpOptionsToCpOptionsInternal :: CpOptions -> CpOptionsInternal -cpOptionsToCpOptionsInternal opts = +cpFileOptionsDefault :: CpFileOptions +cpFileOptionsDefault = + { dereference: false + , force: CpForce_TrueWithoutErrorOnExit + , mode: copyFile_NO_FLAGS + , preserveTimestamps: false + , verbatimSymlinks: false + } + +cpDirOptionsToCpOptionsInternal :: CpDirOptions -> CpOptionsInternal +cpDirOptionsToCpOptionsInternal opts = { dereference: opts.dereference , errorOnExist: case opts.force of CpForce_TrueWithErrorOnExit -> true @@ -295,7 +310,23 @@ cpOptionsToCpOptionsInternal opts = _ -> true , mode: opts.mode , preserveTimestamps: opts.preserveTimestamps - , recursive: opts.recursive + , recursive: true + , verbatimSymlinks: opts.verbatimSymlinks + } + +cpFileOptionsToCpOptionsInternal :: CpFileOptions -> CpOptionsInternal +cpFileOptionsToCpOptionsInternal opts = + { dereference: opts.dereference + , errorOnExist: case opts.force of + CpForce_TrueWithErrorOnExit -> true + _ -> false + , filter: Undefinable.undefined + , force: case opts.force of + CpForce_False -> false + _ -> true + , mode: opts.mode + , preserveTimestamps: opts.preserveTimestamps + , recursive: false , verbatimSymlinks: opts.verbatimSymlinks } diff --git a/src/Node/FS/Sync.purs b/src/Node/FS/Sync.purs index 0a8067f..03d1b0b 100644 --- a/src/Node/FS/Sync.purs +++ b/src/Node/FS/Sync.purs @@ -54,8 +54,10 @@ module Node.FS.Sync , fdWriteString , fdAppend , fdClose - , cp - , cp' + , cpFile + , cpFile' + , cpDir + , cpDir' , fchmod , fchown , fdatasync @@ -96,7 +98,7 @@ import Node.FS.Constants (AccessMode, CopyMode, FileFlags, defaultAccessMode, de import Node.FS.Dir (Dir) import Node.FS.Dirent (Dirent, DirentNameTypeBuffer, DirentNameTypeString) import Node.FS.Internal.Utils (datetimeToUnixEpochTimeInSeconds) -import Node.FS.Options (AppendFileBufferOptions, AppendFileOptionsInternal, AppendFileStringOptions, CpOptions, CpOptionsInternal, FdReadOptions, FdReadOptionsInternal, FdWriteOptions, FdWriteOptionsInternal, GlobDirentOptions, GlobFilePathOptions, GlobOptionsInternal, MkdirOptions, MkdirOptionsInternal, OpendirOptions, OpendirOptionsInternal, ReadFileBufferOptions, ReadFileOptionsInternal, ReadFileStringOptions, ReaddirBufferOptions, ReaddirDirentBufferOptions, ReaddirDirentOptions, ReaddirFilePathOptions, ReaddirOptionsInternal, RealpathOptions, RealpathOptionsInternal, RmOptions, RmdirOptions, WriteFileBufferOptions, WriteFileOptionsInternal, WriteFileStringOptions, appendFileBufferOptionsDefault, appendFileBufferOptionsToInternal, appendFileStringOptionsDefault, appendFileStringOptionsToInternal, cpOptionsDefault, cpOptionsToCpOptionsInternal, fdReadOptionsToInternal, fdWriteOptionsToInternal, globDirentOptionsDefault, globDirentOptionsToInternal, globFilePathOptionsDefault, globFilePathOptionsToInternal, mkdirOptionsDefault, mkdirOptionsToInternal, opendirOptionsDefault, opendirOptionsToInternal, readFileBufferOptionsDefault, readFileBufferOptionsToInternal, readFileStringOptionsDefault, readFileStringOptionsToInternal, readdirBufferOptionsDefault, readdirBufferOptionsToInternal, readdirDirentBufferOptionsDefault, readdirDirentBufferOptionsToInternal, readdirDirentOptionsDefault, readdirDirentOptionsToInternal, readdirFilePathOptionsDefault, readdirFilePathOptionsToInternal, realpathOptionsDefault, realpathOptionsToInternal, rmOptionsDefault, rmdirOptionsDefault, writeFileBufferOptionsDefault, writeFileBufferOptionsToInternal, writeFileStringOptionsDefault, writeFileStringOptionsToInternal) +import Node.FS.Options import Node.FS.Perms (Perms, permsToString) import Node.FS.Stats (Stats) import Node.FS.Types (BufferLength, BufferOffset, ByteCount, FileDescriptor, FileMode, FilePosition, SymlinkType, EncodingString, symlinkTypeToNode) @@ -537,13 +539,23 @@ fdAppend fd buff = do fdClose :: FileDescriptor -> Effect Unit fdClose fd = runEffectFn1 closeSyncImpl fd --- | Copy a file synchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fspromises_copyfile_src_dest_mode) +-- | Copy a file synchronously using a `cp` command. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsyncsrc-dest-options) -- | for details. -cp :: FilePath -> FilePath -> Effect Unit -cp src dest = cp' src dest cpOptionsDefault +cpFile :: FilePath -> FilePath -> Effect Unit +cpFile src dest = cpFile' src dest cpFileOptionsDefault -cp' :: FilePath -> FilePath -> CpOptions -> Effect Unit -cp' src dest opts = runEffectFn3 cpSyncImpl src dest (cpOptionsToCpOptionsInternal opts) +cpFile' :: FilePath -> FilePath -> CpFileOptions -> Effect Unit +cpFile' src dest opts = runEffectFn3 cpSyncImpl src dest (cpFileOptionsToCpOptionsInternal opts) + +-- | Copy a directory synchronously using a `cp` command with option `recursive = true`. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsyncsrc-dest-options) +-- | for details. +cpDir :: FilePath -> FilePath -> Effect Unit +cpDir src dest = cpDir' src dest cpDirOptionsDefault + +cpDir' :: FilePath -> FilePath -> CpDirOptions -> Effect Unit +cpDir' src dest opts = runEffectFn3 cpSyncImpl src dest (cpDirOptionsToCpOptionsInternal opts) -- | Change permissions on a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchmod_fd_mode_callback) -- | for details. diff --git a/test/Test/Node/FS/Sync.purs b/test/Test/Node/FS/Sync.purs index ab27c19..c53f8a6 100644 --- a/test/Test/Node/FS/Sync.purs +++ b/test/Test/Node/FS/Sync.purs @@ -8,18 +8,20 @@ import Data.Traversable (for_, traverse) import Effect (Effect) import Effect.Console (log) import Effect.Exception (Error, catchException, error, message, throw, throwException, try) +import Effect.Exception as Error 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.Options (rmOptionsDefault) 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.Options (rmOptionsDefault) import Node.FS.Sync (chmod) import Node.FS.Sync as S import Node.Path as Path +import Test.Assert (assertEqual') import Unsafe.Coerce (unsafeCoerce) -- Cheat to allow `main` to type check. See also issue #5 in @@ -192,12 +194,30 @@ main = do S.copyFile readableFixturePath destReadPath unlessM (S.exists destReadPath) do throw $ destReadPath <> " does not exist after copy" - let destReadPath2 = Path.concat [ tempDir, "readable2.txt" ] - S.cp readableFixturePath destReadPath2 - unlessM (S.exists destReadPath2) do - throw $ destReadPath2 <> " 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" + + log "copy file using cp: ok" + let destReadPath2 = Path.concat [ tempDir, "readable2.txt" ] + S.cpFile readableFixturePath destReadPath2 + unlessM (S.exists destReadPath2) do + throw $ destReadPath2 <> " does not exist after copy" + + log "copy dir using cp: if copy using cpDir - ok" + S.cpDir (tempDir <> "/") (tempDir <> "2") + + log "copy dir using cp: if copy using cpDir - error - ERR_FS_EISDIR \"Recursive option not enabled\"" + let + cpFileShouldThrow_from = tempDir <> "/" + cpFileShouldThrow_to = tempDir <> "3" + cpDirError <- try $ S.cpFile cpFileShouldThrow_from cpFileShouldThrow_to + case cpDirError of + Left cpDirError' -> do + let + cpDirError'_message = Error.message cpDirError' + cpDirError'_code = (unsafeCoerce cpDirError' :: { code :: String }).code + assertEqual' "cpDirError'_message" { actual: cpDirError'_message, expected: "Recursive option not enabled, cannot copy a directory: " <> cpFileShouldThrow_from } + assertEqual' "cpDirError'_code" { actual: cpDirError'_code, expected: "ERR_FS_EISDIR" } + Right _ -> throw $ "cpFileShouldThrow: should have failed " <> show { cpFileShouldThrow_from, cpFileShouldThrow_to }