Make format parsing build the modifiers into a list, standardize how

arguments are given and allow multiple modifiers on a format (separated
by ;).
pull/1633/head
nicm 2019-03-13 14:10:34 +00:00
parent feaa5660a3
commit 71e00c718c
1 changed files with 321 additions and 175 deletions

544
format.c
View File

@ -92,8 +92,8 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
#define FORMAT_TIMESTRING 0x1 #define FORMAT_TIMESTRING 0x1
#define FORMAT_BASENAME 0x2 #define FORMAT_BASENAME 0x2
#define FORMAT_DIRNAME 0x4 #define FORMAT_DIRNAME 0x4
#define FORMAT_SUBSTITUTE 0x8 #define FORMAT_QUOTE 0x8
#define FORMAT_QUOTE 0x10 #define FORMAT_LITERAL 0x10
/* Entry in format tree. */ /* Entry in format tree. */
struct format_entry { struct format_entry {
@ -121,6 +121,15 @@ struct format_tree {
static int format_entry_cmp(struct format_entry *, struct format_entry *); static int format_entry_cmp(struct format_entry *, struct format_entry *);
RB_GENERATE_STATIC(format_entry_tree, format_entry, entry, format_entry_cmp); RB_GENERATE_STATIC(format_entry_tree, format_entry, entry, format_entry_cmp);
/* Format modifiers. */
struct format_modifier {
char modifier[3];
u_int size;
char **argv;
int argc;
};
/* Format entry tree comparison function. */ /* Format entry tree comparison function. */
static int static int
format_entry_cmp(struct format_entry *fe1, struct format_entry *fe2) format_entry_cmp(struct format_entry *fe1, struct format_entry *fe2)
@ -885,7 +894,7 @@ found:
/* Skip until end. */ /* Skip until end. */
static const char * static const char *
format_skip(const char *s, char end) format_skip(const char *s, const char *end)
{ {
int brackets = 0; int brackets = 0;
@ -898,7 +907,7 @@ format_skip(const char *s, char end)
} }
if (*s == '}') if (*s == '}')
brackets--; brackets--;
if (*s == end && brackets == 0) if (strchr(end, *s) != NULL && brackets == 0)
break; break;
} }
if (*s == '\0') if (*s == '\0')
@ -908,17 +917,25 @@ format_skip(const char *s, char end)
/* Return left and right alternatives separated by commas. */ /* Return left and right alternatives separated by commas. */
static int static int
format_choose(char *s, char **left, char **right) format_choose(struct format_tree *ft, const char *s, char **left, char **right,
int expand)
{ {
char *cp; const char *cp;
char *left0, *right0;
cp = (char *)format_skip(s, ','); cp = format_skip(s, ",");
if (cp == NULL) if (cp == NULL)
return (-1); return (-1);
*cp = '\0'; left0 = xstrndup(s, cp - s);
right0 = xstrdup(cp + 1);
*left = s; if (expand) {
*right = cp + 1; *left = format_expand(ft, left0);
*right = format_expand(ft, right0);
} else {
*left = left0;
*right = right0;
}
return (0); return (0);
} }
@ -931,209 +948,166 @@ format_true(const char *s)
return (0); return (0);
} }
/* Replace a key. */ /* Check if modifier end. */
static int static int
format_replace(struct format_tree *ft, const char *key, size_t keylen, format_is_end(char c)
char **buf, size_t *len, size_t *off)
{ {
struct window_pane *wp = ft->wp; return (c == ';' || c == ':');
char *copy, *copy0, *endptr, *ptr, *found, *new, sep; }
char *value, *from = NULL, *to = NULL, *left, *right;
size_t valuelen, newlen, fromlen, tolen, used;
long limit = 0;
int modifiers = 0, compare = 0, search = 0;
int literal = 0;
/* Make a copy of the key. */ /* Add to modifier list. */
copy0 = copy = xmalloc(keylen + 1); static void
memcpy(copy, key, keylen); format_add_modifier(struct format_modifier **list, u_int *count,
copy[keylen] = '\0'; const char *c, size_t n, char **argv, int argc)
{
struct format_modifier *fm;
/* Is there a length limit or whatnot? */ *list = xreallocarray(*list, (*count) + 1, sizeof **list);
switch (copy[0]) { fm = &(*list)[(*count)++];
case 'l':
if (copy[1] != ':')
break;
literal = 1;
copy += 2;
break;
case 'm':
if (copy[1] != ':')
break;
compare = -2;
copy += 2;
break;
case 'C':
if (copy[1] != ':')
break;
search = 1;
copy += 2;
break;
case '|':
if (copy[1] != '|' || copy[2] != ':')
break;
compare = -3;
copy += 3;
break;
case '&':
if (copy[1] != '&' || copy[2] != ':')
break;
compare = -4;
copy += 3;
break;
case '!':
if (copy[1] == '=' && copy[2] == ':') {
compare = -1;
copy += 3;
break;
}
break;
case '=':
if (copy[1] == '=' && copy[2] == ':') {
compare = 1;
copy += 3;
break;
}
errno = 0;
limit = strtol(copy + 1, &endptr, 10);
if (errno == ERANGE && (limit == LONG_MIN || limit == LONG_MAX))
break;
if (*endptr != ':')
break;
copy = endptr + 1;
break;
case 'b':
if (copy[1] != ':')
break;
modifiers |= FORMAT_BASENAME;
copy += 2;
break;
case 'd':
if (copy[1] != ':')
break;
modifiers |= FORMAT_DIRNAME;
copy += 2;
break;
case 't':
if (copy[1] != ':')
break;
modifiers |= FORMAT_TIMESTRING;
copy += 2;
break;
case 'q':
if (copy[1] != ':')
break;
modifiers |= FORMAT_QUOTE;
copy += 2;
break;
case 's':
sep = copy[1];
if (sep == ':' || !ispunct((u_char)sep))
break;
from = copy + 2;
for (copy = from; *copy != '\0' && *copy != sep; copy++)
/* nothing */;
if (copy[0] != sep || copy == from) {
copy = copy0;
break;
}
copy[0] = '\0';
to = copy + 1;
for (copy = to; *copy != '\0' && *copy != sep; copy++)
/* nothing */;
if (copy[0] != sep || copy[1] != ':') {
copy = copy0;
break;
}
copy[0] = '\0';
modifiers |= FORMAT_SUBSTITUTE; memcpy(fm->modifier, c, n);
copy += 2; fm->modifier[n] = '\0';
break; fm->size = n;
}
/* Is this a literal string? */ fm->argv = argv;
if (literal) { fm->argc = argc;
value = xstrdup(copy); }
goto done;
}
/* Is this a comparison or a conditional? */ /* Free modifier list. */
if (search) { static void
/* Search in pane. */ format_free_modifiers(struct format_modifier *list, u_int count)
if (wp == NULL) {
value = xstrdup("0"); u_int i;
else
xasprintf(&value, "%u", window_pane_search(wp, copy)); for (i = 0; i < count; i++)
} else if (compare != 0) { cmd_free_argv(list[i].argc, list[i].argv);
/* Comparison: compare comma-separated left and right. */ free(list);
if (format_choose(copy, &left, &right) != 0) }
goto fail;
left = format_expand(ft, left); /* Build modifier list. */
right = format_expand(ft, right); static struct format_modifier *
if (compare == -3 && format_build_modifiers(struct format_tree *ft, const char **s, u_int *count)
(format_true(left) || format_true(right))) {
value = xstrdup("1"); const char *cp = *s, *end;
else if (compare == -4 && struct format_modifier *list = NULL;
(format_true(left) && format_true(right))) char c, last[] = "X;:", **argv, *value;
value = xstrdup("1"); int argc;
else if (compare == 1 && strcmp(left, right) == 0)
value = xstrdup("1");
else if (compare == -1 && strcmp(left, right) != 0)
value = xstrdup("1");
else if (compare == -2 && fnmatch(left, right, 0) == 0)
value = xstrdup("1");
else
value = xstrdup("0");
free(right);
free(left);
} else if (*copy == '?') {
/* Conditional: check first and choose second or third. */
ptr = (char *)format_skip(copy, ',');
if (ptr == NULL)
goto fail;
*ptr = '\0';
found = format_find(ft, copy + 1, modifiers);
if (found == NULL) {
/* /*
* If the conditional not found, try to expand it. If * Modifiers are a ; separated list of the forms:
* the expansion doesn't have any effect, then assume * l,m,C,b,d,t,q
* false. * =a
* =/a
* =/a/
* s/a/b/
* s/a/b
* ||,&&,!=,==
*/ */
found = format_expand(ft, copy + 1);
if (strcmp(found, copy + 1) == 0) { *count = 0;
free(found);
found = xstrdup(""); while (*cp != '\0' && *cp != ':') {
} /* Skip and separator character. */
} if (*cp == ';')
if (format_choose(ptr + 1, &left, &right) != 0) { cp++;
free(found);
goto fail; /* Check single character modifiers with no arguments. */
if (strchr("lmCbdtq", cp[0]) != NULL && format_is_end(cp[1])) {
format_add_modifier(&list, count, cp, 1, NULL, 0);
cp++;
continue;
} }
if (format_true(found)) /* Then try double character with no arguments. */
value = format_expand(ft, left); if ((memcmp("||", cp, 2) == 0 ||
else memcmp("&&", cp, 2) == 0 ||
value = format_expand(ft, right); memcmp("!=", cp, 2) == 0 ||
free(found); memcmp("==", cp, 2) == 0) &&
} else { format_is_end(cp[2])) {
/* Neither: look up directly. */ format_add_modifier(&list, count, cp, 2, NULL, 0);
value = format_find(ft, copy, modifiers); cp += 2;
if (value == NULL) continue;
value = xstrdup("");
} }
/* Perform substitution if any. */ /* Now try single character with arguments. */
if (modifiers & FORMAT_SUBSTITUTE) { if (strchr("s=", cp[0]) == NULL)
break;
c = cp[0];
/* No arguments provided. */
if (format_is_end(cp[1])) {
format_add_modifier(&list, count, cp, 1, NULL, 0);
cp++;
continue;
}
argv = NULL;
argc = 0;
/* Single argument with no wrapper character. */
if (!ispunct(cp[1]) || cp[1] == '-') {
end = format_skip(cp + 1, ":;");
if (end == NULL)
break;
argv = xcalloc(1, sizeof *argv);
value = xstrndup(cp + 1, end - (cp + 1));
argv[0] = format_expand(ft, value);
free(value);
argc = 1;
format_add_modifier(&list, count, &c, 1, argv, argc);
cp = end;
continue;
}
/* Multiple arguments with a wrapper character. */
last[0] = cp[1];
cp++;
do {
if (cp[0] == last[0] && format_is_end(cp[1])) {
cp++;
break;
}
end = format_skip(cp + 1, last);
if (end == NULL)
break;
cp++;
argv = xreallocarray (argv, argc + 1, sizeof *argv);
value = xstrndup(cp, end - cp);
argv[argc++] = format_expand(ft, value);
free(value);
cp = end;
} while (!format_is_end(cp[0]));
format_add_modifier(&list, count, &c, 1, argv, argc);
}
if (*cp != ':') {
format_free_modifiers(list, *count);
*count = 0;
return (NULL);
}
*s = cp + 1;
return list;
}
/* Perform substitution in string. */
static char *
format_substitute(const char *source, const char *from, const char *to)
{
char *copy, *new;
const char *cp;
size_t fromlen, tolen, newlen, used;
fromlen = strlen(from); fromlen = strlen(from);
tolen = strlen(to); tolen = strlen(to);
newlen = strlen(value) + 1; newlen = strlen(source) + 1;
copy = new = xmalloc(newlen); copy = new = xmalloc(newlen);
for (ptr = value; *ptr != '\0'; /* nothing */) {
if (strncmp(ptr, from, fromlen) != 0) { for (cp = source; *cp != '\0'; /* nothing */) {
*new++ = *ptr++; if (strncmp(cp, from, fromlen) != 0) {
*new++ = *cp++;
continue; continue;
} }
used = new - copy; used = new - copy;
@ -1145,11 +1119,181 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen,
memcpy(new, to, tolen); memcpy(new, to, tolen);
new += tolen; new += tolen;
ptr += fromlen; cp += fromlen;
} }
*new = '\0'; *new = '\0';
return (copy);
}
/* Replace a key. */
static int
format_replace(struct format_tree *ft, const char *key, size_t keylen,
char **buf, size_t *len, size_t *off)
{
struct window_pane *wp = ft->wp;
const char *errptr, *copy, *cp;
char *copy0, *condition, *found, *new;
char *value, *left, *right;
char tmp[64];
size_t valuelen;
int modifiers = 0, limit = 0;
struct format_modifier *list, *fm, *cmp = NULL, *search = NULL;
struct format_modifier *sub = NULL;
u_int i, count;
/* Make a copy of the key. */
copy = copy0 = xstrndup(key, keylen);
/* Process modifier list. */
list = format_build_modifiers(ft, &copy, &count);
for (i = 0; i < count; i++) {
xsnprintf(tmp, sizeof tmp, "%s: modifier %u", __func__, i);
log_debug("%s = %s", tmp, list[i].modifier);
cmd_log_argv(list[i].argc, list[i].argv, tmp);
fm = &list[i];
if (fm->size == 1) {
switch (fm->modifier[0]) {
case 'm':
cmp = fm;
break;
case 'C':
search = fm;
break;
case 's':
if (fm->argc != 2)
break;
sub = fm;
break;
case '=':
if (fm->argc != 1)
break;
limit = strtonum(fm->argv[0], INT_MIN, INT_MAX,
&errptr);
if (errptr != NULL)
limit = 0;
break;
case 'l':
modifiers |= FORMAT_LITERAL;
break;
case 'b':
modifiers |= FORMAT_BASENAME;
break;
case 'd':
modifiers |= FORMAT_DIRNAME;
break;
case 't':
modifiers |= FORMAT_TIMESTRING;
break;
case 'q':
modifiers |= FORMAT_QUOTE;
break;
}
} else if (fm->size == 2) {
if (strcmp(fm->modifier, "||") == 0 ||
strcmp(fm->modifier, "&&") == 0 ||
strcmp(fm->modifier, "==") == 0 ||
strcmp(fm->modifier, "!=") == 0)
cmp = fm;
}
}
log_debug("%s: remaining = '%s'", __func__, copy);
/* Is this a literal string? */
if (modifiers & FORMAT_LITERAL) {
value = xstrdup(copy);
goto done;
}
/* Is this a comparison or a conditional? */
if (search != NULL) {
/* Search in pane. */
if (wp == NULL)
value = xstrdup("0");
else
xasprintf(&value, "%u", window_pane_search(wp, copy));
} else if (cmp != NULL) {
/* Comparison of left and right. */
if (format_choose(ft, copy, &left, &right, 1) != 0)
goto fail;
if (strcmp(cmp->modifier, "||") == 0) {
if (format_true(left) || format_true(right))
value = xstrdup("1");
else
value = xstrdup("0");
} else if (strcmp(cmp->modifier, "&&") == 0) {
if (format_true(left) && format_true(right))
value = xstrdup("1");
else
value = xstrdup("0");
} else if (strcmp(cmp->modifier, "==") == 0) {
if (strcmp(left, right) == 0)
value = xstrdup("1");
else
value = xstrdup("0");
} else if (strcmp(cmp->modifier, "!=") == 0) {
if (strcmp(left, right) != 0)
value = xstrdup("1");
else
value = xstrdup("0");
}
else if (strcmp(cmp->modifier, "m") == 0) {
if (fnmatch(left, right, 0) == 0)
value = xstrdup("1");
else
value = xstrdup("0");
}
free(right);
free(left);
} else if (*copy == '?') {
/* Conditional: check first and choose second or third. */
cp = format_skip(copy + 1, ",");
if (cp == NULL)
goto fail;
condition = xstrndup(copy + 1, cp - (copy + 1));
found = format_find(ft, condition, modifiers);
if (found == NULL) {
/*
* If the condition not found, try to expand it. If
* the expansion doesn't have any effect, then assume
* false.
*/
found = format_expand(ft, condition);
if (strcmp(found, condition) == 0) {
free(found);
found = xstrdup("");
}
}
free(condition);
if (format_choose(ft, cp + 1, &left, &right, 0) != 0) {
free(found);
goto fail;
}
if (format_true(found))
value = format_expand(ft, left);
else
value = format_expand(ft, right);
free(right);
free(left);
free(found);
} else {
/* Neither: look up directly. */
value = format_find(ft, copy, modifiers);
if (value == NULL)
value = xstrdup("");
}
/* Perform substitution if any. */
if (sub != NULL) {
new = format_substitute(value, sub->argv[0], sub->argv[1]);
free(value); free(value);
value = copy; value = new;
} }
/* Truncate the value if needed. */ /* Truncate the value if needed. */
@ -1174,10 +1318,12 @@ done:
*off += valuelen; *off += valuelen;
free(value); free(value);
format_free_modifiers(list, count);
free(copy0); free(copy0);
return (0); return (0);
fail: fail:
format_free_modifiers(list, count);
free(copy0); free(copy0);
return (-1); return (-1);
} }
@ -1262,7 +1408,7 @@ format_expand(struct format_tree *ft, const char *fmt)
fmt += n + 1; fmt += n + 1;
continue; continue;
case '{': case '{':
ptr = format_skip(fmt - 2, '}'); ptr = format_skip((char *)fmt - 2, "}");
if (ptr == NULL) if (ptr == NULL)
break; break;
n = ptr - fmt; n = ptr - fmt;
@ -1305,7 +1451,7 @@ format_expand(struct format_tree *ft, const char *fmt)
} }
buf[off] = '\0'; buf[off] = '\0';
log_debug("format '%s' -> '%s'", saved, buf); log_debug("%s: '%s' -> '%s'", __func__, saved, buf);
return (buf); return (buf);
} }