vim-patch:9.0.0379: cleaning up after writefile() is a hassle

Problem:    Cleaning up after writefile() is a hassle.
Solution:   Add the 'D' flag to defer deleting the written file.  Very useful
            in tests.

806a273f3c

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
zeertzjq 2023-04-16 11:07:48 +08:00
parent b75634e55e
commit 0167649ce4
5 changed files with 91 additions and 61 deletions

View File

@ -9507,31 +9507,43 @@ writefile({object}, {fname} [, {flags}])
When {object} is a |List| write it to file {fname}. Each list
item is separated with a NL. Each list item must be a String
or Number.
When {flags} contains "b" then binary mode is used: There will
not be a NL after the last list item. An empty item at the
end does cause the last line in the file to end in a NL.
When {object} is a |Blob| write the bytes to file {fname}
unmodified.
When {flags} contains "a" then append mode is used, lines are
appended to the file: >
:call writefile(["foo"], "event.log", "a")
:call writefile(["bar"], "event.log", "a")
<
When {flags} contains "S" fsync() call is not used, with "s"
it is used, 'fsync' option applies by default. No fsync()
means that writefile() will finish faster, but writes may be
left in OS buffers and not yet written to disk. Such changes
will disappear if system crashes before OS does writing.
All NL characters are replaced with a NUL character.
Inserting CR characters needs to be done before passing {list}
to writefile().
When {object} is a |Blob| write the bytes to file {fname}
unmodified, also when binary mode is not specified.
{flags} must be a String. These characters are recognized:
'b' Binary mode is used: There will not be a NL after the
last list item. An empty item at the end does cause the
last line in the file to end in a NL.
'a' Append mode is used, lines are appended to the file: >
:call writefile(["foo"], "event.log", "a")
:call writefile(["bar"], "event.log", "a")
<
'D' Delete the file when the current function ends. This
works like: >
:defer delete({fname})
< Fails when not in a function. Also see |:defer|.
's' fsync() is called after writing the file. This flushes
the file to disk, if possible. This takes more time but
avoids losing the file if the system crashes.
'S' fsync() is not called, even when 'fsync' is set.
When {flags} does not contain "S" or "s" then fsync() is
called if the 'fsync' option is set.
An existing file is overwritten, if possible.
When the write fails -1 is returned, otherwise 0. There is an
error message if the file can't be created or when writing
fails.
Also see |readfile()|.
To copy a file byte for byte: >
:let fl = readfile("foo", "b")

View File

@ -9296,6 +9296,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool binary = false;
bool append = false;
bool defer = false;
bool do_fsync = !!p_fs;
bool mkdir_p = false;
if (argvars[2].v_type != VAR_UNKNOWN) {
@ -9309,6 +9310,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
binary = true; break;
case 'a':
append = true; break;
case 'D':
defer = true; break;
case 's':
do_fsync = true; break;
case 'S':
@ -9328,6 +9331,12 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (fname == NULL) {
return;
}
if (defer && get_current_funccal() == NULL) {
semsg(_(e_str_not_inside_function), "defer");
return;
}
FileDescriptor fp;
int error;
if (*fname == NUL) {
@ -9336,9 +9345,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
((append ? kFileAppend : kFileTruncate)
| (mkdir_p ? kFileMkDir : kFileCreate)
| kFileCreate), 0666)) != 0) {
semsg(_("E482: Can't open file %s for writing: %s"),
fname, os_strerror(error));
semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error));
} else {
if (defer) {
typval_T tv = {
.v_type = VAR_STRING,
.v_lock = VAR_UNLOCKED,
.vval.v_string = xstrdup(fname),
};
add_defer("delete", 1, &tv);
}
bool write_ok;
if (argvars[0].v_type == VAR_BLOB) {
write_ok = write_blob(&fp, argvars[0].vval.v_blob);

View File

@ -478,6 +478,7 @@ void emsg_funcname(const char *errmsg, const char *name)
/// Get function arguments at "*arg" and advance it.
/// Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
/// On failure FAIL is returned but the "argvars[argcount]" are still set.
static int get_func_arguments(char **arg, evalarg_T *const evalarg, int partial_argc,
typval_T *argvars, int *argcount)
{
@ -3119,16 +3120,28 @@ static int ex_defer_inner(char *name, char **arg, evalarg_T *const evalarg)
{
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
int argcount = 0; // number of arguments found
int ret = FAIL;
if (current_funccal == NULL) {
semsg(_(e_str_not_inside_function), "defer");
return FAIL;
}
if (get_func_arguments(arg, evalarg, false, argvars, &argcount) == FAIL) {
goto theend;
while (--argcount >= 0) {
tv_clear(&argvars[argcount]);
}
return FAIL;
}
add_defer(name, argcount, argvars);
return OK;
}
/// Add a deferred call for "name" with arguments "argvars[argcount]".
/// Consumes "argvars[]".
/// Caller must check that current_funccal is not NULL.
void add_defer(char *name, int argcount_arg, typval_T *argvars)
{
char *saved_name = xstrdup(name);
int argcount = argcount_arg;
if (current_funccal->fc_defer.ga_itemsize == 0) {
ga_init(&current_funccal->fc_defer, sizeof(defer_T), 10);
@ -3140,13 +3153,6 @@ static int ex_defer_inner(char *name, char **arg, evalarg_T *const evalarg)
argcount--;
dr->dr_argvars[argcount] = argvars[argcount];
}
ret = OK;
theend:
while (--argcount >= 0) {
tv_clear(&argvars[argcount]);
}
return ret;
}
/// Invoked after a function has finished: invoke ":defer" functions.

View File

@ -6250,28 +6250,6 @@ func Test_very_long_error_line()
call setqflist([], 'f')
endfunc
" The test depends on deferred delete and string interpolation, which haven't
" been ported, so override it with a rewrite that doesn't use these features.
func! Test_very_long_error_line()
let msg = repeat('abcdefghijklmn', 146)
let emsg = 'Xlonglines.c:1:' . msg
call writefile([msg, emsg], 'Xerror')
cfile Xerror
call delete('Xerror')
cwindow
call assert_equal('|| ' .. msg, getline(1))
call assert_equal('Xlonglines.c|1| ' .. msg, getline(2))
cclose
let l = execute('clist!')->split("\n")
call assert_equal([' 1: ' .. msg, ' 2 Xlonglines.c:1: ' .. msg], l)
let l = execute('cc')->split("\n")
call assert_equal(['(2 of 2): ' .. msg], l)
call setqflist([], 'f')
endfunc
" In the quickfix window, spaces at the beginning of an informational line
" should not be removed but should be removed from an error line.
func Test_info_line_with_space()

View File

@ -924,19 +924,36 @@ endfunc
" Test for ':write ++bin' and ':write ++nobin'
func Test_write_binary_file()
" create a file without an eol/eof character
call writefile(0z616161, 'Xfile1', 'b')
new Xfile1
write ++bin Xfile2
write ++nobin Xfile3
call assert_equal(0z616161, readblob('Xfile2'))
call writefile(0z616161, 'Xwbfile1', 'b')
new Xwbfile1
write ++bin Xwbfile2
write ++nobin Xwbfile3
call assert_equal(0z616161, readblob('Xwbfile2'))
if has('win32')
call assert_equal(0z6161610D.0A, readblob('Xfile3'))
call assert_equal(0z6161610D.0A, readblob('Xwbfile3'))
else
call assert_equal(0z6161610A, readblob('Xfile3'))
call assert_equal(0z6161610A, readblob('Xwbfile3'))
endif
call delete('Xfile1')
call delete('Xfile2')
call delete('Xfile3')
call delete('Xwbfile1')
call delete('Xwbfile2')
call delete('Xwbfile3')
endfunc
func DoWriteDefer()
call writefile(['some text'], 'XdeferDelete', 'D')
call assert_equal(['some text'], readfile('XdeferDelete'))
endfunc
" def DefWriteDefer()
" writefile(['some text'], 'XdefdeferDelete', 'D')
" assert_equal(['some text'], readfile('XdefdeferDelete'))
" enddef
func Test_write_with_deferred_delete()
call DoWriteDefer()
call assert_equal('', glob('XdeferDelete'))
" call DefWriteDefer()
" call assert_equal('', glob('XdefdeferDelete'))
endfunc
" Check that buffer is written before triggering QuitPre