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

PATCH: dynamic named directories



Problem: six feet deep in WiFi and UWB bugs to fix.  Solution:  do
something else entirely.

This morning I had a brainwave/brainstorm that turned out to be easy to
implement and test without interfering with the rest of the shell, which
is the sort of feature I like.  I'm sure you'll tell me if it's a stupid
idea.

It's based on the way I use an Emacs function suite called "bufname"
that a colleague and I wrote a few years ago.  I have lots of parallel
directory hierarchies with slightly different names at different levels,
so normal static directory naming isn't really flexible enough.

Now you can use ~[<stuff>] and <stuff> gets passed down to a function
zsh_directory_name, which either parses it and picks an appropriate
directory or refuses.  (If the function isn't defined this syntax is
treated the way it always was, which typically wasn't all that useful.)
There's also a mechanism for going the other way.  See documentation and
examples.

Much of the patch is actually another simplification to doshfunc() which
was obvious when I worked out what the third argument was for.  It
should mean any PM_* flags applicable to functions (such as PM_TAGGED)
are handled more consistently.

Index: Doc/Zsh/expn.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/expn.yo,v
retrieving revision 1.91
diff -u -r1.91 expn.yo
--- Doc/Zsh/expn.yo	16 Jun 2008 16:39:18 -0000	1.91
+++ Doc/Zsh/expn.yo	25 Sep 2008 12:39:43 -0000
@@ -1308,8 +1308,73 @@
 option exchanges the effects of `tt(~PLUS())' and `tt(~-)' where they are
 followed by a number.
 
-cindex(directories, named)
-cindex(named directories)
+subsect(Dynamic named directories)
+cindex(directories, named, dynamic)
+cindex(named directories, dynamicic)
+cindex(dynamic named directories)
+
+The feature described here is only available if the shell function
+tt(zsh_directory_name) exists.
+
+A `tt(~)' followed by a string var(namstr) in unquoted square brackets is
+treated specially as a dynamic directory name.  Note that the first
+unquoted closing square bracket always terminates var(namstr).  The shell
+function is passed two arguments: the string tt(n) (for name) and
+var(namstr).  It should either set the array tt(reply) to a single element
+which is the directory corresponding to the name and return status zero
+(executing an assignment as the last statement is usually sufficient), or
+it should return status non-zero.  In the former case the element of reply
+is used as the directory; in the latter case the substitution is deemed to
+have failed and tt(NOMATCH) handling is applied if the option is set.
+
+The function tt(zsh_directory_name) is also used to see if a directory can
+be turned into a name, for example when printing the directory stack or
+when expanding tt(%~) in prompts.  In this case the function is passed two
+arguments: the string tt(d) (for directory) and the candidate for dynamic
+naming.  The function should either return non-zero status, if the
+directory cannot be named by the function, or it should set the array reply
+to consist of two elements: the first is the dynamic name for the directory
+(as would appear within `tt(~[)var(...)tt(])'), and the second is the
+prefix length of the directory to be replaced.  For example, if the trial
+directory is tt(/home/myname/src/zsh) and the dynamic name for
+tt(/home/myname/src) (which has 16 characters) is tt(s), then the function
+sets
+
+example(reply=(s 16))
+
+The directory name so returned is compared with possible static names for
+parts of the directory path, as described below; it is used if the prefix
+length matched (16 in the example) is longer than that matched by any
+static name.
+
+As a working example, here is a function that expands any dynamic names
+beginning with the string tt(p:) to directories below
+tt(/home/pws/perforce).  In this simple case a static name for the
+directory would be just as effective.
+
+example(zsh_directory_name() {
+  emulate -L zsh
+  setopt extendedglob
+  local -a match mbegin mend
+  if [[ $1 = d ]]; then
+    if [[ $2 = (#b)(/home/pws/perforce/)([^/]##)* ]]; then
+      typeset -ga reply
+      reply=(p:$match[2] $(( ${#match[1]} + ${#match[2]} )) )
+    else
+      return 1
+    fi
+  else
+    [[ $2 != (#b)p:(?*) ]] && return 1
+    typeset -ga reply
+    reply=(/home/pws/perforce/$match[1])
+  fi
+  return 0
+})
+
+subsect(Static named directories)
+cindex(directories, named, static)
+cindex(named directories, static)
+cindex(static named directories)
 A `tt(~)' followed by anything not already covered is looked up as a
 named directory, and replaced by the value of that named directory if found.
 Named directories are typically home directories for users on the system.
@@ -1329,6 +1394,8 @@
 except when the directory is tt(/) itself.  The parameters tt($PWD) and
 tt($OLDPWD) are never abbreviated in this fashion.
 
+subsect(`=' expansion)
+
 If a word begins with an unquoted `tt(=)'
 and the tt(EQUALS) option is set,
 the remainder of the word is taken as the
@@ -1336,6 +1403,8 @@
 exists by that name, the word is replaced
 by the full pathname of the command.
 
+subsect(Notes)
+
 Filename expansion is performed on the right hand side of a parameter
 assignment, including those appearing after commands of the
 tt(typeset) family.  In this case, the right hand side will be treated
@@ -1349,6 +1418,7 @@
 argument in the form `var(identifier)tt(=)var(expression)' becomes eligible
 for file expansion as described in the previous paragraph.  Quoting the
 first `tt(=)' also inhibits this.
+
 texinode(Filename Generation)()(Filename Expansion)(Expansion)
 sect(Filename Generation)
 cindex(filename generation)
Index: Src/exec.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/exec.c,v
retrieving revision 1.155
diff -u -r1.155 exec.c
--- Src/exec.c	22 Sep 2008 17:32:43 -0000	1.155
+++ Src/exec.c	25 Sep 2008 12:39:43 -0000
@@ -518,7 +518,7 @@
 	return 127;
 
     pushnode(args, arg0);
-    return doshfunc(shf, args, shf->node.flags, 1);
+    return doshfunc(shf, args, 1);
 }
 
 /* execute an external command */
@@ -4064,7 +4064,7 @@
     cmdsp = 0;
     if ((osfc = sfcontext) == SFC_NONE)
 	sfcontext = SFC_DIRECT;
-    doshfunc(shf, args, shf->node.flags, 0);
+    doshfunc(shf, args, 0);
     sfcontext = osfc;
     free(cmdstack);
     cmdstack = ocs;
@@ -4191,8 +4191,6 @@
  * in which the first element is the function name (even if
  * FUNCTIONARGZERO is set as this is handled inside this function).
  *
- * flags are a set of the PM_ flags associated with the function.
- *
  * If noreturnval is nonzero, then reset the current return
  * value (lastval) to its value before the shell function
  * was executed.  However, in any case return the status value
@@ -4202,13 +4200,14 @@
 
 /**/
 mod_export int
-doshfunc(Shfunc shfunc, LinkList doshargs, int flags, int noreturnval)
+doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
 {
     char **tab, **x, *oargv0;
     int oldzoptind, oldlastval, oldoptcind, oldnumpipestats, ret;
     int *oldpipestats = NULL;
     char saveopts[OPT_SIZE], *oldscriptname = scriptname;
     char *name = shfunc->node.nam;
+    int flags = shfunc->node.flags;
     char *fname = dupstring(name);
     int obreaks, saveemulation ;
     Eprog prog;
Index: Src/math.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/math.c,v
retrieving revision 1.34
diff -u -r1.34 math.c
--- Src/math.c	16 Sep 2008 15:07:12 -0000	1.34
+++ Src/math.c	25 Sep 2008 12:39:43 -0000
@@ -872,7 +872,7 @@
 			if (!shfunc)
 			    zerr("no such function: %s", shfnam);
 			else {
-			    doshfunc(shfunc, l, 0, 1);
+			    doshfunc(shfunc, l, 1);
 			    return lastmathval;
 			}
 		    } else {
Index: Src/signals.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/signals.c,v
retrieving revision 1.51
diff -u -r1.51 signals.c
--- Src/signals.c	16 Sep 2008 15:07:24 -0000	1.51
+++ Src/signals.c	25 Sep 2008 12:39:43 -0000
@@ -1160,7 +1160,7 @@
 	trapisfunc = isfunc = 1;
 
 	sfcontext = SFC_SIGNAL;
-	doshfunc((Shfunc)sigfn, args, 0, 1);
+	doshfunc((Shfunc)sigfn, args, 1);
 	sfcontext = osc;
 	freelinklist(args, (FreeFunc) NULL);
 	zsfree(name);
Index: Src/subst.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/subst.c,v
retrieving revision 1.86
diff -u -r1.86 subst.c
--- Src/subst.c	1 Sep 2008 20:18:48 -0000	1.86
+++ Src/subst.c	25 Sep 2008 12:39:45 -0000
@@ -528,7 +528,8 @@
     char *str = *namptr;
 
     if (*str == Tilde && str[1] != '=' && str[1] != Equals) {
-	char *ptr;
+	Shfunc dirfunc;
+	char *ptr, *tmp, *res;
 	int val;
 
 	val = zstrtol(str + 1, &ptr, 10);
@@ -539,9 +540,23 @@
 	    *namptr = dyncat(pwd, str + 2);
 	    return 1;
 	} else if (str[1] == '-' && isend(str[2])) {   /* ~- */
-	    char *tmp;
 	    *namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2);
 	    return 1;
+	} else if (str[1] == Inbrack &&
+		   (dirfunc = getshfunc("zsh_directory_name")) &&
+		   (ptr = strchr(str+2, Outbrack))) {
+	    char **arr;
+	    untokenize(tmp = dupstrpfx(str+2, ptr - (str+2)));
+	    remnulargs(tmp);
+	    arr = subst_string_by_func(dirfunc, "n", tmp);
+	    res = arr ? *arr : NULL;
+	    if (res) {
+		*namptr = dyncat(res, ptr+1);
+		return 1;
+	    }
+	    if (isset(NOMATCH))
+		zerr("no directory expansion: ~[%s]", tmp);
+	    return 0;
 	} else if (!inblank(str[1]) && isend(*ptr) &&
 		   (!idigit(str[1]) || (ptr - str < 4))) {
 	    char *ds;
Index: Src/utils.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/utils.c,v
retrieving revision 1.200
diff -u -r1.200 utils.c
--- Src/utils.c	16 Sep 2008 15:07:28 -0000	1.200
+++ Src/utils.c	25 Sep 2008 12:39:45 -0000
@@ -826,6 +826,7 @@
 {
     static struct nameddir homenode = { {NULL, "", 0}, NULL, 0 };
     static int ffsz;
+    Shfunc func = getshfunc("zsh_directory_name");
 
     /* Invalidate directory cache if argument is NULL.  This is called *
      * whenever a node is added to or removed from the hash table, and *
@@ -841,7 +842,8 @@
 	return finddir_last = NULL;
     }
 
-    if(!strcmp(s, finddir_full) && *finddir_full)
+    /* It's not safe to use the cache while we have function transformations.*/
+    if(!func && !strcmp(s, finddir_full) && *finddir_full)
 	return finddir_last;
 
     if ((int)strlen(s) >= ffsz) {
@@ -853,6 +855,21 @@
     finddir_last=NULL;
     finddir_scan(&homenode.node, 0);
     scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0);
+
+    if (func) {
+	char **ares = subst_string_by_func(func, "d", finddir_full);
+	int len;
+	if (ares && arrlen(ares) >= 2 &&
+	    (len = (int)zstrtol(ares[1], NULL, 10)) > finddir_best) {
+	    /* better duplicate this string since it's come from REPLY */
+	    finddir_last = (Nameddir)hcalloc(sizeof(struct nameddir));
+	    finddir_last->node.nam = tricat("[", dupstring(ares[0]), "]");
+	    finddir_last->dir = dupstrpfx(finddir_full, len);
+	    finddir_last->diff = len - strlen(finddir_last->node.nam);
+	    finddir_best = len;
+	}
+    }
+
     return finddir_last;
 }
 
@@ -1146,7 +1163,7 @@
     sfcontext = SFC_HOOK;
 
     if ((shfunc = getshfunc(name))) {
-	ret = doshfunc(shfunc, lnklst, 0, 1);
+	ret = doshfunc(shfunc, lnklst, 1);
 	stat = 0;
     }
 
@@ -1162,7 +1179,7 @@
 	if ((arrptr = getaparam(arrnam))) {
 	    for (; *arrptr; arrptr++) {
 		if ((shfunc = getshfunc(*arrptr))) {
-		    int newret = doshfunc(shfunc, lnklst, 0, 1);
+		    int newret = doshfunc(shfunc, lnklst, 1);
 		    if (!ret)
 			ret = newret;
 		    stat = 0;
@@ -2901,6 +2918,32 @@
     return (Shfunc) shfunctab->getnode(shfunctab, nam);
 }
 
+/*
+ * Call the function func to substitute string orig by setting
+ * the parameter reply.
+ * Return the array from reply, or NULL if the function returned
+ * non-zero status.
+ * The returned value comes directly from the parameter and
+ * so should be used before there is any chance of that
+ * being changed or unset.
+ * If arg1 is not NULL, it is used as an initial argument to
+ * the function, with the original string as the second argument.
+ */
+
+/**/
+char **
+subst_string_by_func(Shfunc func, char *arg1, char *orig)
+{
+    LinkList l = newlinklist();
+    addlinknode(l, func->node.nam);
+    if (arg1)
+	addlinknode(l, arg1);
+    addlinknode(l, orig);
+    if (doshfunc(func, l, 1))
+	return NULL;
+    return getaparam("reply");
+}
+
 /**/
 mod_export char **
 mkarray(char *s)
Index: Src/Modules/zftp.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Modules/zftp.c,v
retrieving revision 1.49
diff -u -r1.49 zftp.c
--- Src/Modules/zftp.c	16 Sep 2008 15:09:21 -0000	1.49
+++ Src/Modules/zftp.c	25 Sep 2008 12:39:46 -0000
@@ -1480,7 +1480,7 @@
 	int osc = sfcontext;
 
 	sfcontext = SFC_HOOK;
-	doshfunc(shfunc, NULL, 0, 1);
+	doshfunc(shfunc, NULL, 1);
 	sfcontext = osc;
 	/* Now add in the bit of the file we've got/sent already */
 	sofar = last_sofar = startat;
@@ -1613,7 +1613,7 @@
 
 	    zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER);
 	    sfcontext = SFC_HOOK;
-	    doshfunc(shfunc, NULL, 0, 1);
+	    doshfunc(shfunc, NULL, 1);
 	    sfcontext = osc;
 	    last_sofar = sofar;
 	}
@@ -2395,7 +2395,7 @@
 	int osc = sfcontext;
 
 	sfcontext = SFC_HOOK;
-	doshfunc(shfunc, NULL, 0, 1);
+	doshfunc(shfunc, NULL, 1);
 	sfcontext = osc;
     }
     return 0;
@@ -2615,7 +2615,7 @@
 	    zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"),
 		       ZFPM_READONLY);
 	    sfcontext = SFC_HOOK;
-	    doshfunc(shfunc, NULL, 0, 1);
+	    doshfunc(shfunc, NULL, 1);
 	    sfcontext = osc;
 	}
 	if (rest) {
@@ -2770,7 +2770,7 @@
 	    int osc = sfcontext;
 
 	    sfcontext = SFC_HOOK;
-	    doshfunc(shfunc, NULL, 0, 1);
+	    doshfunc(shfunc, NULL, 1);
 	    sfcontext = osc;
 	}
     }
Index: Src/Zle/compcore.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/compcore.c,v
retrieving revision 1.97
diff -u -r1.97 compcore.c
--- Src/Zle/compcore.c	16 Sep 2008 15:12:08 -0000	1.97
+++ Src/Zle/compcore.c	25 Sep 2008 12:39:46 -0000
@@ -814,7 +814,7 @@
 		while (*p)
 		    addlinknode(largs, dupstring(*p++));
 	    }
-	    doshfunc(shfunc, largs, 0, 0);
+	    doshfunc(shfunc, largs, 0);
 	    cfret = lastval;
 	    lastval = olv;
 	} OLDHEAPS;
Index: Src/Zle/compctl.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/compctl.c,v
retrieving revision 1.36
diff -u -r1.36 compctl.c
--- Src/Zle/compctl.c	16 Sep 2008 15:12:12 -0000	1.36
+++ Src/Zle/compctl.c	25 Sep 2008 12:39:47 -0000
@@ -3664,7 +3664,7 @@
 		incompctlfunc = 1;
 	    sfcontext = SFC_COMPLETE;
 	    /* Call the function. */
-	    doshfunc(shfunc, args, 0, 1);
+	    doshfunc(shfunc, args, 1);
 	    sfcontext = osc;
 	    incompctlfunc = 0;
 	    /* And get the result from the reply parameter. */
@@ -3839,7 +3839,7 @@
 	    if (incompfunc != 1)
 		incompctlfunc = 1;
 	    sfcontext = SFC_COMPLETE;
-	    doshfunc(shfunc, args, 0, 1);
+	    doshfunc(shfunc, args, 1);
 	    sfcontext = osc;
 	    incompctlfunc = 0;
 	    uv = "reply";
Index: Src/Zle/zle_main.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_main.c,v
retrieving revision 1.117
diff -u -r1.117 zle_main.c
--- Src/Zle/zle_main.c	22 Sep 2008 18:37:51 -0000	1.117
+++ Src/Zle/zle_main.c	25 Sep 2008 12:39:47 -0000
@@ -1330,7 +1330,7 @@
 	    makezleparams(0);
 	    sfcontext = SFC_WIDGET;
 	    opts[XTRACE] = 0;
-	    ret = doshfunc(shf, largs, shf->node.flags, 1);
+	    ret = doshfunc(shf, largs, 1);
 	    opts[XTRACE] = oxt;
 	    sfcontext = osc;
 	    endparamscope();
Index: Src/Zle/zle_misc.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_misc.c,v
retrieving revision 1.55
diff -u -r1.55 zle_misc.c
--- Src/Zle/zle_misc.c	16 Sep 2008 15:12:24 -0000	1.55
+++ Src/Zle/zle_misc.c	25 Sep 2008 12:39:47 -0000
@@ -1384,7 +1384,7 @@
 	    startparamscope();
 	    makezleparams(0);
 	    sfcontext = SFC_COMPLETE;
-	    doshfunc(shfunc, args, 0, 1);
+	    doshfunc(shfunc, args, 1);
 	    sfcontext = osc;
 	    endparamscope();
 
Index: Test/D01prompt.ztst
===================================================================
RCS file: /cvsroot/zsh/zsh/Test/D01prompt.ztst,v
retrieving revision 1.3
diff -u -r1.3 D01prompt.ztst
--- Test/D01prompt.ztst	19 Oct 2005 08:39:03 -0000	1.3
+++ Test/D01prompt.ztst	25 Sep 2008 12:39:47 -0000
@@ -1,5 +1,7 @@
 %prep
 
+  mkdir prompt.tmp
+  cd prompt.tmp
   mydir=$PWD
   SHLVL=2
   setopt extendedglob
@@ -104,3 +106,44 @@
     print "Years do not agree in $date2, $date3"
   fi
 0:Dates produced by prompt escapes
+
+  mkdir foo
+  mkdir foo/bar
+  mkdir foo/bar/rod
+  (zsh_directory_name() {
+    emulate -L zsh
+    setopt extendedglob
+    local -a match mbegin mend
+    if [[ $1 = d ]]; then
+      if [[ $2 = (#b)(*bar)/rod ]]; then
+        reply=(barmy ${#match[1]})
+      else
+        return 1
+      fi
+    else
+      if [[ $2 = barmy ]]; then
+        reply=($mydir/foo/bar)
+      else
+        return 1
+      fi
+    fi
+  }
+  # success
+  print ~[barmy]/anything
+  cd foo/bar/rod
+  print -P %~
+  # failure
+  setopt nonomatch
+  print ~[scuzzy]/rubbish
+  cd ../..
+  print -P %~
+  # catastrophic failure
+  unsetopt nonomatch
+  print ~[scuzzy]/rubbish
+  )
+1q:Dynamic named directories
+>$mydir/foo/bar/anything
+>~[barmy]/rod
+>~[scuzzy]/rubbish
+>~mydir/foo
+?(eval):33: no directory expansion: ~[scuzzy]


-- 
Peter Stephenson <pws@xxxxxxx>                  Software Engineer
CSR PLC, Churchill House, Cambridge Business Park, Cowley Road
Cambridge, CB4 0WZ, UK                          Tel: +44 (0)1223 692070



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