Subject: Re: Printf builtin missing v flag support
From: Bart Schaefer <[email-masked]>
To: [email-masked]
Date: Thu, 31 Dec 2015 10:48:58 -0800
List-Id: Zsh Workers List <zsh-workers.zsh.org>

On Dec 31, 10:44am, Peter Stephenson wrote:
}
} The feature is obviously useful but the print implementation is
} a nightmare of special cases making it hard to change without
} considerable refactoring. That would probably be a Good Work, given
} enough test cases to check it, but is going to have to wait for a
} volunteer.
}
} I haven't checked whether -v is already in use in which case this is moot.

-v is not in use, and the print implementation has already been refactored
to support the -z and -s options in printf, so this is actually rather
easy.

I noticed that "print" uses metafy() on the aguments to -z and -s, but
"printf" did not, so I added metafy() for those cases.  Somebody holler
if this is wrong.

diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 120ec82..dc0b947 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -1124,7 +1124,7 @@ tt(popd) that do not change the environment seen by an interactive user.
 )
 findex(print)
 xitem(tt(print )[ tt(-abcDilmnNoOpPrsSz) ] [ tt(-u) var(n) ] [ tt(-f) var(format) ] [ tt(-C) var(cols) ])
-item(SPACES()[ tt(-xX) var(tab-stop) ] [ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
+item(SPACES()[ tt(-v) var(name) ] [ tt(-xX) var(tabstop) ] [ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
 With the `tt(-f)' option the arguments are printed as described by tt(printf).
 With no flags or with the flag `tt(-)', the arguments are printed on
 the standard output as described by tt(echo), with the following differences:
@@ -1219,6 +1219,9 @@ tt(HIST_LEX_WORDS) option active.
 item(tt(-u) var(n))(
 Print the arguments to file descriptor var(n).
 )
+item(tt(-v) var(name))(
+Store the printed arguments as the value of the parameter var(name).
+)
 item(tt(-x) var(tab-stop))(
 Expand leading tabs on each line of output in the printed string
 assuming a tab stop every var(tab-stop) characters.  This is appropriate
@@ -1250,7 +1253,7 @@ If any of `tt(-m)', `tt(-o)' or `tt(-O)' are used in combination with
 case of `tt(-m)') then nothing is printed.
 )
 findex(printf)
-item(tt(printf) var(format) [ var(arg) ... ])(
+item(tt(printf) [ -v var(name) ] var(format) [ var(arg) ... ])(
 Print the arguments according to the format specification. Formatting
 rules are the same as used in C. The same escape sequences as for tt(echo)
 are recognised in the format. All C conversion specifications ending in
@@ -1279,6 +1282,9 @@ until all arguments have been consumed. With the tt(print) builtin, this
 can be suppressed by using the tt(-r) option. If more arguments are
 required by the format than have been specified, the behaviour is as if
 zero or an empty string had been specified as the argument.
+
+The tt(-v) option causes the output to be stored as the value of the
+parameter var(name), instead of printed.
 )
 findex(pushd)
 pindex(PUSHD_TO_HOME, use of)
diff --git a/Src/builtin.c b/Src/builtin.c
index b06bc6d..128bc36 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -99,8 +99,8 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 1, BIN_POPD, "q", NULL),
-    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:x:X:z-", NULL),
-    BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, NULL, NULL),
+    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:v:x:X:z-", NULL),
+    BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, "v:", NULL),
     BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_PUSHD, "qsPL", NULL),
     BUILTIN("pushln", 0, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
@@ -4032,6 +4032,11 @@ bin_print(char *name, char **args, Options ops, int func)
     zulong zulongval;
     char *stringval;
 
+    if (OPT_ISSET(ops, 'z') + OPT_ISSET(ops, 's') + OPT_ISSET(ops, 'v') > 1) {
+	zwarnnam(name, "only one of -z, -s, or -v allowed");
+	return 1;
+    }
+
     if (func == BIN_PRINTF) {
         if (!strcmp(*args, "--") && !*++args) {
             zwarnnam(name, "not enough arguments");
@@ -4157,8 +4162,8 @@ bin_print(char *name, char **args, Options ops, int func)
     if ((OPT_HASARG(ops,'u') || OPT_ISSET(ops,'p')) &&
 	/* rule out conflicting options -- historical precedence */
 	((!fmt && (OPT_ISSET(ops,'c') || OPT_ISSET(ops,'C'))) ||
-	!(OPT_ISSET(ops, 'z') ||
-	  OPT_ISSET(ops, 's') || OPT_ISSET(ops, 'S')))) {
+	 !(OPT_ISSET(ops, 'z') || OPT_ISSET(ops, 'v') ||
+	   OPT_ISSET(ops, 's') || OPT_ISSET(ops, 'S')))) {
 	int fdarg, fd;
 
 	if (OPT_ISSET(ops, 'p')) {
@@ -4359,7 +4364,8 @@ bin_print(char *name, char **args, Options ops, int func)
 
     /* normal output */
     if (!fmt) {
-	if (OPT_ISSET(ops, 'z') || OPT_ISSET(ops, 's')) {
+	if (OPT_ISSET(ops, 'z') || OPT_ISSET(ops, 's') ||
+	    OPT_ISSET(ops, 'v')) {
 	    /*
 	     * We don't want the arguments unmetafied after all.
 	     */
@@ -4367,6 +4373,13 @@ bin_print(char *name, char **args, Options ops, int func)
 		metafy(args[n], len[n], META_NOALLOC);
 	}
 
+	/* -v option -- store the arguments in the named parameter */
+	if (OPT_ISSET(ops,'v')) {
+	    queue_signals();
+	    assignsparam(OPT_ARG(ops, 'v'), sepjoin(args, NULL, 0), 0);
+	    unqueue_signals();
+	    return 0;
+	}
 	/* -z option -- push the arguments onto the editing buffer stack */
 	if (OPT_ISSET(ops,'z')) {
 	    queue_signals();
@@ -4474,7 +4487,7 @@ bin_print(char *name, char **args, Options ops, int func)
      * special cases of printing to a ZLE buffer or the history, however.
      */
 
-    if (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s')) {
+    if (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s') || OPT_ISSET(ops, 'v')) {
 #ifdef HAVE_OPEN_MEMSTREAM
     	if ((fout = open_memstream(&buf, &mcount)) == NULL)
 	    zwarnnam(name, "open_memstream failed");
@@ -4853,7 +4866,7 @@ bin_print(char *name, char **args, Options ops, int func)
 	/* if there are remaining args, reuse format string */
     } while (*argp && argp != first && !fmttrunc && !OPT_ISSET(ops,'r'));
 
-    if (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s')) {
+    if (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s') || OPT_ISSET(ops,'v')) {
 #ifdef HAVE_OPEN_MEMSTREAM
 	putc(0, fout);
 	fclose(fout);
@@ -4865,11 +4878,14 @@ bin_print(char *name, char **args, Options ops, int func)
 	buf[count] = '\0';
 #endif
 	queue_signals();
+	stringval = metafy(buf, -1, META_REALLOC);
 	if (OPT_ISSET(ops,'z')) {
-	    zpushnode(bufstack, buf);
+	    zpushnode(bufstack, stringval);
+	} else if (OPT_ISSET(ops,'v')) {
+	    assignsparam(OPT_ARG(ops, 'v'), stringval, 0);
 	} else {
 	    ent = prepnexthistent();
-	    ent->node.nam = buf;
+	    ent->node.nam = stringval;
 	    ent->stim = ent->ftim = time(NULL);
 	    ent->node.flags = 0;
 	    ent->words = (short *)NULL;
diff --git a/Test/B03print.ztst b/Test/B03print.ztst
index eb79c4d..2e9bf99 100644
--- a/Test/B03print.ztst
+++ b/Test/B03print.ztst
@@ -301,3 +301,12 @@
 >one two three   four
 >    one two three   four
 >        one     two     three       four
+
+ unset foo
+ print -v foo once more
+ print -r -- $foo
+ printf -v foo "%s-" into the breach
+ print -r -- $foo
+0:print and printf into a variable
+>once more
+>into-the-breach-