skip to content
==> src/views.c <==
1/* View records: building, the text and JSON forms, parsing. */
2#include "mesh/views.h"
3
4#include <stdarg.h>
5#include <stdlib.h>
6#include <string.h>
7
8const char *const MESH_VIEWS_STRING_KEYS[37] = {
9    "job", "hash", "out", "host", "name", "program", "programs", "at", "pin", "up", "suspect", "cordon", "busy_members",
10    "members", "fed_by", "consumers", "consumer", "producer", "type", "exec", "gpu", "arch", "role", "state", "verdict",
11    "mode", "majority", "moving", "reserved", "migrate", "backends", "replicas", "items", "group", "reconcile", "on_loss",
12    "queued",
13};
14
15mesh_rec *mesh_recs_add(mesh_recs *rs, const char *kind) {
16    if (rs->len == rs->cap) {
17        rs->cap = rs->cap ? 2 * rs->cap : 8;
18        rs->items = mesh_xrealloc(rs->items, rs->cap * sizeof *rs->items);
19    }
20    mesh_rec *r = &rs->items[rs->len++];
21    memset(r, 0, sizeof *r);
22    r->kind = mesh_xstrdup(kind);
23    r->text = mesh_xstrdup("");
24    return r;
25}
26
27static void rec_free(mesh_rec *r) {
28    free(r->kind);
29    for (size_t i = 0; i < r->nkv; i++) { free(r->kv[i].k); free(r->kv[i].v); }
30    free(r->kv);
31    free(r->text);
32}
33
34void mesh_recs_free(mesh_recs *rs) {
35    for (size_t i = 0; i < rs->len; i++) rec_free(&rs->items[i]);
36    free(rs->items);
37    rs->items = NULL;
38    rs->len = rs->cap = 0;
39}
40
41static void push_raw(mesh_rec *r, const char *k, char *v) {
42    if (r->nkv == r->cap) {
43        r->cap = r->cap ? 2 * r->cap : 8;
44        r->kv = mesh_xrealloc(r->kv, r->cap * sizeof *r->kv);
45    }
46    r->kv[r->nkv].k = mesh_xstrdup(k);
47    r->kv[r->nkv].v = v;
48    r->nkv++;
49}
50
51void mesh_recs_extend(mesh_recs *dst, const mesh_recs *src) {
52    for (size_t i = 0; i < src->len; i++) {
53        const mesh_rec *s = &src->items[i];
54        mesh_rec *d = mesh_recs_add(dst, s->kind);
55        for (size_t j = 0; j < s->nkv; j++) push_raw(d, s->kv[j].k, mesh_xstrdup(s->kv[j].v));
56        free(d->text);
57        d->text = mesh_xstrdup(s->text);
58    }
59}
60
61/* The UTF-8 sequence at s: its code point and length (an invalid byte is itself, length 1). */
62static unsigned utf8(const unsigned char *s, size_t *n) {
63    if (s[0] < 0x80) { *n = 1; return s[0]; }
64    if ((s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80) { *n = 2; return (s[0] & 0x1fu) << 6 | (s[1] & 0x3fu); }
65    if ((s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80) {
66        *n = 3;
67        return (s[0] & 0x0fu) << 12 | (s[1] & 0x3fu) << 6 | (s[2] & 0x3fu);
68    }
69    *n = 1;
70    return s[0];
71}
72
73/* char::is_whitespace: the Unicode White_Space property. */
74static bool whitespace(unsigned c) {
75    return (c >= 9 && c <= 13) || c == ' ' || c == 0x85 || c == 0xa0 || c == 0x1680 || (c >= 0x2000 && c <= 0x200a) ||
76           c == 0x2028 || c == 0x2029 || c == 0x202f || c == 0x205f || c == 0x3000;
77}
78
79void mesh_rec_kv(mesh_rec *r, const char *k, const char *v) {
80    mesh_buf b = MESH_BUF_INIT;
81    if (!*v) mesh_buf_putc(&b, '-');
82    for (const unsigned char *p = (const unsigned char *)v; *p;) {
83        size_t n;
84        unsigned c = utf8(p, &n);
85        if (whitespace(c) || c == '=') mesh_buf_putc(&b, '_');
86        else mesh_buf_put(&b, p, n);
87        p += n;
88    }
89    push_raw(r, k, mesh_buf_take(&b));
90}
91
92void mesh_rec_kvf(mesh_rec *r, const char *k, const char *fmt, ...) {
93    mesh_buf b = MESH_BUF_INIT;
94    va_list ap;
95    va_start(ap, fmt);
96    mesh_buf_vprintf(&b, fmt, ap);
97    va_end(ap);
98    mesh_rec_kv(r, k, mesh_buf_str(&b));
99    mesh_buf_free(&b);
100}
101
102void mesh_rec_kvu(mesh_rec *r, const char *k, uint64_t v) { mesh_rec_kvf(r, k, "%llu", (unsigned long long)v); }
103void mesh_rec_kvi(mesh_rec *r, const char *k, int64_t v) { mesh_rec_kvf(r, k, "%lld", (long long)v); }
104void mesh_rec_none(mesh_rec *r, const char *k) { mesh_rec_kv(r, k, "-"); }
105
106void mesh_rec_f(mesh_rec *r, const char *k, bool some, double v, int p) {
107    if (!some || v != v || v - v != 0) mesh_rec_kv(r, k, "-");
108    else mesh_rec_kvf(r, k, "%.*f", p, v);
109}
110
111void mesh_rec_text(mesh_rec *r, const char *t) {
112    free(r->text);
113    r->text = mesh_xstrdup(t);
114    for (char *c = r->text; *c; c++) if (*c == '\n' || *c == '\r') *c = ' ';
115}
116
117const char *mesh_rec_get(const mesh_rec *r, const char *k) {
118    for (size_t i = 0; i < r->nkv; i++)
119        if (strcmp(r->kv[i].k, k) == 0) return strcmp(r->kv[i].v, "-") == 0 ? NULL : r->kv[i].v;
120    return NULL;
121}
122
123void mesh_views_text(mesh_buf *out, const mesh_recs *rs) {
124    for (size_t i = 0; i < rs->len; i++) {
125        const mesh_rec *r = &rs->items[i];
126        mesh_buf_puts(out, r->kind);
127        for (size_t j = 0; j < r->nkv; j++) mesh_buf_printf(out, " %s=%s", r->kv[j].k, r->kv[j].v);
128        if (*r->text) mesh_buf_printf(out, " -- %s", r->text);
129        mesh_buf_putc(out, '\n');
130    }
131}
132
133static void json_str(mesh_buf *out, const char *s) {
134    mesh_buf_putc(out, '"');
135    for (const unsigned char *c = (const unsigned char *)s; *c; c++) {
136        if (*c == '"') mesh_buf_puts(out, "\\\"");
137        else if (*c == '\\') mesh_buf_puts(out, "\\\\");
138        else if (*c < 0x20) mesh_buf_printf(out, "\\u%04x", *c);
139        else mesh_buf_putc(out, (char)*c);
140    }
141    mesh_buf_putc(out, '"');
142}
143
144static bool digit(char c) { return c >= '0' && c <= '9'; }
145
146bool mesh_views_json_number(const char *v) {
147    size_t i = v[0] == '-', n = strlen(v);
148    size_t s = i;
149    while (i < n && digit(v[i])) i++;
150    if (i == s) return false;
151    if (i < n && v[i] == '.') {
152        i++;
153        s = i;
154        while (i < n && digit(v[i])) i++;
155        if (i == s) return false;
156    }
157    if (i < n && (v[i] == 'e' || v[i] == 'E')) {
158        i++;
159        if (i < n && (v[i] == '+' || v[i] == '-')) i++;
160        s = i;
161        while (i < n && digit(v[i])) i++;
162        if (i == s) return false;
163    }
164    /* A leading zero followed by digits ("007") is not JSON: kept a string. */
165    const char *in = v + (v[0] == '-');
166    if (strlen(in) > 1 && in[0] == '0' && digit(in[1])) return false;
167    return i == n;
168}
169
170static bool string_key(const char *k) {
171    for (size_t i = 0; i < 37; i++) if (strcmp(MESH_VIEWS_STRING_KEYS[i], k) == 0) return true;
172    return false;
173}
174
175void mesh_views_json(mesh_buf *out, const mesh_recs *rs) {
176    mesh_buf_putc(out, '{');
177    bool first_kind = true;
178    for (size_t i = 0; i < rs->len; i++) {
179        const char *kind = rs->items[i].kind;
180        bool seen = false;
181        for (size_t p = 0; p < i && !seen; p++) seen = strcmp(rs->items[p].kind, kind) == 0;
182        if (seen) continue;
183        if (!first_kind) mesh_buf_putc(out, ',');
184        first_kind = false;
185        json_str(out, kind);
186        mesh_buf_puts(out, ":[");
187        bool first = true;
188        for (size_t j = i; j < rs->len; j++) {
189            const mesh_rec *r = &rs->items[j];
190            if (strcmp(r->kind, kind) != 0) continue;
191            if (!first) mesh_buf_putc(out, ',');
192            first = false;
193            mesh_buf_putc(out, '{');
194            for (size_t f = 0; f < r->nkv; f++) {
195                if (f > 0) mesh_buf_putc(out, ',');
196                json_str(out, r->kv[f].k);
197                mesh_buf_putc(out, ':');
198                const char *v = r->kv[f].v;
199                if (strcmp(v, "-") == 0) mesh_buf_puts(out, "null");
200                else if (!string_key(r->kv[f].k) && mesh_views_json_number(v)) mesh_buf_puts(out, v);
201                else json_str(out, v);
202            }
203            if (*r->text) {
204                if (r->nkv) mesh_buf_putc(out, ',');
205                mesh_buf_puts(out, "\"text\":");
206                json_str(out, r->text);
207            }
208            mesh_buf_putc(out, '}');
209        }
210        mesh_buf_putc(out, ']');
211    }
212    mesh_buf_puts(out, "}\n");
213}
214
215static bool blank(const char *s, size_t n) {
216    for (size_t i = 0; i < n; i++) if (!(s[i] == ' ' || (s[i] >= 9 && s[i] <= 13))) return false;
217    return true;
218}
219
220void mesh_views_parse(const char *text, mesh_recs *out) {
221    const char *l = text;
222    while (*l) {
223        const char *e = strchr(l, '\n');
224        size_t n = e ? (size_t)(e - l) : strlen(l);
225        size_t ln = n;
226        if (ln && l[ln - 1] == '\r') ln--; /* str::lines drops a \r before the \n */
227        if (!blank(l, ln)) {
228            char *line = mesh_xstrndup(l, ln);
229            char *sep = strstr(line, " -- ");
230            const char *txt = "";
231            if (sep) { *sep = 0; txt = sep + 4; }
232            mesh_rec *r = NULL;
233            char *save = NULL;
234            for (char *w = strtok_r(line, " \t\n\v\f\r", &save); w; w = strtok_r(NULL, " \t\n\v\f\r", &save)) {
235                if (!r) { r = mesh_recs_add(out, w); continue; }
236                char *eq = strchr(w, '=');
237                if (eq) { *eq = 0; push_raw(r, w, mesh_xstrdup(eq + 1)); }
238                else push_raw(r, w, mesh_xstrdup(""));
239            }
240            if (!r) r = mesh_recs_add(out, "");
241            free(r->text);
242            r->text = mesh_xstrdup(txt);
243            free(line);
244        }
245        if (!e) break;
246        l = e + 1;
247    }
248}
249
250int mesh_views_format_of(const char *query, bool *json, char **err) {
251    *json = false;
252    const char *p = query;
253    while (*p) {
254        const char *amp = strchr(p, '&');
255        size_t n = amp ? (size_t)(amp - p) : strlen(p);
256        if (n > 7 && strncmp(p, "format=", 7) == 0) {
257            const char *v = p + 7;
258            size_t vn = n - 7;
259            if (vn == 4 && strncmp(v, "json", 4) == 0) { *json = true; return 0; }
260            if (vn == 4 && strncmp(v, "text", 4) == 0) { *json = false; return 0; }
261            *err = mesh_buf_fmt("format=%.*s: json or text", (int)vn, v);
262            return -1;
263        }
264        if (n == 7 && strncmp(p, "format=", 7) == 0) { *err = mesh_xstrdup("format=: json or text"); return -1; }
265        if (!amp) break;
266        p = amp + 1;
267    }
268    return 0;
269}
270
271void mesh_views_render(mesh_buf *out, const mesh_recs *rs, bool json) {
272    if (json) mesh_views_json(out, rs);
273    else mesh_views_text(out, rs);
274}