From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 4311 invoked by alias); 19 Sep 2016 16:57:55 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: X-Seq: 39389 Received: (qmail 11675 invoked from network); 19 Sep 2016 16:57:55 -0000 X-Qmail-Scanner-Diagnostics: from nm12-vm4.bullet.mail.ir2.yahoo.com by f.primenet.com.au (envelope-from , uid 7791) with qmail-scanner-2.11 (clamdscan: 0.99.2/21882. spamassassin: 3.4.1. Clear:RC:0(212.82.96.174):SA:0(0.0/5.0):. Processed in 0.611211 secs); 19 Sep 2016 16:57:55 -0000 X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=FREEMAIL_FROM,SPF_PASS, T_DKIM_INVALID autolearn=unavailable autolearn_force=no version=3.4.1 X-Envelope-From: okiddle@yahoo.co.uk X-Qmail-Scanner-Mime-Attachments: | X-Qmail-Scanner-Zip-Files: | Received-SPF: pass (ns1.primenet.com.au: SPF record at _spf.mail.yahoo.com designates 212.82.96.174 as permitted sender) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.co.uk; s=s2048; t=1474303844; bh=gQ0AQoYPjU0H+TYQJ4FT5HktVVvnM9RNScQ89l/vBmA=; h=From:To:Subject:Date:From:Subject; b=oTRuKvHHLEfhOep4OMo4tdcuH7/NlqVTLxZVm+/nayJHi4yWNdVezsmzpcOc91EaOtaPEszB225b8pducOATCc1dx3He4CX8lLr2umR11LJuc7GNk1ODxaRJIQz3PeOos6zK9uQkDr4okoAZ/3hq/a+jt0jAo9SV22oKvBcf8jNBAxunFHYrFodvPanjbQhMvx/zMsfsWv6t5FUKsdCYJnI2OYBHKrZhzwNEcahu6eTXL/mqxSBg3MaYzEMgMZnBOrHF8FJ34L6e9vY/Gz9BHEdSstGQS6AqyyuaADkTJU4rWLwDaz+QSVChNLFvEurZsm1l9lvUIa0o0dC6kg/1Zg== X-Yahoo-Newman-Id: 570067.34170.bm@smtp132.mail.ir2.yahoo.com X-Yahoo-Newman-Property: ymail-3 X-YMail-OSG: GH_q1DkVM1lO2RT_os2a0GKQoBqXtRKVuSBhPB5n2Awy2AB Y0nFUGQfkmSluPbuznmaOY1iz8107m8XLYsiVG7zHvI4Bco2gpUjNhgG15Ez v5mXvzkCwMz3avdbnjKkPTQTKuKUOvKi0bbCmouCOb2RhQAWlRPVsAcdwVp8 skh0HKUmmRVsqUD93n723SqdEIv9l_9vq_UX0Z2TT02Km1Gv4nY3zueceMkY PwytC8855usbCUy50T6AOkaOhsO2fmYQlzmvGPjLLg1qbOpqn1NIsIg9IxH7 efH2MT8BYKKqjOQe4VBYGaWK1P3_n4PwZecGjVNjigKaCPAHC3_uUZtbYZtP g9DGGrCdwOryOgrqlTV.cUlZ5aBEbv8zi6tgaRBCoglNSmgWHIQKeZWIM4of biY2ImX9ShC96JeVQJg1RWTDI0pSuGHwElr9BAnB6g8o63z3PZHadDMcR48R LuKKNslJMUFLmwebFb.K6lQdcfIIz3NaTHSIgH7ctT0RThPRplUNnQ.x7MLB vChKmCEOpXRcxzKa8cegm0ylKd_T9yc2TqFnLAjbwzP8- X-Yahoo-SMTP: opAkk_CswBAce_kJ3nIPlH80cJI- From: Oliver Kiddle To: Zsh workers Subject: PATCH: print -v with an array MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <53991.1474303843.1@hydra.kiddle.eu> Date: Mon, 19 Sep 2016 18:50:43 +0200 Message-ID: <53992.1474303843@hydra.kiddle.eu> A feature of the shell printf implementations is that the format string will be reused if additional arguments remain. So you can do things like print out an associative array: printf '%s - %s\n' ${(kv)_comps} This patch makes print -v check if the variable is an array and if so, each reuse of the format string will correspond to a separate array element. So you can combine or skip elements: perhaps matches and descriptions in a completion function. In general, I find any case where I can apply an operation to all elements of an array to be very useful. This is also something that can't be done directly with command substitution. Ksh93 has the right basic idea in not forking for builtins and -v otherwise feels somehow not right. This is not fully bash compatible. Neither actually was our previous implementation because bash assigns to the first element of the array leaving subsequent elements unchanged. printf -v was added after zsh 5.2 was released so we've no compatibility concern there. Alternatives to checking if the variable is an array first would be a -A option or perhaps making the feature specific to print (which bash lacks) and not to printf. With a bit of rearrangment of bin_print it would now be possible to use this to allow more mixing of print options: -f with -c, for example. Back in January, when I wasn't paying attention, Bart wrote: > - Should "print -v foo bar" write the trailing newline into $foo ? In the > patch below I've chosen to make -n implicit with -v. This does not > involve "printf" which always needs an explicit newline. I'd suggest we include the trailing newline if the -l option was specified. Seems it does get included with -c. With print -v the open_memstream stuff might see more usage. Should we be concerned with more efficient alternatives to the temporary file fallback? It appears to work to use shm_open followed by fdopen, do the writes, a final fflush and then mmap the file descriptor to get at what was written. That's fairly guaranteed to be in memory and shared memory limits shouldn't be a problem. pipe() also works but it tends to have a limited buffer size in the region of 16-64K. A memfd_create system call was recently added to Linux. Combined with fdopen, that would provide an alternative on Linux where libc is something other than glibc. FreeBSD appears to have open_memstream. On Solaris a tmpfs /tmp has been standard for a very long time so the temporary file is unlikely to ever touch a disc anyway. Oliver diff --git a/Completion/Zsh/Command/_print b/Completion/Zsh/Command/_print index 38f17ab..8df0941 100644 --- a/Completion/Zsh/Command/_print +++ b/Completion/Zsh/Command/_print @@ -1,15 +1,18 @@ #compdef print printf -local state expl line eflag pflag rest ret=1 +local state expl line eflag pflag rflag rest ret=1 if [[ $service = print ]]; then # -e flag available only after -R eflag="${words[1,CURRENT-1][(r)-*R*]:+-e[enable escapes]}" + rflag='-r[ignore escape conventions of echo]' # -p flag only relevant if we have a coprocess (:>&p) 2>/dev/null && pflag='(-s -u -z)-p[print arguments to input of coprocess]' + [[ -n ${words[(r)-*f]} ]] && rflag='-r[disable reuse of format string]' + if [[ -n ${words[1,CURRENT][(r)-*P*]} ]]; then rest='*: :->prompt' else @@ -17,11 +20,11 @@ if [[ $service = print ]]; then fi _arguments -C -s -A "-*" -S \ - '(-f)-r[ignore escape conventions of echo]' \ + '-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:->printfformat' \ + '-m[remove arguments not matching specified pattern]:pattern' \ + '(-n -R -l -N -c)-f+[print arguments as for the printf builtin]:format:->printfformat' \ '(-u -p -z)-s[place results in the history list]' \ '(-c -f)-n[do not add a newline to the result]' \ '(-N -c -f)-l[print arguments separated by newlines]' \ @@ -33,11 +36,13 @@ if [[ $service = print ]]; then '(-n -l -N -f -C -s -z)-c[print arguments in columns]' \ '(-n -l -N -f -c -s -z)-C+[print arguments in specified number of columns]:columns' \ '(-s -p -z)-u+[specify file descriptor to print arguments to]:file descriptor:_file_descriptors' \ - '-v[store output in named parameter]:parameter:_parameters' \ + '(-s -z -p -u)-v[store output in named parameter]:parameter:_parameters' \ '(-s -p -u)-z[push arguments onto editing buffer stack]' \ '-D[substitute any arguments which are named directories using ~ notation]' \ '-P[perform prompt expansion]' \ - $pflag $eflag $rest && ret=0 + '(-X -f -a -C -c -z)-x+[expand leading tabs]:tab width' \ + '(-x -f -a -C -c -z)-X+[expand all tabs]:tab width' \ + $pflag $eflag $rflag $rest && ret=0 elif [[ $service = printf ]]; then state=printf fi diff --git a/Completion/Zsh/Type/_globquals b/Completion/Zsh/Type/_globquals index 6eef168..a904bdf 100644 --- a/Completion/Zsh/Type/_globquals +++ b/Completion/Zsh/Type/_globquals @@ -121,8 +121,7 @@ while [[ -n $PREFIX ]]; do tmatch=( s m h d w M ) if zstyle -t ":completion:${curcontext}:time-specifiers" verbose; then zstyle -s ":completion:${curcontext}:time-specifiers" list-separator sep || sep=-- - print -v tdisp -f "%s ${sep//(#m)[%\\]/$MATCH$MATCH} %s\0" ${tmatch:^^tdisp} - tdisp=( ${(0)tdisp} ) + print -v tdisp -f "%s ${sep//(#m)[%\\]/$MATCH$MATCH} %s" ${tmatch:^^tdisp} fi alts+=( "time-specifiers:time specifier:compadd -E 0 -d tdisp -S '' -a tmatch" ) fi diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index ac4441c..1676c25 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1289,7 +1289,9 @@ 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. +parameter var(name), instead of printed. If var(name) is an array and +the format string is reused when consuming arguments then one +array element will be used for each use of the format string. ) findex(pushd) pindex(PUSHD_TO_HOME, use of) diff --git a/Src/builtin.c b/Src/builtin.c index 248f929..47dcc4c 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -4051,10 +4051,11 @@ bin_print(char *name, char **args, Options ops, int func) { int flen, width, prec, type, argc, n, narg, curlen = 0; int nnl = 0, fmttrunc = 0, ret = 0, maxarg = 0, nc = 0; - int flags[6], *len; + int flags[6], *len, visarr = 0; char *start, *endptr, *c, *d, *flag, *buf = NULL, spec[14], *fmt = NULL; char **first, **argp, *curarg, *flagch = "'0+- #", save = '\0', nullstr = '\0'; size_t rcount = 0, count = 0; + size_t *cursplit, *splits = 0; FILE *fout = stdout; #ifdef HAVE_OPEN_MEMSTREAM size_t mcount; @@ -4086,7 +4087,7 @@ bin_print(char *name, char **args, Options ops, int func) return 1; \ } \ unlink(tmpf); \ - if ((fout = fdopen(tempfd, "w+")) == NULL) { \ + if ((FOUT = fdopen(tempfd, "w+")) == NULL) { \ close(tempfd); \ zwarnnam(name, "can't open temp file: %e", errno); \ return 1; \ @@ -4611,11 +4612,23 @@ 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,'v')) { + struct value vbuf; + char* s = OPT_ARG(ops,'v'); + Value v = getvalue(&vbuf, &s, 0); + visarr = v && PM_TYPE(v->pm->node.flags) == PM_ARRAY; + } /* printf style output */ *spec = '%'; argp = args; do { rcount = count; + if (argp > args && visarr) { /* reusing format string */ + if (!splits) + cursplit = splits = (size_t *)zhalloc(sizeof(size_t) * + (arrlen(args) / (argp - args) + 1)); + *cursplit++ = count; + } if (maxarg) { first += maxarg; argc -= maxarg; @@ -4982,18 +4995,30 @@ bin_print(char *name, char **args, Options ops, int func) if (buf) free(buf); } else { - stringval = metafy(buf, rcount, META_REALLOC); - if (OPT_ISSET(ops,'z')) { - zpushnode(bufstack, stringval); - } else if (OPT_ISSET(ops,'v')) { - setsparam(OPT_ARG(ops, 'v'), stringval); + if (visarr) { + char **arrayval = zshcalloc((cursplit - splits + 2) * sizeof(char *)); + for (;cursplit >= splits; cursplit--) { + int start = cursplit == splits ? 0 : cursplit[-1]; + arrayval[cursplit - splits] = + metafy(buf + start, count - start, META_DUP); + count = start; + } + setaparam(OPT_ARG(ops, 'v'), arrayval); + free(buf); } else { - ent = prepnexthistent(); - ent->node.nam = stringval; - ent->stim = ent->ftim = time(NULL); - ent->node.flags = 0; - ent->words = (short *)NULL; - addhistnode(histtab, ent->node.nam, ent); + stringval = metafy(buf, rcount, META_REALLOC); + if (OPT_ISSET(ops,'z')) { + zpushnode(bufstack, stringval); + } else if (OPT_ISSET(ops,'v')) { + setsparam(OPT_ARG(ops, 'v'), stringval); + } else { + ent = prepnexthistent(); + ent->node.nam = stringval; + ent->stim = ent->ftim = time(NULL); + ent->node.flags = 0; + ent->words = (short *)NULL; + addhistnode(histtab, ent->node.nam, ent); + } } } unqueue_signals(); diff --git a/Test/B03print.ztst b/Test/B03print.ztst index befe2f2..74e34f3 100644 --- a/Test/B03print.ztst +++ b/Test/B03print.ztst @@ -310,3 +310,9 @@ 0:print and printf into a variable >typeset foo='once more' >typeset foo=$'into\C-@the-breach\C-@-' + + foo=() + print -f '%2$d %4s' -v foo one 1 two 2 three 3 + typeset -p foo +0:printf into an array variable +>typeset -a foo=( '1 one' '2 two' '3 three' )