about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOliver Kiddle <opk@users.sourceforge.net>2001-10-08 10:47:46 +0000
committerOliver Kiddle <opk@users.sourceforge.net>2001-10-08 10:47:46 +0000
commit648d1c275613f7e43d01278fb3ffb5bca34640e2 (patch)
treea1f9616537f0836eebca8157ea836e414a8dba74
parent0ca8bb74f02b1291786f80d9e9f3f884f4ae0fa8 (diff)
downloadzsh-648d1c275613f7e43d01278fb3ffb5bca34640e2.tar.gz
zsh-648d1c275613f7e43d01278fb3ffb5bca34640e2.tar.xz
zsh-648d1c275613f7e43d01278fb3ffb5bca34640e2.zip
add print -f option, %n format specifier and tests for print/printf (15973)
-rw-r--r--ChangeLog7
-rw-r--r--Completion/Zsh/Command/_print13
-rw-r--r--Doc/Zsh/builtins.yo12
-rw-r--r--Src/builtin.c171
-rw-r--r--Src/hashtable.h1
-rw-r--r--Test/.distfiles2
-rw-r--r--Test/B03print.ztst135
7 files changed, 250 insertions, 91 deletions
diff --git a/ChangeLog b/ChangeLog
index af679e64f..a729ca16a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2001-10-08  Oliver Kiddle  <opk@zsh.org>
+
+	* 15973: Completion/Zsh/Command/_print, Doc/Zsh/builtins.yo,
+	Src/builtin.c, Src/hashtable.h, Test/.distfiles, Test/B03print.ztst:
+	merge bin_printf and bin_print to allow print -f option and add
+	%n format specifier and tests
+
 2001-10-08  Peter Stephenson  <pws@csr.com>
 
 	* 15965: Src/exec.c, Src/glob.c, Src/subst.c: rename glob() to
diff --git a/Completion/Zsh/Command/_print b/Completion/Zsh/Command/_print
index 76b39f613..9e52da5ab 100644
--- a/Completion/Zsh/Command/_print
+++ b/Completion/Zsh/Command/_print
@@ -10,18 +10,19 @@ eflag="${words[1,CURRENT-1][(r)-*R*]:+-e[enable escapes]}"
   pflag='(-s -u -z)-p[print arguments to input of coprocess]'
 
 _arguments -C -s -A "-*" -S \
-  '-r[ignore escape conventions of echo]' \
-  '(-r -b -m -s -l -N -o -O -i -c -u -p -z -D -P)-R[emulate BSD echo (no escapes, -n & -e flags only)]' \
+  '(-f)-r[ignore escape conventions of echo]' \
+  '(-r -b -f -m -s -l -N -o -O -i -c -u -p -z -D -P)-R[emulate BSD echo (no escapes, -n & -e flags only)]' \
   '-b[recognise bindkey escape sequences]' \
   '-m[remove arguments matching specified pattern]' \
+  '(-r -n -R -l -N -c)-f[print arguments as for the printf builtin]:format' \
   '(-u -p -z)-s[place results in the history list]' \
-  '(-N -c)-n[do not add a newline to the result]' \
-  '(-N -c)-l[print arguments separated by newlines]' \
-  '(-n -l -c)-N[print arguments separated and terminated by nulls]' \
+  '(-c -f)-n[do not add a newline to the result]' \
+  '(-N -c -f)-l[print arguments separated by newlines]' \
+  '(-n -l -c -f)-N[print arguments separated and terminated by nulls]' \
   '(-O)-o[sort arguments in ascending order]' \
   '(-o)-O[sort arguments in descending order]' \
   '-i[case-insensitive sorting]' \
-  '(-n -l -N)-c[print arguments in columns]' \
+  '(-n -l -N -f)-c[print arguments in columns]' \
   '(-s -p -z)-u+[specify file-descriptor to print arguments to]:file-descriptor:_file_descriptors' \
   '(-s -p -u)-z[push arguments onto editing buffer stack]' \
   '-D[substitute any arguments which are named directories using ~ notation]' \
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index eceb3258b..8d23fc175 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -638,7 +638,8 @@ If the tt(PUSHD_MINUS) option is set, the meanings of `tt(PLUS())' and
 `tt(-)' in this context are swapped.
 )
 findex(print)
-item(tt(print) [ tt(-bnrslzpNDPoOicm) ] [ tt(-u)var(n) ] [ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
+item(tt(print) [ tt(-bnrslzpNDPoOicm) ] [ tt(-u)var(n) ] [ tt(-f) var(format) ] [ 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 flag `tt(-)', the arguments are printed on
 the standard output as described by tt(echo), with the following differences:
 the escape sequence `tt(\M-)var(x)' metafies the character
@@ -715,19 +716,24 @@ ifnzman(noderef(Prompt Expansion))\
 ).
 )
 enditem()
+
+If any of `tt(-m)', `tt(-o)' or `tt(-O)' are used in combination with
+`tt(-f)' and there are no arguments (after the removal process in the
+case of `tt(-m)') then nothing is printed.
 )
 findex(printf)
 item(tt(printf) 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 format specifications ending in one of
-csdiouxXeEfgG are handled. In addition to this, `tt(%b)' can be used
+csdiouxXeEfgGn are handled. In addition to this, `tt(%b)' can be used
 instead of `tt(%s)' to cause escape sequences in the argument to be
 recognised and `tt(%q)' can be used to quote the argument in such a way
 that allows it to be reused as shell input. With the numeric format
 specifiers, if the corresponding argument starts with a quote character,
 the numeric value of the following character is used as the number to
-print.
+print. With `tt(%n)', the corresponding argument is taken as an identifier
+which is created as an integer parameter.
 
 If arguments remain unused after formatting, the format string is reused
 until all arguments have been consumed. If more arguments are required by
diff --git a/Src/builtin.c b/Src/builtin.c
index 4ccc26314..a04b57163 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -91,8 +91,8 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL),
-    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrslzNu0123456789pioOcm-", NULL),
-    BUILTIN("printf", 0, bin_printf, 1, -1, 0, NULL, NULL),
+    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrsflzNu0123456789pioOcm-", NULL),
+    BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, NULL, NULL),
     BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL),
     BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
@@ -296,10 +296,19 @@ execbuiltin(LinkList args, Builtin bn)
 	    }
 	    arg = (char *) ugetnode(args);
 	    /* for the "print" builtin, the options after -R are treated as
-	    options to "echo" */
-	    if ((flags & BINF_PRINTOPTS) && ops['R']) {
-		optstr = "ne";
-		flags |= BINF_ECHOPTS;
+	    options to "echo" and -f takes an extra argument */
+	    if (flags & BINF_PRINTOPTS) {
+		if (ops['R'] && !ops['f']) {
+		    optstr = "ne";
+		    flags |= BINF_ECHOPTS;
+		} else if (execop == 'f') {
+		    if (!arg) {
+			zwarnnam(name, "-f: format argument expected", NULL, 0);
+			return 1;
+		    }
+		    auxdata = arg;
+		    arg = (char *) ugetnode(args);
+		}
 	    }
 	    /* the option -- indicates the end of the options */
 	    if (ops['-'])
@@ -2871,17 +2880,42 @@ bin_false(char *name, char **argv, char *ops, int func)
 /**/
 mod_export LinkList bufstack;
 
-/* echo, print, pushln */
+/* echo, print, printf, pushln */
+
+#define print_val(VAL) \
+  if (width >= 0) { \
+      if (prec >= 0) \
+	  count += fprintf(fout, start, width, prec, VAL); \
+      else \
+	  count += fprintf(fout, start, width, VAL); \
+  } else { \
+      if (prec >= 0) \
+	  count += fprintf(fout, start, prec, VAL); \
+      else \
+	  count += fprintf(fout, start, VAL); \
+  }
 
 /**/
 int
 bin_print(char *name, char **args, char *ops, int func)
 {
-    int nnl = 0, fd, argc, n;
+    int flen, width, prec, type, argc, n, nnl = 0, ret = 0;
     int *len;
-    Histent ent;
+    char *start, *endptr, *c, *fmt = NULL;
+    char **first, nullstr = '\0', save = '\0';
+    zlong count = 0;
     FILE *fout = stdout;
 
+    double doubleval;
+    int intval;
+    unsigned int uintval;
+    char *stringval;
+
+    if (func == BIN_PRINTF) auxdata = *args++;
+    if (auxdata)
+	fmt = getkeystring(auxdata, &flen, ops['b'] ? 2 : 0, &nnl);
+    first = args;
+
     /* -m option -- treat the first argument as a pattern and remove
      * arguments not matching */
     if (ops['m']) {
@@ -2894,16 +2928,19 @@ bin_print(char *name, char **args, char *ops, int func)
 	    zwarnnam(name, "bad pattern : %s", *args, 0);
 	    return 1;
 	}
-	for (p = ++args; *p; p++)
-	    if (!pattry(pprog, *p))
-		for (t = p--; (*t = t[1]); t++);
+	for (t = p = ++args; *p; p++)
+	    if (pattry(pprog, *p))
+		*t++ = *p;
+	*t = NULL;
+	first = args;
+	if (fmt && !*args) return 0;
     }
     /* compute lengths, and interpret according to -P, -D, -e, etc. */
     argc = arrlen(args);
     len = (int *) hcalloc(argc * sizeof(int));
     for(n = 0; n < argc; n++) {
 	/* first \ sequences */
-	if (!ops['e'] && (ops['R'] || ops['r'] || ops['E']))
+	if (fmt || !ops['e'] && (ops['R'] || ops['r'] || ops['E']))
 	    unmetafy(args[n], &len[n]);
 	else
 	    args[n] = getkeystring(args[n], &len[n], ops['b'] ? 2 :
@@ -2948,6 +2985,7 @@ bin_print(char *name, char **args, char *ops, int func)
     if (ops['s']) {
 	int nwords = 0, nlen, iwords;
 	char **pargs = args;
+	Histent ent;
 
 	queue_signals();
 	ent = prepnexthistent();
@@ -2971,8 +3009,11 @@ bin_print(char *name, char **args, char *ops, int func)
 	unqueue_signals();
 	return 0;
     }
+
     /* -u and -p -- output to other than standard output */
     if (ops['u'] || ops['p']) {
+	int fd;
+
 	if (ops['u']) {
 	    for (fd = 0; fd < 10; fd++)
 		if (ops[fd + '0'])
@@ -2993,15 +3034,15 @@ bin_print(char *name, char **args, char *ops, int func)
 
     /* -o and -O -- sort the arguments */
     if (ops['o']) {
+	if (fmt && !*args) return 0;
 	if (ops['i'])
 	    qsort(args, arrlen(args), sizeof(char *), cstrpcmp);
-
 	else
 	    qsort(args, arrlen(args), sizeof(char *), strpcmp);
     } else if (ops['O']) {
+	if (fmt && !*args) return 0;
 	if (ops['i'])
 	    qsort(args, arrlen(args), sizeof(char *), invcstrpcmp);
-
 	else
 	    qsort(args, arrlen(args), sizeof(char *), invstrpcmp);
     }
@@ -3041,62 +3082,37 @@ bin_print(char *name, char **args, char *ops, int func)
 	    fclose(fout);
 	return 0;
     }
+    
     /* normal output */
-    for (; *args; args++, len++) {
-	fwrite(*args, *len, 1, fout);
-	if (args[1])
-	    fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout);
+    if (!fmt) {
+	for (; *args; args++, len++) {
+	    fwrite(*args, *len, 1, fout);
+	    if (args[1])
+		fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout);
+	}
+	if (!(ops['n'] || nnl))
+	    fputc(ops['N'] ? '\0' : '\n', fout);
+	if (fout != stdout)
+	    fclose(fout);
+	return 0;
     }
-    if (!(ops['n'] || nnl))
-	fputc(ops['N'] ? '\0' : '\n', fout);
-    if (fout != stdout)
-	fclose(fout);
-    return 0;
-}
-
-/* printf */
-
-#define print_val(VAL) \
-  if (width >= 0) { \
-      if (prec >= 0) \
-	  printf(start, width, prec, VAL); \
-      else \
-	  printf(start, width, VAL); \
-  } else { \
-      if (prec >= 0) \
-	  printf(start, prec, VAL); \
-      else \
-	  printf(start, VAL); \
-  }
-
-/**/
-int
-bin_printf(char *name, char **args, char *ops, int func)
-{
-    int len, nnl, width, prec, type, ret = 0;
-    char *start, *endptr, *c, *fmt = getkeystring(*args, &len, 0, &nnl);
-    char **first = ++args, nullstr = '\0', save = '\0';
-
-    double doubleval;
-    int intval;
-    unsigned int uintval;
-    char *stringval;
-
+    
+    /* printf style output */
     do {
-
-	for (c = fmt;c-fmt < len;c++) {
-	    type = prec = width = -1;
-
+	for (c = fmt;c-fmt < flen;c++) {
 	    if (*c != '%') {
-		putchar(*c);
+		putc(*c, fout);
+		++count;
 		continue;
 	    }
 
 	    start = c++;
 	    if (*c == '%') {
 		putchar('%');
+		++count;
 		continue;
 	    }
+	    type = prec = width = -1;
 
 	    if (strchr("+- #", *c)) c++;
 
@@ -3126,34 +3142,26 @@ bin_printf(char *name, char **args, char *ops, int func)
 	    switch (*c) {
 	    case 'c':
 		if (*args) {
-		    if (**args == Meta)
-			intval = (*args)[1] ^ 32;
-		    else
-			intval = **args;
+		    intval = **args;
 		    args++;
 		} else
 		    intval = 0;
 		print_val(intval);
 		break;
 	    case 's':
-		if (*args)
-		    stringval = unmetafy(*args++, NULL);
-		else
-		    stringval = &nullstr;
+		stringval = *args ? *args++ : &nullstr;
 		print_val(stringval);
 		break;
 	    case 'b':
 		if (*args) {
 		    int l;
-		    char *b = getkeystring(*args++, &l, 0, &nnl);
-		    fwrite(b, l, 1, stdout);
+		    char *b = getkeystring(*args++, &l, ops['b'] ? 2 : 0, &nnl);
+		    fwrite(b, l, 1, fout);
+		    count += l;
 		}
-		continue;
+		break;
 	    case 'q':
-		if (*args)
-		    stringval = bslashquote(unmetafy(*args++, NULL), NULL, 0);
-		else
-		    stringval = &nullstr;
+		stringval = *args ? bslashquote(*args++, NULL, 0) : &nullstr;
 		*c = 's';
 		print_val(stringval);
 		break;
@@ -3174,6 +3182,9 @@ bin_printf(char *name, char **args, char *ops, int func)
 	    case 'X':
 		type=3;
 		break;
+	    case 'n':
+		if (*args) setiparam(*args++, count);
+		break;
 	    default:
 		zerrnam(name, "%s: invalid directive", start, 0);
 		ret = 1;
@@ -3185,12 +3196,12 @@ bin_printf(char *name, char **args, char *ops, int func)
 			doubleval = (*args)[1];
 			print_val(doubleval);
 		    } else {
-		    	intval = (*args)[1];
+			intval = (*args)[1];
 			print_val(intval);
 		    }
 		    args++;
 		} else {
-	    	    switch (type) {
+		    switch (type) {
 		    case 1:
 			intval = (*args) ? strtol(*args, &endptr, 0) : 0;
 			print_val(intval);
@@ -3224,6 +3235,8 @@ bin_printf(char *name, char **args, char *ops, int func)
     /* if there are remaining args, reuse format string */
     } while (*args && args != first);
 
+    if (fout != stdout)
+	fclose(fout);
     return ret;
 }
 
@@ -3429,16 +3442,12 @@ bin_break(char *name, char **argv, char *ops, int func)
 	}
 	/*FALLTHROUGH*/
     case BIN_EXIT:
-	if (locallevel > forklevel) {
+	if (locallevel) {
 	    /*
 	     * We don't exit directly from functions to allow tidying
 	     * up, in particular EXIT traps.  We still need to perform
 	     * the usual interactive tests to see if we can exit at
 	     * all, however.
-	     *
-	     * The forklevel test means we *do* exit from a subshell
-	     * inside a function when we reach the level of the
-	     * function itself.
 	     */
 	    if (stopmsg || (zexit(0,2), !stopmsg)) {
 		retflag = 1;
diff --git a/Src/hashtable.h b/Src/hashtable.h
index 5b78c9c18..aa12ad3cb 100644
--- a/Src/hashtable.h
+++ b/Src/hashtable.h
@@ -56,6 +56,7 @@
 #define BIN_ECHO     22
 #define BIN_DISABLE  23
 #define BIN_ENABLE   24
+#define BIN_PRINTF   25
 
 /* These currently depend on being 0 and 1. */
 #define BIN_SETOPT    0
diff --git a/Test/.distfiles b/Test/.distfiles
index e019d06f8..fc74ba7fb 100644
--- a/Test/.distfiles
+++ b/Test/.distfiles
@@ -8,6 +8,6 @@ A03quoting.ztst      C04funcdef.ztst      Makefile.in          ztst.zsh
 A04redirect.ztst     D01prompt.ztst       V02zregexparse.ztst
 A05execution.ztst    D02glob.ztst         Y01completion.ztst
 D06subscript.ztst    V01zmodload.ztst     E01options.ztst
-B02typeset.ztst
+B02typeset.ztst      B03print.ztst
 README
 '
diff --git a/Test/B03print.ztst b/Test/B03print.ztst
new file mode 100644
index 000000000..0986aa6e4
--- /dev/null
+++ b/Test/B03print.ztst
@@ -0,0 +1,135 @@
+# Tests for the echo, print, printf and pushln builtins
+
+# Tested elsewhere:
+#  Use of print -p to output to coprocess	A01grammar
+#  Prompt expansion with print -P		D01prompt
+#  -l, -r, -R and -n indirectly tested in various places
+
+# Not yet tested:
+#  echo and pushln
+#  print's -b -c -s -z -N options
+
+
+%test
+
+ print -D "${HOME:-~}"
+0:replace directory name
+>~
+
+ print -u2 'error message'
+0:output to file-descriptor
+?error message
+
+ print -o foo bar Baz
+0:argument sorting
+>Baz bar foo
+
+ print -f
+1:print -f needs a format specified
+?(eval):print:1: -f: format argument expected
+
+ print -Of '%s\n' foo bar baz
+0:reverse argument sorting
+>foo
+>baz
+>bar
+
+ print -io a B c
+0:case-insensitive argument sorting
+>a B c
+
+ print -m '[0-9]' one 2 three 4 five 6
+0:removal of non-matching arguments
+>2 4 6
+
+ printf '%s\n' string
+0:test s format specifier
+>string
+
+ printf '%b' '\t\\\n'
+0:test b format specifier
+>	\
+
+# test %q here - it doesn't quite work yet
+
+ printf '%c\n' char
+0:test c format specifier
+>c
+
+ printf '%.10e%n\n' 1 count >/dev/null
+ printf '%d\n' $count
+0:test n format specifier
+>16
+
+ printf '%d\n' 123
+0:test d format specifier
+>123
+
+ printf '%g\n' 123.45
+0:test g format specifier
+>123.45
+
+# Is anyone not using ASCII
+ printf '%d\n' \'A
+0:initial quote to get numeric value of character with int
+>65
+
+ printf '%.1E\n' \'B
+0:initial quote to get numeric value of character with double
+>6.6E+01
+
+# code will probably be changed to print the literal `%s' in this case
+ printf '\x25s\n' arg
+0:using \x25 to introduce a format specifier
+>arg
+
+ printf '%3c\n' c
+0:width specified in format specifier
+>  c
+
+ printf '%.4s\n' chopped
+0:precision specified in format specifier
+>chop
+
+ printf '%*.*f\n' 6 2 10.2
+0:width/precision specified in arguments
+> 10.20
+
+ printf '%d\n' 3000000000
+1d:out of range numeric result
+?(eval):printf:1: `3000000000' arithmetic overflow
+
+ printf '%G\n' letters
+1:non numeric argument
+?(eval):printf:1: `letters' expected numeric value
+>0
+
+ print -f '%d\n' 2e4
+1:letters in numeric argument
+?(eval):print:1: `2e4' not completely converted
+>2
+
+ printf '%z'
+1:test invalid directive
+?(eval):printf:1: %z: invalid directive
+
+ print -m -f 'format - %s.\n' 'z' a b c
+0:format not printed if no arguments left after -m removal
+
+ print -f 'format - %s.\n'
+0:format printed despite lack of arguments
+>format - .
+
+ printf 'x%4sx\n'
+0:with no arguments empty string where string needed
+>x    x
+
+ printf '%d\n'
+0:with no arguments, zero used where number needed
+>0
+
+ printf '%s\t%c:%#x%%\n' one a 1 two b 2 three c 3
+0:multiple arguments with format reused
+>one	a:0x1%
+>two	b:0x2%
+>three	c:0x3%