summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2023-05-09 15:54:24 -0700
committerVito Caputo <vcaputo@pengaru.com>2023-05-11 15:19:25 -0700
commit40feb616242c4e29395659ff1873c9fa35b31dcd (patch)
tree8ff59da6db7e5b45e9c9f2ff11364b2e5c4a180b
parenta4d52bcd363ee185ff20c74b3c97de96e314d381 (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.c94
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;
}
© All Rights Reserved