aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncaen <mail@duncano.de>2016-07-22 03:10:17 +0200
committerChristian Neukirchen <chneukirchen@gmail.com>2016-07-28 17:32:24 +0200
commitf86bb4d88ff1b7e1dd10eb51ac155948106902d5 (patch)
tree4b92e2168c8ec3a175a9641eff7c45431fcedaa5
parentdc43475d7641ad4d410279a9d80ec2b5a838c8ce (diff)
downloadmblaze-f86bb4d88ff1b7e1dd10eb51ac155948106902d5.tar.gz
add mpick
-rw-r--r--Makefile3
-rw-r--r--mpick.c865
2 files changed, 867 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index cb97127..864de84 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
CFLAGS=-g -O1 -Wall -Wno-switch -Wextra -fstack-protector-strong -D_FORTIFY_SOURCE=2
-ALL = maddr mdeliver mdirs mflag mhdr minc mlist mmime mscan mseq mshow msort mthread
+ALL = maddr mdeliver mdirs mflag mhdr minc mlist mmime mpick mscan mseq mshow msort mthread
all: $(ALL)
@@ -12,6 +12,7 @@ mhdr: mhdr.o blaze822.o seq.o rfc2047.o mymemmem.o
minc: minc.o
mlist: mlist.o
mmime: mmime.o
+mpick: mpick.o blaze822.o seq.o rfc2047.c mymemmem.o
mscan: mscan.o blaze822.o seq.o rfc2047.o mymemmem.o
mseq: mseq.o seq.o
mshow: mshow.o blaze822.o seq.o rfc2045.o rfc2047.c mymemmem.o
diff --git a/mpick.c b/mpick.c
new file mode 100644
index 0000000..0df64af
--- /dev/null
+++ b/mpick.c
@@ -0,0 +1,865 @@
+#define _GNU_SOURCE
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <locale.h>
+#include <regex.h>
+#include <fnmatch.h>
+
+#include "blaze822.h"
+
+enum op {
+ EXPR_OR = 1,
+ EXPR_AND,
+ EXPR_NOT,
+ EXPR_LT,
+ EXPR_LE,
+ EXPR_EQ,
+ EXPR_NEQ,
+ EXPR_GE,
+ EXPR_GT,
+ EXPR_STREQ,
+ EXPR_STREQI,
+ EXPR_GLOB,
+ EXPR_GLOBI,
+ EXPR_REGEX,
+ EXPR_REGEXI,
+ EXPR_PRINT,
+ EXPR_TYPE,
+ EXPR_ALLSET,
+ EXPR_ANYSET,
+};
+
+enum prop {
+ PROP_ATIME = 1,
+ PROP_CTIME,
+ PROP_DEPTH,
+ PROP_MTIME,
+ PROP_PATH,
+ PROP_SIZE,
+ PROP_TOTAL,
+ PROP_SUBJECT,
+ PROP_FROM,
+ PROP_FROM_NAME,
+ PROP_FROM_ADDR,
+ PROP_TO,
+ PROP_TO_NAME,
+ PROP_TO_ADDR,
+ PROP_INDEX,
+ PROP_DATE,
+ PROP_FLAG,
+};
+
+enum flags {
+ FLAG_PASSED = 1,
+ FLAG_REPLIED = 2,
+ FLAG_SEEN = 4,
+ FLAG_TRASHED = 8,
+ FLAG_DRAFT = 16,
+ FLAG_FLAGGED = 32,
+ /* custom */
+ FLAG_NEW = 64,
+ FLAG_CUR = 128,
+};
+
+struct expr {
+ enum op op;
+ union {
+ enum prop prop;
+ struct expr *expr;
+ char *string;
+ int64_t num;
+ regex_t *regex;
+ } a, b;
+};
+
+struct mailinfo {
+ char *fpath;
+ struct stat *sb;
+ struct message *msg;
+ time_t date;
+ int depth;
+ int index;
+ long flags;
+ off_t total;
+ char subject[100];
+};
+
+static char *argv0;
+
+static long kept;
+
+static struct expr *expr;
+static char *cur;
+static char *pos;
+static time_t now;
+
+static void
+ws()
+{
+ while (isspace((unsigned char) *pos))
+ pos++;
+}
+
+static int
+token(const char *token)
+{
+ if (strncmp(pos, token, strlen(token)) == 0) {
+ pos += strlen(token);
+ ws();
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static void
+parse_error(const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ fprintf(stderr, "%s: parse error: ", argv0);
+ vfprintf(stderr, msg, ap);
+ fprintf(stderr, "\n");
+ exit(2);
+}
+
+static struct expr *
+mkexpr(enum op op)
+{
+ struct expr *e = malloc(sizeof (struct expr));
+ if (!e)
+ parse_error("out of memory");
+ e->op = op;
+ return e;
+}
+
+static struct expr *
+chain(struct expr *e1, enum op op, struct expr *e2)
+{
+ struct expr *i, *j, *e;
+ if (!e1)
+ return e2;
+ if (!e2)
+ return e1;
+ for (j = 0, i = e1; i->op == op; j = i, i = i->b.expr)
+ ;
+ if (!j) {
+ e = mkexpr(op);
+ e->a.expr = e1;
+ e->b.expr = e2;
+ return e;
+ } else {
+ e = mkexpr(op);
+ e->a.expr = i;
+ e->b.expr = e2;
+ j->b.expr = e;
+ return e1;
+ }
+}
+
+static enum op
+parse_op()
+{
+ if (token("<="))
+ return EXPR_LE;
+ else if (token("<"))
+ return EXPR_LT;
+ else if (token(">="))
+ return EXPR_GE;
+ else if (token(">"))
+ return EXPR_GT;
+ else if (token("==") || token("="))
+ return EXPR_EQ;
+ else if (token("!="))
+ return EXPR_NEQ;
+
+ return 0;
+}
+
+static struct expr *parse_cmp();
+static struct expr *parse_or();
+
+static struct expr *
+parse_inner()
+{
+ if (token("print")) {
+ struct expr *e = mkexpr(EXPR_PRINT);
+ return e;
+ } else if (token("!")) {
+ struct expr *e = parse_cmp();
+ struct expr *not = mkexpr(EXPR_NOT);
+ not->a.expr = e;
+ return not;
+ } else if (token("(")) {
+ struct expr *e = parse_or();
+ if (token(")"))
+ return e;
+ parse_error("missing ) at '%.15s'", pos);
+ return 0;
+ } else {
+ parse_error("unknown expression at '%.15s'", pos);
+ return 0;
+ }
+}
+
+static int
+parse_string(char **s)
+{
+ char *buf = 0;
+ size_t bufsiz = 0;
+ size_t len = 0;
+
+ if (*pos == '"') {
+ pos++;
+ while (*pos &&
+ (*pos != '"' || (*pos == '"' && *(pos+1) == '"'))) {
+ if (len >= bufsiz) {
+ bufsiz = 2*bufsiz + 16;
+ buf = realloc(buf, bufsiz);
+ if (!buf)
+ parse_error("string too long");
+ }
+ if (*pos == '"')
+ pos++;
+ buf[len++] = *pos++;
+ }
+ if (!*pos)
+ parse_error("unterminated string");
+ if (buf)
+ buf[len] = 0;
+ pos++;
+ ws();
+ *s = buf ? buf : (char *) "";
+ return 1;
+ } else if (*pos == '$') {
+ char t;
+ char *e = ++pos;
+
+ while (isalnum((unsigned char) *pos) || *pos == '_')
+ pos++;
+ if (e == pos)
+ parse_error("invalid environment variable name");
+
+ t = *pos;
+ *pos = 0;
+ *s = getenv(e);
+ if (!*s)
+ *s = (char *) "";
+ *pos = t;
+ ws();
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct expr *
+parse_strcmp()
+{
+ enum prop prop;
+ enum op op;
+
+ if (token("subject"))
+ prop = PROP_SUBJECT;
+ else if (token("from"))
+ prop = PROP_FROM;
+ else if (token("to"))
+ prop = PROP_TO;
+ else
+ return parse_inner();
+
+ if (token("~~~"))
+ op = EXPR_GLOBI;
+ else if (token("~~"))
+ op = EXPR_GLOB;
+ else if (token("=~~"))
+ op = EXPR_REGEXI;
+ else if (token("=~"))
+ op = EXPR_REGEX;
+ else if (token("==="))
+ op = EXPR_STREQI;
+ else if (token("=="))
+ op = EXPR_STREQ;
+ else if (token("="))
+ op = EXPR_STREQ;
+ else
+ parse_error("invalid string operator at '%.15s'", pos);
+
+ char *s;
+ if (parse_string(&s)) {
+ int r = 0;
+ struct expr *e = mkexpr(op);
+ e->a.prop = prop;
+ 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) {
+ e->b.regex = malloc(sizeof (regex_t));
+ r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+ } 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);
+ }
+
+ return e;
+ }
+
+ parse_error("invalid string at '%.15s'", pos);
+ return 0;
+}
+
+static int64_t
+parse_num(int64_t *r)
+{
+ char *s = pos;
+ if (isdigit((unsigned char) *pos)) {
+ int64_t n;
+
+ for (n = 0; isdigit((unsigned char) *pos) && n <= INT64_MAX / 10 - 10; pos++)
+ n = 10 * n + (*pos - '0');
+ if (isdigit((unsigned char) *pos))
+ parse_error("number too big: %s", s);
+ if (token("c")) ;
+ else if (token("b")) n *= 512LL;
+ else if (token("k")) n *= 1024LL;
+ else if (token("M")) n *= 1024LL * 1024;
+ else if (token("G")) n *= 1024LL * 1024 * 1024;
+ else if (token("T")) n *= 1024LL * 1024 * 1024 * 1024;
+ ws();
+ *r = n;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static struct expr *
+parse_flag()
+{
+ enum flags flag;
+
+ if (token("passed")) {
+ flag = FLAG_PASSED;
+ } else if (token("replied")) {
+ flag = FLAG_REPLIED;
+ } else if (token("seen")) {
+ flag = FLAG_SEEN;
+ } else if (token("trashed")) {
+ flag = FLAG_TRASHED;
+ } else if (token("draft")) {
+ flag = FLAG_DRAFT;
+ } else if (token("flagged")) {
+ flag = FLAG_FLAGGED;
+ } else if (token("new")) {
+ flag = FLAG_NEW;
+ } else if (token("cur")) {
+ flag = FLAG_CUR;
+ } else
+ return parse_strcmp();
+
+ struct expr *e = mkexpr(EXPR_ANYSET);
+ e->a.prop = PROP_FLAG;
+ e->b.num = flag;
+
+ return e;
+}
+
+
+static struct expr *
+parse_cmp()
+{
+ enum prop prop;
+ enum op op;
+
+ op = 0;
+
+ if (token("depth"))
+ prop = PROP_DEPTH;
+ else if (token("index"))
+ prop = PROP_INDEX;
+ else if (token("size"))
+ prop = PROP_SIZE;
+ else if (token("total"))
+ prop = PROP_TOTAL;
+ else
+ return parse_flag();
+
+ if (!(op = parse_op()))
+ parse_error("invalid comparison at '%.15s'", pos);
+
+ int64_t n;
+ if (parse_num(&n)) {
+ struct expr *e = mkexpr(op);
+ e->a.prop = prop;
+ e->b.num = n;
+ return e;
+ }
+
+ return 0;
+}
+
+static int
+parse_dur(int64_t *n)
+{
+ char *s, *r;
+ if (!parse_string(&s))
+ return 0;
+
+ if (*s == '/' || *s == '.') {
+ struct stat st;
+ if (stat(s, &st) < 0)
+ parse_error("can't stat file '%s': %s",
+ s, strerror(errno));
+ *n = st.st_mtime;
+ return 1;
+ }
+
+ struct tm tm = { 0 };
+ r = strptime(s, "%Y-%m-%d %H:%M:%S", &tm);
+ if (r && !*r) {
+ *n = mktime(&tm);
+ return 1;
+ }
+ 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;
+ }
+ r = strptime(s, "%H:%M:%S", &tm);
+ if (r && !*r) {
+ struct tm *tmnow = localtime(&now);
+ tm.tm_year = tmnow->tm_year;
+ tm.tm_mon = tmnow->tm_mon;
+ tm.tm_mday = tmnow->tm_mday;
+ *n = mktime(&tm);
+ return 1;
+ }
+ r = strptime(s, "%H:%M", &tm);
+ if (r && !*r) {
+ struct tm *tmnow = localtime(&now);
+ tm.tm_year = tmnow->tm_year;
+ tm.tm_mon = tmnow->tm_mon;
+ tm.tm_mday = tmnow->tm_mday;
+ tm.tm_sec = 0;
+ *n = mktime(&tm);
+ return 1;
+ }
+
+ if (*s == '-') {
+ s++;
+
+ errno = 0;
+ int64_t d;
+ d = strtol(s, &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;
+ }
+ if (errno == 0 && r[0] == 'h' && !r[1]) {
+ *n = now - (d*60*60);
+ return 1;
+ }
+ if (errno == 0 && r[0] == 'm' && !r[1]) {
+ *n = now - (d*60);
+ return 1;
+ }
+ if (errno == 0 && r[0] == 's' && !r[1]) {
+ *n = now - d;
+ return 1;
+ }
+ parse_error("invalid relative time format '%s'", s-1);
+ }
+
+ parse_error("invalid time format '%s'", s);
+ return 0;
+}
+
+static struct expr *
+parse_timecmp()
+{
+ enum prop prop;
+ enum op op;
+
+ if (token("atime"))
+ prop = PROP_ATIME;
+ else if (token("ctime"))
+ prop = PROP_CTIME;
+ else if (token("mtime"))
+ prop = PROP_MTIME;
+ else if (token("date"))
+ prop = PROP_DATE;
+ else
+ return parse_cmp();
+
+ op = parse_op();
+ if (!op)
+ parse_error("invalid comparison at '%.15s'", pos);
+
+ int64_t n;
+ if (parse_num(&n) || parse_dur(&n)) {
+ struct expr *e = mkexpr(op);
+ e->a.prop = prop;
+ e->b.num = n;
+ return e;
+ }
+
+ return 0;
+}
+
+static struct expr *
+parse_and()
+{
+ struct expr *e1 = parse_timecmp();
+ struct expr *r = e1;
+
+ while (token("&&")) {
+ struct expr *e2 = parse_timecmp();
+ r = chain(r, EXPR_AND, e2);
+ }
+
+ return r;
+}
+
+static struct expr *
+parse_or()
+{
+ struct expr *e1 = parse_and();
+ struct expr *r = e1;
+
+ while (token("||")) {
+ struct expr *e2 = parse_and();
+ r = chain(r, EXPR_OR, e2);
+ }
+
+ return r;
+}
+
+static struct expr *
+parse_expr(const char *s)
+{
+ pos = (char *)s;
+ struct expr *e = parse_or();
+ if (*pos)
+ parse_error("trailing garbage at '%.15s'", pos);
+ return e;
+}
+
+static struct expr *
+parse_msglist(const char *s)
+{
+ int64_t n, m;
+ int r;
+ struct expr *e1, *e2;
+ char *d;
+
+ switch (*s) {
+ case '/':
+ s++;
+ e1 = mkexpr(EXPR_REGEXI);
+ e1->a.prop = PROP_SUBJECT;
+ e1->b.regex = malloc(sizeof (regex_t));
+ r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+ if (r != 0) {
+ char msg[256];
+ regerror(r, e1->b.regex, msg, sizeof msg);
+ parse_error("invalid regex '%s': %s", s, msg);
+ }
+ return e1;
+ case ':':
+ if (strlen(s) <= 1)
+ parse_error("missing type at '%.15s'", s);
+
+ enum flags flag;
+ n = 0;
+
+ switch (*++s) {
+ case 'P': flag = FLAG_PASSED; break;
+ case 'F': flag = FLAG_FLAGGED; break;
+ case 'D': flag = FLAG_DRAFT; break;
+ case 'd': /* FALL TROUGH */
+ case 'T': flag = FLAG_TRASHED; break;
+ case 'u': n = 1; /* FALL TROUGH */
+ case 'r': /* FALL TROUGH */
+ case 'S': flag = FLAG_SEEN; break;
+ case 'o': n = 1; /* FALL TROUGH */
+ case 'n': flag = FLAG_NEW; break;
+ default: parse_error("unknown type at '%.15s'", s);
+ }
+
+ e1 = mkexpr(EXPR_ANYSET);
+ e1->a.prop = PROP_FLAG;
+ e1->b.num = flag;
+
+ if (!n)
+ return e1;
+
+ e2 = mkexpr(EXPR_NOT);
+ e2->a.expr = e1;
+ return e2;
+ default:
+ pos = (char *)s;
+
+ if ((d = strchr(s, '-')) && parse_num(&n) &&
+ (pos = (char *)d + 1) && parse_num(&m)) {
+ /* index >= n */
+ e1 = mkexpr(EXPR_GE);
+ e1->a.prop = PROP_INDEX;
+ e1->b.num = n;
+
+ /* index <= m */
+ e2 = mkexpr(EXPR_LE);
+ e2->a.prop = PROP_INDEX;
+ e2->b.num = m;
+
+ /* e1 && e2 */
+ return chain(e1, EXPR_AND, e2);
+ } else if (parse_num(&n)) {
+ e1 = mkexpr(EXPR_EQ);
+ e1->a.prop = PROP_INDEX;
+ e1->b.num = n;
+
+ return e1;
+ } else {
+ expr = chain(parse_expr("from.addr == 's'"), EXPR_AND, expr);
+ }
+ }
+ return NULL;
+}
+
+time_t
+msg_date(struct mailinfo *m)
+{
+ if (m->date)
+ return m->date;
+
+ char *b;
+ if ((b = blaze822_hdr(m->msg, "date")))
+ return (m->date = blaze822_date(b));
+
+ return -1;
+}
+
+char *
+msg_subject(struct mailinfo *m)
+{
+ if (*m->subject != '\0')
+ return m->subject;
+
+ char *b;
+ if ((b = blaze822_hdr(m->msg, "subject")) == '\0')
+ return "";
+
+ blaze822_decode_rfc2047(m->subject, b, sizeof m->subject - 1, "UTF-8");
+ return m->subject;
+}
+
+int
+eval(struct expr *e, struct mailinfo *m)
+{
+ switch (e->op) {
+ case EXPR_OR:
+ 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_NOT:
+ return !eval(e->a.expr, m);
+ return 1;
+ case EXPR_PRINT:
+ return 1;
+ case EXPR_LT:
+ case EXPR_LE:
+ case EXPR_EQ:
+ case EXPR_NEQ:
+ case EXPR_GE:
+ case EXPR_GT:
+ case EXPR_ALLSET:
+ case EXPR_ANYSET: {
+ long v = 0;
+
+ if (m->sb == '\0' && (
+ 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)) != NULL &&
+ stat(m->fpath, m->sb) != 0) {
+ fprintf(stderr, "stat");
+ exit(2);
+ }
+
+ switch (e->a.prop) {
+ case PROP_ATIME: v = m->sb->st_atime; break;
+ case PROP_CTIME: v = m->sb->st_ctime; break;
+ case PROP_MTIME: v = m->sb->st_mtime; break;
+ case PROP_SIZE: v = m->sb->st_size; break;
+ case PROP_DATE: v = msg_date(m);
+ case PROP_FLAG: v = m->flags; break;
+ case PROP_INDEX: v = m->index; break;
+ case PROP_DEPTH: v = m->depth; break;
+ default:
+ parse_error("unknown property");
+ }
+
+ switch (e->op) {
+ case EXPR_LT: return v < e->b.num;
+ case EXPR_LE: return v <= e->b.num;
+ case EXPR_EQ: return v == e->b.num;
+ case EXPR_NEQ: return v != e->b.num;
+ case EXPR_GE: return v >= e->b.num;
+ case EXPR_GT: return v > e->b.num;
+ case EXPR_ALLSET: return (v & e->b.num) == e->b.num;
+ case EXPR_ANYSET: return (v & e->b.num) > 0;
+ }
+ }
+ case EXPR_STREQ:
+ case EXPR_STREQI:
+ case EXPR_GLOB:
+ case EXPR_GLOBI:
+ case EXPR_REGEX:
+ case EXPR_REGEXI: {
+ const char *s = "";
+ switch(e->a.prop) {
+ case PROP_PATH: s = m->fpath; break;
+ case PROP_SUBJECT: s = msg_subject(m); break;
+ }
+ 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;
+ }
+ }
+ }
+ return 0;
+}
+
+void
+oneline(char *line, long idx)
+{
+ static int init;
+ if (!init) {
+ // delay loading of the seqmap until we need to scan the first
+ // file, in case someone in the pipe updated the map before
+ char *seqmap = blaze822_seq_open(0);
+ blaze822_seq_load(seqmap);
+ cur = blaze822_seq_cur();
+ init = 1;
+ }
+
+ struct mailinfo m;
+
+ memset(m.subject, 0, sizeof m.subject);
+ m.fpath = line;
+ m.index = idx;
+ m.flags = 0;
+ m.depth = 0;
+ m.sb = 0;
+
+ while (*m.fpath == ' ' || *m.fpath== '\t') {
+ m.depth++;
+ m.fpath++;
+ }
+
+ char *e = m.fpath + strlen(m.fpath) - 1;
+ while (m.fpath < e && (*e == ' ' || *e == '\t'))
+ *e-- = 0;
+
+ m.msg = blaze822(m.fpath);
+ if (!m.msg) {
+ return;
+ }
+
+ if (strstr(m.fpath, "/new/") != NULL)
+ m.flags |= FLAG_NEW;
+
+ if (cur && strcmp(cur, m.fpath) == 0)
+ m.flags |= FLAG_CUR;
+
+ char *f = strstr(m.fpath, ":2,");
+ if (f) {
+ if (strchr(f, 'P'))
+ m.flags |= FLAG_PASSED;
+ if (strchr(f, 'R'))
+ m.flags |= FLAG_REPLIED;
+ if (strchr(f, 'S'))
+ m.flags |= FLAG_SEEN;
+ if (strchr(f, 'T'))
+ m.flags |= FLAG_TRASHED;
+ if (strchr(f, 'D'))
+ m.flags |= FLAG_DRAFT;
+ if (strchr(f, 'F'))
+ m.flags |= FLAG_FLAGGED;
+ }
+
+ if (expr && !eval(expr, &m))
+ goto out;
+
+ kept++;
+ printf("%s\n", line);
+
+out:
+ free(m.sb);
+ blaze822_free(m.msg);
+}
+
+int
+main(int argc, char *argv[])
+{
+ long i;
+ int c;
+ char *f, *a;
+
+ argv0 = argv[0];
+
+ while ((c = getopt(argc, argv, "t:")) != -1)
+ switch (c) {
+ case 't': expr = chain(expr, EXPR_AND, parse_expr(optarg)); break;
+ }
+
+ if (optind != argc)
+ for (c = optind; c < argc; c++)
+ expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
+
+ struct blaze822_seq_iter iter = { 0 };
+
+ char *map = blaze822_seq_open(0);
+ if (!map)
+ return 1;
+
+ a = ":";
+ i = 0;
+
+ while ((f = blaze822_seq_next(map, a, &iter))) {
+ i = iter.line - 1;
+ oneline(f, i);
+ free(f);
+ }
+
+ fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept);
+ return 0;
+}