Skip to content

Commit

Permalink
fix: cp -> split on cpFile and cpDir
Browse files Browse the repository at this point in the history
  • Loading branch information
srghma committed Oct 11, 2024
1 parent 84bba38 commit e6785fb
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 47 deletions.
28 changes: 20 additions & 8 deletions src/Node/FS/Aff.purs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ module Node.FS.Aff
, fdWriteString
, fdAppend
, fdClose
, cp
, cp'
, cpFile
, cpFile'
, cpDir
, cpDir'
, fchmod
, fchown
, fdatasync
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
28 changes: 20 additions & 8 deletions src/Node/FS/Async.purs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ module Node.FS.Async
, fdWriteString
, fdAppend
, fdClose
, cp
, cp'
, cpFile
, cpFile'
, cpDir
, cpDir'
, fchmod
, fchown
, fdatasync
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
65 changes: 48 additions & 17 deletions src/Node/FS/Options.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down
28 changes: 20 additions & 8 deletions src/Node/FS/Sync.purs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ module Node.FS.Sync
, fdWriteString
, fdAppend
, fdClose
, cp
, cp'
, cpFile
, cpFile'
, cpDir
, cpDir'
, fchmod
, fchown
, fdatasync
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
32 changes: 26 additions & 6 deletions test/Test/Node/FS/Sync.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }

0 comments on commit e6785fb

Please sign in to comment.