Skip to content

Commit 806a273

Browse files
committed
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.
1 parent c1eb131 commit 806a273

11 files changed

Lines changed: 250 additions & 81 deletions

File tree

runtime/doc/builtin.txt

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10448,34 +10448,43 @@ writefile({object}, {fname} [, {flags}])
1044810448
When {object} is a |List| write it to file {fname}. Each list
1044910449
item is separated with a NL. Each list item must be a String
1045010450
or Number.
10451-
When {flags} contains "b" then binary mode is used: There will
10452-
not be a NL after the last list item. An empty item at the
10453-
end does cause the last line in the file to end in a NL.
10451+
All NL characters are replaced with a NUL character.
10452+
Inserting CR characters needs to be done before passing {list}
10453+
to writefile().
1045410454

1045510455
When {object} is a |Blob| write the bytes to file {fname}
10456-
unmodified.
10456+
unmodified, also when binary mode is not specified.
1045710457

10458-
When {flags} contains "a" then append mode is used, lines are
10459-
appended to the file: >
10458+
{flags} must be a String. These characters are recognized:
10459+
10460+
'b' Binary mode is used: There will not be a NL after the
10461+
last list item. An empty item at the end does cause the
10462+
last line in the file to end in a NL.
10463+
10464+
'a' Append mode is used, lines are appended to the file: >
1046010465
:call writefile(["foo"], "event.log", "a")
1046110466
:call writefile(["bar"], "event.log", "a")
1046210467
<
10463-
When {flags} contains "s" then fsync() is called after writing
10464-
the file. This flushes the file to disk, if possible. This
10465-
takes more time but avoids losing the file if the system
10466-
crashes.
10467-
When {flags} does not contain "S" or "s" then fsync() is
10468-
called if the 'fsync' option is set.
10469-
When {flags} contains "S" then fsync() is not called, even
10470-
when 'fsync' is set.
10468+
'D' Delete the file when the current function ends. This
10469+
works like: >
10470+
:defer delete({fname})
10471+
< Fails when not in a function. Also see |:defer|.
10472+
10473+
's' fsync() is called after writing the file. This flushes
10474+
the file to disk, if possible. This takes more time but
10475+
avoids losing the file if the system crashes.
10476+
10477+
'S' fsync() is not called, even when 'fsync' is set.
10478+
10479+
When {flags} does not contain "S" or "s" then fsync() is
10480+
called if the 'fsync' option is set.
1047110481

10472-
All NL characters are replaced with a NUL character.
10473-
Inserting CR characters needs to be done before passing {list}
10474-
to writefile().
1047510482
An existing file is overwritten, if possible.
10483+
1047610484
When the write fails -1 is returned, otherwise 0. There is an
1047710485
error message if the file can't be created or when writing
1047810486
fails.
10487+
1047910488
Also see |readfile()|.
1048010489
To copy a file byte for byte: >
1048110490
:let fl = readfile("foo", "b")

src/filepath.c

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,7 @@ f_writefile(typval_T *argvars, typval_T *rettv)
22322232
{
22332233
int binary = FALSE;
22342234
int append = FALSE;
2235+
int defer = FALSE;
22352236
#ifdef HAVE_FSYNC
22362237
int do_fsync = p_fs;
22372238
#endif
@@ -2285,6 +2286,8 @@ f_writefile(typval_T *argvars, typval_T *rettv)
22852286
binary = TRUE;
22862287
if (vim_strchr(arg2, 'a') != NULL)
22872288
append = TRUE;
2289+
if (vim_strchr(arg2, 'D') != NULL)
2290+
defer = TRUE;
22882291
#ifdef HAVE_FSYNC
22892292
if (vim_strchr(arg2, 's') != NULL)
22902293
do_fsync = TRUE;
@@ -2297,37 +2300,59 @@ f_writefile(typval_T *argvars, typval_T *rettv)
22972300
if (fname == NULL)
22982301
return;
22992302

2303+
if (defer && !in_def_function() && get_current_funccal() == NULL)
2304+
{
2305+
semsg(_(e_str_not_inside_function), "defer");
2306+
return;
2307+
}
2308+
23002309
// Always open the file in binary mode, library functions have a mind of
23012310
// their own about CR-LF conversion.
23022311
if (*fname == NUL || (fd = mch_fopen((char *)fname,
23032312
append ? APPENDBIN : WRITEBIN)) == NULL)
23042313
{
2305-
semsg(_(e_cant_create_file_str), *fname == NUL ? (char_u *)_("<empty>") : fname);
2314+
semsg(_(e_cant_create_file_str),
2315+
*fname == NUL ? (char_u *)_("<empty>") : fname);
23062316
ret = -1;
23072317
}
2308-
else if (blob)
2309-
{
2310-
if (write_blob(fd, blob) == FAIL)
2311-
ret = -1;
2312-
#ifdef HAVE_FSYNC
2313-
else if (do_fsync)
2314-
// Ignore the error, the user wouldn't know what to do about it.
2315-
// May happen for a device.
2316-
vim_ignored = vim_fsync(fileno(fd));
2317-
#endif
2318-
fclose(fd);
2319-
}
23202318
else
23212319
{
2322-
if (write_list(fd, list, binary) == FAIL)
2323-
ret = -1;
2320+
if (defer)
2321+
{
2322+
typval_T tv;
2323+
2324+
tv.v_type = VAR_STRING;
2325+
tv.v_lock = 0;
2326+
tv.vval.v_string = vim_strsave(fname);
2327+
if (tv.vval.v_string == NULL
2328+
|| add_defer((char_u *)"delete", 1, &tv) == FAIL)
2329+
{
2330+
ret = -1;
2331+
fclose(fd);
2332+
(void)mch_remove(fname);
2333+
}
2334+
}
2335+
2336+
if (ret == 0)
2337+
{
2338+
if (blob)
2339+
{
2340+
if (write_blob(fd, blob) == FAIL)
2341+
ret = -1;
2342+
}
2343+
else
2344+
{
2345+
if (write_list(fd, list, binary) == FAIL)
2346+
ret = -1;
2347+
}
23242348
#ifdef HAVE_FSYNC
2325-
else if (do_fsync)
2326-
// Ignore the error, the user wouldn't know what to do about it.
2327-
// May happen for a device.
2328-
vim_ignored = vim_fsync(fileno(fd));
2349+
if (ret == 0 && do_fsync)
2350+
// Ignore the error, the user wouldn't know what to do about
2351+
// it. May happen for a device.
2352+
vim_ignored = vim_fsync(fileno(fd));
23292353
#endif
2330-
fclose(fd);
2354+
fclose(fd);
2355+
}
23312356
}
23322357

23332358
rettv->vval.v_number = ret;

src/proto/userfunc.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ void func_ptr_unref(ufunc_T *fp);
5858
void func_ref(char_u *name);
5959
void func_ptr_ref(ufunc_T *fp);
6060
void ex_return(exarg_T *eap);
61+
int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
6162
void handle_defer(void);
6263
void ex_call(exarg_T *eap);
6364
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv);

src/proto/vim9cmds.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ char_u *compile_finally(char_u *arg, cctx_T *cctx);
2121
char_u *compile_endtry(char_u *arg, cctx_T *cctx);
2222
char_u *compile_throw(char_u *arg, cctx_T *cctx);
2323
char_u *compile_eval(char_u *arg, cctx_T *cctx);
24+
int get_defer_var_idx(cctx_T *cctx);
2425
char_u *compile_defer(char_u *arg_start, cctx_T *cctx);
2526
char_u *compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx);
2627
char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx);

src/proto/vim9execute.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ void to_string_error(vartype_T vartype);
33
void update_has_breakpoint(ufunc_T *ufunc);
44
void funcstack_check_refcount(funcstack_T *funcstack);
55
int set_ref_in_funcstacks(int copyID);
6+
int in_def_function(void);
7+
int add_defer_function(char_u *name, int argcount, typval_T *argvars);
68
char_u *char_from_string(char_u *str, varnumber_T index);
79
char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive);
810
int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx);

src/testdir/test_writefile.vim

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,23 @@ func Test_write_binary_file()
933933
call delete('Xwbfile3')
934934
endfunc
935935

936+
func DoWriteDefer()
937+
call writefile(['some text'], 'XdeferDelete', 'D')
938+
call assert_equal(['some text'], readfile('XdeferDelete'))
939+
endfunc
940+
941+
def DefWriteDefer()
942+
writefile(['some text'], 'XdefdeferDelete', 'D')
943+
assert_equal(['some text'], readfile('XdefdeferDelete'))
944+
enddef
945+
946+
func Test_write_with_deferred_delete()
947+
call DoWriteDefer()
948+
call assert_equal('', glob('XdeferDelete'))
949+
call DefWriteDefer()
950+
call assert_equal('', glob('XdefdeferDelete'))
951+
endfunc
952+
936953
" Check that buffer is written before triggering QuitPre
937954
func Test_wq_quitpre_autocommand()
938955
edit Xsomefile

src/userfunc.c

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,7 @@ emsg_funcname(char *ermsg, char_u *name)
17281728
/*
17291729
* Get function arguments at "*arg" and advance it.
17301730
* Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
1731+
* On failure FAIL is returned but the "argvars[argcount]" are still set.
17311732
*/
17321733
static int
17331734
get_func_arguments(
@@ -5570,33 +5571,58 @@ ex_defer_inner(char_u *name, char_u **arg, evalarg_T *evalarg)
55705571
{
55715572
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
55725573
int argcount = 0; // number of arguments found
5573-
defer_T *dr;
5574-
int ret = FAIL;
5575-
char_u *saved_name;
55765574

55775575
if (current_funccal == NULL)
55785576
{
55795577
semsg(_(e_str_not_inside_function), "defer");
55805578
return FAIL;
55815579
}
55825580
if (get_func_arguments(arg, evalarg, FALSE, argvars, &argcount) == FAIL)
5583-
goto theend;
5584-
saved_name = vim_strsave(name);
5585-
if (saved_name == NULL)
5586-
goto theend;
5581+
{
5582+
while (--argcount >= 0)
5583+
clear_tv(&argvars[argcount]);
5584+
return FAIL;
5585+
}
5586+
return add_defer(name, argcount, argvars);
5587+
}
55875588

5588-
if (current_funccal->fc_defer.ga_itemsize == 0)
5589-
ga_init2(&current_funccal->fc_defer, sizeof(defer_T), 10);
5590-
if (ga_grow(&current_funccal->fc_defer, 1) == FAIL)
5589+
/*
5590+
* Add a deferred call for "name" with arguments "argvars[argcount]".
5591+
* Consumes "argvars[]".
5592+
* Caller must check that in_def_function() returns TRUE or current_funccal is
5593+
* not NULL.
5594+
* Returns OK or FAIL.
5595+
*/
5596+
int
5597+
add_defer(char_u *name, int argcount_arg, typval_T *argvars)
5598+
{
5599+
char_u *saved_name = vim_strsave(name);
5600+
int argcount = argcount_arg;
5601+
defer_T *dr;
5602+
int ret = FAIL;
5603+
5604+
if (saved_name == NULL)
55915605
goto theend;
5592-
dr = ((defer_T *)current_funccal->fc_defer.ga_data)
5593-
+ current_funccal->fc_defer.ga_len++;
5594-
dr->dr_name = saved_name;
5595-
dr->dr_argcount = argcount;
5596-
while (argcount > 0)
5606+
if (in_def_function())
5607+
{
5608+
if (add_defer_function(saved_name, argcount, argvars) == OK)
5609+
argcount = 0;
5610+
}
5611+
else
55975612
{
5598-
--argcount;
5599-
dr->dr_argvars[argcount] = argvars[argcount];
5613+
if (current_funccal->fc_defer.ga_itemsize == 0)
5614+
ga_init2(&current_funccal->fc_defer, sizeof(defer_T), 10);
5615+
if (ga_grow(&current_funccal->fc_defer, 1) == FAIL)
5616+
goto theend;
5617+
dr = ((defer_T *)current_funccal->fc_defer.ga_data)
5618+
+ current_funccal->fc_defer.ga_len++;
5619+
dr->dr_name = saved_name;
5620+
dr->dr_argcount = argcount;
5621+
while (argcount > 0)
5622+
{
5623+
--argcount;
5624+
dr->dr_argvars[argcount] = argvars[argcount];
5625+
}
56005626
}
56015627
ret = OK;
56025628

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,8 @@ static char *(features[]) =
703703

704704
static int included_patches[] =
705705
{ /* Add new patch number below this line */
706+
/**/
707+
379,
706708
/**/
707709
378,
708710
/**/

src/vim9cmds.c

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,27 @@ compile_eval(char_u *arg, cctx_T *cctx)
16841684
return skipwhite(p);
16851685
}
16861686

1687+
/*
1688+
* Get the local variable index for deferred function calls.
1689+
* Reserve it when not done already.
1690+
* Returns zero for failure.
1691+
*/
1692+
int
1693+
get_defer_var_idx(cctx_T *cctx)
1694+
{
1695+
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
1696+
+ cctx->ctx_ufunc->uf_dfunc_idx;
1697+
if (dfunc->df_defer_var_idx == 0)
1698+
{
1699+
lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
1700+
TRUE, &t_list_any);
1701+
if (lvar == NULL)
1702+
return 0;
1703+
dfunc->df_defer_var_idx = lvar->lv_idx + 1;
1704+
}
1705+
return dfunc->df_defer_var_idx;
1706+
}
1707+
16871708
/*
16881709
* Compile "defer func(arg)".
16891710
*/
@@ -1693,7 +1714,7 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
16931714
char_u *p;
16941715
char_u *arg = arg_start;
16951716
int argcount = 0;
1696-
dfunc_T *dfunc;
1717+
int defer_var_idx;
16971718
type_T *type;
16981719
int func_idx;
16991720

@@ -1730,16 +1751,10 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
17301751

17311752
// TODO: check argument count with "type"
17321753

1733-
dfunc = ((dfunc_T *)def_functions.ga_data) + cctx->ctx_ufunc->uf_dfunc_idx;
1734-
if (dfunc->df_defer_var_idx == 0)
1735-
{
1736-
lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
1737-
TRUE, &t_list_any);
1738-
if (lvar == NULL)
1739-
return NULL;
1740-
dfunc->df_defer_var_idx = lvar->lv_idx + 1;
1741-
}
1742-
if (generate_DEFER(cctx, dfunc->df_defer_var_idx - 1, argcount) == FAIL)
1754+
defer_var_idx = get_defer_var_idx(cctx);
1755+
if (defer_var_idx == 0)
1756+
return NULL;
1757+
if (generate_DEFER(cctx, defer_var_idx - 1, argcount) == FAIL)
17431758
return NULL;
17441759

17451760
return skipwhite(arg);

0 commit comments

Comments
 (0)