about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog9
-rw-r--r--Doc/Zsh/expn.yo45
-rw-r--r--Src/hist.c141
-rw-r--r--Src/subst.c14
4 files changed, 190 insertions, 19 deletions
diff --git a/ChangeLog b/ChangeLog
index 34d3618d8..25d857495 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2009-03-15  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* 26736: Doc/Zsh/expn.yo: document 26731.
+
+	* Michael Hwang: 26731 (with cosmetic changes): Src/hist.c,
+	Src/subst.c: add `a' and `A' modifiers.
+
 2009-03-14  Wayne Davison  <wayned@users.sourceforge.net>
 
 	* 26735: Src/Modules/files.c, Src/Modules/mapfile.c,
@@ -11409,5 +11416,5 @@
 
 *****************************************************
 * This is used by the shell to define $ZSH_PATCHLEVEL
-* $Revision: 1.4617 $
+* $Revision: 1.4618 $
 *****************************************************
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index 446933e43..16d42fbfc 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -216,20 +216,24 @@ of em(filename generation) and em(parameter expansion), except where
 noted.
 
 startitem()
-item(tt(h))(
-Remove a trailing pathname component, leaving the head.  This works
-like `tt(dirname)'.
+item(tt(a))(
+Turn a file name into an absolute path:  prepends the current directory,
+if necessary, and resolves any use of `tt(..)' and `tt(.)' in the path.
 )
-item(tt(r))(
-Remove a filename extension of the form `tt(.)var(xxx)', leaving
-the root name.
+item(tt(A))(
+As `tt(a)', but also resolve use of symbolic links where possible.
+Note that resolution of `tt(..)' occurs em(before) resolution of symbolic
+links.
 )
 item(tt(e))(
 Remove all but the extension.
 )
-item(tt(t))(
-Remove all leading pathname components, leaving the tail.  This works
-like `tt(basename)'.
+item(tt(h))(
+Remove a trailing pathname component, leaving the head.  This works
+like `tt(dirname)'.
+)
+item(tt(l))(
+Convert the words to all lowercase.
 )
 item(tt(p))(
 Print the new command but do not execute it.  Only works with history
@@ -244,15 +248,9 @@ by tt(eval).
 item(tt(Q))(
 Remove one level of quotes from the substituted words.
 )
-item(tt(x))(
-Like tt(q), but break into words at whitespace.  Does not work with
-parameter expansion.
-)
-item(tt(l))(
-Convert the words to all lowercase.
-)
-item(tt(u))(
-Convert the words to all uppercase.
+item(tt(r))(
+Remove a filename extension of the form `tt(.)var(xxx)', leaving
+the root name.
 )
 item(tt(s/)var(l)tt(/)var(r)[tt(/)])(
 Substitute var(r) for var(l) as described below.
@@ -272,6 +270,17 @@ 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(u))(
+Convert the words to all uppercase.
+)
+item(tt(x))(
+Like tt(q), but break into words at whitespace.  Does not work with
+parameter expansion.
+)
 enditem()
 
 The tt(s/l/r/) substitution works as follows.  By default the left-hand
diff --git a/Src/hist.c b/Src/hist.c
index 637976de7..c8625c7af 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -623,6 +623,21 @@ histsubchar(int c)
 	    case 'p':
 		histdone = HISTFLAG_DONE | HISTFLAG_NOEXEC;
 		break;
+	    case 'a':
+		if (!chabspath(&sline)) {
+		    herrflush();
+		    zerr("modifier failed: a");
+		    return -1;
+		}
+		break;
+
+	    case 'A':
+		if (!chrealpath(&sline)) {
+		    herrflush();
+		    zerr("modifier failed: A");
+		    return -1;
+		}
+		break;
 	    case 'h':
 		if (!remtpath(&sline)) {
 		    herrflush();
@@ -1484,6 +1499,132 @@ hcomsearch(char *str)
 
 /**/
 int
+chabspath(char **junkptr)
+{
+    char *current, *dest;
+
+    if (!**junkptr)
+	return 1;
+
+    if (**junkptr != '/') {
+	*junkptr = zhtricat(zgetcwd(), "/", *junkptr);
+    }
+
+    current = *junkptr;
+    dest = *junkptr;
+
+#ifdef HAVE_SUPERROOT
+    while (*current == '/' && current[1] == '.' && current[2] == '.' &&
+	   (!current[3] || current[3] == '/')) {
+	*dest++ = '/';
+	*dest++ = '.';
+	*dest++ = '.';
+	current += 3;
+    }
+#endif
+	
+    for (;;) {
+	if (*current == '/') {
+#ifdef __CYGWIN__
+	    if (current == *junkptr && current[1] == '/')
+		*dest++ = *current++;
+#endif
+	    *dest++ = *current++;
+	    while (*current == '/')
+		current++;
+	} else if (!*current) {
+	    while (dest > *junkptr + 1 && dest[-1] == '/')
+		dest--;
+	    *dest = '\0';
+	    break;
+	} else if (current[0] == '.' && current[1] == '.' &&
+		   (!current[2] || current[2] == '/')) {
+		if (current == *junkptr || dest == *junkptr) {
+		    *dest++ = '.';
+		    *dest++ = '.';
+		    current += 2;
+		} else if (dest > *junkptr + 2 &&
+			   !strncmp(dest - 3, "../", 3)) {
+		    *dest++ = '.';
+		    *dest++ = '.';
+		    current += 2;
+		} else if (dest > *junkptr + 1) {
+		    *dest = '\0';
+		    for (dest--;
+			 dest > *junkptr + 1 && dest[-1] != '/';
+			 dest--);
+		    if (dest[-1] != '/')
+			dest--;
+		    current += 2;
+		} else if (dest == *junkptr + 1) {
+		    /* This might break with Cygwin's leading double slashes? */
+		    current += 2;
+		} else {
+		    return 0;
+		}
+	} else if (current[0] == '.' && (current[1] == '/' || !current[1])) {
+	     while (*++current == '/');
+	} else {
+	    while (*current != '/' && *current != '\0')
+		if ((*dest++ = *current++) == Meta)
+		    dest[-1] = *current++ ^ 32;
+	}
+    }
+    return 1;
+}
+
+/**/
+int
+chrealpath(char **junkptr)
+{
+    char *lastpos, *nonreal, real[PATH_MAX];
+
+    if (!**junkptr)
+	return 1;
+
+    /* Notice that this means ..'s are applied before symlinks are resolved! */
+    if (!chabspath(junkptr))
+	return 0;
+
+    /*
+     * Notice that this means you cannot pass relative paths into this
+     * function!
+     */
+    if (**junkptr != '/')
+	return 0;
+
+    lastpos = strend(*junkptr);
+    nonreal = lastpos + 1;
+
+    while (!realpath(*junkptr, real)) {
+	if (errno == EINVAL || errno == ELOOP ||
+	    errno == ENAMETOOLONG || errno == ENOMEM)
+	    return 0;
+
+	if (nonreal == *junkptr) {
+	    *real = '\0';
+	    break;
+	}
+
+	while (*nonreal != '/' && nonreal >= *junkptr)
+	    nonreal--;
+	*nonreal = '\0';
+    }
+
+    char *str = nonreal;
+    while (str <= lastpos) {
+	if (*str == '\0')
+	    *str = '/';
+	str++;
+    }
+
+    *junkptr = bicat(real, nonreal);
+
+    return 1;
+}
+
+/**/
+int
 remtpath(char **junkptr)
 {
     char *str = strend(*junkptr);
diff --git a/Src/subst.c b/Src/subst.c
index 9e3f06fe3..5033dd492 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -3199,6 +3199,8 @@ modify(char **str, char **ptr)
 
 	for (; !c && **ptr;) {
 	    switch (**ptr) {
+            case 'a':
+            case 'A':
 	    case 'h':
 	    case 'r':
 	    case 'e':
@@ -3337,6 +3339,12 @@ modify(char **str, char **ptr)
 			copy = dupstring(tt);
 		    *e = tc;
 		    switch (c) {
+                    case 'a':
+			chabspath(&copy);
+			break;
+		    case 'A':
+			chrealpath(&copy);
+			break;
 		    case 'h':
 			remtpath(&copy);
 			break;
@@ -3396,6 +3404,12 @@ modify(char **str, char **ptr)
 
 	    } else {
 		switch (c) {
+		case 'a':
+		    chabspath(str);
+		    break;
+		case 'A':
+		    chrealpath(str);
+		    break;
 		case 'h':
 		    remtpath(str);
 		    break;