version 1.168, 2019/03/12 11:16:50 |
version 1.169, 2019/03/13 14:10:34 |
|
|
#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 { |
|
|
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) |
|
|
|
|
/* 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; |
|
|
|
|
} |
} |
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') |
|
|
|
|
/* 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); |
} |
} |
|
|
|
|
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] != ':') |
memcpy(fm->modifier, c, n); |
break; |
fm->modifier[n] = '\0'; |
literal = 1; |
fm->size = n; |
copy += 2; |
|
break; |
fm->argv = argv; |
case 'm': |
fm->argc = argc; |
if (copy[1] != ':') |
} |
break; |
|
compare = -2; |
/* Free modifier list. */ |
copy += 2; |
static void |
break; |
format_free_modifiers(struct format_modifier *list, u_int count) |
case 'C': |
{ |
if (copy[1] != ':') |
u_int i; |
break; |
|
search = 1; |
for (i = 0; i < count; i++) |
copy += 2; |
cmd_free_argv(list[i].argc, list[i].argv); |
break; |
free(list); |
case '|': |
} |
if (copy[1] != '|' || copy[2] != ':') |
|
break; |
/* Build modifier list. */ |
compare = -3; |
static struct format_modifier * |
copy += 3; |
format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) |
break; |
{ |
case '&': |
const char *cp = *s, *end; |
if (copy[1] != '&' || copy[2] != ':') |
struct format_modifier *list = NULL; |
break; |
char c, last[] = "X;:", **argv, *value; |
compare = -4; |
int argc; |
copy += 3; |
|
break; |
/* |
case '!': |
* Modifiers are a ; separated list of the forms: |
if (copy[1] == '=' && copy[2] == ':') { |
* l,m,C,b,d,t,q |
compare = -1; |
* =a |
copy += 3; |
* =/a |
break; |
* =/a/ |
|
* s/a/b/ |
|
* s/a/b |
|
* ||,&&,!=,== |
|
*/ |
|
|
|
*count = 0; |
|
|
|
while (*cp != '\0' && *cp != ':') { |
|
/* Skip and separator character. */ |
|
if (*cp == ';') |
|
cp++; |
|
|
|
/* 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; |
} |
} |
break; |
|
case '=': |
/* Then try double character with no arguments. */ |
if (copy[1] == '=' && copy[2] == ':') { |
if ((memcmp("||", cp, 2) == 0 || |
compare = 1; |
memcmp("&&", cp, 2) == 0 || |
copy += 3; |
memcmp("!=", cp, 2) == 0 || |
break; |
memcmp("==", cp, 2) == 0) && |
|
format_is_end(cp[2])) { |
|
format_add_modifier(&list, count, cp, 2, NULL, 0); |
|
cp += 2; |
|
continue; |
} |
} |
errno = 0; |
|
limit = strtol(copy + 1, &endptr, 10); |
/* Now try single character with arguments. */ |
if (errno == ERANGE && (limit == LONG_MIN || limit == LONG_MAX)) |
if (strchr("s=", cp[0]) == NULL) |
break; |
break; |
if (*endptr != ':') |
c = cp[0]; |
break; |
|
copy = endptr + 1; |
/* No arguments provided. */ |
break; |
if (format_is_end(cp[1])) { |
case 'b': |
format_add_modifier(&list, count, cp, 1, NULL, 0); |
if (copy[1] != ':') |
cp++; |
break; |
continue; |
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'; |
argv = NULL; |
to = copy + 1; |
argc = 0; |
for (copy = to; *copy != '\0' && *copy != sep; copy++) |
|
/* nothing */; |
/* Single argument with no wrapper character. */ |
if (copy[0] != sep || copy[1] != ':') { |
if (!ispunct(cp[1]) || cp[1] == '-') { |
copy = copy0; |
end = format_skip(cp + 1, ":;"); |
break; |
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; |
} |
} |
copy[0] = '\0'; |
|
|
|
modifiers |= FORMAT_SUBSTITUTE; |
/* Multiple arguments with a wrapper character. */ |
copy += 2; |
last[0] = cp[1]; |
break; |
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); |
|
tolen = strlen(to); |
|
|
|
newlen = strlen(source) + 1; |
|
copy = new = xmalloc(newlen); |
|
|
|
for (cp = source; *cp != '\0'; /* nothing */) { |
|
if (strncmp(cp, from, fromlen) != 0) { |
|
*new++ = *cp++; |
|
continue; |
|
} |
|
used = new - copy; |
|
|
|
newlen += tolen; |
|
copy = xrealloc(copy, newlen); |
|
|
|
new = copy + used; |
|
memcpy(new, to, tolen); |
|
|
|
new += tolen; |
|
cp += fromlen; |
|
} |
|
|
|
*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, ©, &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? */ |
/* Is this a literal string? */ |
if (literal) { |
if (modifiers & FORMAT_LITERAL) { |
value = xstrdup(copy); |
value = xstrdup(copy); |
goto done; |
goto done; |
} |
} |
|
|
/* Is this a comparison or a conditional? */ |
/* Is this a comparison or a conditional? */ |
if (search) { |
if (search != NULL) { |
/* Search in pane. */ |
/* Search in pane. */ |
if (wp == NULL) |
if (wp == NULL) |
value = xstrdup("0"); |
value = xstrdup("0"); |
else |
else |
xasprintf(&value, "%u", window_pane_search(wp, copy)); |
xasprintf(&value, "%u", window_pane_search(wp, copy)); |
} else if (compare != 0) { |
} else if (cmp != NULL) { |
/* Comparison: compare comma-separated left and right. */ |
/* Comparison of left and right. */ |
if (format_choose(copy, &left, &right) != 0) |
if (format_choose(ft, copy, &left, &right, 1) != 0) |
goto fail; |
goto fail; |
left = format_expand(ft, left); |
|
right = format_expand(ft, right); |
if (strcmp(cmp->modifier, "||") == 0) { |
if (compare == -3 && |
if (format_true(left) || format_true(right)) |
(format_true(left) || format_true(right))) |
value = xstrdup("1"); |
value = xstrdup("1"); |
else |
else if (compare == -4 && |
value = xstrdup("0"); |
(format_true(left) && format_true(right))) |
} else if (strcmp(cmp->modifier, "&&") == 0) { |
value = xstrdup("1"); |
if (format_true(left) && format_true(right)) |
else if (compare == 1 && strcmp(left, right) == 0) |
value = xstrdup("1"); |
value = xstrdup("1"); |
else |
else if (compare == -1 && strcmp(left, right) != 0) |
value = xstrdup("0"); |
value = xstrdup("1"); |
} else if (strcmp(cmp->modifier, "==") == 0) { |
else if (compare == -2 && fnmatch(left, right, 0) == 0) |
if (strcmp(left, right) == 0) |
value = xstrdup("1"); |
value = xstrdup("1"); |
else |
else |
value = xstrdup("0"); |
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(right); |
free(left); |
free(left); |
} else if (*copy == '?') { |
} else if (*copy == '?') { |
/* Conditional: check first and choose second or third. */ |
/* Conditional: check first and choose second or third. */ |
ptr = (char *)format_skip(copy, ','); |
cp = format_skip(copy + 1, ","); |
if (ptr == NULL) |
if (cp == NULL) |
goto fail; |
goto fail; |
*ptr = '\0'; |
condition = xstrndup(copy + 1, cp - (copy + 1)); |
|
|
found = format_find(ft, copy + 1, modifiers); |
found = format_find(ft, condition, modifiers); |
if (found == NULL) { |
if (found == NULL) { |
/* |
/* |
* If the conditional not found, try to expand it. If |
* If the condition not found, try to expand it. If |
* the expansion doesn't have any effect, then assume |
* the expansion doesn't have any effect, then assume |
* false. |
* false. |
*/ |
*/ |
found = format_expand(ft, copy + 1); |
found = format_expand(ft, condition); |
if (strcmp(found, copy + 1) == 0) { |
if (strcmp(found, condition) == 0) { |
free(found); |
free(found); |
found = xstrdup(""); |
found = xstrdup(""); |
} |
} |
} |
} |
if (format_choose(ptr + 1, &left, &right) != 0) { |
free(condition); |
|
|
|
if (format_choose(ft, cp + 1, &left, &right, 0) != 0) { |
free(found); |
free(found); |
goto fail; |
goto fail; |
} |
} |
|
|
if (format_true(found)) |
if (format_true(found)) |
value = format_expand(ft, left); |
value = format_expand(ft, left); |
else |
else |
value = format_expand(ft, right); |
value = format_expand(ft, right); |
|
free(right); |
|
free(left); |
|
|
free(found); |
free(found); |
} else { |
} else { |
/* Neither: look up directly. */ |
/* Neither: look up directly. */ |
|
|
} |
} |
|
|
/* Perform substitution if any. */ |
/* Perform substitution if any. */ |
if (modifiers & FORMAT_SUBSTITUTE) { |
if (sub != NULL) { |
fromlen = strlen(from); |
new = format_substitute(value, sub->argv[0], sub->argv[1]); |
tolen = strlen(to); |
|
|
|
newlen = strlen(value) + 1; |
|
copy = new = xmalloc(newlen); |
|
for (ptr = value; *ptr != '\0'; /* nothing */) { |
|
if (strncmp(ptr, from, fromlen) != 0) { |
|
*new++ = *ptr++; |
|
continue; |
|
} |
|
used = new - copy; |
|
|
|
newlen += tolen; |
|
copy = xrealloc(copy, newlen); |
|
|
|
new = copy + used; |
|
memcpy(new, to, tolen); |
|
|
|
new += tolen; |
|
ptr += fromlen; |
|
} |
|
*new = '\0'; |
|
free(value); |
free(value); |
value = copy; |
value = new; |
} |
} |
|
|
/* Truncate the value if needed. */ |
/* Truncate the value if needed. */ |
|
|
*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); |
} |
} |
|
|
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; |
|
|
} |
} |
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); |
} |
} |
|
|