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

[PATCH] zformat: replace -qn by spec quoting indicator syntax



i'd been thinking more about how to solve the %-quoting problems in
completion (w/54573) and i decided that the -qn functionality i added to
zformat (w/54602) is not generic enough -- there's no value of n that
_description could use that works for all helpers. we could make it so
that you could pass the n down somehow, but here's a different approach
which incorporates an idea from mikael again

instead of index-based quoting with -qn, allow each spec to explicitly
indicate its quoting behaviour with the syntax %d: (quoted) and %%d:
(unquoted). this is similar to how you can toggle the effect of certain
options in parameter expansion (e.g. ^ and ^^). so:

  # without -qQ: don't quote by default
  % zformat -F REPLY '%a, %b, %c, %d, %%e' a:%aaa% %b:%bbb% %%c:%ccc%
  % echo $REPLY
  %aaa%, %%bbb%%, %ccc%, %d, %e

  # with -Q: quote specs by default, don't leave quotes in format string
  % zformat -QF REPLY '%a, %b, %c, %d, %%e' a:%aaa% %b:%bbb% %%c:%ccc%
  % echo $REPLY
  %%aaa%%, %%bbb%%, %ccc%, %d, %e

  # with -q: quote specs by default, leave quotes in format string
  % zformat -qF REPLY '%a, %b, %c, %d, %%e' a:%aaa% %b:%bbb% %%c:%ccc%
  % echo $REPLY
  %%aaa%%, %%bbb%%, %ccc%, %d, %%e

another patch will follow to make use of this in completion

dana


diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo
index 50d4576af..11fe8a073 100644
--- a/Doc/Zsh/mod_zutil.yo
+++ b/Doc/Zsh/mod_zutil.yo
@@ -158,8 +158,8 @@ var(pattern) matches at least one of the strings in the value.
 enditem()
 )
 findex(zformat)
-xitem(tt(zformat -f) [ tt(-qQ) [ var(n) ] ] var(param) var(format) var(spec) ...)
-xitem(tt(zformat -F) [ tt(-qQ) [ var(n) ] ] var(param) var(format) var(spec) ...)
+xitem(tt(zformat -f) [ tt(-qQ) ] ] var(param) var(format) var(spec) ...)
+xitem(tt(zformat -F) [ tt(-qQ) ] ] var(param) var(format) var(spec) ...)
 item(tt(zformat -a) var(array) var(sep) var(spec) ...)(
 This builtin provides different forms of formatting.
 
@@ -180,6 +180,11 @@ can be achieved by giving a negative minimum field width.  If a maximum
 field width is specified, the var(string) will be truncated after that
 many characters.
 
+The var(char) in each var(spec) may optionally be preceded by an
+escaping indicator, either `tt(%)' to explicitly enable the
+var(spec)-escaping behaviour of tt(-q) described below, or `tt(%%)' to
+explicitly disable it.  The default behaviour is to em(not) escape specs.
+
 Any `tt(%)' sequence in tt(format) that does not match a given var(spec)
 (or one of the special sequences described below) is output as-is.  If
 desired, these sequences may be processed by a second round of
@@ -222,8 +227,8 @@ number the condition is true when the width is em(greater than) that
 number, and with a negative number the condition is true when the width
 is em(less than or equal to) the absolute value of that number.
 
-With tt(-q), `tt(%)' characters in the var(spec)s are escaped as they
-are inserted into the formatted string, and pre-escaped `tt(%)' and
+With tt(-q), `tt(%)' characters in the var(spec)s are escaped by default
+as they are inserted into the formatted string, and pre-escaped `tt(%)' and
 `tt(RPAR())` characters in the format string are left as they are.  For
 example:
 
@@ -235,14 +240,6 @@ the tt(%B)...tt(%b) sequences could be used with prompt expansion to
 produce bold text.  One notable use case is formatting a description to
 be passed to tt(compadd -x) in a completion function.
 
-tt(-q) may be followed by an optional integer argument var(n) to escape
-only the first var(n) var(spec)s.  For example,
-
-example(zformat -Fq1 REPLY '%D %d' d:%foo% D:%bar%)
-
-outputs `tt(%bar% %%foo%%)' to tt(REPLY).  This is the only case where
-the order that var(spec)s are given in is significant.
-
 Since the output with tt(-q) is expected to be subject to further
 processing, width specifiers don't count the extra escape characters,
 ensuring that the widths are correct em(after) that processing.
@@ -252,6 +249,13 @@ against the em(pre-escaped) spec lengths.
 tt(-Q) is like tt(-q) except that it interprets pre-escaped `tt(%)' and
 `tt(RPAR())` characters in the format string as normal.
 
+The optional escaping indicator can be used to exempt individual
+var(spec)s from escaping as described above.  For example:
+
+example(zformat -qF REPLY '%d %D' d:%foo% %%D:%bar%)
+
+outputs `tt(%%foo%% %bar%)' to tt(REPLY).
+
 The form using the tt(-a) option can be used for aligning
 strings.  Here, the var(spec)s are of the form
 `var(left)tt(:)var(right)' where `var(left)' and `var(right)' are
diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c
index add055460..0b412c50a 100644
--- a/Src/Modules/zutil.c
+++ b/Src/Modules/zutil.c
@@ -984,21 +984,12 @@ static int
 bin_zformat(char *nam, char **args, Options ops, UNUSED(int func))
 {
     unsigned char qopt = OPT_ISSET(ops, 'q') ? 'q' : OPT_ISSET(ops, 'Q') ? 'Q' : 0;
-    int presence = 0, quote = INT_MAX;
+    int presence = 0;
 
     if (OPT_ISSET(ops, 'q') && OPT_ISSET(ops, 'Q')) {
 	zwarnnam(nam, "only one of -qQ allowed");
 	return 1;
     }
-    // the error here is more meaningful than the following ones with e.g. -q1F
-    if (OPT_HASARG(ops, qopt)) {
-	char *qptr;
-	quote = (int) zstrtol(OPT_ARG(ops, qopt), &qptr, 10);
-	if (quote < 0 || *qptr) {
-	    zwarnnam(nam, "bad argument to -%c: %s", qopt, OPT_ARG(ops, qopt));
-	    return 1;
-	}
-    }
     if (OPT_ISSET(ops, 'a') + OPT_ISSET(ops, 'f') + OPT_ISSET(ops, 'F') < 1) {
 	zwarnnam(nam, "one of -afF expected");
 	return 1;
@@ -1007,8 +998,8 @@ bin_zformat(char *nam, char **args, Options ops, UNUSED(int func))
 	zwarnnam(nam, "only one of -afF allowed");
 	return 1;
     }
-    if (OPT_ISSET(ops, 'a') && OPT_ISSET(ops, 'q')) {
-	zwarnnam(nam, "-q not allowed with -a");
+    if (OPT_ISSET(ops, 'a') && qopt) {
+	zwarnnam(nam, "-qQ not allowed with -a");
 	return 1;
     }
 
@@ -1018,26 +1009,37 @@ bin_zformat(char *nam, char **args, Options ops, UNUSED(int func))
 	/* fall-through */
     case 'f':
 	{
-	    char **ap, *specs[256] = {0}, *out;
-	    int i, olen, oused = 0;
+	    char **ap, *specs[256] = {0}, *out, *arg;
+	    int quote, olen, oused = 0;
 	    int qspecs[256] = {0};
 
 	    /* Parse the specs in argv. */
-	    for (i = 1, ap = args + 2; *ap; i++, ap++) {
-		if (!ap[0][0] || ap[0][0] == '-' || ap[0][0] == '.' ||
-		    ap[0][0] == '%' || ap[0][0] == ')' ||
-		    idigit(ap[0][0]) || ap[0][1] != ':') {
+	    for (ap = args + 2; (arg = *ap); ap++) {
+		// quote by default (spec like d:...) with -qQ
+		quote = !!qopt;
+
+		// spec like %%d:... -- explicitly disable quoting
+		if (arg[0] == '%' && arg[1] == '%') {
+		    quote = 0;
+		    arg += 2;
+		// spec like %d:... -- explicitly enable quoting
+		} else if (arg[0] == '%') {
+		    quote = 1;
+		    arg += 1;
+		}
+
+		if (!arg[0] || arg[0] == '-' || arg[0] == '.' ||
+		    arg[0] == '%' || arg[0] == ')' ||
+		    idigit(arg[0]) || arg[1] != ':') {
 		    zwarnnam(nam, "invalid spec: %s", *ap);
 		    return 1;
 		}
 
-		// need to quote specs here because zformat_substring() won't
-		// know the order
-		if (qopt && quote >= i) {
+		if (quote) {
 		    int len = 0, pct = 0;
-		    char *aptr, *sptr, *spec = *ap + 2;
+		    char *aptr, *sptr, *spec = arg + 2;
 
-		    for (aptr = *ap + 2; *aptr; aptr++, len++) {
+		    for (aptr = arg + 2; *aptr; aptr++, len++) {
 			if (*aptr == '%') {
 			    len++, pct++;
 			}
@@ -1046,7 +1048,7 @@ bin_zformat(char *nam, char **args, Options ops, UNUSED(int func))
 		    if (pct) {
 			spec = (char *) zhalloc(len + 1);
 			sptr = spec;
-			for (aptr = *ap + 2; *aptr; aptr++) {
+			for (aptr = arg + 2; *aptr; aptr++) {
 			    *sptr++ = *aptr;
 			    if (*aptr == '%') {
 				*sptr++ = *aptr;
@@ -1055,10 +1057,10 @@ bin_zformat(char *nam, char **args, Options ops, UNUSED(int func))
 			*sptr = '\0';
 		    }
 
-		    specs[(unsigned char) ap[0][0]] = spec;
-		    qspecs[(unsigned char) ap[0][0]] = pct;
+		    specs[(unsigned char) *arg] = spec;
+		    qspecs[(unsigned char) *arg] = pct;
 		} else {
-		    specs[(unsigned char) ap[0][0]] = ap[0] + 2;
+		    specs[(unsigned char) *arg] = arg + 2;
 		}
 	    }
 
@@ -2141,7 +2143,7 @@ bin_zparseopts(char *nam, char **args, Options ops, UNUSED(int func))
 }
 
 static struct builtin bintab[] = {
-    BUILTIN("zformat", 0, bin_zformat, 2, -1, 0, "afFq:%Q:%", NULL),
+    BUILTIN("zformat", 0, bin_zformat, 2, -1, 0, "afFqQ", NULL),
     BUILTIN("zparseopts", 0, bin_zparseopts, 0, -1, 0, "a:A:DEFGKMn:v:", NULL),
     BUILTIN("zregexparse", 0, bin_zregexparse, 3, -1, 0, "c", NULL),
     BUILTIN("zstyle", 0, bin_zstyle, 0, -1, 0, NULL, NULL),
diff --git a/Test/V13zformat.ztst b/Test/V13zformat.ztst
index 449b96338..63654bb27 100644
--- a/Test/V13zformat.ztst
+++ b/Test/V13zformat.ztst
@@ -275,35 +275,32 @@
 
   zformat -qa reply .
   zformat -aq reply .
-1:-a + -q not allowed
-?(eval):zformat:1: -q not allowed with -a
-?(eval):zformat:2: -q not allowed with -a
+  zformat -aQ reply .
+1:-a + -qQ not allowed
+?(eval):zformat:1: -qQ not allowed with -a
+?(eval):zformat:2: -qQ not allowed with -a
+?(eval):zformat:3: -qQ not allowed with -a
 
   zformat -Fq     REPLY F && print -r - $REPLY
   zformat -qF     REPLY F && print -r - $REPLY
   zformat -FqF    REPLY F && print -r - $REPLY
-  zformat -q1 -F  REPLY F && print -r - $REPLY
-  zformat -q1F    REPLY F && print -r - $REPLY
-  zformat -q-1 -F REPLY F && print -r - $REPLY
-1:optional argument to -q
+  zformat -qqF    REPLY F && print -r - $REPLY
+  zformat -qQF    REPLY F && print -r - $REPLY
+1:more than one of -qQ not allowed
 >F
 >F
 >F
 >F
-?(eval):zformat:5: bad argument to -q: 1F
-?(eval):zformat:6: bad option: --
+?(eval):zformat:5: only one of -qQ allowed
 
-# the spec order in the format string differs from the order in the arguments
-# here to make sure we're testing -qn's effects on the latter
-  for 1 in '' 0 1 2 3; do
-    zformat -Fq$1   REPLY '%%x %) %. %X %D %d' d:%foo% D:%bar% && print -r - $REPLY
+  for 1 in -F -FQ -Fq; do
+    zformat $1 REPLY '%a, %b, %c, %d, %%e' a:%aaa% %b:%bbb% %%c:%ccc%
+    print -r - $REPLY
   done
-0:-q with and without optarg
->%%x %) %. %X %%bar%% %%foo%%
->%%x %) %. %X %bar% %foo%
->%%x %) %. %X %bar% %%foo%%
->%%x %) %. %X %%bar%% %%foo%%
->%%x %) %. %X %%bar%% %%foo%%
+0:spec quoting indicators, -Q vs -q
+>%aaa%, %%bbb%%, %ccc%, %d, %e
+>%%aaa%%, %%bbb%%, %ccc%, %d, %e
+>%%aaa%%, %%bbb%%, %ccc%, %d, %%e
 
   zformat -Fq REPLY '%(x.%%/%d.%%/%D)' x:1 d:%foo% D:%bar% && print -r - $REPLY
   zformat -Fq REPLY '%(X.%%/%d.%%/%D)' x:1 d:%foo% D:%bar% && print -r - $REPLY
@@ -340,11 +337,3 @@
 0:-q: ternary width test ignores extra %s
 >t f f
 >t t f
-
-  zformat -FQ  REPLY '%%x %) %. %X %D %d' d:%foo% D:%bar% && print -r - $REPLY
-  zformat -FQ0 REPLY '%%x %) %. %X %D %d' d:%foo% D:%bar% && print -r - $REPLY
-  zformat -FQ1 REPLY '%%x %) %. %X %D %d' d:%foo% D:%bar% && print -r - $REPLY
-0:-Q, -Q0, -Q1
->%x ) %. %X %%bar%% %%foo%%
->%x ) %. %X %bar% %foo%
->%x ) %. %X %bar% %%foo%%




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