commit 2ad37309cdb65969b8c5512ce34c2c14c3f98f07
parent 38847929a349c9fe0613c5053213f5b17e9d4d0b
Author: ssnf <ssnf@ssnf.xyz>
Date: Sun, 3 Aug 2025 02:05:44 +0000
libsdbr: fix parsing, escaping and documentation
Diffstat:
11 files changed, 147 insertions(+), 110 deletions(-)
diff --git a/man/man3/sdbr.3 b/man/man3/sdbr.3
@@ -119,8 +119,9 @@ parses a string
.I s
containing space-separated attribute=value pairs and adds them to record
.IR r .
+The input string is not modified.
Values containing spaces can be quoted with double quotes.
-Escape sequences \e0, \et, \en, \e", and \e\e are supported within quoted values.
+Escape sequences \e0, \ef, \en, \er, \et, \ev, \e", and \e\e are supported within quoted values.
.PP
.I Sdbr_arg2r
parses command-line arguments
@@ -135,7 +136,7 @@ returns the value of attribute
in record
.IR r .
If the attribute is not found, it returns an empty string.
-The attribute name can contain glob patterns.
+The attribute name can contain wildcards.
.PP
.I Sdbr_attr
returns the index of attribute
@@ -149,9 +150,12 @@ tests whether
.I value
matches
.IR pattern .
-The pattern supports glob-style wildcards (*) and comparison operators
+The pattern supports wildcards (*) and comparison operators
(>, >=, <, <=).
-These operators perform lexicographic string comparison, not numeric.
+The * wildcard matches zero or more characters.
+When a * is followed by a literal character, it finds the first occurrence
+of that character in the remaining string and continues matching from there.
+Comparison operators perform lexicographic string comparison, not numeric.
Literal asterisks can be escaped with backslash.
.PP
.I Sdbr_query
@@ -188,12 +192,28 @@ It returns a pointer to the formatted string.
prints record
.I r
to standard output in attribute=value format.
+The record must have at least one attribute.
.PP
.I Vsdbr_join
-appends record
+adds record
.I r
to the vector of records
.IR v .
+The vector must be initialized with
+.I Vecinitf(&v, sdbr_init, sdbr_close)
+to properly manage the Sdbr structures.
+If a record in
+.I v
+already has the same first attribute name and value as
+.IR r ,
+that record is updated with all attributes from
+.IR r .
+Otherwise,
+.I r
+is appended to the vector.
+The record
+.I r
+must have at least one attribute.
.SH EXAMPLES
.PP
Basic record operations:
@@ -241,7 +261,7 @@ sdbr_close(&q);
Pattern matching:
.IP
.EX
-if(sdbr_match("*user*", "admin_user"))
+if(sdbr_match("*user", "admin_user"))
print("Pattern matches\en");
if(sdbr_match(">=100", "150"))
print("Comparison matches\en");
@@ -253,7 +273,7 @@ Process tracking:
Sdbr r;
sdbr_init(&r);
-sdbr_str2r(&r, "pid=1234 cmd=rc user=glenda cpu=0.5");
+sdbr_str2r(&r, Str("pid=1234 cmd=rc user=glenda cpu=0.5"));
sdbr_add(&r, Str("tags"), Str("shell,interactive"));
sdbr_add(&r, Str("started"), Str("2025-01-15T10:30:00"));
sdbr_print(r);
@@ -267,8 +287,8 @@ Sdbr def, usr;
sdbr_init(&def);
sdbr_init(&usr);
-sdbr_str2r(&def, "editor=sam theme=light font=lucsans");
-sdbr_str2r(&usr, "theme=dark font=go");
+sdbr_str2r(&def, Str("editor=sam theme=light font=lucsans"));
+sdbr_str2r(&usr, Str("theme=dark font=go"));
sdbr_edit(&def, usr);
print("Font: %s\en", sdbr_val(def, "font").s);
sdbr_close(&def);
@@ -282,10 +302,10 @@ Sdbr ev, q;
sdbr_init(&ev);
sdbr_init(&q);
-sdbr_str2r(&ev, "timestamp=2025-01-15T14:30:00 level=error");
-sdbr_str2r(&q,
+sdbr_str2r(&ev, Str("timestamp=2025-01-15T14:30:00 level=error"));
+sdbr_str2r(&q, Str(
"timestamp=>=2025-01-15T14:00:00 "
- "timestamp=<=2025-01-15T15:00:00");
+ "timestamp=<=2025-01-15T15:00:00"));
if(sdbr_query(ev, q))
print("Event in time range\en");
sdbr_close(&ev);
@@ -299,10 +319,10 @@ Sdbr loc, q;
sdbr_init(&loc);
sdbr_init(&q);
-sdbr_str2r(&loc, "lat=40.7128 lon=-74.0060 city=\"New York\"");
-sdbr_str2r(&q,
+sdbr_str2r(&loc, Str("lat=40.7128 lon=-74.0060 city=\"New York\""));
+sdbr_str2r(&q, Str(
"lat=>=40.0 lat=<=41.0 "
- "lon=>=-73.0 lon=<=-75.0");
+ "lon=>=-73.0 lon=<=-75.0"));
if(sdbr_query(loc, q))
print("Location in bounding box\en");
sdbr_close(&loc);
diff --git a/src/libsdbr/mkfile b/src/libsdbr/mkfile
@@ -3,7 +3,6 @@
LIB=libsdbr.a
OFILES=\
- sbdr_val.$O\
sdbr_add.$O\
sdbr_arg2r.$O\
sdbr_attr.$O\
@@ -15,6 +14,7 @@ OFILES=\
sdbr_print.$O\
sdbr_query.$O\
sdbr_str2r.$O\
+ sdbr_val.$O\
sdbr_zero.$O\
vsdbr_join.$O\
diff --git a/src/libsdbr/sbdr_val.c b/src/libsdbr/sbdr_val.c
@@ -1,12 +0,0 @@
-#include "std.h"
-
-String
-sdbr_val(Sdbr r, char *attr)
-{
- ulong i;
-
- for (i = 0; i < Vecsiz(r.attr); ++i)
- if (sdbr_match(attr, r.attr[i].s))
- return r.val[i];
- return Strn(NULL, 0);
-}
diff --git a/src/libsdbr/sdbr_arg2r.c b/src/libsdbr/sdbr_arg2r.c
@@ -3,6 +3,8 @@
void
sdbr_arg2r(Sdbr* r, char* argv[])
{
+ if (!argv)
+ sysfatal("sdbr_arg2r: nil argv");
for (;argv[0] != NULL; argv += 2) {
if (argv[1] == NULL) {
sdbr_add(r, Str(argv[0]), Str(""));
diff --git a/src/libsdbr/sdbr_escape.c b/src/libsdbr/sdbr_escape.c
@@ -3,37 +3,42 @@
char*
sdbr_escape(String *s, String val)
{
- String q;
- ulong i;
- uchar c, w;
+ ulong i;
+ uchar c, e;
Strzero(s);
- w = 0;
+ e = 0;
+ if (val.s[0] == '"')
+ e = 1;
+ else for (i = 0; i < val.n; ++i) {
+ c = val.s[i];
+ if (c == '\0' || isspace(c)) {
+ e = 1;
+ break;
+ }
+ }
for (i = 0; i < val.n; ++i) {
- if (!w && isspace(val.s[i]) && val.s[i] != '\n')
- w = 1;
- switch (val.s[i]) {
- case '\0': c = '0'; break;
- case '\t': c = 't'; break;
- case '\n': c = 'n'; break;
- case '"': c = '"'; break;
- case '\\':
- if (val.s[i + 1] && val.s[i + 1] != 'n') {
- c = '\\';
- break;
- }
- default:
- Straddc(s, val.s[i]);
- continue;
+ c = val.s[i];
+ switch (c) {
+ case '\0': c = '0'; break;
+ case '\f': c = 'f'; break;
+ case '\n': c = 'n'; break;
+ case '\r': c = 'r'; break;
+ case '\t': c = 't'; break;
+ case '\v': c = 'v'; break;
+ case '"':
+ case '\\':
+ if (e)
+ break;
+ default:
+ Straddc(s, c);
+ continue;
}
Straddc(s, '\\');
Straddc(s, c);
}
- if (w) {
- Strinit(&q);
- Straddc(&q, '"');
- Strinsert(s, q, 0);
- Strclose(&q);
+ if (e) {
+ Strinsert(s, Strn("\"", 1), 0);
Straddc(s, '"');
}
return s->s;
diff --git a/src/libsdbr/sdbr_match.c b/src/libsdbr/sdbr_match.c
@@ -5,6 +5,10 @@ sdbr_match(char *k, char *v)
{
size_t l, n;
+ if (!k)
+ sysfatal("sdbr_match: nil key");
+ if (!v)
+ sysfatal("sdbr_match: nil value");
if (*k == '>') {
if (*++k == '=')
return strcmp(v, k + 1) >= 0 ? 1 : 0;
@@ -17,7 +21,7 @@ sdbr_match(char *k, char *v)
for (;*k;) {
if (*k == '*') {
if (!*++k)
- return *v ? 1 : 0;
+ return 1;
if (k[strcspn(k, "*\\")]) {
v = strchr(v, *k);
return v == NULL ? 0 : sdbr_match(k, v);
diff --git a/src/libsdbr/sdbr_print.c b/src/libsdbr/sdbr_print.c
@@ -6,9 +6,12 @@ sdbr_print(Sdbr r)
String s;
ulong i;
+ if (!Vecsiz(r.attr))
+ sysfatal("sdbr_print: empty record");
Strinit(&s);
- for (i = 0; i < Vecsiz(r.attr); ++i)
- print("%s=%s", r.attr[i].s, sdbr_escape(&s, r.val[i]));
+ print("%s=%s", r.attr[0].s, sdbr_escape(&s, r.val[0]));
+ for (i = 1; i < Vecsiz(r.attr); ++i)
+ print(" %s=%s", r.attr[i].s, sdbr_escape(&s, r.val[i]));
Strclose(&s);
write(1, "\n", 1);
}
diff --git a/src/libsdbr/sdbr_query.c b/src/libsdbr/sdbr_query.c
@@ -15,7 +15,7 @@ sdbr_query(Sdbr r, Sdbr q)
continue;
return 0;
}
- if (!sdbr_match(q.val[i].s, r.val[m].s))
+ if (sdbr_match(q.val[i].s, r.val[m].s) == (attr[0] == '!'))
return 0;
}
return 1;
diff --git a/src/libsdbr/sdbr_str2r.c b/src/libsdbr/sdbr_str2r.c
@@ -1,62 +1,63 @@
#include "std.h"
-void
-sdbr_str2r(Sdbr *sr, String s)
+static char*
+parsetuple(char *p, Sdbr *r)
{
- char *p, *q, *k, *v;
+ char *k, *v, *q;
ulong vn;
- v = nil;
- for (p = s.s; *p;) {
- vn = 0;
- for (;isspace(*p); ++p);
- for (k = p; *p; ++p) {
- if (isspace(*p)) {
- *p++ = '\0';
- break;
- } else if (*p != '=')
- continue;
- *p++ = '\0';
- for (v = p; *p; ++p) {
- if (isspace(*p)) {
- *p++ = '\0';
- break;
- } else if (*p != '"') {
- ++vn;
- continue;
- }
- for (v = q = ++p
- ; *q != '"' && *q != '\n'
- ; ++q
- ) {
- if (*q == '\\')
- switch (*(q + 1)) {
- case '0':
- *++q = '\0';
- break;
- case 't':
- *++q = '\t';
- break;
- case 'n':
- *++q = '\n';
- break;
- case '"':
- *++q = '"';
- break;
- case '\\':
- *++q = '\\';
- break;
- }
- *p++ = *q;
- ++vn;
- }
- *p = '\0';
- p = q + 1;
- break;
+ for (;isspace(*p); ++p);
+ k = p;
+ for (;*p && !isspace(*p) && *p != '='; ++p);
+ if (!*p || *p != '=') {
+ if (k != p)
+ sdbr_add(r, Strn(k, p - k), Str(nil));
+ return p;
+ }
+ *p++ = '\0';
+ if (*p != '"') {
+ v = p;
+ for (;*p && !isspace(*p); ++p);
+ if (v != p)
+ sdbr_add(r, Str(k), Strn(v, p - v));
+ else
+ sdbr_add(r, Str(k), Str(nil));
+ return p;
+ }
+ v = ++p;
+ for (q = p; *q && *q != '"' && *q != '\n'; ++q) {
+ if (*q == '\\')
+ switch (*(q + 1)) {
+ case '\\': ++q; break;
+ case '0': *++q = '\0'; break;
+ case 'f': *++q = '\f'; break;
+ case 'n': *++q = '\n'; break;
+ case 'r': *++q = '\r'; break;
+ case 't': *++q = '\t'; break;
+ case 'v': *++q = '\v'; break;
+ case '"': *++q = '"'; break;
}
- break;
- }
- sdbr_add(sr, Str(k), Strn(v, vn));
- for (;isspace(*p); ++p);
+ *p++ = *q;
+ }
+ sdbr_add(r, Str(k), Strn(v, p - v));
+ return *q ? q + 1 : q;
+}
+
+void
+sdbr_str2r(Sdbr *sr, String s)
+{
+ String t;
+ char *p, *q;
+
+ Strinit(&t);
+ Strdup(&t, s);
+ for (p = t.s; *p; ++p) {
+ if (*p != '#')
+ continue;
+ for (q = p; *q && *q != '\n'; ++q);
+ Strdelete(&t, p - t.s, q - p);
+ --p;
}
+ for (p = t.s; *p; p = parsetuple(p, sr));
+ Strclose(&t);
}
diff --git a/src/libsdbr/sdbr_val.c b/src/libsdbr/sdbr_val.c
@@ -0,0 +1,12 @@
+#include "std.h"
+
+String
+sdbr_val(Sdbr r, char *attr)
+{
+ ulong i;
+
+ for (i = 0; i < Vecsiz(r.attr); ++i)
+ if (sdbr_match(attr, r.attr[i].s))
+ return r.val[i];
+ return Strn(nil, 0);
+}
diff --git a/src/libsdbr/vsdbr_join.c b/src/libsdbr/vsdbr_join.c
@@ -5,6 +5,8 @@ vsdbr_join(Sdbr *v[], Sdbr r)
{
int i, n;
+ if (!Vecsiz(r.attr))
+ sysfatal("vsdbr_join: empty record");
for (i = 0; i < Vecsiz(*v); ++i) {
n = sdbr_attr((*v)[i], r.attr[0].s);
if (n < 0 || strcmp((*v)[i].val[n].s, r.val[0].s))