diff options
author | Peter Stephenson <p.stephenson@samsung.com> | 2019-06-20 11:13:05 +0100 |
---|---|---|
committer | Peter Stephenson <p.stephenson@samsung.com> | 2019-06-20 11:13:05 +0100 |
commit | b8dc5a7f6d52df98a546ad3b39104f4b8e7b8daf (patch) | |
tree | 360211057d08021d5e1e7291e5d81d4d9ba2bf76 | |
parent | 80aa807a61cf10ebf459ba8e06621a5ec33041dc (diff) | |
download | zsh-b8dc5a7f6d52df98a546ad3b39104f4b8e7b8daf.tar.gz zsh-b8dc5a7f6d52df98a546ad3b39104f4b8e7b8daf.tar.xz zsh-b8dc5a7f6d52df98a546ad3b39104f4b8e7b8daf.zip |
44435: Handling digita arguments for :h and :t.
Pick number of leading or trailing path components to substitute. Active in history, brace parameters, glob qualifiers. Add tests for all three environments.
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | Doc/Zsh/expn.yo | 29 | ||||
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | README | 21 | ||||
-rw-r--r-- | Src/Zle/compctl.c | 2 | ||||
-rw-r--r-- | Src/glob.c | 2 | ||||
-rw-r--r-- | Src/hist.c | 92 | ||||
-rw-r--r-- | Src/subst.c | 37 | ||||
-rw-r--r-- | Test/D02glob.ztst | 28 | ||||
-rw-r--r-- | Test/D04parameter.ztst | 77 | ||||
-rw-r--r-- | Test/W01history.ztst | 23 |
11 files changed, 293 insertions, 31 deletions
diff --git a/ChangeLog b/ChangeLog index 28a3e371c..034e2ae9f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2019-06-20 Peter Stephenson <p.stephenson@samsung.com> + + * 44435: Doc/Zsh/expn.yo, NEWS, README, Src/Zle/compctl.c, + Src/glob.c, Src/hist.c, Src/subst.c, Test/D02glob.ztst, + Test/D04parameter.ztst, Test/W01history.ztst: Handle + trailing digit arguments of :t and :h modifiers. + 2019-06-19 Peter Stephenson <p.stephenson@samsung.com> * Roman Perepelitsa: 44430: Src/prompt.c: various problems with diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index a212d742d..5f7aaa1b3 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -260,9 +260,23 @@ see the definition of the filename extension in the description of the tt(r) modifier below. Note that according to that definition the result will be empty if the string ends with a `tt(.)'. ) -item(tt(h))( -Remove a trailing pathname component, leaving the head. This works -like `tt(dirname)'. +item(tt(h) [ var(digits) ])( +Remove a trailing pathname component, shortening the path by one +directory level: this is the `head' of the pathname. This works like +`tt(dirname)'. If the tt(h) is followed immediately (with no spaces or +other separator) by any number of decimal digits, and the value of the +resulting number is non-zero, that number of leading components is +preserved instead of the final component being removed. In an +absolute path the leading `tt(/)' is the first component, so, +for example, if tt(var=/my/path/to/something), then tt(${var:h3}) +substitutes tt(/my/path). Consecutive `/'s are treated the same as +a single `/'. In parameter substitution, digits may only be +used if the expression is in braces, so for example the short form +substitution tt($var:h2) is treated as tt(${var:h}2), not as +tt(${var:h2}). No restriction applies to the use of digits in history +substitution or globbing qualifiers. If more components are requested +than are present, the entire path is substituted (so this does not +trigger a `failed modifier' error in history expansion). ) item(tt(l))( Convert the words to all lowercase. @@ -316,9 +330,12 @@ immediately by a tt(g). In parameter expansion the tt(&) must appear inside braces, and in filename generation it must be quoted with a backslash. ) -item(tt(t))( -Remove all leading pathname components, leaving the tail. This works -like `tt(basename)'. +item(tt(t) [ var(digits) ])( +Remove all leading pathname components, leaving the final component (tail). +This works like `tt(basename)'. Any trailing slashes are first removed. +Decimal digits are handled as described above for (h), but in this +case that number of trailing components is preserved instead of +the default 1; 0 is treated the same as 1. ) item(tt(u))( Convert the words to all uppercase. diff --git a/NEWS b/NEWS index ec20b4982..4603c62a3 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,12 @@ specify the order of completion matches. This affects the display of candidate matches and the order in which they are selected when cycling between them using menu completion. +The :h and :t modifiers in parameter expansion (if braces are present), +glob qualifiers and history expansion may take following decimal digit +arguments in order to keep that many leading or trailing path components +instead of the defaults of all but one (:h) and one (:t). In an absolute +path the leading '/' counts as one component. + Changes from 5.6.2 to 5.7.1 --------------------------- diff --git a/README b/README index 9763e7aa6..be7929164 100644 --- a/README +++ b/README @@ -30,9 +30,28 @@ Zsh is a shell with lots of features. For a list of some of these, see the file FEATURES, and for the latest changes see NEWS. For more details, see the documentation. -Incompatibilities since 5.6.2 +Incompatibilities since 5.7.1 ----------------------------- +The history expansion !:1:t2 used to be interpreted such that the 2 +was a separate character added after the history expansion. Now +it is an argument to the :t modifier. + +For example + +% echo /my/interesting/path +% echo !:1:t2 + +used to echo "path2", but now echoes "interesting/path". + +The behaviour of :h has similarly changed. + +The behaviour has also changed in forms such as ${foo:t2) and *(:t2), +but in those cases the previous behaviour was not meaningful. + +Incompatibilities between 5.6.2 and 5.7.1 +----------------------------------------- + 1) vcs_info git: The gen-unapplied-string hook receives the patches in order (next to be applied first). This is consistent with the hg backend and with one of two contradictory claims in the documentation diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c index f963d5712..f242e1b28 100644 --- a/Src/Zle/compctl.c +++ b/Src/Zle/compctl.c @@ -2511,7 +2511,7 @@ makecomplistcmd(char *os, int incmd, int flags) else if (!(cmdstr && (((ccp = (Compctlp) compctltab->getnode(compctltab, cmdstr)) && (cc = ccp->cc)) || - ((s = dupstring(cmdstr)) && remlpaths(&s) && + ((s = dupstring(cmdstr)) && remlpaths(&s, 1) && (ccp = (Compctlp) compctltab->getnode(compctltab, s)) && (cc = ccp->cc))))) { if (flags & CFN_DEFAULT) diff --git a/Src/glob.c b/Src/glob.c index ed2c90bd8..92fd64e7c 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -400,7 +400,7 @@ insert(char *s, int checked) if (colonmod) { /* Handle the remainder of the qualifier: e.g. (:r:s/foo/bar/). */ char *mod = colonmod; - modify(&news, &mod); + modify(&news, &mod, 1); } if (!statted && (gf_sorts & GS_NORMAL)) { statfullpath(s, &buf, 1); diff --git a/Src/hist.c b/Src/hist.c index 901cd3b1a..fd5606dc3 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -555,6 +555,27 @@ substfailed(void) return -1; } +/* + * Return a count given by decimal digits after a modifier. + */ +static int +digitcount(void) +{ + int c = ingetc(), count; + + if (idigit(c)) { + count = 0; + do { + count = 10 * count + (c - '0'); + c = ingetc(); + } while (idigit(c)); + } + else + count = 0; + inungetc(c); + return count; +} + /* Perform history substitution, returning the next character afterwards. */ /**/ @@ -835,7 +856,7 @@ histsubchar(int c) } break; case 'h': - if (!remtpath(&sline)) { + if (!remtpath(&sline, digitcount())) { herrflush(); zerr("modifier failed: h"); return -1; @@ -856,7 +877,7 @@ histsubchar(int c) } break; case 't': - if (!remlpaths(&sline)) { + if (!remlpaths(&sline, digitcount())) { herrflush(); zerr("modifier failed: t"); return -1; @@ -1974,16 +1995,18 @@ chrealpath(char **junkptr) /**/ int -remtpath(char **junkptr) +remtpath(char **junkptr, int count) { char *str = strend(*junkptr); /* ignore trailing slashes */ while (str >= *junkptr && IS_DIRSEP(*str)) --str; - /* skip filename */ - while (str >= *junkptr && !IS_DIRSEP(*str)) - --str; + if (!count) { + /* skip filename */ + while (str >= *junkptr && !IS_DIRSEP(*str)) + --str; + } if (str < *junkptr) { if (IS_DIRSEP(**junkptr)) *junkptr = dupstring ("/"); @@ -1992,6 +2015,34 @@ remtpath(char **junkptr) return 0; } + + if (count) + { + /* + * Return this many components, so start from the front. + * Leading slash counts as one component, consistent with + * behaviour of repeated applications of :h. + */ + char *strp = *junkptr; + while (strp < str) { + if (IS_DIRSEP(*strp)) { + if (--count <= 0) { + if (strp == *junkptr) + ++strp; + *strp = '\0'; + return 1; + } + /* Count consecutive separators as one */ + while (IS_DIRSEP(strp[1])) + ++strp; + } + ++strp; + } + + /* Full string needed */ + return 1; + } + /* repeated slashes are considered like a single slash */ while (str > *junkptr && IS_DIRSEP(str[-1])) --str; @@ -2040,7 +2091,7 @@ rembutext(char **junkptr) /**/ mod_export int -remlpaths(char **junkptr) +remlpaths(char **junkptr, int count) { char *str = strend(*junkptr); @@ -2050,12 +2101,29 @@ remlpaths(char **junkptr) --str; str[1] = '\0'; } - for (; str >= *junkptr; --str) - if (IS_DIRSEP(*str)) { - *str = '\0'; - *junkptr = dupstring(str + 1); - return 1; + for (;;) { + for (; str >= *junkptr; --str) { + if (IS_DIRSEP(*str)) { + if (--count > 0) { + if (str > *junkptr) { + --str; + break; + } else { + /* Whole string needed */ + return 1; + } + } + *str = '\0'; + *junkptr = dupstring(str + 1); + return 1; + } } + /* Count consecutive separators as 1 */ + while (str >= *junkptr && IS_DIRSEP(*str)) + --str; + if (str <= *junkptr) + break; + } return 0; } diff --git a/Src/subst.c b/Src/subst.c index 60eb33390..b132f251b 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -3438,7 +3438,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, s--; if (unset(KSHARRAYS) || inbrace) { if (!isarr) - modify(&val, &s); + modify(&val, &s, inbrace); else { char *ss; char **ap = aval; @@ -3447,12 +3447,12 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, while ((*pp = *ap++)) { ss = s; - modify(pp++, &ss); + modify(pp++, &ss, inbrace); } if (pp == aval) { char *t = ""; ss = s; - modify(&t, &ss); + modify(&t, &ss, inbrace); } s = ss; } @@ -4182,6 +4182,12 @@ arithsubst(char *a, char **bptr, char *rest) * PTR is an in/out parameter. On entry it contains the string of colon * modifiers. On return it points past the last recognised modifier. * + * INBRACE is non-zero if we are in some form of a bracketed or + * parenthesised expression; it is zero for modifiers ocurring + * in an an unbracketed variable substitution. This means that + * $foo:t222 is treated ias ${foo:t}222 rather than ${foo:t222} + * for backward compatibility. + * * Example: * ENTRY: *str is "." *ptr is ":AN" * RETURN: *str is "/home/foobar" (equal to $PWD) *ptr points to the "N" @@ -4189,7 +4195,7 @@ arithsubst(char *a, char **bptr, char *rest) /**/ void -modify(char **str, char **ptr) +modify(char **str, char **ptr, int inbrace) { char *ptr1, *ptr2, *ptr3, *lptr, c, *test, *sep, *t, *tt, tc, *e; char *copy, *all, *tmp, sav, sav1, *ptr1end; @@ -4202,6 +4208,8 @@ modify(char **str, char **ptr) *str = dupstring(*str); while (**ptr == ':') { + int count = 0; + lptr = *ptr; (*ptr)++; wall = gbal = 0; @@ -4214,10 +4222,8 @@ modify(char **str, char **ptr) case 'a': case 'A': case 'c': - case 'h': case 'r': case 'e': - case 't': case 'l': case 'u': case 'q': @@ -4226,6 +4232,17 @@ modify(char **str, char **ptr) c = **ptr; break; + case 'h': + case 't': + c = **ptr; + if (inbrace && idigit((*ptr)[1])) { + do { + count = 10 * count + ((*ptr)[1] - '0'); + ++(*ptr); + } while (idigit((*ptr)[1])); + } + break; + case 's': c = **ptr; (*ptr)++; @@ -4392,7 +4409,7 @@ modify(char **str, char **ptr) break; } case 'h': - remtpath(©); + remtpath(©, count); break; case 'r': remtext(©); @@ -4401,7 +4418,7 @@ modify(char **str, char **ptr) rembutext(©); break; case 't': - remlpaths(©); + remlpaths(©, count); break; case 'l': copy = casemodify(tt, CASMOD_LOWER); @@ -4478,7 +4495,7 @@ modify(char **str, char **ptr) break; } case 'h': - remtpath(str); + remtpath(str, count); break; case 'r': remtext(str); @@ -4487,7 +4504,7 @@ modify(char **str, char **ptr) rembutext(str); break; case 't': - remlpaths(str); + remlpaths(str, count); break; case 'l': *str = casemodify(*str, CASMOD_LOWER); diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst index 08b71dc8e..5638e1255 100644 --- a/Test/D02glob.ztst +++ b/Test/D02glob.ztst @@ -700,3 +700,31 @@ print ${value//[${foo}b-z]/x} 0:handling of - range in complicated pattern context >xx + + pathtotest=glob.tmp/my/test/dir/that/does/not/exist + mkdir -p $pathtotest + print $pathtotest(:h) + print $pathtotest(:h0) + print $pathtotest(:h10) + print $pathtotest(:h3) + print $pathtotest(:h2) + print $pathtotest(:h1) + print $pathtotest(:t) + print $pathtotest(:t0) + print $pathtotest(:t10) + print $pathtotest(:t3) + print $pathtotest(:t2) + print $pathtotest(:t1) +0:modifiers :h and :t with numbers (main test is in D04parameter.ztst) +>glob.tmp/my/test/dir/that/does/not +>glob.tmp/my/test/dir/that/does/not +>glob.tmp/my/test/dir/that/does/not/exist +>glob.tmp/my/test +>glob.tmp/my +>glob.tmp +>exist +>exist +>glob.tmp/my/test/dir/that/does/not/exist +>does/not/exist +>not/exist +>exist diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst index 1ec650352..194c3e287 100644 --- a/Test/D04parameter.ztst +++ b/Test/D04parameter.ztst @@ -2445,3 +2445,80 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888 : <<< ${(F)x/y} } 0:Separation / join logic regresssion test + + testpath=/one/two/three/four + for (( i = 0; i <= 6; ++i )); do + eval "print \$testpath:t$i" + eval "print \${testpath:t$i}" + done +0:t with trailing digits +>four0 +>four +>four1 +>four +>four2 +>three/four +>four3 +>two/three/four +>four4 +>one/two/three/four +>four5 +>/one/two/three/four +>four6 +>/one/two/three/four + + testpath=/one/two/three/four + for (( i = 0; i <= 6; ++i )); do + eval "print \$testpath:h$i" + eval "print \${testpath:h$i}" + done +0:h with trailing digits +>/one/two/three0 +>/one/two/three +>/one/two/three1 +>/ +>/one/two/three2 +>/one +>/one/two/three3 +>/one/two +>/one/two/three4 +>/one/two/three +>/one/two/three5 +>/one/two/three/four +>/one/two/three6 +>/one/two/three/four + + testpath=/a/quite/long/path/to/do/messy/stuff/with + print $testpath:h2:t3:h5:t16:h2n2 + print ${testpath:t5:h2} + print ${testpath:t5:h2:t} + print ${testpath:h6:t4:h3:t2:h} + print ${testpath:h10:t10:t6:h3} + print ${testpath:t9:h} + print ${testpath:t9:h:t} +0:Combinations of :h and :t with and without trailing digits +>/a/quite/long/path/to/do/messy/stuff2:t3:h5:t16:h2n2 +>to/do +>do +>long +>path/to/do +>a/quite/long/path/to/do/messy/stuff +>stuff + + testpath=///this//has////lots//and////lots//of////slashes + print ${testpath:h3} + print ${testpath:t4} +0:Multiple slashes are treated as one in :h and :t but are not removed +>///this//has +>and////lots//of////slashes + + testpath=trailing/slashes/are/removed/// + print ${testpath:h} + print ${testpath:h2} + print ${testpath:t} + print ${testpath:t2} +0:Modifiers :h and :t remove trailing slashes before examining path +>trailing/slashes/are +>trailing/slashes +>removed +>are/removed diff --git a/Test/W01history.ztst b/Test/W01history.ztst index 6ef9b11cc..96d0beb61 100644 --- a/Test/W01history.ztst +++ b/Test/W01history.ztst @@ -58,3 +58,26 @@ *?* F:Check that a history bug introduced by workers/34160 is working again. # Discarded line of error output consumes prompts printed by "zsh -i". + + $ZTST_testdir/../Src/zsh -fis <<<' + echo /my/path/for/testing + echo !1:1:h10 + echo !1:1:h3 + echo !1:1:h2 + echo !1:1:h1 + echo !1:1:t10 + echo !1:1:t3 + echo !1:1:t2 + echo !1:1:t1 + echo !1:1:t3:h2' 2>/dev/null +0:Modifiers :h and :t with arguments +>/my/path/for/testing +>/my/path/for/testing +>/my/path +>/my +>/ +>/my/path/for/testing +>path/for/testing +>for/testing +>testing +>path/for |