1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-13 11:06:17 +02:00

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.
This commit is contained in:
Peter Stephenson 2019-06-20 11:13:05 +01:00
parent 80aa807a61
commit b8dc5a7f6d
11 changed files with 293 additions and 31 deletions

View File

@ -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

View File

@ -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.

6
NEWS
View File

@ -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
---------------------------

21
README
View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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(&copy);
remtpath(&copy, count);
break;
case 'r':
remtext(&copy);
@ -4401,7 +4418,7 @@ modify(char **str, char **ptr)
rembutext(&copy);
break;
case 't':
remlpaths(&copy);
remlpaths(&copy, 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);

View File

@ -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

View File

@ -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

View File

@ -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