==> 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}