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:
parent
80aa807a61
commit
b8dc5a7f6d
|
@ -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
|
||||
|
|
|
@ -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
6
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
|
||||
---------------------------
|
||||
|
||||
|
|
21
README
21
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
92
Src/hist.c
92
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;
|
||||
}
|
||||
|
||||
|
|
37
Src/subst.c
37
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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue