Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

[PATCH] Re: (Y) modifier: up to N matches?



Bart Schaefer wrote on Mon, Jun 02, 2014 at 20:46:03 -0700:
> On Jun 2,  6:23pm, Daniel Shahaf wrote:
> }
> } Would it make sense to have (Y) take a numeric argument specifying the
> } maximal number of files to match? e.g., *(NY5) would expand to between
> } 0 and 5 filenames (but never more than 5).
> 
> My concern is that people are going to expect the (o)/(O) qualifiers to
> take effect before (Y) does, and will be confused about the "skipped"
> files when it takes effect after.  If (Y) can't return more than one
> result, there's nothing to sort.

The expectations about sorting are just as much of a problem with (Y) as
they would be with (Y42): in both cases there might be "skipped" files.
For example, in the source dir, *(Y/on) [or *(Y1/on)] might result in "Etc"
even though "Doc" exists.

Let's clarify that in the documentation of (Y).

> However, I can't come up with any other objection.

Two patches attached.  The first patch implements (Y42) and adds completion
and the above-mentioned docs clarification.  (The two latter parts should be
useful even if we don't add an argument to (Y).)  The second patch isn't
strictly required, but it cleans up the return type changes that are no
longer needed after the first patch.

FWIW, I'm intentionally making (Y) without argument an error; we can settle
on its semantics later after (Y42) has seen some "in the field" use.  The
spelling (Y1) can be used instead.

Cheers,

Daniel
From d4bd736da686b5927a3c655d3e25f9d0b2fc7f99 Mon Sep 17 00:00:00 2001
From: Daniel Shahaf <d.s@xxxxxxxxxxxxxxxxxx>
Date: Mon, 2 Jun 2014 19:14:10 +0000
Subject: [PATCH 1/2] Teach (Y) to take an argument

---
 Completion/Zsh/Type/_globquals |    1 +
 Doc/Zsh/expn.yo                |    7 ++++---
 Src/glob.c                     |   34 +++++++++++++++++++++++++++-------
 Test/D02glob.ztst              |   23 ++++++++++++++---------
 4 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/Completion/Zsh/Type/_globquals b/Completion/Zsh/Type/_globquals
index c98bd0c..37db161 100644
--- a/Completion/Zsh/Type/_globquals
+++ b/Completion/Zsh/Type/_globquals
@@ -251,6 +251,7 @@ case $state in
     "o:+ sort order, up"
     "O:+ sort order, down"
     "P:prepend word"
+    "Y:+ at most ARG matches"
     "[:+ range of files"
     "):end of qualifiers"
     "\::modifier"
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index 25247f9..2f91fec 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -2564,9 +2564,10 @@ item(tt(n))(
 sets the tt(NUMERIC_GLOB_SORT) option for the current pattern
 pindex(NUMERIC_GLOB_SORT, setting in pattern)
 )
-item(tt(Y))(
-enables short-circuit mode: the pattern will expand to just the first
-matching filename, if any.
+item(tt(Y)var(n))(
+enables short-circuit mode: the pattern will expand to at most var(n)
+filenames.  If more than var(n) matches exist, only the first var(n)
+matches in directory traversal order will be considered.
 )
 item(tt(o)var(c))(
 specifies how the names of the files should be sorted. If var(c) is
diff --git a/Src/glob.c b/Src/glob.c
index 0ca63fc..33b49f1 100644
--- a/Src/glob.c
+++ b/Src/glob.c
@@ -473,7 +473,8 @@ scanner(Complist q, int shortcircuit)
 	if (q->closure == 2)	/* (foo/)## - match one or more dirs */
 	    q->closure = 1;
 	else
-	    if (scanner(q->next, shortcircuit) == 1)
+	    scanner(q->next, shortcircuit);
+	    if (shortcircuit && shortcircuit == matchct)
 		return 1;
     }
     p = q->pat;
@@ -520,7 +521,8 @@ scanner(Complist q, int shortcircuit)
 		if (add) {
 		    addpath(str, l);
 		    if (!closure || !statfullpath("", NULL, 1))
-			if (scanner((q->closure) ? q : q->next, shortcircuit) == 1)
+			scanner((q->closure) ? q : q->next, shortcircuit);
+			if (shortcircuit && shortcircuit == matchct)
 			    return 1;
 		    pathbuf[pathpos = oppos] = '\0';
 		}
@@ -528,7 +530,8 @@ scanner(Complist q, int shortcircuit)
 	} else {
 	    if (str[l])
 		str = dupstrpfx(str, l);
-	    if (insert(str, 0) == 1 && shortcircuit)
+	    insert(str, 0);
+	    if (shortcircuit && shortcircuit == matchct)
 		return 1;
 	}
     } else {
@@ -619,7 +622,8 @@ scanner(Complist q, int shortcircuit)
 		    subdirlen += sizeof(int);
 		} else
 		    /* if the last filename component, just add it */
-		    if (insert(fn, 1) == 1 && shortcircuit)
+		    insert(fn, 1);
+		    if (shortcircuit && shortcircuit == matchct)
 			return 1;
 	    }
 	}
@@ -633,7 +637,9 @@ scanner(Complist q, int shortcircuit)
 		fn += l + 1;
 		memcpy((char *)&errsfound, fn, sizeof(int));
 		fn += sizeof(int);
-		if (scanner((q->closure) ? q : q->next, shortcircuit) == 1) /* scan next level */
+		/* scan next level */
+		scanner((q->closure) ? q : q->next, shortcircuit); 
+		if (shortcircuit && shortcircuit == matchct)
 		    return 1;
 		pathbuf[pathpos = oppos] = '\0';
 	    }
@@ -1150,7 +1156,8 @@ zglob(LinkList list, LinkNode np, int nountok)
 					/* and index+1 of the last match */
     struct globdata saved;		/* saved glob state              */
     int nobareglob = !isset(BAREGLOBQUAL);
-    int shortcircuit = 0;
+    int shortcircuit = 0;		/* How many files to match;      */
+					/* 0 means no limit              */
 
     if (unset(GLOBOPT) || !haswilds(ostr) || unset(EXECOPT)) {
 	if (!nountok)
@@ -1502,9 +1509,22 @@ zglob(LinkList list, LinkNode np, int nountok)
 		    gf_numsort = !(sense & 1);
 		    break;
 		case 'Y':
-		    /* Short circuit: just check if there are any matches */
+		{
+		    /* Short circuit: limit number of matches */
+		    const char *s_saved = s;
 		    shortcircuit = !(sense & 1);
+		    if (shortcircuit) {
+			/* Parse the argument. */
+			data = qgetnum(&s);
+			if ((shortcircuit = data) != data) {
+			    /* Integer overflow */
+			    zerr("value too big: Y%s", s_saved);
+			    restore_globstate(saved);
+			    return;
+			}
+		    }
 		    break;
+		}
 		case 'a':
 		    /* Access time in given range */
 		    g_amc = 0;
diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst
index 9e29de2..c00bbe3 100644
--- a/Test/D02glob.ztst
+++ b/Test/D02glob.ztst
@@ -543,17 +543,22 @@
 >Multiple files matched
 >Normal string if nullglob not set
 
- (){ print $#@ } glob.tmp/dir*(Y)
- (){ print $#@ } glob.tmp/file*(NY)
- (){ [[ $1 = glob.tmp/dir? ]] && echo "(Y) returns a matching filename" } glob.tmp/dir*(Y)
- # Can be negated
- (){ print $@:t } glob.tmp/dir*(Y^Y)
- (){ [[ $#@ -eq 1 ]] && print Globs before last path component } glob.tmp/dir?/subdir(NY)
- (){ [[ $#@ -eq 0 ]] && print Respects qualifiers } glob.tmp/dir?/subdir(NY.)
+ (){ print $#@ } glob.tmp/dir*(Y1)
+ (){ print $#@ } glob.tmp/file*(NY1)
+ (){ [[ "$*" == */dir?\ */dir? ]] && print Returns matching filenames } glob.tmp/dir*(Y2)
+ (){ print "Limit is upper bound:" $@:t } glob.tmp/dir*(Y5)
+ (){ print "Negated:" $@:t } glob.tmp/dir*(Y1^Y)
+ (){ print "Sorting:" $@:t } glob.tmp/dir*(Y4On)
+ (){ [[ $#@ -eq 1 ]] && print Globs before last path component } glob.tmp/dir?/subdir(NY1)
+ (){ [[ $#@ -eq 0 ]] && print Respects qualifiers } glob.tmp/dir*(NY1.)
+ (print -- *(Y)) 2>/dev/null || print "Argument required"
 0:short-circuit modifier
 >1
 >0
->(Y) returns a matching filename
->dir1 dir2 dir3 dir4
+>Returns matching filenames
+>Limit is upper bound: dir1 dir2 dir3 dir4
+>Negated: dir1 dir2 dir3 dir4
+>Sorting: dir4 dir3 dir2 dir1
 >Globs before last path component
 >Respects qualifiers
+>Argument required
-- 
1.7.10.4

From b3ad6fe33263861671e19329d3a773128b220358 Mon Sep 17 00:00:00 2001
From: Daniel Shahaf <d.s@xxxxxxxxxxxxxxxxxx>
Date: Mon, 2 Jun 2014 23:11:37 +0000
Subject: [PATCH 2/2] glob.c: Undo now-unneeded return type changes

---
 Src/glob.c |   40 +++++++++++++++++++---------------------
 1 file changed, 19 insertions(+), 21 deletions(-)

diff --git a/Src/glob.c b/Src/glob.c
index 33b49f1..c74a560 100644
--- a/Src/glob.c
+++ b/Src/glob.c
@@ -297,16 +297,15 @@ statfullpath(const char *s, struct stat *st, int l)
 
 char **inserts;
 
-/* add a match to the list.  Return 1 if it was inserted, 0 otherwise. */
+/* add a match to the list */
 
 /**/
-static int
+static void
 insert(char *s, int checked)
 {
     struct stat buf, buf2, *bp;
     char *news = s;
     int statted = 0;
-    int inserted = 0;
 
     queue_signals();
     inserts = NULL;
@@ -317,7 +316,7 @@ insert(char *s, int checked)
 	checked = statted = 1;
 	if (statfullpath(s, &buf, 1)) {
 	    unqueue_signals();
-	    return inserted;
+	    return;
 	}
 	mode = buf.st_mode;
 	if (gf_follow) {
@@ -341,7 +340,7 @@ insert(char *s, int checked)
 
 	if (!statted && statfullpath(s, &buf, 1)) {
 	    unqueue_signals();
-	    return inserted;
+	    return;
 	}
 	news = dyncat(pathbuf, news);
 
@@ -366,7 +365,7 @@ insert(char *s, int checked)
 		/* Try next alternative, or return if there are no more */
 		if (!(qo = qo->or)) {
 		    unqueue_signals();
-		    return inserted;
+		    return;
 		}
 		qn = qo;
 		continue;
@@ -376,7 +375,7 @@ insert(char *s, int checked)
     } else if (!checked) {
 	if (statfullpath(s, NULL, 1)) {
 	    unqueue_signals();
-	    return inserted;
+	    return;
 	}
 	statted = 1;
 	news = dyncat(pathbuf, news);
@@ -436,7 +435,6 @@ insert(char *s, int checked)
 	}
 	matchptr++;
 
-	inserted = 1;
 	if (++matchct == matchsz) {
 	    matchbuf = (Gmatch )realloc((char *)matchbuf,
 					sizeof(struct gmatch) * (matchsz *= 2));
@@ -447,7 +445,7 @@ insert(char *s, int checked)
 	    break;
     }
     unqueue_signals();
-    return inserted;
+    return;
 }
 
 /* Do the globbing:  scanner is called recursively *
@@ -455,7 +453,7 @@ insert(char *s, int checked)
  * tried all of it.                                */
 
 /**/
-static int
+static void
 scanner(Complist q, int shortcircuit)
 {
     Patprog p;
@@ -466,7 +464,7 @@ scanner(Complist q, int shortcircuit)
 
     init_dirsav(&ds);
     if (!q)
-	return -1;
+	return;
 
     if ((closure = q->closure)) {
 	/* (foo/)# - match zero or more dirs */
@@ -475,7 +473,7 @@ scanner(Complist q, int shortcircuit)
 	else
 	    scanner(q->next, shortcircuit);
 	    if (shortcircuit && shortcircuit == matchct)
-		return 1;
+		return;
     }
     p = q->pat;
     /* Now the actual matching for the current path section. */
@@ -490,13 +488,13 @@ scanner(Complist q, int shortcircuit)
 	    int err;
 
 	    if (l >= PATH_MAX)
-		return -1;
+		return;
 	    err = lchdir(pathbuf + pathbufcwd, &ds, 0);
 	    if (err == -1)
-		return -1;
+		return;
 	    if (err) {
 		zerr("current directory lost during glob");
-		return -1;
+		return;
 	    }
 	    pathbufcwd = pathpos;
 	}
@@ -523,7 +521,7 @@ scanner(Complist q, int shortcircuit)
 		    if (!closure || !statfullpath("", NULL, 1))
 			scanner((q->closure) ? q : q->next, shortcircuit);
 			if (shortcircuit && shortcircuit == matchct)
-			    return 1;
+			    return;
 		    pathbuf[pathpos = oppos] = '\0';
 		}
 	    }
@@ -532,7 +530,7 @@ scanner(Complist q, int shortcircuit)
 		str = dupstrpfx(str, l);
 	    insert(str, 0);
 	    if (shortcircuit && shortcircuit == matchct)
-		return 1;
+		return;
 	}
     } else {
 	/* Do pattern matching on current path section. */
@@ -543,7 +541,7 @@ scanner(Complist q, int shortcircuit)
 	int subdirlen = 0;
 
 	if (lock == NULL)
-	    return -1;
+	    return;
 	while ((fn = zreaddir(lock, 1)) && !errflag) {
 	    /* prefix and suffix are zle trickery */
 	    if (!dirs && !colonmod &&
@@ -624,7 +622,7 @@ scanner(Complist q, int shortcircuit)
 		    /* if the last filename component, just add it */
 		    insert(fn, 1);
 		    if (shortcircuit && shortcircuit == matchct)
-			return 1;
+			return;
 	    }
 	}
 	closedir(lock);
@@ -640,7 +638,7 @@ scanner(Complist q, int shortcircuit)
 		/* scan next level */
 		scanner((q->closure) ? q : q->next, shortcircuit); 
 		if (shortcircuit && shortcircuit == matchct)
-		    return 1;
+		    return;
 		pathbuf[pathpos = oppos] = '\0';
 	    }
 	    hrealloc(subdirs, subdirlen, 0);
@@ -654,7 +652,7 @@ scanner(Complist q, int shortcircuit)
 	    close(ds.dirfd);
 	pathbufcwd = pbcwdsav;
     }
-    return 0;
+    return;
 }
 
 /* This function tokenizes a zsh glob pattern */
-- 
1.7.10.4



Messages sorted by: Reverse Date, Date, Thread, Author