mpc-bar

macOS menu bar client for the Music Player Daemon
Log | Files | Refs | README | LICENSE

format.c (4343B)


      1 // SPDX-License-Identifier: GPL-2.0-or-later
      2 // Copyright The Music Player Daemon Project
      3 
      4 #include "format.h"
      5 
      6 #include <stdbool.h>
      7 #include <stdio.h>
      8 #include <string.h>
      9 #include <stdlib.h>
     10 
     11 /**
     12  * Reallocate the given string and append the source string.
     13  */
     14 gcc_malloc
     15 static char *
     16 string_append(char *dest, const char *src, size_t len)
     17 {
     18 	size_t destlen = dest != NULL
     19 		? strlen(dest)
     20 		: 0;
     21 
     22 	dest = realloc(dest, destlen + len + 1);
     23 	memcpy(dest + destlen, src, len);
     24 	dest[destlen + len] = '\0';
     25 
     26 	return dest;
     27 }
     28 
     29 /**
     30  * Skip the format string until the current group is closed by either
     31  * '&', '|' or ']' (supports nesting).
     32  */
     33 gcc_pure
     34 static const char *
     35 skip_format(const char *p)
     36 {
     37 	unsigned stack = 0;
     38 
     39 	while (*p != '\0') {
     40 		if (*p == '[')
     41 			stack++;
     42 		else if (*p == '#' && p[1] != '\0')
     43 			/* skip escaped stuff */
     44 			++p;
     45 		else if (stack > 0) {
     46 			if (*p == ']')
     47 				--stack;
     48 		} else if (*p == '&' || *p == '|' || *p == ']')
     49 			break;
     50 
     51 		++p;
     52 	}
     53 
     54 	return p;
     55 }
     56 
     57 static bool
     58 is_name_char(char ch)
     59 {
     60 	return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
     61 		(ch >= '0' && ch <= '9') || ch == '_';
     62 }
     63 
     64 static char *
     65 format_object2(const char *format, const char **last, const void *object,
     66 	       const char *(*getter)(const void *object, const char *name))
     67 {
     68 	char *ret = NULL;
     69 	const char *p;
     70 	bool found = false;
     71 
     72 	for (p = format; *p != '\0';) {
     73 		switch (p[0]) {
     74 		case '|':
     75 			++p;
     76 			if (!found) {
     77 				/* nothing found yet: try the next
     78 				   section */
     79 				free(ret);
     80 				ret = NULL;
     81 			} else
     82 				/* already found a value: skip the
     83 				   next section */
     84 				p = skip_format(p);
     85 			break;
     86 
     87 		case '&':
     88 			++p;
     89 			if (!found)
     90 				/* nothing found yet, so skip this
     91 				   section */
     92 				p = skip_format(p);
     93 			else
     94 				/* we found something yet, but it will
     95 				   only be used if the next section
     96 				   also found something, so reset the
     97 				   flag */
     98 				found = false;
     99 			break;
    100 
    101 		case '[': {
    102 			char *t = format_object2(p + 1, &p, object, getter);
    103 			if (t != NULL) {
    104 				ret = string_append(ret, t, strlen(t));
    105 				free(t);
    106 				found = true;
    107 			}
    108 		}
    109 			break;
    110 
    111 		case ']':
    112 			if (last != NULL)
    113 				*last = p + 1;
    114 			if (!found) {
    115 				free(ret);
    116 				ret = NULL;
    117 			}
    118 			return ret;
    119 
    120 		case '\\': {
    121 			/* take care of escape sequences */
    122 			char ltemp;
    123 			switch (p[1]) {
    124 			case 'a':
    125 				ltemp = '\a';
    126 				break;
    127 
    128 			case 'b':
    129 				ltemp = '\b';
    130 				break;
    131 
    132 			case 'e':
    133 				ltemp = '\033';
    134 				break;
    135 
    136 			case 't':
    137 				ltemp = '\t';
    138 				break;
    139 
    140 			case 'n':
    141 				ltemp = '\n';
    142 				break;
    143 
    144 			case 'v':
    145 				ltemp = '\v';
    146 				break;
    147 
    148 			case 'f':
    149 				ltemp = '\f';
    150 				break;
    151 
    152 			case 'r':
    153 				ltemp = '\r';
    154 				break;
    155 
    156 			case '[':
    157 			case ']':
    158 				ltemp = p[1];
    159 				break;
    160 
    161 			default:
    162 				/* unknown escape: copy the
    163 				   backslash */
    164 				ltemp = p[0];
    165 				--p;
    166 				break;
    167 			}
    168 
    169 			ret = string_append(ret, &ltemp, 1);
    170 			p += 2;
    171 		}
    172 			break;
    173 
    174 		case '%': {
    175 			/* find the extent of this format specifier
    176 			   (stop at \0, ' ', or esc) */
    177 			const char *end = p + 1;
    178 			while (is_name_char(*end))
    179 				++end;
    180 
    181 			const size_t length = end - p + 1;
    182 
    183 			if (*end != '%') {
    184 				ret = string_append(ret, p, length - 1);
    185 				p = end;
    186 				continue;
    187 			}
    188 
    189 			char name[32];
    190 			if (length > (int)sizeof(name)) {
    191 				ret = string_append(ret, p, length);
    192 				p = end + 1;
    193 				continue;
    194 			}
    195 
    196 			memcpy(name, p + 1, length - 2);
    197 			name[length - 2] = 0;
    198 
    199 			const char *value = getter(object, name);
    200 			size_t value_length;
    201 			if (value != NULL) {
    202 				if (*value != 0)
    203 					found = true;
    204 				value_length = strlen(value);
    205 			} else {
    206 				/* unknown variable: copy verbatim
    207 				   from format string */
    208 				value = p;
    209 				value_length = length;
    210 			}
    211 
    212 			ret = string_append(ret, value, value_length);
    213 
    214 			/* advance past the specifier */
    215 			p = end + 1;
    216 		}
    217 			break;
    218 
    219 		case '#':
    220 			/* let the escape character escape itself */
    221 			if (p[1] != '\0') {
    222 				ret = string_append(ret, p + 1, 1);
    223 				p += 2;
    224 				break;
    225 			}
    226 
    227 			/* fall through */
    228 
    229 		default:
    230 			/* pass-through non-escaped portions of the format string */
    231 			ret = string_append(ret, p, 1);
    232 			++p;
    233 		}
    234 	}
    235 
    236 	if (last != NULL)
    237 		*last = p;
    238 	return ret;
    239 }
    240 
    241 char *
    242 format_object(const char *format, const void *object,
    243 	      const char *(*getter)(const void *object, const char *name))
    244 {
    245 	return format_object2(format, NULL, object, getter);
    246 }