* Re: [PR PATCH] [Updated] mpick craziness
[not found] <gh-mailinglist-notifications-fa6558a0-26e0-48f6-803f-f5a8af34f6a8-mblaze-150@inbox.vuxu.org>
@ 2020-05-15 16:37 ` Duncaen
0 siblings, 0 replies; only message in thread
From: Duncaen @ 2020-05-15 16:37 UTC (permalink / raw)
To: ml
[-- Attachment #1: Type: text/plain, Size: 289 bytes --]
There is an updated pull request by Duncaen against master on the mblaze repository
https://github.com/Duncaen/mblaze mpick-craziness
https://github.com/leahneukirchen/mblaze/pull/150
mpick craziness
A patch file from https://github.com/leahneukirchen/mblaze/pull/150.patch is attached
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-mpick-craziness-150.patch --]
[-- Type: text/x-diff, Size: 54954 bytes --]
From d62ee33aeee47f7388f2c8636c7414ef414306fa Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Sun, 27 Jan 2019 22:18:22 +0100
Subject: [PATCH 01/20] mdeliver.1: add mrefile to SEE ALSO
---
man/mdeliver.1 | 1 +
1 file changed, 1 insertion(+)
diff --git a/man/mdeliver.1 b/man/mdeliver.1
index 07bc476..36a890f 100644
--- a/man/mdeliver.1
+++ b/man/mdeliver.1
@@ -69,6 +69,7 @@ Override the flags of the new message file to be
.Ex -std
.Sh SEE ALSO
.Xr mexport 1 ,
+.Xr mrefile 1 ,
.Xr maildir 5 ,
.Xr mbox 5
.Pp
From 694b7965c04737327052265d8e6eff16b9dc8849 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 10:53:05 +0100
Subject: [PATCH 02/20] mpick: add pipes and file redirection
---
man/mpick.1 | 3 ++
mpick.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 112 insertions(+), 5 deletions(-)
diff --git a/man/mpick.1 b/man/mpick.1
index 89bf619..2ad942e 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -96,6 +96,9 @@ tests are given by the following EBNF:
| <expr> && <expr> -- conjunction
| ! <expr> -- negation
| ( <expr> )
+ | <expr> "|" <str> -- pipe current mail to command
+ | <expr> ">>" <str> -- append current mail to file
+ | <expr> ">" <str> -- write current mail to file
| <flagprop>
| <timeprop> <numop> <dur>
| <numprop> <numop> <num>
diff --git a/mpick.c b/mpick.c
index 1704044..0427519 100644
--- a/mpick.c
+++ b/mpick.c
@@ -61,6 +61,8 @@ enum op {
EXPR_REGEXI,
EXPR_PRUNE,
EXPR_PRINT,
+ EXPR_REDIR_FILE,
+ EXPR_REDIR_PIPE,
EXPR_TYPE,
EXPR_ALLSET,
EXPR_ANYSET,
@@ -143,6 +145,14 @@ struct thread {
struct mlist *cur;
};
+struct file {
+ enum op op;
+ const char *name;
+ const char *mode;
+ FILE *fp;
+ struct file *next;
+};
+
static struct thread *thr;
static char *argv0;
@@ -160,6 +170,8 @@ static char *pos;
static time_t now;
static int prune;
+static struct file *files, *fileq = NULL;
+
static void
ws()
{
@@ -179,6 +191,12 @@ token(char *token)
}
}
+static int
+peek(char *token)
+{
+ return strncmp(pos, token, strlen(token)) == 0;
+}
+
noreturn static void
parse_error(char *msg, ...)
{
@@ -642,14 +660,43 @@ parse_timecmp()
return 0;
}
+static struct expr *
+parse_redir(struct expr *e)
+{
+ char *s;
+ const char *m;
+
+ if (peek("||"))
+ return e;
+
+ if (token("|")) {
+ if (!parse_string(&s))
+ parse_error("expected command");
+ struct expr *r = mkexpr(EXPR_REDIR_PIPE);
+ r->a.string = s;
+ r->b.string = "w";
+ return chain(e, EXPR_AND, r);
+ }
+ else if (token(">>")) m = "a+";
+ else if (token(">")) m = "w+";
+ else return e;
+
+ if (!parse_string(&s))
+ parse_error("expected file name");
+ struct expr *r = mkexpr(EXPR_REDIR_FILE);
+ r->a.string = s;
+ r->b.string = m;
+ return chain(e, EXPR_AND, r);
+}
+
static struct expr *
parse_and()
{
- struct expr *e1 = parse_timecmp();
+ struct expr *e1 = parse_redir(parse_timecmp());
struct expr *r = e1;
while (token("&&")) {
- struct expr *e2 = parse_timecmp();
+ struct expr *e2 = parse_redir(parse_timecmp());
r = chain(r, EXPR_AND, e2);
}
@@ -843,9 +890,48 @@ msg_addr(struct mailinfo *m, char *h, int t)
}
}
+FILE *
+redir(struct expr *e)
+{
+ struct file *file;
+ FILE *fp;
+
+ for (file = files; file; file = file->next) {
+ if (e->op == file->op &&
+ strcmp(e->a.string, file->name) == 0 &&
+ strcmp(e->b.string, file->mode) == 0)
+ return file->fp;
+ }
+
+ fflush(stdout);
+ fp = NULL;
+ switch (e->op) {
+ case EXPR_REDIR_FILE: fp = fopen(e->a.string, e->b.string); break;
+ case EXPR_REDIR_PIPE: fp = popen(e->a.string, e->b.string); break;
+ }
+ if (!fp) {
+ fprintf(stderr, "%s: %s: %s\n", argv0, e->a.string, strerror(errno));
+ exit(3);
+ }
+ file = calloc(1, sizeof (struct file));
+ if (!file) {
+ perror("calloc");
+ exit(2);
+ }
+ file->op = e->op;
+ file->name = e->a.string;
+ file->mode = e->b.string;
+ file->fp = fp;
+ if (!files) files = file;
+ if (fileq) fileq->next = file;
+ fileq = file;
+ return fp;
+}
+
int
eval(struct expr *e, struct mailinfo *m)
{
+ FILE *fp;
switch (e->op) {
case EXPR_OR:
return eval(e->a.expr, m) || eval(e->b.expr, m);
@@ -859,6 +945,13 @@ eval(struct expr *e, struct mailinfo *m)
return 1;
case EXPR_PRINT:
return 1;
+ case EXPR_REDIR_FILE:
+ case EXPR_REDIR_PIPE:
+ fp = redir(e);
+ fputs(m->fpath, fp);
+ putc('\n', fp);
+ fflush(fp);
+ return 1;
case EXPR_LT:
case EXPR_LE:
case EXPR_EQ:
@@ -1040,10 +1133,10 @@ do_thr()
if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) {
int i;
for (i = 0; i < ml->m->depth; i++)
- putchar(' ');
+ putc(' ', stdout);
fputs(ml->m->fpath, stdout);
- putchar('\n');
+ putc('\n', stdout);
kept++;
}
@@ -1126,7 +1219,8 @@ oneline(char *file)
goto out;
fputs(file, stdout);
- putchar('\n');
+ putc('\n', stdout);
+ fflush(stdout);
kept++;
out:
@@ -1174,5 +1268,15 @@ main(int argc, char *argv[])
if (vflag)
fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept);
+
+ for (; files; files = fileq) {
+ fileq = files->next;
+ if (files->op == EXPR_REDIR_PIPE)
+ pclose(files->fp);
+ else
+ fclose(files->fp);
+ free(files);
+ }
+
return 0;
}
From c1fee74966c8ec70871364f205fd39604e696bc8 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 10:58:27 +0100
Subject: [PATCH 03/20] mpick: add skip expression
---
man/mpick.1 | 1 +
mpick.c | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/man/mpick.1 b/man/mpick.1
index 2ad942e..64a63d1 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -105,6 +105,7 @@ tests are given by the following EBNF:
| <strprop> <strop> <str>
| prune -- do not match further messages in thread
| print -- always true value
+ | skip -- always false value
<flagprop> ::= child | draft | flagged | info | new | parent | passed
| replied | seen | selected | trashed
diff --git a/mpick.c b/mpick.c
index 0427519..c4a4207 100644
--- a/mpick.c
+++ b/mpick.c
@@ -274,6 +274,11 @@ parse_inner()
} else if (token("print")) {
struct expr *e = mkexpr(EXPR_PRINT);
return e;
+ } else if (token("skip")) {
+ struct expr *e = mkexpr(EXPR_PRINT);
+ struct expr *not = mkexpr(EXPR_NOT);
+ not->a.expr = e;
+ return not;
} else if (token("!")) {
struct expr *e = parse_cmp();
struct expr *not = mkexpr(EXPR_NOT);
From 8e29dd22917b4dbdeff2da9f48e8f705651d278e Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 11:12:18 +0100
Subject: [PATCH 04/20] mpick: add ternary (conditional) operator
---
man/mpick.1 | 3 ++-
mpick.c | 38 +++++++++++++++++++++++++++++++++-----
2 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/man/mpick.1 b/man/mpick.1
index 64a63d1..744fa24 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -92,7 +92,8 @@ Unread messages.
.Nm
tests are given by the following EBNF:
.Bd -literal
-<expr> ::= <expr> || <expr> -- disjunction
+<expr> ::= <expr> ? <expr> : <expr> -- ternary operator
+ | <expr> || <expr> -- disjunction
| <expr> && <expr> -- conjunction
| ! <expr> -- negation
| ( <expr> )
diff --git a/mpick.c b/mpick.c
index c4a4207..f9da79d 100644
--- a/mpick.c
+++ b/mpick.c
@@ -46,6 +46,7 @@
enum op {
EXPR_OR = 1,
EXPR_AND,
+ EXPR_COND,
EXPR_NOT,
EXPR_LT,
EXPR_LE,
@@ -115,7 +116,7 @@ struct expr {
int64_t num;
regex_t *regex;
enum var var;
- } a, b;
+ } a, b, c;
int extra;
};
@@ -263,7 +264,7 @@ parse_op()
}
static struct expr *parse_cmp();
-static struct expr *parse_or();
+static struct expr *parse_cond();
static struct expr *
parse_inner()
@@ -285,7 +286,7 @@ parse_inner()
not->a.expr = e;
return not;
} else if (token("(")) {
- struct expr *e = parse_or();
+ struct expr *e = parse_cond();
if (token(")"))
return e;
parse_error("missing ) at '%.15s'", pos);
@@ -723,10 +724,33 @@ parse_or()
}
static struct expr *
-parse_expr(char *s)
+parse_cond()
+{
+ struct expr *e1 = parse_or();
+
+ if (token("?")) {
+ struct expr *e2 = parse_or();
+ if (token(":")) {
+ struct expr *e3 = parse_cond();
+ struct expr *r = mkexpr(EXPR_COND);
+ r->a.expr = e1;
+ r->b.expr = e2;
+ r->c.expr = e3;
+
+ return r;
+ } else {
+ parse_error("expected : at '%.15s'", pos);
+ }
+ }
+
+ return e1;
+}
+
+static struct expr *
+parse_expr()
{
pos = s;
- struct expr *e = parse_or();
+ struct expr *e = parse_cond();
if (*pos)
parse_error("trailing garbage at '%.15s'", pos);
return e;
@@ -942,6 +966,10 @@ eval(struct expr *e, struct mailinfo *m)
return eval(e->a.expr, m) || eval(e->b.expr, m);
case EXPR_AND:
return eval(e->a.expr, m) && eval(e->b.expr, m);
+ case EXPR_COND:
+ return eval(e->a.expr, m)
+ ? eval(e->b.expr, m)
+ : eval(e->c.expr, m);
case EXPR_NOT:
return !eval(e->a.expr, m);
return 1;
From 86f24ed094a81f9763b0ea048d365174a79a0d90 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 14:38:05 +0100
Subject: [PATCH 05/20] mpick: add freeexpr
---
mpick.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/mpick.c b/mpick.c
index f9da79d..e1f9478 100644
--- a/mpick.c
+++ b/mpick.c
@@ -220,6 +220,50 @@ mkexpr(enum op op)
return e;
}
+static void
+freeexpr(struct expr *e)
+{
+ if (!e) return;
+ switch (e->op) {
+ case EXPR_OR:
+ case EXPR_AND:
+ freeexpr(e->a.expr);
+ freeexpr(e->b.expr);
+ break;
+ case EXPR_COND:
+ freeexpr(e->a.expr);
+ freeexpr(e->b.expr);
+ freeexpr(e->c.expr);
+ break;
+ case EXPR_NOT:
+ freeexpr(e->a.expr);
+ break;
+ case EXPR_REDIR_FILE:
+ case EXPR_REDIR_PIPE:
+ free(e->a.string);
+ free(e->b.string);
+ break;
+ case EXPR_STREQ:
+ case EXPR_STREQI:
+ case EXPR_GLOB:
+ case EXPR_GLOBI:
+ case EXPR_REGEX:
+ case EXPR_REGEXI:
+ switch (e->a.prop) {
+ case PROP_PATH:
+ case PROP_FROM:
+ case PROP_TO:
+ break;
+ default: free(e->a.string);
+ }
+ if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI)
+ regfree(e->b.regex);
+ else
+ free(e->b.string);
+ }
+ free(e);
+}
+
static struct expr *
chain(struct expr *e1, enum op op, struct expr *e2)
{
@@ -1299,6 +1343,8 @@ main(int argc, char *argv[])
if (Tflag && thr)
do_thr();
+ freeexpr(expr);
+
if (vflag)
fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept);
From ce5bab3f5fb0c17b11de579ecf7c90541f265b0e Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 14:45:40 +0100
Subject: [PATCH 06/20] mpick: add xcalloc and xstrdup
---
mpick.c | 66 +++++++++++++++++++++++++++++++++------------------------
1 file changed, 38 insertions(+), 28 deletions(-)
diff --git a/mpick.c b/mpick.c
index e1f9478..78509d4 100644
--- a/mpick.c
+++ b/mpick.c
@@ -173,6 +173,26 @@ static int prune;
static struct file *files, *fileq = NULL;
+static void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *r;
+ if ((r = calloc(nmemb, size)))
+ return r;
+ perror("calloc");
+ exit(2);
+}
+
+static char *
+xstrdup(const char *s)
+{
+ char *r;
+ if ((r = strdup(s)))
+ return r;
+ perror("strdup");
+ exit(2);
+}
+
static void
ws()
{
@@ -460,28 +480,27 @@ parse_strcmp()
char *disp, *addr;
blaze822_addr(s, &disp, &addr);
if (!disp && !addr)
- parse_error("invalid address at '%.15s'", pos);
- s = strdup((disp) ? disp : addr);
+ parse_error_at(NULL, "invalid address");
+ free(s);
+ s = xstrdup((disp) ? disp : addr);
e->extra = (disp) ? 0 : 1;
}
- if (op == EXPR_REGEX) {
- e->b.regex = malloc(sizeof (regex_t));
- r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB);
- } else if (op == EXPR_REGEXI) {
+ if (op == EXPR_REGEX || op == EXPR_REGEXI) {
e->b.regex = malloc(sizeof (regex_t));
- r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+ r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB |
+ (op == EXPR_REGEXI ? REG_ICASE : 0));
+ if (r != 0) {
+ char msg[256];
+ regerror(r, e->b.regex, msg, sizeof msg);
+ parse_error("invalid regex '%s': %s", s, msg);
+ exit(2);
+ }
+ free(s);
} else {
e->b.string = s;
}
- if (r != 0) {
- char msg[256];
- regerror(r, e->b.regex, msg, sizeof msg);
- parse_error("invalid regex '%s': %s", s, msg);
- exit(2);
- }
-
if (negate) {
struct expr *not = mkexpr(EXPR_NOT);
not->a.expr = e;
@@ -724,7 +743,7 @@ parse_redir(struct expr *e)
parse_error("expected command");
struct expr *r = mkexpr(EXPR_REDIR_PIPE);
r->a.string = s;
- r->b.string = "w";
+ r->b.string = xstrdup("w");
return chain(e, EXPR_AND, r);
}
else if (token(">>")) m = "a+";
@@ -735,7 +754,7 @@ parse_redir(struct expr *e)
parse_error("expected file name");
struct expr *r = mkexpr(EXPR_REDIR_FILE);
r->a.string = s;
- r->b.string = m;
+ r->b.string = xstrdup(m);
return chain(e, EXPR_AND, r);
}
@@ -883,7 +902,7 @@ parse_msglist(char *s)
if (!disp && !addr)
parse_error("invalid address at '%.15s'", pos);
- d = strdup((disp) ? disp : addr);
+ d = xstrdup((disp) ? disp : addr);
e1 = mkexpr(EXPR_REGEXI);
e1->a.prop = PROP_FROM;
@@ -986,11 +1005,7 @@ redir(struct expr *e)
fprintf(stderr, "%s: %s: %s\n", argv0, e->a.string, strerror(errno));
exit(3);
}
- file = calloc(1, sizeof (struct file));
- if (!file) {
- perror("calloc");
- exit(2);
- }
+ file = xcalloc(1, sizeof (struct file));
file->op = e->op;
file->name = e->a.string;
file->mode = e->b.string;
@@ -1247,12 +1262,7 @@ collect(char *file)
do_thr();
/* new thread */
- thr = calloc(1, sizeof *thr);
- if (!thr) {
- fprintf(stderr, "calloc");
- exit(2);
- }
-
+ thr = xcalloc(1, sizeof *thr);
thr->matched = 0;
ml = thr->cur = thr->childs;
thr->cur->m = m;
From 067f81e363d245abb11d1670bed5cef0b0c6fcad Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:00:56 +0100
Subject: [PATCH 07/20] mpick: add support for multiline expressions and
comments
---
mpick.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 57 insertions(+), 12 deletions(-)
diff --git a/mpick.c b/mpick.c
index 78509d4..eb6afc9 100644
--- a/mpick.c
+++ b/mpick.c
@@ -154,6 +154,12 @@ struct file {
struct file *next;
};
+struct pos {
+ char *pos;
+ char *line;
+ size_t linenr;
+};
+
static struct thread *thr;
static char *argv0;
@@ -167,10 +173,14 @@ static long num;
static struct expr *expr;
static long cur_idx;
static char *cur;
-static char *pos;
static time_t now;
static int prune;
+static char *pos;
+static const char *fname;
+static char *line = NULL;
+static int linenr = 0;
+
static struct file *files, *fileq = NULL;
static void *
@@ -196,8 +206,21 @@ xstrdup(const char *s)
static void
ws()
{
- while (isspace((unsigned char)*pos))
- pos++;
+ for (; *pos;) {
+ while (isspace((unsigned char)*pos)) {
+ if (*pos == '\n') {
+ line = pos+1;
+ linenr++;
+ }
+ pos++;
+ }
+ if (*pos != '#')
+ break;
+
+ pos += strcspn(pos, "\n\0");
+ if (*pos != '\n')
+ break;
+ }
}
static int
@@ -223,12 +246,34 @@ parse_error(char *msg, ...)
{
va_list ap;
va_start(ap, msg);
- fprintf(stderr, "%s: parse error: ", argv0);
+ fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1);
vfprintf(stderr, msg, ap);
fprintf(stderr, "\n");
exit(2);
}
+noreturn static void
+parse_error_at(struct pos *savepos, char *msg, ...)
+{
+ char *e;
+
+ if (savepos) {
+ pos = savepos->pos;
+ line = savepos->line;
+ linenr = savepos->linenr;
+ }
+
+ va_list ap;
+ va_start(ap, msg);
+ fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1);
+ vfprintf(stderr, msg, ap);
+ fprintf(stderr, " at '");
+ for (e = pos+15; *pos && *pos != '\n' && pos <= e; pos++)
+ putc(*pos, stderr);
+ fprintf(stderr, "'\n");
+ exit(2);
+}
+
static struct expr *
mkexpr(enum op op)
{
@@ -460,11 +505,11 @@ parse_strcmp()
else if (token("!==") || token("!="))
negate = 1, op = EXPR_STREQ;
else
- parse_error("invalid string operator at '%.15s'", pos);
+ parse_error_at(NULL, "invalid string operator");
char *s;
if (!parse_string(&s)) {
- parse_error("invalid string at '%.15s'", pos);
+ parse_error_at(NULL, "invalid string");
return 0;
}
@@ -598,7 +643,7 @@ parse_cmp()
return parse_flag();
if (!(op = parse_op()))
- parse_error("invalid comparison at '%.15s'", pos);
+ parse_error_at(NULL, "invalid comparison");
int64_t n;
if (parse_num(&n)) {
@@ -716,7 +761,7 @@ parse_timecmp()
op = parse_op();
if (!op)
- parse_error("invalid comparison at '%.15s'", pos);
+ parse_error_at(NULL, "invalid comparison");
int64_t n;
if (parse_num(&n) || parse_dur(&n)) {
@@ -740,7 +785,7 @@ parse_redir(struct expr *e)
if (token("|")) {
if (!parse_string(&s))
- parse_error("expected command");
+ parse_error_at(NULL, "expected command");
struct expr *r = mkexpr(EXPR_REDIR_PIPE);
r->a.string = s;
r->b.string = xstrdup("w");
@@ -751,7 +796,7 @@ parse_redir(struct expr *e)
else return e;
if (!parse_string(&s))
- parse_error("expected file name");
+ parse_error_at(NULL, "expected file name");
struct expr *r = mkexpr(EXPR_REDIR_FILE);
r->a.string = s;
r->b.string = xstrdup(m);
@@ -802,7 +847,7 @@ parse_cond()
return r;
} else {
- parse_error("expected : at '%.15s'", pos);
+ parse_error_at(NULL, "expected :", pos);
}
}
@@ -815,7 +860,7 @@ parse_expr()
pos = s;
struct expr *e = parse_cond();
if (*pos)
- parse_error("trailing garbage at '%.15s'", pos);
+ parse_error_at(NULL, "trailing garbage");
return e;
}
From 35f473afc6138a7b5d0060471b61bac821a64012 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:10:24 +0100
Subject: [PATCH 08/20] mpick: refactor mailfile handling
- fix some memory leaks
- fix handling of unexisting files/mthread containers
- in oneline mode use one mailinfo struct for each line
---
mpick.c | 131 ++++++++++++++++++++++++++++----------------------------
1 file changed, 66 insertions(+), 65 deletions(-)
diff --git a/mpick.c b/mpick.c
index eb6afc9..27ee831 100644
--- a/mpick.c
+++ b/mpick.c
@@ -121,6 +121,7 @@ struct expr {
};
struct mailinfo {
+ char *file;
char *fpath;
struct stat *sb;
struct message *msg;
@@ -973,8 +974,13 @@ msg_date(struct mailinfo *m)
if (m->date)
return m->date;
- if (!m->msg)
- m->msg = blaze822(m->fpath);
+ // XXX: date comparisation should handle zero dates
+ if (!m->msg && m->fpath) {
+ if (!(m->msg = blaze822(m->fpath))) {
+ m->fpath = NULL;
+ return -1;
+ }
+ }
char *b;
if (m->msg && (b = blaze822_hdr(m->msg, "date")))
@@ -986,28 +992,35 @@ msg_date(struct mailinfo *m)
char *
msg_hdr(struct mailinfo *m, const char *h)
{
- if (!m->msg)
- m->msg = blaze822(m->fpath);
+ static char hdrbuf[4096];
- char *b;
- if (!m->msg || !(b = blaze822_chdr(m->msg, h)))
- goto err;
+ if (!m->msg && m->fpath) {
+ if (!(m->msg = blaze822(m->fpath))) {
+ m->fpath = NULL;
+ *hdrbuf = 0;
+ return hdrbuf;
+ }
+ }
- char buf[4096];
- blaze822_decode_rfc2047(buf, b, sizeof buf - 1, "UTF-8");
- if (!*buf)
- goto err;
+ char *b;
+ if (!m->msg || !(b = blaze822_chdr(m->msg, h))) {
+ *hdrbuf = 0;
+ return hdrbuf;
+ }
- return strdup(buf);
-err:
- return "";
+ blaze822_decode_rfc2047(hdrbuf, b, sizeof hdrbuf - 1, "UTF-8");
+ return hdrbuf;
}
char *
msg_addr(struct mailinfo *m, char *h, int t)
{
- if (!m->msg)
- m->msg = blaze822(m->fpath);
+ if (!m->msg && m->fpath) {
+ if (!(m->msg = blaze822(m->fpath))) {
+ m->fpath = NULL;
+ return "";
+ }
+ }
char *b;
if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0)
@@ -1085,7 +1098,7 @@ eval(struct expr *e, struct mailinfo *m)
case EXPR_REDIR_FILE:
case EXPR_REDIR_PIPE:
fp = redir(e);
- fputs(m->fpath, fp);
+ fputs(m->file, fp);
putc('\n', fp);
fflush(fp);
return 1;
@@ -1099,15 +1112,15 @@ eval(struct expr *e, struct mailinfo *m)
case EXPR_ANYSET: {
long v = 0, n;
- if (!m->sb && (
+ if (!m->sb && m->fpath && (
e->a.prop == PROP_ATIME ||
e->a.prop == PROP_CTIME ||
e->a.prop == PROP_MTIME ||
- e->a.prop == PROP_SIZE) &&
- (m->sb = calloc(1, sizeof *m->sb)) &&
- stat(m->fpath, m->sb) != 0) {
- fprintf(stderr, "stat");
- exit(2);
+ e->a.prop == PROP_SIZE)) {
+ m->sb = xcalloc(1, sizeof *m->sb);
+ if (stat(m->fpath, m->sb) == -1)
+ m->fpath = NULL;
+ // XXX: stat based expressions should handle 0
}
n = e->b.num;
@@ -1154,9 +1167,9 @@ eval(struct expr *e, struct mailinfo *m)
case EXPR_GLOBI:
case EXPR_REGEX:
case EXPR_REGEXI: {
- const char *s = "";
+ const char *s;
switch (e->a.prop) {
- case PROP_PATH: s = m->fpath; break;
+ case PROP_PATH: s = m->fpath ? m->fpath : ""; break;
case PROP_FROM: s = msg_addr(m, "from", e->extra); break;
case PROP_TO: s = msg_addr(m, "to", e->extra); break;
default: s = msg_hdr(m, e->a.string); break;
@@ -1175,7 +1188,7 @@ eval(struct expr *e, struct mailinfo *m)
}
struct mailinfo *
-mailfile(char *file)
+mailfile(struct mailinfo *m, char *file)
{
static int init;
if (!init) {
@@ -1186,45 +1199,36 @@ mailfile(char *file)
cur = blaze822_seq_cur();
init = 1;
}
+ char *fpath = file;
- struct mailinfo *m;
- m = calloc(1, sizeof *m);
- if (!m) {
- fprintf(stderr, "calloc");
- exit(2);
- }
- m->fpath = file;
m->index = num++;
- m->flags = 0;
- m->replies = 0;
- m->depth = 0;
- m->sb = 0;
- m->msg = 0;
+ m->file = file;
- while (*m->fpath == ' ' || *m->fpath == '\t') {
+ while (*fpath == ' ' || *fpath == '\t') {
m->depth++;
- m->fpath++;
+ fpath++;
}
- char *e = m->fpath + strlen(m->fpath) - 1;
- while (m->fpath < e && (*e == ' ' || *e == '\t'))
+ char *e = fpath + strlen(fpath) - 1;
+ while (fpath < e && (*e == ' ' || *e == '\t'))
*e-- = 0;
- if (m->fpath[0] == '<') {
+ if (fpath[0] == '<') {
m->flags |= FLAG_SEEN | FLAG_INFO;
+ m->fpath = NULL;
return m;
}
- if ((e = strrchr(m->fpath, '/') - 1) && (e - m->fpath) >= 2 &&
+ if ((e = strrchr(fpath, '/') - 1) && (e - fpath) >= 2 &&
*e-- == 'w' && *e-- == 'e' && *e-- == 'n')
m->flags |= FLAG_NEW;
- if (cur && strcmp(cur, m->fpath) == 0) {
+ if (cur && strcmp(cur, fpath) == 0) {
m->flags |= FLAG_CUR;
cur_idx = m->index;
}
- char *f = strstr(m->fpath, ":2,");
+ char *f = strstr(fpath, ":2,");
if (f) {
if (strchr(f, 'P'))
m->flags |= FLAG_PASSED;
@@ -1240,6 +1244,7 @@ mailfile(char *file)
m->flags |= FLAG_FLAGGED;
}
+ m->fpath = fpath;
return m;
}
@@ -1268,11 +1273,7 @@ do_thr()
break;
if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) {
- int i;
- for (i = 0; i < ml->m->depth; i++)
- putc(' ', stdout);
-
- fputs(ml->m->fpath, stdout);
+ fputs(ml->m->file, stdout);
putc('\n', stdout);
kept++;
@@ -1285,7 +1286,7 @@ do_thr()
if (ml->m->sb)
free(ml->m->sb);
- free(ml->m->fpath);
+ free(ml->m->file);
free(ml->m);
}
@@ -1298,11 +1299,14 @@ collect(char *file)
{
struct mailinfo *m;
struct mlist *ml;
+ char *f;
- if ((m = mailfile(file)) == 0)
- return;
+ f = xstrdup(file);
+ m = xcalloc(1, sizeof *m);
+ m = mailfile(m, f);
if (m->depth == 0) {
+ /* process previous thread */
if (thr)
do_thr();
@@ -1337,17 +1341,15 @@ collect(char *file)
for (ml = ml->parent; ml; ml = ml->parent)
ml->m->replies++;
-
- m->fpath = strdup(m->fpath);
}
void
oneline(char *file)
{
- struct mailinfo *m;
-
- m = mailfile(file);
- if (expr && !eval(expr, m))
+ struct mailinfo m = { 0 };
+ m.index = num++;
+ (void) mailfile(&m, file);
+ if (expr && !eval(expr, &m))
goto out;
fputs(file, stdout);
@@ -1356,11 +1358,10 @@ oneline(char *file)
kept++;
out:
- if (m->msg)
- blaze822_free(m->msg);
- if (m->sb)
- free(m->sb);
- free(m);
+ if (m.msg)
+ blaze822_free(m.msg);
+ if (m.sb)
+ free(m.sb);
}
int
From 66958e44f0d5eafadc7bd909079597bc85a40762 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:23:27 +0100
Subject: [PATCH 09/20] mpick: minor cleanups/memleak fixes for parse_msglist
---
mpick.c | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/mpick.c b/mpick.c
index 27ee831..16028a7 100644
--- a/mpick.c
+++ b/mpick.c
@@ -872,12 +872,13 @@ parse_msglist(char *s)
int r;
struct expr *e1, *e2;
char *d;
+ enum flags flag;
switch (*s) {
case '/':
s++;
e1 = mkexpr(EXPR_REGEXI);
- e1->a.string = "subject";
+ e1->a.string = xstrdup("subject");
e1->b.regex = malloc(sizeof (regex_t));
r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
if (r != 0) {
@@ -887,13 +888,10 @@ parse_msglist(char *s)
}
return e1;
case ':':
- if (strlen(s) <= 1)
- parse_error("missing type at '%.15s'", s);
-
- enum flags flag;
n = 0;
switch (*++s) {
+ case '\0': parse_error("missing flag at '%s'", s-1);
case 'P': flag = FLAG_PASSED; break;
case 'F': flag = FLAG_FLAGGED; break;
case 'D': flag = FLAG_DRAFT; break;
@@ -905,7 +903,7 @@ parse_msglist(char *s)
case 'o': n = 1; /* FALL THROUGH */
case 'n': flag = FLAG_NEW; break;
case 'R': flag = FLAG_REPLIED; break;
- default: parse_error("unknown type at '%.15s'", s);
+ default: parse_error("unknown flag at '%s'", s);
}
e1 = mkexpr(EXPR_ANYSET);
@@ -946,9 +944,9 @@ parse_msglist(char *s)
blaze822_addr(s, &disp, &addr);
if (!disp && !addr)
- parse_error("invalid address at '%.15s'", pos);
+ parse_error("invalid address '%s'", s);
- d = xstrdup((disp) ? disp : addr);
+ d = (disp) ? disp : addr;
e1 = mkexpr(EXPR_REGEXI);
e1->a.prop = PROP_FROM;
From 0ec4e3332e53e44c56ccccda539675afbb51fb67 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:30:19 +0100
Subject: [PATCH 10/20] mpick: allow reading expressions from files i.e. using
#!/bin/mpick
---
mpick.c | 45 ++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 40 insertions(+), 5 deletions(-)
diff --git a/mpick.c b/mpick.c
index 16028a7..1a6c810 100644
--- a/mpick.c
+++ b/mpick.c
@@ -23,6 +23,7 @@
#endif
#include <sys/ioctl.h>
+#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -858,8 +859,18 @@ parse_cond()
static struct expr *
parse_expr()
{
- pos = s;
- struct expr *e = parse_cond();
+ ws();
+ return parse_cond();
+}
+
+static struct expr *
+parse_buf(const char *f, char *s)
+{
+ struct expr *e;
+ fname = f;
+ line = pos = s;
+ linenr = 1;
+ e = parse_expr();
if (*pos)
parse_error_at(NULL, "trailing garbage");
return e;
@@ -1377,16 +1388,40 @@ main(int argc, char *argv[])
while ((c = getopt(argc, argv, "Tt:v")) != -1)
switch (c) {
case 'T': Tflag = need_thr = 1; break;
- case 't': expr = chain(expr, EXPR_AND, parse_expr(optarg)); break;
+ case 't': expr = chain(expr, EXPR_AND, parse_buf("argv", optarg)); break;
case 'v': vflag = 1; break;
default:
fprintf(stderr, "Usage: %s [-Tv] [-t test] [msglist ...]\n", argv0);
exit(1);
}
- if (optind != argc)
- for (c = optind; c < argc; c++)
+ if (optind != argc) {
+ for (c = optind; c < argc; c++) {
+ if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0)
+ break;
expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
+ }
+
+ struct stat st;
+ int fd;
+ size_t len;
+ for (; c < argc; c++) {
+ if ((fd = open(argv[c], O_RDONLY)) == -1)
+ exit(1);
+ if (fstat(fd, &st) == -1)
+ exit(1);
+ len = st.st_size;
+ char *s = mmap(0, len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (s == MAP_FAILED) {
+ perror("mmap");
+ exit(1);
+ }
+ s[len+1] = '\0';
+ close(fd);
+ expr = chain(expr, EXPR_AND, parse_buf(argv[c], s));
+ munmap(s, len+1);
+ }
+ }
if (isatty(0))
i = blaze822_loop1(":", need_thr ? collect : oneline);
From 9e8989c1e638a7fc2c8359dfa76fe61f4e885963 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:41:29 +0100
Subject: [PATCH 11/20] mpick: better error for missing ) over multiple lines
---
mpick.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/mpick.c b/mpick.c
index 1a6c810..71e27ee 100644
--- a/mpick.c
+++ b/mpick.c
@@ -381,11 +381,9 @@ static struct expr *
parse_inner()
{
if (token("prune")) {
- struct expr *e = mkexpr(EXPR_PRUNE);
- return e;
+ return mkexpr(EXPR_PRUNE);
} else if (token("print")) {
- struct expr *e = mkexpr(EXPR_PRINT);
- return e;
+ return mkexpr(EXPR_PRINT);
} else if (token("skip")) {
struct expr *e = mkexpr(EXPR_PRINT);
struct expr *not = mkexpr(EXPR_NOT);
@@ -396,11 +394,14 @@ parse_inner()
struct expr *not = mkexpr(EXPR_NOT);
not->a.expr = e;
return not;
- } else if (token("(")) {
+ }
+ if (peek("(")) {
+ struct pos savepos = { pos, line, linenr };
+ (void) token("(");
struct expr *e = parse_cond();
if (token(")"))
return e;
- parse_error("missing ) at '%.15s'", pos);
+ parse_error_at(&savepos, "unterminated (");
return 0;
} else {
parse_error("unknown expression at '%.15s'", pos);
From 9456402d69785ad6a2e4e5d08db0795f0c6e12dd Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 16:22:29 +0100
Subject: [PATCH 12/20] mpick: add let expressions
---
man/mpick.1 | 11 +++++
mpick.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 121 insertions(+), 4 deletions(-)
diff --git a/man/mpick.1 b/man/mpick.1
index 744fa24..53d5120 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -107,6 +107,8 @@ tests are given by the following EBNF:
| prune -- do not match further messages in thread
| print -- always true value
| skip -- always false value
+ | <let>
+ | <ident>
<flagprop> ::= child | draft | flagged | info | new | parent | passed
| replied | seen | selected | trashed
@@ -149,6 +151,15 @@ tests are given by the following EBNF:
<str> ::= " ([^"] | "")+ " -- use "" for a single " inside "
| $[A-Za-z0-9_]+ -- environment variable
+
+-- let expressions evaluate the expression following the `in` keyword,
+-- the bindings are lazily evaluated.
+<let> ::= { let <ident> = <scope> } in <scope>
+
+-- Inside the scope previously defined idents are replaced with expressions
+<scope> ::= <expr>
+
+<ident> ::= [A-Za-z_][A-Za-z0-9_]+
.Ed
.Sh EXIT STATUS
.Ex -std
diff --git a/mpick.c b/mpick.c
index 71e27ee..3dc6660 100644
--- a/mpick.c
+++ b/mpick.c
@@ -162,6 +162,21 @@ struct pos {
size_t linenr;
};
+struct binding {
+ char *name;
+ size_t nlen;
+ size_t refcount;
+ struct expr *expr;
+ struct binding *next;
+};
+
+struct scope {
+ struct binding *bindings;
+ struct scope *prev;
+};
+
+struct scope *scopeq = NULL;
+
static struct thread *thr;
static char *argv0;
@@ -374,9 +389,102 @@ parse_op()
return 0;
}
-static struct expr *parse_cmp();
+static int
+parse_ident(char **sp, size_t *lp)
+{
+ char *p = pos;
+ if (!isalpha(*pos) && *pos != '_')
+ return 0;
+ p++;
+ while (*p && (isalnum(*p) || *p == '_'))
+ p++;
+ *sp = pos;
+ *lp = p-pos;
+ pos = p;
+ ws();
+ return 1;
+}
+
+static struct expr *
+parse_binding()
+{
+ struct scope *sc;
+ struct binding *b;
+ char *s;
+ size_t l = 0;
+
+ if (parse_ident(&s, &l)) {
+ for (sc = scopeq; sc; sc = sc->prev) {
+ for (b = sc->bindings; b; b = b->next) {
+ if (b->nlen == l && strncmp(b->name, s, l) == 0) {
+ b->refcount++;
+ return b->expr;
+ }
+ }
+ }
+ }
+ // back to the start of the ident if there was one
+ pos = pos-l;
+ parse_error_at(NULL, "unknown expression");
+ return 0;
+}
+
static struct expr *parse_cond();
+static struct expr *
+parse_let()
+{
+ if (!token("let"))
+ return parse_binding();
+
+ struct scope *sc;
+ char *s;
+ size_t l;
+
+ sc = xcalloc(1, sizeof (struct scope));
+ sc->prev = scopeq;
+ scopeq = sc;
+
+ struct binding *b, *bq;
+ struct expr *e;
+ bq = NULL;
+ for (;;) {
+ if (!parse_ident(&s, &l))
+ parse_error_at(NULL, "missing ident");
+ if (!token("="))
+ parse_error_at(NULL, "missing =");
+ e = parse_cond();
+ b = xcalloc(1, sizeof (struct binding));
+ b->name = s;
+ b->nlen = l;
+ b->expr = e;
+ if (!sc->bindings) sc->bindings = b;
+ if (bq) bq->next = b;
+ bq = b;
+ if (!token("let"))
+ break;
+ }
+ if (!token("in"))
+ parse_error_at(NULL, "missing `in`");
+
+ e = parse_cond();
+
+ struct binding *bs;
+ for (b = sc->bindings; b; b = bs) {
+ bs = b->next;
+ if (b->refcount == 0)
+ freeexpr(b->expr);
+ free(b);
+ }
+
+ scopeq = sc->prev;
+ free(sc);
+
+ return e;
+}
+
+static struct expr *parse_cmp();
+
static struct expr *
parse_inner()
{
@@ -403,10 +511,8 @@ parse_inner()
return e;
parse_error_at(&savepos, "unterminated (");
return 0;
- } else {
- parse_error("unknown expression at '%.15s'", pos);
- return 0;
}
+ return parse_let();
}
static int
From 2095a16bceeb4aec7165b1608ba73d02d2975a6a Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Tue, 29 Jan 2019 14:07:55 +0100
Subject: [PATCH 13/20] mpick: add header decodeop for better address matching
---
man/mpick.1 | 10 ++-
mpick.c | 176 +++++++++++++++++++++++++++++--------------------
t/2000-mpick.t | 4 +-
3 files changed, 115 insertions(+), 75 deletions(-)
diff --git a/man/mpick.1 b/man/mpick.1
index 53d5120..bb7d324 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -103,6 +103,7 @@ tests are given by the following EBNF:
| <flagprop>
| <timeprop> <numop> <dur>
| <numprop> <numop> <num>
+ | <hdrprop> <decodeop> <strop> <str>
| <strprop> <strop> <str>
| prune -- do not match further messages in thread
| print -- always true value
@@ -139,8 +140,13 @@ tests are given by the following EBNF:
| T )? -- *1024*1024*1024*1024
| cur -- index of cur message
-<strprop> ::= from | subject | to
- | <str> -- header name
+<hdrprop> ::= from | to | subject | <str>
+
+<decodeop> ::= . addr -- match address parts
+ | . disp -- match address display parts
+ | -- empty matches raw headers
+
+<strprop> ::= path
<strop> ::= == | = | != -- string (in)equality
| === | !=== -- case insensitive string (in)equality
diff --git a/mpick.c b/mpick.c
index 3dc6660..bff90b8 100644
--- a/mpick.c
+++ b/mpick.c
@@ -80,11 +80,12 @@ enum prop {
PROP_REPLIES,
PROP_SIZE,
PROP_TOTAL,
- PROP_FROM,
- PROP_TO,
PROP_INDEX,
PROP_DATE,
PROP_FLAG,
+ PROP_HDR,
+ PROP_HDR_ADDR,
+ PROP_HDR_DISP,
};
enum flags {
@@ -332,16 +333,17 @@ freeexpr(struct expr *e)
case EXPR_REGEX:
case EXPR_REGEXI:
switch (e->a.prop) {
- case PROP_PATH:
- case PROP_FROM:
- case PROP_TO:
- break;
- default: free(e->a.string);
+ case PROP_PATH: break;
+ case PROP_HDR:
+ case PROP_HDR_ADDR:
+ case PROP_HDR_DISP:
+ free(e->b.string);
+ default: return;
}
if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI)
- regfree(e->b.regex);
+ regfree(e->c.regex);
else
- free(e->b.string);
+ free(e->c.string);
}
free(e);
}
@@ -579,14 +581,25 @@ parse_strcmp()
negate = 0;
if (token("from"))
- prop = PROP_FROM;
+ h = xstrdup("from");
else if (token("to"))
- prop = PROP_TO;
+ h = xstrdup("to");
else if (token("subject"))
- h = "subject";
+ h = xstrdup("subject");
+ else if (token("path"))
+ prop = PROP_PATH;
else if (!parse_string(&h))
return parse_inner();
+ if (!prop) {
+ if (token(".")) {
+ if (token("addr")) prop = PROP_HDR_ADDR;
+ else if (token("disp")) prop = PROP_HDR_DISP;
+ else parse_error_at(NULL, "unknown decode parameter");
+ } else
+ prop = PROP_HDR;
+ }
+
if (token("~~~"))
op = EXPR_GLOBI;
else if (token("~~"))
@@ -624,35 +637,29 @@ parse_strcmp()
int r = 0;
struct expr *e = mkexpr(op);
-
- if (prop)
- e->a.prop = prop;
- else
- e->a.string = h;
-
- if (prop == PROP_FROM || prop == PROP_TO) {
- char *disp, *addr;
- blaze822_addr(s, &disp, &addr);
- if (!disp && !addr)
- parse_error_at(NULL, "invalid address");
- free(s);
- s = xstrdup((disp) ? disp : addr);
- e->extra = (disp) ? 0 : 1;
+ e->a.prop = prop;
+ switch (prop) {
+ case PROP_HDR:
+ case PROP_HDR_ADDR:
+ case PROP_HDR_DISP:
+ e->b.string = h;
+ break;
+ case PROP_PATH: break;
}
if (op == EXPR_REGEX || op == EXPR_REGEXI) {
- e->b.regex = malloc(sizeof (regex_t));
- r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB |
+ e->c.regex = malloc(sizeof (regex_t));
+ r = regcomp(e->c.regex, s, REG_EXTENDED | REG_NOSUB |
(op == EXPR_REGEXI ? REG_ICASE : 0));
if (r != 0) {
char msg[256];
- regerror(r, e->b.regex, msg, sizeof msg);
+ regerror(r, e->c.regex, msg, sizeof msg);
parse_error("invalid regex '%s': %s", s, msg);
exit(2);
}
free(s);
} else {
- e->b.string = s;
+ e->c.string = s;
}
if (negate) {
@@ -996,12 +1003,13 @@ parse_msglist(char *s)
case '/':
s++;
e1 = mkexpr(EXPR_REGEXI);
- e1->a.string = xstrdup("subject");
- e1->b.regex = malloc(sizeof (regex_t));
- r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+ e1->a.prop = PROP_HDR;
+ e1->b.string = xstrdup("subject");
+ e1->c.regex = malloc(sizeof (regex_t));
+ r = regcomp(e1->c.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
if (r != 0) {
char msg[256];
- regerror(r, e1->b.regex, msg, sizeof msg);
+ regerror(r, e1->c.regex, msg, sizeof msg);
parse_error("invalid regex '%s': %s", s, msg);
}
return e1;
@@ -1067,14 +1075,14 @@ parse_msglist(char *s)
d = (disp) ? disp : addr;
e1 = mkexpr(EXPR_REGEXI);
- e1->a.prop = PROP_FROM;
- e1->b.regex = malloc(sizeof (regex_t));
- e1->extra = (disp) ? 0 : 1;
+ e1->a.prop = (disp) ? PROP_HDR_DISP : PROP_HDR_ADDR;
+ e1->b.string = xstrdup("from");
+ e1->c.regex = malloc(sizeof (regex_t));
- r = regcomp(e1->b.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+ r = regcomp(e1->c.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE);
if (r != 0) {
char msg[256];
- regerror(r, e1->b.regex, msg, sizeof msg);
+ regerror(r, e1->c.regex, msg, sizeof msg);
parse_error("invalid regex '%s': %s", d, msg);
}
@@ -1106,54 +1114,64 @@ msg_date(struct mailinfo *m)
}
char *
-msg_hdr(struct mailinfo *m, const char *h)
+msg_hdr(char **s, const char *h, struct mailinfo *m)
{
static char hdrbuf[4096];
if (!m->msg && m->fpath) {
if (!(m->msg = blaze822(m->fpath))) {
m->fpath = NULL;
- *hdrbuf = 0;
- return hdrbuf;
+ return NULL;
}
}
+ // XXX: only return one header for now
+ if (*s)
+ return NULL;
+
char *b;
- if (!m->msg || !(b = blaze822_chdr(m->msg, h))) {
- *hdrbuf = 0;
- return hdrbuf;
- }
+ if (!m->msg || !(b = blaze822_chdr(m->msg, h)))
+ return NULL;
+ *s = b;
blaze822_decode_rfc2047(hdrbuf, b, sizeof hdrbuf - 1, "UTF-8");
return hdrbuf;
}
char *
-msg_addr(struct mailinfo *m, char *h, int t)
+msg_hdr_addr(char **s, const char *h, struct mailinfo *m, int rdisp)
{
if (!m->msg && m->fpath) {
if (!(m->msg = blaze822(m->fpath))) {
m->fpath = NULL;
- return "";
+ return NULL;
}
}
- char *b;
- if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0)
- return "";
+ char *b = *s;
+ if (!b) {
+ if (!m->msg || !(b = blaze822_chdr(m->msg, h)))
+ return NULL;
+ }
char *disp, *addr;
- blaze822_addr(b, &disp, &addr);
+ *s = blaze822_addr(b, &disp, &addr);
- if (t) {
- if (!addr)
- return "";
- return addr;
- } else {
- if (!disp)
- return "";
+ if (rdisp)
return disp;
- }
+ return addr;
+}
+
+char *
+msg_hdr_address(char **s, const char *h, struct mailinfo *m)
+{
+ return msg_hdr_addr(s, h, m, 0);
+}
+
+char *
+msg_hdr_display(char **s, const char *h, struct mailinfo *m)
+{
+ return msg_hdr_addr(s, h, m, 1);
}
FILE *
@@ -1283,21 +1301,34 @@ eval(struct expr *e, struct mailinfo *m)
case EXPR_GLOBI:
case EXPR_REGEX:
case EXPR_REGEXI: {
- const char *s;
+ const char *s = NULL;
+ char *p = NULL;
+ char *(*fn)(char **, const char *, struct mailinfo *) = 0;
+ int rv = 0;
switch (e->a.prop) {
+ case PROP_HDR: fn = msg_hdr; break;
+ case PROP_HDR_ADDR: fn = msg_hdr_address; break;
+ case PROP_HDR_DISP: fn = msg_hdr_display; break;
case PROP_PATH: s = m->fpath ? m->fpath : ""; break;
- case PROP_FROM: s = msg_addr(m, "from", e->extra); break;
- case PROP_TO: s = msg_addr(m, "to", e->extra); break;
- default: s = msg_hdr(m, e->a.string); break;
+ default: return 0;
}
- switch (e->op) {
- case EXPR_STREQ: return strcmp(e->b.string, s) == 0;
- case EXPR_STREQI: return strcasecmp(e->b.string, s) == 0;
- case EXPR_GLOB: return fnmatch(e->b.string, s, 0) == 0;
- case EXPR_GLOBI: return fnmatch(e->b.string, s, FNM_CASEFOLD) == 0;
- case EXPR_REGEX:
- case EXPR_REGEXI: return regexec(e->b.regex, s, 0, 0, 0) == 0;
+ for (;;) {
+ if (fn && !(s = fn(&p, e->b.string, m)))
+ break;
+ switch (e->op) {
+ case EXPR_STREQ: rv = strcmp(e->c.string, s) == 0; break;
+ case EXPR_STREQI: rv = strcasecmp(e->c.string, s) == 0; break;
+ case EXPR_GLOB: rv = fnmatch(e->c.string, s, 0) == 0; break;
+ case EXPR_GLOBI:
+ rv = fnmatch(e->c.string, s, FNM_CASEFOLD) == 0; break;
+ case EXPR_REGEX:
+ case EXPR_REGEXI:
+ rv = regexec(e->c.regex, s, 0, 0, 0) == 0;
+ break;
+ }
+ if (!fn || rv) return rv;
}
+ return 0;
}
}
return 0;
@@ -1504,8 +1535,9 @@ main(int argc, char *argv[])
if (optind != argc) {
for (c = optind; c < argc; c++) {
- if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0)
+ if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0) {
break;
+ }
expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
}
diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 8e32aec..9af444d 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
-plan 13
+plan 15
rm -rf test.dir
mkdir test.dir
@@ -66,5 +66,7 @@ check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l'
check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l'
check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"pdf\"" | wc -l'
check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l'
+check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l'
+check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l'
)
From 8c2c1640eb1d64f351e63e49eee28e9d9f82a88c Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Fri, 8 Feb 2019 19:48:02 +0100
Subject: [PATCH 14/20] t/2000-mpick.t: add tests for all the new features
---
t/2000-mpick.t | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 9af444d..802ea83 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
-plan 15
+plan 26
rm -rf test.dir
mkdir test.dir
@@ -61,6 +61,12 @@ Greetings
#application/pdf ../../mshow
!
+cat <<! >shebang
+#!$(command -v mpick)
+from.addr == "peter@example.org" && from.disp == "Peter Example"
+!
+chmod +x shebang
+
check 'search subject' 'mlist inbox | mpick /wow | grep -q inbox/cur/9:2,'
check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l'
check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l'
@@ -68,5 +74,48 @@ check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"
check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l'
check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l'
check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l'
+check_test 'shebang' -eq 2 'mlist inbox | ./shebang | wc -l'
+check_test 'ternary' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\" ? print : skip" | wc -l'
+
+check_same 'pipe command' 'mlist inbox | mpick -t "print |\"cat -n\" && skip"' 'mlist inbox | cat -n'
+check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
+check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
+check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox'
+
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = from.disp == "Peter Example"
+# random comment
+in
+ foo && bar # another comment
+!
+check_test 'let expression' -eq 2 'mlist inbox | mpick ./expr | wc -l'
+
+cat <<! >expr
+let foo =
+ let bar = from.disp == "Peter Example"
+ in
+ bar && from.addr == "peter@example.org"
+in
+ foo
+!
+check_test 'let expression nested' -eq 2 'mlist inbox | mpick ./expr | wc -l'
+
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = foo && subject =~ "wow"
+in
+ bar
+!
+check_test 'let scoping' -eq 1 'mlist inbox | mpick ./expr | wc -l'
+
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = from.disp == "Peter Example"
+in
+ foo |"sed ""s/^/1:&/""" && bar |"sed ""s/^/2:&/""" && skip
+!
+check_test 'multi redir' -eq 4 'mlist inbox | mpick ./expr | wc -l'
+check_test 'multi redir prefixes' -eq 2 'mlist inbox | mpick ./expr | cut -d: -f1 | sort -u | wc -l'
)
From 4b413aa6a192a81bd080cad47c393d843ef0a33c Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Thu, 6 Jun 2019 21:48:24 +0200
Subject: [PATCH 15/20] mpick: always point to malloc memory in parse_string
---
mpick.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mpick.c b/mpick.c
index bff90b8..45c350d 100644
--- a/mpick.c
+++ b/mpick.c
@@ -544,7 +544,7 @@ parse_string(char **s)
buf[len] = 0;
pos++;
ws();
- *s = buf ? buf : "";
+ *s = buf ? buf : xstrdup("");
return 1;
} else if (*pos == '$') {
char t;
@@ -559,7 +559,7 @@ parse_string(char **s)
*pos = 0;
*s = getenv(e);
if (!*s)
- *s = "";
+ *s = xstrdup("");
*pos = t;
ws();
return 1;
From 1d662aee124da84e80aa257b4161191c712e0e72 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Thu, 6 Jun 2019 21:48:57 +0200
Subject: [PATCH 16/20] mpick: fix memory leak in parse_dur
---
mpick.c | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/mpick.c b/mpick.c
index 45c350d..1e3d0de 100644
--- a/mpick.c
+++ b/mpick.c
@@ -791,20 +791,20 @@ parse_dur(int64_t *n)
parse_error("can't stat file '%s': %s",
s, strerror(errno));
*n = st.st_mtime;
- return 1;
+ goto ret;
}
struct tm tm = { 0 };
r = strptime(s, "%Y-%m-%d %H:%M:%S", &tm);
if (r && !*r) {
*n = mktime(&tm);
- return 1;
+ goto ret;
}
r = strptime(s, "%Y-%m-%d", &tm);
if (r && !*r) {
tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
*n = mktime(&tm);
- return 1;
+ goto ret;
}
r = strptime(s, "%H:%M:%S", &tm);
if (r && !*r) {
@@ -813,7 +813,7 @@ parse_dur(int64_t *n)
tm.tm_mon = tmnow->tm_mon;
tm.tm_mday = tmnow->tm_mday;
*n = mktime(&tm);
- return 1;
+ goto ret;
}
r = strptime(s, "%H:%M", &tm);
if (r && !*r) {
@@ -823,39 +823,40 @@ parse_dur(int64_t *n)
tm.tm_mday = tmnow->tm_mday;
tm.tm_sec = 0;
*n = mktime(&tm);
- return 1;
+ goto ret;
}
if (*s == '-') {
- s++;
-
- errno = 0;
int64_t d;
- d = strtol(s, &r, 10);
+ errno = 0;
+ d = strtol(s+1, &r, 10);
if (errno == 0 && r[0] == 'd' && !r[1]) {
struct tm *tmnow = localtime(&now);
tmnow->tm_mday -= d;
tmnow->tm_hour = tmnow->tm_min = tmnow->tm_sec = 0;
*n = mktime(tmnow);
- return 1;
+ goto ret;
}
if (errno == 0 && r[0] == 'h' && !r[1]) {
*n = now - (d*60*60);
- return 1;
+ goto ret;
}
if (errno == 0 && r[0] == 'm' && !r[1]) {
*n = now - (d*60);
- return 1;
+ goto ret;
}
if (errno == 0 && r[0] == 's' && !r[1]) {
*n = now - d;
- return 1;
+ goto ret;
}
- parse_error("invalid relative time format '%s'", s-1);
+ parse_error("invalid relative time format '%s'", s);
}
parse_error("invalid time format '%s'", s);
return 0;
+ret:
+ free(s);
+ return 1;
}
static struct expr *
From a6ea4cb92bee43fe9ac66ad476355cff958491a9 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Fri, 7 Jun 2019 01:24:49 +0200
Subject: [PATCH 17/20] mpick: use slurp instead of mmaping files
---
mpick.c | 21 +++++++--------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/mpick.c b/mpick.c
index 1e3d0de..adf6b09 100644
--- a/mpick.c
+++ b/mpick.c
@@ -1542,24 +1542,17 @@ main(int argc, char *argv[])
expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
}
- struct stat st;
- int fd;
- size_t len;
for (; c < argc; c++) {
- if ((fd = open(argv[c], O_RDONLY)) == -1)
- exit(1);
- if (fstat(fd, &st) == -1)
- exit(1);
- len = st.st_size;
- char *s = mmap(0, len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
- if (s == MAP_FAILED) {
- perror("mmap");
+ char *s;
+ off_t len;
+ int r = slurp(argv[c], &s, &len);
+ if (r != 0) {
+ fprintf(stderr, "%s: error opening file '%s': %s\n",
+ argv0, argv[c], strerror(r));
exit(1);
}
- s[len+1] = '\0';
- close(fd);
expr = chain(expr, EXPR_AND, parse_buf(argv[c], s));
- munmap(s, len+1);
+ free(s);
}
}
From 475fe4447ba5fb03a26bcc9893e5a2b7e21e5ae8 Mon Sep 17 00:00:00 2001
From: Duncan Overbruck <mail@duncano.de>
Date: Wed, 5 Feb 2020 15:30:53 +0100
Subject: [PATCH 18/20] mpick: fix memory leaks
---
mpick.c | 32 ++++++++++++++++++++++++--------
1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/mpick.c b/mpick.c
index adf6b09..d72301c 100644
--- a/mpick.c
+++ b/mpick.c
@@ -68,6 +68,7 @@ enum op {
EXPR_TYPE,
EXPR_ALLSET,
EXPR_ANYSET,
+ EXPR_BINDING,
};
enum prop {
@@ -118,6 +119,7 @@ struct expr {
int64_t num;
regex_t *regex;
enum var var;
+ struct binding *binding;
} a, b, c;
int extra;
};
@@ -321,6 +323,13 @@ freeexpr(struct expr *e)
case EXPR_NOT:
freeexpr(e->a.expr);
break;
+ case EXPR_BINDING:
+ e->a.binding->refcount -= 1;
+ if (e->a.binding->refcount == 0) {
+ freeexpr(e->a.binding->expr);
+ free(e->a.binding);
+ }
+ break;
case EXPR_REDIR_FILE:
case EXPR_REDIR_PIPE:
free(e->a.string);
@@ -338,12 +347,12 @@ freeexpr(struct expr *e)
case PROP_HDR_ADDR:
case PROP_HDR_DISP:
free(e->b.string);
- default: return;
}
if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI)
regfree(e->c.regex);
else
free(e->c.string);
+ break;
}
free(e);
}
@@ -419,8 +428,10 @@ parse_binding()
for (sc = scopeq; sc; sc = sc->prev) {
for (b = sc->bindings; b; b = b->next) {
if (b->nlen == l && strncmp(b->name, s, l) == 0) {
+ struct expr *e = mkexpr(EXPR_BINDING);
+ e->a.binding = b;
b->refcount++;
- return b->expr;
+ return e;
}
}
}
@@ -460,9 +471,12 @@ parse_let()
b->name = s;
b->nlen = l;
b->expr = e;
- if (!sc->bindings) sc->bindings = b;
- if (bq) bq->next = b;
- bq = b;
+ if (bq) {
+ bq->next = b;
+ bq = b;
+ } else {
+ bq = sc->bindings = b;
+ }
if (!token("let"))
break;
}
@@ -474,8 +488,9 @@ parse_let()
struct binding *bs;
for (b = sc->bindings; b; b = bs) {
bs = b->next;
- if (b->refcount == 0)
- freeexpr(b->expr);
+ if (b->refcount != 0)
+ continue;
+ freeexpr(b->expr);
free(b);
}
@@ -1224,7 +1239,8 @@ eval(struct expr *e, struct mailinfo *m)
: eval(e->c.expr, m);
case EXPR_NOT:
return !eval(e->a.expr, m);
- return 1;
+ case EXPR_BINDING:
+ return eval(e->a.binding->expr, m);
case EXPR_PRUNE:
prune = 1;
return 1;
From 89be0ace74d43a06d2e2200422ec33aedb73e53c Mon Sep 17 00:00:00 2001
From: Duncan Overbruck <mail@duncano.de>
Date: Wed, 5 Feb 2020 15:47:29 +0100
Subject: [PATCH 19/20] t/2000-mpick.t: check for double free with bindings
---
t/2000-mpick.t | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 802ea83..3cff87e 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
-plan 26
+plan 27
rm -rf test.dir
mkdir test.dir
@@ -91,6 +91,15 @@ in
!
check_test 'let expression' -eq 2 'mlist inbox | mpick ./expr | wc -l'
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = from.disp == "Peter Example"
+# random comment
+in
+ foo && foo
+!
+check_test 'let expression double free' -eq 2 'mlist inbox | mpick ./expr | wc -l'
+
cat <<! >expr
let foo =
let bar = from.disp == "Peter Example"
From 65016bed8bef29ae2bebed958a763e7257e57652 Mon Sep 17 00:00:00 2001
From: Duncan Overbruck <mail@duncano.de>
Date: Wed, 5 Feb 2020 16:07:41 +0100
Subject: [PATCH 20/20] mpick: improve unknown binding error and add test case
---
mpick.c | 6 +++---
t/2000-mpick.t | 5 ++++-
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/mpick.c b/mpick.c
index d72301c..5fcb308 100644
--- a/mpick.c
+++ b/mpick.c
@@ -423,6 +423,7 @@ parse_binding()
struct binding *b;
char *s;
size_t l = 0;
+ struct pos savepos = { pos, line, linenr };
if (parse_ident(&s, &l)) {
for (sc = scopeq; sc; sc = sc->prev) {
@@ -436,9 +437,8 @@ parse_binding()
}
}
}
- // back to the start of the ident if there was one
- pos = pos-l;
- parse_error_at(NULL, "unknown expression");
+
+ parse_error_at(&savepos, "unknown expression");
return 0;
}
diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 3cff87e..0b1296c 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
-plan 27
+plan 28
rm -rf test.dir
mkdir test.dir
@@ -82,6 +82,9 @@ check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat
check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox'
+check_same 'unknown ident' 'mlist inbox | mpick -t "let x = x in x" 2>&1' \
+ "echo \"mpick: parse error: argv:1:9: unknown expression at 'x in x'\""
+
cat <<! >expr
let foo = from.addr == "peter@example.org"
let bar = from.disp == "Peter Example"
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2020-05-15 16:37 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <gh-mailinglist-notifications-fa6558a0-26e0-48f6-803f-f5a8af34f6a8-mblaze-150@inbox.vuxu.org>
2020-05-15 16:37 ` [PR PATCH] [Updated] mpick craziness Duncaen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).