mirror of
https://github.com/neovim/neovim.git
synced 2024-09-17 20:58:20 -04:00
vim-patch:9.1.0465: missing filecopy() function (#29989)
Problem: missing filecopy() function
Solution: implement filecopy() Vim script function
(Shougo Matsushita)
closes: vim/vim#12346
60c8743ab6
Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
This commit is contained in:
parent
b5f92c4e5c
commit
28fbba2092
9
runtime/doc/builtin.txt
generated
9
runtime/doc/builtin.txt
generated
@ -1610,6 +1610,15 @@ feedkeys({string} [, {mode}]) *feedkeys()*
|
||||
|
||||
Return value is always 0.
|
||||
|
||||
filecopy({from}, {to}) *filecopy()*
|
||||
Copy the file pointed to by the name {from} to {to}. The
|
||||
result is a Number, which is |TRUE| if the file was copied
|
||||
successfully, and |FALSE| when it failed.
|
||||
If a file with name {to} already exists, it will fail.
|
||||
Note that it does not handle directories (yet).
|
||||
|
||||
This function is not available in the |sandbox|.
|
||||
|
||||
filereadable({file}) *filereadable()*
|
||||
The result is a Number, which is |TRUE| when a file with the
|
||||
name {file} exists, and can be read. If {file} doesn't exist,
|
||||
|
@ -855,6 +855,7 @@ System functions and manipulation of files:
|
||||
readblob() read a file into a Blob
|
||||
readdir() get a List of file names in a directory
|
||||
writefile() write a List of lines or Blob into a file
|
||||
filecopy() copy a file {from} to {to}
|
||||
|
||||
Date and Time: *date-functions* *time-functions*
|
||||
getftime() get last modification time of a file
|
||||
|
13
runtime/lua/vim/_meta/vimfn.lua
generated
13
runtime/lua/vim/_meta/vimfn.lua
generated
@ -1993,6 +1993,19 @@ function vim.fn.feedkeys(string, mode) end
|
||||
--- @return any
|
||||
function vim.fn.file_readable(file) end
|
||||
|
||||
--- Copy the file pointed to by the name {from} to {to}. The
|
||||
--- result is a Number, which is |TRUE| if the file was copied
|
||||
--- successfully, and |FALSE| when it failed.
|
||||
--- If a file with name {to} already exists, it will fail.
|
||||
--- Note that it does not handle directories (yet).
|
||||
---
|
||||
--- This function is not available in the |sandbox|.
|
||||
---
|
||||
--- @param from string
|
||||
--- @param to string
|
||||
--- @return 0|1
|
||||
function vim.fn.filecopy(from, to) end
|
||||
|
||||
--- The result is a Number, which is |TRUE| when a file with the
|
||||
--- name {file} exists, and can be read. If {file} doesn't exist,
|
||||
--- or is a directory, the result is |FALSE|. {file} is any
|
||||
|
@ -2521,6 +2521,23 @@ M.funcs = {
|
||||
params = { { 'file', 'string' } },
|
||||
signature = 'file_readable({file})',
|
||||
},
|
||||
filecopy = {
|
||||
args = 2,
|
||||
base = 1,
|
||||
desc = [[
|
||||
Copy the file pointed to by the name {from} to {to}. The
|
||||
result is a Number, which is |TRUE| if the file was copied
|
||||
successfully, and |FALSE| when it failed.
|
||||
If a file with name {to} already exists, it will fail.
|
||||
Note that it does not handle directories (yet).
|
||||
|
||||
This function is not available in the |sandbox|.
|
||||
]],
|
||||
name = 'filecopy',
|
||||
params = { { 'from', 'string' }, { 'to', 'string' } },
|
||||
returns = '0|1',
|
||||
signature = 'filecopy({from}, {to})',
|
||||
},
|
||||
filereadable = {
|
||||
args = 1,
|
||||
base = 1,
|
||||
|
@ -154,6 +154,27 @@ void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
rettv->vval.v_string = path;
|
||||
}
|
||||
|
||||
/// "filecopy()" function
|
||||
void f_filecopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
rettv->vval.v_number = false;
|
||||
|
||||
if (check_secure()
|
||||
|| tv_check_for_string_arg(argvars, 0) == FAIL
|
||||
|| tv_check_for_string_arg(argvars, 1) == FAIL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *from = tv_get_string(&argvars[0]);
|
||||
|
||||
FileInfo from_info;
|
||||
if (os_fileinfo_link(from, &from_info)
|
||||
&& (S_ISREG(from_info.stat.st_mode) || S_ISLNK(from_info.stat.st_mode))) {
|
||||
rettv->vval.v_number
|
||||
= vim_copyfile(tv_get_string(&argvars[0]), tv_get_string(&argvars[1])) == OK;
|
||||
}
|
||||
}
|
||||
|
||||
/// "filereadable()" function
|
||||
void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
|
@ -2654,7 +2654,6 @@ static int rename_with_tmp(const char *const from, const char *const to)
|
||||
int vim_rename(const char *from, const char *to)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
char *errmsg = NULL;
|
||||
bool use_tmp_file = false;
|
||||
|
||||
// When the names are identical, there is nothing to do. When they refer
|
||||
@ -2698,13 +2697,49 @@ int vim_rename(const char *from, const char *to)
|
||||
}
|
||||
|
||||
// Rename() failed, try copying the file.
|
||||
int ret = vim_copyfile(from, to);
|
||||
if (ret != OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (os_fileinfo(from, &from_info)) {
|
||||
os_remove(from);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Create the new file with same permissions as the original.
|
||||
/// Return -1 for failure, 0 for success.
|
||||
int vim_copyfile(const char *from, const char *to)
|
||||
{
|
||||
char *errmsg = NULL;
|
||||
|
||||
#ifdef HAVE_READLINK
|
||||
FileInfo from_info;
|
||||
if (os_fileinfo_link(from, &from_info) && S_ISLNK(from_info.stat.st_mode)) {
|
||||
int ret = FAIL;
|
||||
|
||||
char linkbuf[MAXPATHL + 1];
|
||||
ssize_t len = readlink(from, linkbuf, MAXPATHL);
|
||||
if (len > 0) {
|
||||
linkbuf[len] = NUL;
|
||||
|
||||
// Create link
|
||||
ret = symlink(linkbuf, to);
|
||||
}
|
||||
|
||||
return ret == 0 ? OK : FAIL;
|
||||
}
|
||||
#endif
|
||||
|
||||
int perm = os_getperm(from);
|
||||
// For systems that support ACL: get the ACL from the original file.
|
||||
vim_acl_T acl = os_get_acl(from);
|
||||
int fd_in = os_open(from, O_RDONLY, 0);
|
||||
if (fd_in < 0) {
|
||||
os_free_acl(acl);
|
||||
return -1;
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// Create the new file with same permissions as the original.
|
||||
@ -2712,7 +2747,7 @@ int vim_rename(const char *from, const char *to)
|
||||
if (fd_out < 0) {
|
||||
close(fd_in);
|
||||
os_free_acl(acl);
|
||||
return -1;
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// Avoid xmalloc() here as vim_rename() is called by buf_write() when nvim
|
||||
@ -2722,7 +2757,7 @@ int vim_rename(const char *from, const char *to)
|
||||
close(fd_out);
|
||||
close(fd_in);
|
||||
os_free_acl(acl);
|
||||
return -1;
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
int n;
|
||||
@ -2749,10 +2784,9 @@ int vim_rename(const char *from, const char *to)
|
||||
os_free_acl(acl);
|
||||
if (errmsg != NULL) {
|
||||
semsg(errmsg, to);
|
||||
return -1;
|
||||
return FAIL;
|
||||
}
|
||||
os_remove(from);
|
||||
return 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static bool already_warned = false;
|
||||
|
72
test/old/testdir/test_filecopy.vim
Normal file
72
test/old/testdir/test_filecopy.vim
Normal file
@ -0,0 +1,72 @@
|
||||
" Test filecopy()
|
||||
|
||||
source check.vim
|
||||
source shared.vim
|
||||
|
||||
func Test_copy_file_to_file()
|
||||
call writefile(['foo'], 'Xcopy1')
|
||||
|
||||
call assert_true(filecopy('Xcopy1', 'Xcopy2'))
|
||||
|
||||
call assert_equal(['foo'], readfile('Xcopy2'))
|
||||
|
||||
" When the destination file already exists, it should not be overwritten.
|
||||
call writefile(['foo'], 'Xcopy1')
|
||||
call writefile(['bar'], 'Xcopy2', 'D')
|
||||
call assert_false(filecopy('Xcopy1', 'Xcopy2'))
|
||||
call assert_equal(['bar'], readfile('Xcopy2'))
|
||||
|
||||
call delete('Xcopy2')
|
||||
call delete('Xcopy1')
|
||||
endfunc
|
||||
|
||||
func Test_copy_symbolic_link()
|
||||
CheckUnix
|
||||
|
||||
call writefile(['text'], 'Xtestfile', 'D')
|
||||
silent !ln -s -f Xtestfile Xtestlink
|
||||
|
||||
call assert_true(filecopy('Xtestlink', 'Xtestlink2'))
|
||||
call assert_equal('link', getftype('Xtestlink2'))
|
||||
call assert_equal(['text'], readfile('Xtestlink2'))
|
||||
|
||||
" When the destination file already exists, it should not be overwritten.
|
||||
call assert_false(filecopy('Xtestlink', 'Xtestlink2'))
|
||||
|
||||
call delete('Xtestlink2')
|
||||
call delete('Xtestlink')
|
||||
call delete('Xtestfile')
|
||||
endfunc
|
||||
|
||||
func Test_copy_dir_to_dir()
|
||||
call mkdir('Xcopydir1')
|
||||
call writefile(['foo'], 'Xcopydir1/Xfilecopy')
|
||||
call mkdir('Xcopydir2')
|
||||
|
||||
" Directory copy is not supported
|
||||
call assert_false(filecopy('Xcopydir1', 'Xcopydir2'))
|
||||
|
||||
call delete('Xcopydir2', 'rf')
|
||||
call delete('Xcopydir1', 'rf')
|
||||
endfunc
|
||||
|
||||
func Test_copy_fails()
|
||||
CheckUnix
|
||||
|
||||
call writefile(['foo'], 'Xfilecopy', 'D')
|
||||
|
||||
" Can't copy into a non-existing directory.
|
||||
call assert_false(filecopy('Xfilecopy', 'Xdoesnotexist/Xfilecopy'))
|
||||
|
||||
" Can't copy a non-existing file.
|
||||
call assert_false(filecopy('Xdoesnotexist', 'Xfilecopy2'))
|
||||
call assert_equal('', glob('Xfilecopy2'))
|
||||
|
||||
" Can't copy to en empty file name.
|
||||
call assert_false(filecopy('Xfilecopy', ''))
|
||||
|
||||
call assert_fails('call filecopy("Xfilecopy", [])', 'E1174:')
|
||||
call assert_fails('call filecopy(0z, "Xfilecopy")', 'E1174:')
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
Loading…
Reference in New Issue
Block a user