diff options
author | June McEnroe <june@causal.agency> | 2020-01-04 17:26:55 -0500 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2020-01-04 17:26:55 -0500 |
commit | f4ac2faffb17c715a79002d4b90ab638f94bb64a (patch) | |
tree | a1e9b585880596def4be02c014cb16d151641d8a | |
parent | Enable SQLite WAL (diff) | |
download | litterbox-f4ac2faffb17c715a79002d4b90ab638f94bb64a.tar.gz litterbox-f4ac2faffb17c715a79002d4b90ab638f94bb64a.zip |
Add -m regexp option to scoop
-rw-r--r-- | scoop.1 | 8 | ||||
-rw-r--r-- | scoop.c | 55 |
2 files changed, 62 insertions, 1 deletions
diff --git a/scoop.1 b/scoop.1 index 11fc1e9..0d41c12 100644 --- a/scoop.1 +++ b/scoop.1 @@ -20,6 +20,7 @@ .Op Fl f Ar format .Op Fl h Ar host .Op Fl l Ar limit +.Op Fl m Ar regexp .Op Fl n Ar nick .Op Fl t Ar type .Op Fl u Ar user @@ -135,6 +136,13 @@ Match events from users with the hostname Limit the number of events matched, ordered by most recent. . +.It Fl m Ar regexp +Match events with messages +matching the modern regular expression +.Ar regexp . +See +.Xr re_format 7 . +. .It Fl n Ar nick Match events from users with the nickname .Ar nick . diff --git a/scoop.c b/scoop.c index bd1ef2d..ad7b172 100644 --- a/scoop.c +++ b/scoop.c @@ -16,6 +16,7 @@ #include <assert.h> #include <err.h> +#include <regex.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -200,6 +201,7 @@ static const char *Inner = SQL( AND coalesce(names.user = :user, true) AND coalesce(names.host = :host, true) AND coalesce(events.target = :target, true) + AND coalesce(events.message REGEXP :regexp, true) ); static const char *Search = SQL(search MATCH :search); @@ -219,6 +221,50 @@ static const char *Group = SQL( ORDER BY network, context, time, event ); +static void regexpFree(void *_regex) { + regex_t *regex = _regex; + regfree(regex); + free(regex); +} + +static void regexp(sqlite3_context *ctx, int n, sqlite3_value *args[]) { + assert(n == 2); + if (sqlite3_value_type(args[0]) == SQLITE_NULL) { + sqlite3_result_null(ctx); + return; + } + if (sqlite3_value_type(args[1]) == SQLITE_NULL) { + sqlite3_result_int(ctx, false); + return; + } + + regex_t *regex = sqlite3_get_auxdata(ctx, 0); + if (!regex) { + regex = malloc(sizeof(*regex)); + if (!regex) { + sqlite3_result_error_nomem(ctx); + return; + } + sqlite3_set_auxdata(ctx, 0, regex, regexpFree); + + int error = regcomp( + regex, (const char *)sqlite3_value_text(args[0]), + REG_EXTENDED | REG_NOSUB + ); + if (error) { + char msg[256]; + regerror(error, regex, msg, sizeof(msg)); + sqlite3_result_error(ctx, msg, -1); + return; + } + } + + int error = regexec( + regex, (const char *)sqlite3_value_text(args[1]), 0, NULL, 0 + ); + sqlite3_result_int(ctx, !error); +} + static const char *TypeNames[] = { #define X(id, name) [id] = name, ENUM_TYPE @@ -270,7 +316,8 @@ int main(int argc, char *argv[]) { const char *where = NULL; int opt; - while (0 < (opt = getopt(argc, argv, "D:F:N:T:a:b:c:d:f:gh:l:n:pqst:u:vw:"))) { + const char *Opts = "D:F:N:T:a:b:c:d:f:gh:l:m:n:pqst:u:vw:"; + while (0 < (opt = getopt(argc, argv, Opts))) { switch (opt) { break; case 'D': binds[n++] = Bind(":date", optarg, 0); break; case 'F': binds[n++] = Bind(":format", optarg, 0); @@ -284,6 +331,7 @@ int main(int argc, char *argv[]) { break; case 'g': group = true; break; case 'h': binds[n++] = Bind(":host", optarg, 0); break; case 'l': binds[n++] = Bind(":limit", optarg, 0); + break; case 'm': binds[n++] = Bind(":regexp", optarg, 0); break; case 'n': binds[n++] = Bind(":nick", optarg, 0); break; case 'p': binds[n++] = Bind(":query", NULL, 0); break; case 'q': binds[n++] = Bind(":query", NULL, 1); @@ -335,6 +383,11 @@ int main(int argc, char *argv[]) { if (dbVersion() != DatabaseVersion) { errx(EX_CONFIG, "database out of date; migrate with litterbox -m"); } + sqlite3_create_function( + db, "regexp", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL, + regexp, NULL, NULL + ); + int len; char sql[4096]; |