From 648d1c275613f7e43d01278fb3ffb5bca34640e2 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Mon, 8 Oct 2001 10:47:46 +0000 Subject: [PATCH] add print -f option, %n format specifier and tests for print/printf (15973) --- ChangeLog | 7 ++ Completion/Zsh/Command/_print | 13 +-- Doc/Zsh/builtins.yo | 12 ++- Src/builtin.c | 171 ++++++++++++++++++---------------- Src/hashtable.h | 1 + Test/.distfiles | 2 +- Test/B03print.ztst | 135 +++++++++++++++++++++++++++ 7 files changed, 250 insertions(+), 91 deletions(-) create mode 100644 Test/B03print.ztst diff --git a/ChangeLog b/ChangeLog index af679e64f..a729ca16a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2001-10-08 Oliver Kiddle + + * 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 * 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%