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

Re: PATH: autoload with explicit path



On Sat, 28 Jan 2017 10:05:46 -0800 (PST)
Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
> On Fri, 27 Jan 2017, Peter Stephenson wrote:
> 
> > I'm still not clear if your "requested feature" is what I'm talking
> > about or not, i.e. what you can do already with $funcsourcetrace.
> 
> It's not so much about what Sebastian can do as it is about what he has
> to rely upon other function writers to do.
> 
> That is, if I'm in the habit of writing "autoload thing" in the bootstrap
> function for a library of things, but Sebastian wants to put my entire
> thing-library in a place outside of $fpath, he can't do so without first
> editing all my "autoload"s to add a full path.
> 
> If every autoload were relative to the absolute-path information of the
> calling function context, Sebastian's plan would work without needing
> to edit anything, provided that he autoloads the bootstrap function with
> an absolute path.

I'm afraid this is just confusing matters.  Sebastian was asking for a
new option.  There's no question of changing the default behaviour to
prevent any files needing editing, that just screws up the current fpath
way of looking things up.  The question is how to get the function to
use the right directory, not by default, but using information that's
available from the current context.

(It's not entirely clear to me why you can't just "autoload
/wherever/it/is/*" to pick up all the files in the first place, avoiding
any additional autoload, but there are enough possible ways of arranging
a function suite I don't necessarily need to understand that bit.)

Instead of the option, I'm pointing out that using the absolute path
mechanism and the ability to find the path of the function does exactly
the same thing, so yet more potentially confusing (certainly to me)
options aren't actually getting you anything further.

The following patch means that

zmodload zsh/parameter
autoload -Uz ${functions_source[func]:h}/otherfunc

works, further giving you the power to decide where relative to the
source of "func" the code you want is to arbitrary complexity.
($functions_source is effectively a trick to access existing information;
there is no additional storage demand beyond the addition to the code
itself.  It's just a generalisation of the option Sebastian wanted that
presents path information in parameters rather than hiding it.)

I've also made it turn the directory into an absolute one when it's
loaded, so that if the function was found by e.g. fpath=(.) its
directory is recorded based on $PWD.  That's an optional extra, but I
presume it's more useful that being told the directory of a function's
source was ".". The resolution already happens for autoload -r, and you might
imagine that loading the file should resolve the path in the same way,
so I think it's natural anyway.

As this does everything necessary, I will not be adding any new options,
and I've done quite enough on this feature, but if you can argue among
yourselves for something else that doesn't yet work, feel free to go
ahead without me.

diff --git a/Doc/Zsh/mod_parameter.yo b/Doc/Zsh/mod_parameter.yo
index 3d260f8..942e4c5 100644
--- a/Doc/Zsh/mod_parameter.yo
+++ b/Doc/Zsh/mod_parameter.yo
@@ -37,6 +37,30 @@ vindex(dis_functions)
 item(tt(dis_functions))(
 Like tt(functions) but for disabled functions.
 )
+vindex(functions_source)
+item(tt(functions_source))(
+This readonly associative array maps names of enabled functions to the
+name of the file containing the source of the function.
+
+For an autoloaded function that has already been loaded, or marked for
+autoload with an absolute path, or that has had its path resolved with
+`tt(functions -r)', this is the file found for autoloading, resolved
+to an absolute path.
+
+For a function defined within the body of a script or sourced file,
+this is the name of that file.  In this case, this is the exact path
+originally used to that file, which may be a relative path.
+
+For any other function, including any defined at an interactive prompt or
+an autoload function whose path has not yet been resolved, this is
+the empty string.  However, the hash element is reported as defined
+just so long as the function is present:  the keys to this hash are
+the same as those to tt($funcions).
+)
+vindex(dis_functions_source)
+item(tt(dis_functions_source))(
+Like tt(functions_source) but for disabled functions.
+)
 vindex(builtins)
 item(tt(builtins))(
 This associative array gives information about the builtin commands
@@ -202,10 +226,13 @@ defined.  The line number is the line where the `tt(function) var(name)'
 or `var(name) tt(LPAR()RPAR())' started.  In the case of an autoloaded
 function  the line number is reported as zero.
 The format of each element is var(filename)tt(:)var(lineno).
+
 For functions autoloaded from a file in native zsh format, where only the
 body of the function occurs in the file, or for files that have been
 executed by the tt(source) or `tt(.)' builtins, the trace information is
-shown as var(filename)tt(:)var(0), since the entire file is the definition.
+shown as var(filename)tt(:)var(0), since the entire file is the
+definition.  The source file name is resolved to an absolute path when
+the function is loaded or the path to it otherwise resolved.
 
 Most users will be interested in the information in the
 tt(funcfiletrace) array instead.
diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c
index 98bcaba..6e62287 100644
--- a/Src/Modules/parameter.c
+++ b/Src/Modules/parameter.c
@@ -515,6 +515,98 @@ scanpmdisfunctions(HashTable ht, ScanFunc func, int flags)
     scanfunctions(ht, func, flags, DISABLED);
 }
 
+/* Functions for the functions_source special parameter. */
+
+/* Retrieve the source file for a function by explicit name */
+
+/**/
+static HashNode
+getfunction_source(UNUSED(HashTable ht), const char *name, int dis)
+{
+    Shfunc shf;
+    Param pm = NULL;
+
+    pm = (Param) hcalloc(sizeof(struct param));
+    pm->node.nam = dupstring(name);
+    pm->node.flags = PM_SCALAR|PM_READONLY;
+    pm->gsu.s = dis ? &pmdisfunction_gsu :  &pmfunction_gsu;
+
+    if ((shf = (Shfunc) shfunctab->getnode2(shfunctab, name)) &&
+	(dis ? (shf->node.flags & DISABLED) : !(shf->node.flags & DISABLED))) {
+	pm->u.str = getshfuncfile(shf);
+	if (!pm->u.str)
+	    pm->u.str = dupstring("");
+    }
+    return &pm->node;
+}
+
+/* Retrieve the source file for functions by scanning the table */
+
+/**/
+static void
+scanfunctions_source(UNUSED(HashTable ht), ScanFunc func, int flags, int dis)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+
+    memset((void *)&pm, 0, sizeof(struct param));
+    pm.node.flags = PM_SCALAR|PM_READONLY;
+    pm.gsu.s = dis ? &pmdisfunction_gsu : &pmfunction_gsu;
+
+    for (i = 0; i < shfunctab->hsize; i++) {
+	for (hn = shfunctab->nodes[i]; hn; hn = hn->next) {
+	    if (dis ? (hn->flags & DISABLED) : !(hn->flags & DISABLED)) {
+		pm.node.nam = hn->nam;
+		if (func != scancountparams &&
+		    ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		     !(flags & SCANPM_WANTKEYS))) {
+		    pm.u.str = getshfuncfile((Shfunc)hn);
+		    if (!pm.u.str)
+			pm.u.str = dupstring("");
+		}
+		func(&pm.node, flags);
+	    }
+	}
+    }
+}
+
+/* Param table entry for retrieving functions_source element */
+
+/**/
+static HashNode
+getpmfunction_source(HashTable ht, const char *name)
+{
+    return getfunction_source(ht, name, 0);
+}
+
+/* Param table entry for retrieving ds_functions_source element */
+
+/**/
+static HashNode
+getpmdisfunction_source(HashTable ht, const char *name)
+{
+    return getfunction_source(ht, name, 1);
+}
+
+/* Param table entry for scanning functions_source table */
+
+/**/
+static void
+scanpmfunction_source(HashTable ht, ScanFunc func, int flags)
+{
+    scanfunctions_source(ht, func, flags, 0);
+}
+
+/* Param table entry for scanning dis_functions_source table */
+
+/**/
+static void
+scanpmdisfunction_source(HashTable ht, ScanFunc func, int flags)
+{
+    scanfunctions_source(ht, func, flags, 1);
+}
+
 /* Functions for the funcstack special parameter. */
 
 /**/
@@ -2095,6 +2187,8 @@ static struct paramdef partab[] = {
 	    NULL, getpmdisbuiltin, scanpmdisbuiltins),
     SPECIALPMDEF("dis_functions", 0, 
 	    &pmdisfunctions_gsu, getpmdisfunction, scanpmdisfunctions),
+    SPECIALPMDEF("dis_functions_source", PM_READONLY, NULL,
+		 getpmdisfunction_source, scanpmdisfunction_source),
     SPECIALPMDEF("dis_galiases", 0,
 	    &pmdisgaliases_gsu, getpmdisgalias, scanpmdisgaliases),
     SPECIALPMDEF("dis_patchars", PM_ARRAY|PM_READONLY,
@@ -2111,6 +2205,8 @@ static struct paramdef partab[] = {
 	    &funcstack_gsu, NULL, NULL),
     SPECIALPMDEF("functions", 0, &pmfunctions_gsu, getpmfunction,
 		 scanpmfunctions),
+    SPECIALPMDEF("functions_source", PM_READONLY, NULL,
+		 getpmfunction_source, scanpmfunction_source),
     SPECIALPMDEF("functrace", PM_ARRAY|PM_READONLY,
 	    &functrace_gsu, NULL, NULL),
     SPECIALPMDEF("galiases", 0,
diff --git a/Src/hashtable.c b/Src/hashtable.c
index 1f2789d..8987c85 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -1566,6 +1566,15 @@ dircache_set(char **name, char *value)
 	*name = NULL;
     } else {
 	/*
+	 * As the function path has been resolved to a particular
+	 * location, we'll store it as an absolute path.
+	 */
+	if (*value != '/') {
+	    value = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP),
+			     "/", value);
+	    value = xsymlink(value, 1);
+	}
+	/*
 	 * We'll maintain the cache at exactly the right size rather
 	 * than overallocating.  The rationale here is that typically
 	 * we'll get a lot of functions in a small number of directories
diff --git a/Test/C04funcdef.ztst b/Test/C04funcdef.ztst
index 5edbe26..176841d 100644
--- a/Test/C04funcdef.ztst
+++ b/Test/C04funcdef.ztst
@@ -325,10 +325,10 @@
     printf '%s\n' 'oops(){}' 'ninjas-earring(){}' 'oops "$@"' >oops
     autoload oops
     oops
-    whence -v oops
+    whence -v oops | sed -e "s%$PWD%CURDIR%"
   )
 0:whence -v of zsh-style autoload
->oops is a shell function from ./oops
+>oops is a shell function from CURDIR/oops
 
   (
     fpath=(.)
diff --git a/Test/V06parameter.ztst b/Test/V06parameter.ztst
index c7df35d..c2a2a4d 100644
--- a/Test/V06parameter.ztst
+++ b/Test/V06parameter.ztst
@@ -1,15 +1,15 @@
 %test
 
   print 'print In sourced file
-  print $LINENO + $functrace + $funcsourcetrace
+  print $LINENO + $functrace + ${funcsourcetrace/#$PWD/CURDIR}
   ' >sourcedfile
   print -r -- 'print Started functrace.zsh
   module_path=(./Modules)
-  print $LINENO + $functrace + $funcsourcetrace
+  print $LINENO + $functrace + ${funcsourcetrace/#$PWD/CURDIR}
   :
   fn() {
     print Inside function $0
-    print $LINENO + $functrace + $funcsourcetrace
+    print $LINENO + $functrace + ${funcsourcetrace/#$PWD/CURDIR}
   }
   :
   fn
@@ -17,7 +17,7 @@
   fpath=(. $fpath)
   :
   echo '\''print Inside $0
-    print $LINENO + $functrace + $funcsourcetrace
+    print $LINENO + $functrace + ${funcsourcetrace/#$PWD/CURDIR}
   '\'' >autofn
   :
   autoload autofn
@@ -32,9 +32,9 @@
 >Inside function fn
 >2 + ./functrace.zsh:10 + ./functrace.zsh:5
 >Inside autofn
->2 + ./functrace.zsh:20 + ./autofn:0
+>2 + ./functrace.zsh:20 + CURDIR/autofn:0
 >Inside autofn
->2 + ./functrace.zsh:21 + ./autofn:0
+>2 + ./functrace.zsh:21 + CURDIR/autofn:0
 >In sourced file
 >2 + ./functrace.zsh:22 + ./sourcedfile:0
 
@@ -66,6 +66,19 @@
 >./rocky3.zsh:13 (eval):2
 >./rocky3.zsh:14 ./rocky3.zsh:14
 
+  (
+    fpath=($PWD)
+    print "print I have been autoloaded" >myfunc
+    autoload $PWD/myfunc
+    print ${functions_source[myfunc]/#$PWD/CURDIR}
+    myfunc
+    print ${functions_source[myfunc]/#$PWD/CURDIR}
+  )
+0: $functions_source
+>CURDIR/myfunc
+>I have been autoloaded
+>CURDIR/myfunc
+
 %clean
 
- rm -f autofn functrace.zsh rocky3.zsh sourcedfile
+ rm -f autofn functrace.zsh rocky3.zsh sourcedfile myfunc



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