diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2023-05-09 15:54:24 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2023-05-11 15:19:25 -0700 |
commit | 40feb616242c4e29395659ff1873c9fa35b31dcd (patch) | |
tree | 8ff59da6db7e5b45e9c9f2ff11364b2e5c4a180b | |
parent | a4d52bcd363ee185ff20c74b3c97de96e314d381 (diff) |
til_settings: recursive til_settings_as_arg()
Currently in rototiller the only (de)serialization format of
settings is the args strings, so this is a rather critical piece
to get in for recursive settings to really be usable.
This is a quick and dirty implementation utilizing
open_memstream() which despite being POSIX has spotty support (I
don't think MacOS has it for instance). So that's probably
something to rip out in the future.
Nonetheless, this moves things forward and works fine on GNU.
-rw-r--r-- | src/til_settings.c | 94 |
1 files changed, 54 insertions, 40 deletions
diff --git a/src/til_settings.c b/src/til_settings.c index 4631849..239e359 100644 --- a/src/til_settings.c +++ b/src/til_settings.c @@ -464,62 +464,76 @@ int til_setting_spec_check(const til_setting_spec_t *spec, const char *value) } -/* wrapper around sprintf for convenient buffer size computation */ -/* supply NULL buf when computing size, size and offset are ignored. - * supply non-NULL for actual writing into buf of size bytes @ offset. - * return value is number of bytes (potentially if !buf) written - */ -static int snpf(char *buf, size_t size, off_t offset, const char *format, ...) +static inline void fputc_escaped(FILE *out, int c, unsigned depth) { - size_t avail = 0; - va_list ap; - int r; - - if (buf) { - assert(size > offset); + unsigned escapes = 0; - avail = size - offset; - buf += offset; + for (unsigned i = 0; i < depth; i++) { + escapes <<= 1; + escapes += 1; } - va_start(ap, format); - r = vsnprintf(buf, avail, format, ap); - va_end(ap); + for (unsigned i = 0; i < escapes; i++) + fputc('\\', out); - return r; + fputc(c, out); } -char * til_settings_as_arg(const til_settings_t *settings) +static inline void fputs_escaped(FILE *out, const char *value, unsigned depth) { - char *buf = NULL; - size_t off, size; - - /* intentionally avoided open_memstream for portability reasons */ - for (;;) { - unsigned i; + char c; + + while ((c = *value++)) { + switch (c) { + case '\'': /* this isn't strictly necessary, but let's just make settings-as-arg easily quotable for shell purposes, excessive escaping is otherwise benign */ + case '=': + case ',': + case '\\': + fputc_escaped(out, c, depth); + break; + default: + fputc(c, out); + break; + } + } +} - for (i = off = 0; i < settings->num; i++) { - if (i > 0) - off += snpf(buf, size, off, ","); - off += snpf(buf, size, off, "%s", settings->settings[i]->key); +static void settings_as_arg(const til_settings_t *settings, unsigned depth, FILE *out) +{ + for (size_t i = 0; i < settings->num; i++) { + if (i > 0) + fputc_escaped(out, ',', depth); + if (settings->settings[i]->key) { + fputs_escaped(out, settings->settings[i]->key, depth); if (settings->settings[i]->value) - off += snpf(buf, size, off, "=%s", settings->settings[i]->value); + fputc_escaped(out, '=', depth); } - if (!buf) { - size = off + 1; - buf = calloc(size, sizeof(char)); - if (!buf) - return NULL; - - continue; + if (settings->settings[i]->value_as_nested_settings) { + settings_as_arg(settings->settings[i]->value_as_nested_settings, depth + 1, out); + } else if (settings->settings[i]->value) { + fputs_escaped(out, settings->settings[i]->value, depth); } - - break; } +} - return buf; + +char * til_settings_as_arg(const til_settings_t *settings) +{ + FILE *out; + char *outbuf; + size_t outsize; + + out = open_memstream(&outbuf, &outsize); /* TODO FIXME: open_memstream() isn't portable */ + if (!out) + return NULL; + + settings_as_arg(settings, 0, out); + + fclose(out); + + return outbuf; } |