#include <sysexits.h>
#include <ctype.h>
#include <regex.h>
+#include <libgen.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#define FORMATSW 22
{ "format format", 0 },
#define WIDTHSW 23
- { "width columns", 0 },
-#define VERSIONSW 24
+ { "width columns", 0 },
+#define THREADSW 24
+ { "thread", 0 },
+#define FILESW 25
+ { "file file", 0 },
+#define VERSIONSW 26
{ "Version", 0 },
-#define HELPSW 25
+#define HELPSW 27
{ "help", 0 },
{ NULL, 0 }
};
char *version=VERSION;
-struct nexus {
- boolean (*action)(struct field *, int, void *);
- void (*free)(struct nexus **);
- void (*debug)(void *, size_t);
+enum nexus_type {
+ noop_t = 0,
+ not_t,
+ and_t,
+ or_t,
+ date_t,
+ grep_t
+};
+
+struct bin_data {
+ struct nexus *left;
+ struct nexus *right;
+};
- void *data;
+struct date_data {
+ char *datef;
+ boolean after;
+ struct tws tws;
+};
+
+struct grep_data {
+ char *header;
+ char *pattern;
+ regex_t *preg;
+};
+
+struct nexus {
+ enum nexus_type t;
+ boolean match;
+ union {
+ struct bin_data b;
+ struct date_data d;
+ struct grep_data g;
+ } data;
};
static struct nexus *head;
*/
static int pcompile(char **, char *);
static int pmatches(FILE *, int);
+static boolean nexus_match(struct field *, int, struct nexus *);
+static void nexus_free(struct nexus **);
+static void nexus_clear(struct nexus *);
+static void nexus_debug(struct nexus *, size_t);
+static void nexus_debug_grep(struct grep_data *);
+static void print_debug_level(size_t);
+static struct nexus * createonethread(char *);
+static struct nexus * createpickthread(char *);
+static void scan_mbox(char *, char *, int);
static int listsw = -1;
char *form = NULL;
char *fmtstr;
FILE *fp;
+ char *file = NULL;
if (atexit(putzero_done) != 0) {
adios(EX_OSERR, NULL, "atexit failed");
case AFTRSW:
case BEFRSW:
case SRCHSW:
+ case THREADSW:
vec[vecp++] = --cp;
pattern:
if (!(cp = *argp++)) /* allow -xyz arguments */
}
width = atoi(cp);
continue;
+ case FILESW:
+ if (!(cp = *argp++) || (cp[0] == '-' && cp[1])) {
+ adios(EX_USAGE, NULL, "missing argument to %s",
+ argp[-2]);
+ }
+ if (strcmp(file = cp, "-")!=0) {
+ file = mh_xstrdup(expanddir(cp));
+ }
+ continue;
}
}
if (*cp == '+' || *cp == '@') {
fmtstr = new_fs(form, "pick.default");
+ if (file) {
+ if (folder) {
+ adios(EX_USAGE, NULL, "\"+folder\" not allowed with -file");
+ }
+ if (msgs.size) {
+ adios(EX_USAGE, NULL, "\"msgs\" not allowed with -file");
+ }
+ if (vecp) {
+ adios(EX_USAGE, NULL, "section arguments not allowed with -file");
+ }
+
+ scan_mbox(file, fmtstr, width);
+ exit(EX_OK);
+ }
+
/*
** If we didn't specify which messages to search,
** then search the whole folder.
for (msgnum = 0; msgnum < msgs.size; msgnum++)
if (!m_convert(mp, msgs.msgs[msgnum]))
exit(EX_USAGE);
- seq_setprev(mp); /* set the previous-sequence */
/*
** If we aren't saving the results to a sequence,
}
}
- head->free(&head);
+ nexus_free(&head);
mp->lowsel = lo;
mp->hghsel = hi;
return 0;
}
+static void
+scan_mbox(char *file, char *fmtstr, int width)
+{
+ FILE *in;
+ int msgnum;
+ int state;
+
+ if (strcmp(file, "-") == 0) {
+ in = stdin;
+ file = "stdin";
+ } else if (!(in = fopen(file, "r"))) {
+ adios(EX_IOERR, file, "unable to open");
+ }
+
+ for (msgnum = 1; ;msgnum++) {
+ state = scan(in, msgnum, SCN_MBOX, fmtstr, width, 0, 0);
+ if (state != SCNMSG) {
+ break;
+ }
+ }
+ fclose(in);
+}
void
putzero_done()
}
}
-
static struct swit parswit[] = {
#define PRAND 0
{ "and", 0 },
{ "before date", 0 },
#define PRDATF 14
{ "datefield field", 5 },
+#define PRTHREAD 15
+ { "thread msg", 0 },
{ NULL, 0 }
};
-/* DEFINITIONS FOR PATTERN MATCHING */
-
-/*
-** We really should be using re_comp() and re_exec() here. Unfortunately,
-** pick advertises that lowercase characters matches characters of both
-** cases. Since re_exec() doesn't exhibit this behavior, we are stuck
-** with this version. Furthermore, we need to be able to save and restore
-** the state of the pattern matcher in order to do things "efficiently".
-**
-** The matching power of this algorithm isn't as powerful as the re_xxx()
-** routines (no \(xxx\) and \n constructs). Such is life.
-*/
-
-#define CCHR 2
-#define CDOT 4
-#define CCL 6
-#define NCCL 8
-#define CDOL 10
-#define CEOF 11
-
-#define STAR 01
-
-#define LBSIZE 1024
-#define ESIZE 1024
-
/*
** DEFINITIONS FOR NEXUS
*/
#define padvise if (!talked++) advise
-
-enum nexus_type {
- TYPE_GREP,
- TYPE_DATE,
- TYPE_OR,
- TYPE_AND,
- TYPE_NOT
-};
-
-struct bin_data {
- struct nexus *left;
- struct nexus *right;
- enum nexus_type type;
- int oldmsgnum;
- boolean leftmatch;
- boolean rightmatch;
- boolean match;
-};
-
-struct date_data {
- char *datef;
- boolean after;
- struct tws tws;
-};
-
-struct grep_data {
- char *header;
- char *pattern;
- regex_t *preg;
-};
-
static int talked;
static int pdebug = 0;
static char *datesw;
static char **argp;
-
/*
** prototypes for date routines
*/
static struct nexus *nexp3(void);
static struct nexus *newnexus(enum nexus_type);
-static boolean BINaction(struct field *, int, void *);
-static boolean NOTaction(struct field *, int, void *);
-static boolean GREPaction(struct field *, int, void *);
-static boolean DATEaction(struct field *, int, void *);
-
-static void BINfree(struct nexus **);
-static void GREPfree(struct nexus **);
-static void DATEfree(struct nexus **);
-
-static void BINdebug(void *, size_t);
-static void GREPdebug(void *, size_t);
-static void DATEdebug(void *, size_t);
-
static int
pcompile(char **vec, char *date)
{
return NULL;
case PROR:
- o = newnexus(TYPE_OR);
- bin = o->data;
+ o = newnexus(or_t);
+ bin = &o->data.b;
bin->left = n;
if ((bin->right = parse()))
return o;
return NULL;
case PRAND:
- o = newnexus(TYPE_AND);
- bin = o->data;
+ o = newnexus(and_t);
+ bin = &o->data.b;
bin->left = n;
if ((bin->right = nexp1()))
return o;
return NULL;
case PRNOT:
- n = newnexus(TYPE_NOT);
- bin = n->data;
+ n = newnexus(not_t);
+ bin = &n->data.b;
if ((bin->left = nexp3()))
return n;
padvise(NULL, "missing negation");
prvarg();
return NULL;
+ case PRTHREAD:
+ if (!(cp = nxtarg())) { /* allow -xyz arguments */
+ padvise(NULL, "missing argument to %s", argp[-2]);
+ }
+ return createpickthread(cp);
case PRCC:
case PRDATE:
case PRFROM:
padvise(NULL, "missing argument to %s", argp[-2]);
return NULL;
}
- n = newnexus(TYPE_GREP);
- gdata = n->data;
+ n = newnexus(grep_t);
+ gdata = &n->data.g;
gdata->header = mh_xstrdup(dp);
snprintf(buffer, sizeof(buffer), "%s", cp);
dp = buffer;
goto pattern;
case PRSRCH:
- n = newnexus(TYPE_GREP);
- gdata = n->data;
+ n = newnexus(grep_t);
+ gdata = &n->data.g;
gdata->header = NULL;
body = TRUE;
if (!(cp = nxtarg())) { /* allow -xyz arguments */
padvise(NULL, "missing argument to %s", argp[-2]);
return NULL;
}
- n = newnexus(TYPE_DATE);
- twsd = n->data;
+ n = newnexus(date_t);
+ twsd = &n->data.d;
twsd->datef = datesw;
if (!tcompile(cp, &twsd->tws, twsd->after = i == PRAFTR)) {
padvise(NULL, "unable to parse %s %s", argp[-2], cp);
newnexus(enum nexus_type t)
{
struct nexus *p = NULL;
- struct bin_data *bin;
-
p = mh_xcalloc(1, sizeof(struct nexus));
+ p->t = t;
+ return p;
- switch (t) {
- case TYPE_NOT:
- p->action = NOTaction;
- p->debug = BINdebug;
- p->free = BINfree;
- p->data = bin = mh_xcalloc(1, sizeof(struct bin_data));
- bin->type = t;
- break;
- case TYPE_AND:
- case TYPE_OR:
- p->action = BINaction;
- p->debug = BINdebug;
- p->free = BINfree;
- p->data = bin = mh_xcalloc(1, sizeof(struct bin_data));
- bin->type = t;
- break;
- case TYPE_GREP:
- p->action = GREPaction;
- p->debug = GREPdebug;
- p->free = GREPfree;
- p->data = mh_xcalloc(1, sizeof(struct grep_data));
- break;
- case TYPE_DATE:
- p->action = DATEaction;
- p->debug = DATEdebug;
- p->free = DATEfree;
- p->data = mh_xcalloc(1, sizeof(struct date_data));
+}
+
+static void nexus_clear(struct nexus *n)
+{
+ n->match = FALSE;
+ switch(n->t) {
+ case and_t:
+ case or_t:
+ nexus_clear(n->data.b.right);
+ /* FALL */
+ case not_t:
+ nexus_clear(n->data.b.left);
break;
default:
- adios(EX_SOFTWARE, NULL, "unknown nexus type %d", t);
+ break;
}
-
- return p;
-
}
static int
struct field f = {{0}};
enum state s = FLD2;
+
if (!head)
return 1;
- if (!talked++ && pdebug && head->debug) {
- head->debug(head->data, 0);
+ nexus_clear(head);
+ if (!talked++ && pdebug) {
+ nexus_debug(head, 0);
}
while (s == FLD2 || s == BODY2) {
s = FLD2;
/* FALL */
case FLD2:
- if (head->action(&f, msgnum, head->data)) {
- return TRUE;
- }
+ nexus_match(&f, msgnum, head);
break;
case BODY2:
if (!body) {
- return FALSE;
- }
- if (head->action(&f, msgnum, head->data)) {
- return TRUE;
+ return head->match;
}
+ nexus_match(&f, msgnum, head);
break;
case IOERR2:
advise(NULL, "IOERR in message %d\n", msgnum);
return FALSE;
case FILEEOF2:
- return FALSE;
+ break;
default:
adios(EX_SOFTWARE, "m_getfld2", "returned unknown state %d at message %d", s, msgnum);
}
}
- return FALSE;
+ return head->match;
}
-void
-print_debug_level(size_t level)
+static boolean
+match_grep(struct field *f, struct grep_data *g)
{
- size_t i;
+ int ret;
+ char buf[BUFSIZ];
- for (i = 0; i < level; i++) {
- fputs("| ", stderr);
+ if (!g->header && *f->name) {
+ return FALSE;
}
-}
-void
-BINdebug(void *data, size_t level)
-{
- struct bin_data *bd = data;
-
- print_debug_level(level);
+ if (!g->header) {
+ ret = regexec(g->preg, f->value, 0, NULL, 0);
+ goto out;
+ }
- switch (bd->type) {
- case TYPE_OR:
- fputs("OR\n", stderr);
- break;
- case TYPE_AND:
- fputs("AND\n", stderr);
- break;
- case TYPE_NOT:
- fputs("NOT\n", stderr);
- break;
- default:
- advise(NULL, "binary nexus has unknown type: %d\n", bd->type);
- return;
+ /* check for the right field */
+ if (!(g->header && *g->header && mh_strcasecmp(g->header, f->name)==0)) {
+ return FALSE;
}
- if (bd->left && bd->left->debug) {
- bd->left->debug(bd->left->data, level+1);
+ if (decode_rfc2047(f->value, buf, sizeof(buf))) {
+ ret = regexec(g->preg, buf, 0, NULL, 0);
} else {
- print_debug_level(level+1);
- fputs("can't debug left child\n", stderr);
+ ret = regexec(g->preg, f->value, 0, NULL, 0);
}
-
- if (bd->right && bd->right->debug) {
- bd->right->debug(bd->right->data, level+1);
- } else if (bd->type != TYPE_NOT) {
- print_debug_level(level+1);
- fputs("can't debug right child\n", stderr);
+out:
+ switch (ret) {
+ case 0:
+ return TRUE;
+ case REG_NOMATCH:
+ return FALSE;
+ default:
+ regerror(ret, g->preg, buf, sizeof(buf));
+ fprintf(stderr, "%s\n", buf);
+ return FALSE;
}
}
static boolean
-NOTaction(struct field *f, int msgnum, void *data)
-{
- struct bin_data *bin = data;
- return !bin->left->action(f, msgnum, bin->left->data);
-}
-
-static boolean
-BINaction(struct field *f, int msgnum, void *data)
+match_date(struct field *f, int msgnum, struct date_data *dd)
{
- struct bin_data *bin = data;
+ struct tws *tw;
+ char *bp;
+ boolean ret = FALSE;
- if (bin->oldmsgnum != msgnum) {
- bin->oldmsgnum = msgnum;
- bin->match = FALSE;
- bin->leftmatch = FALSE;
- bin->rightmatch = FALSE;
+ if (mh_strcasecmp(f->name, dd->datef)!=0) {
+ return FALSE;
}
-
- if (bin->match) {
- return bin->match;
+ bp = mh_xstrdup(f->value);
+ if ((tw = dparsetime(bp)) == NULL) {
+ advise(NULL, "unable to parse %s field in message %d, not matching...", dd->datef, msgnum);
+ } else if (dd->after) {
+ ret = twsort(tw, &dd->tws) > 0;
+ } else {
+ ret = twsort(tw, &dd->tws) < 0;
}
- bin->leftmatch = bin->leftmatch || bin->left->action(f, msgnum, bin->left->data);
- bin->rightmatch = bin->rightmatch || bin->right->action(f, msgnum, bin->right->data);
+ mh_free0(&bp);
+ return ret;
+}
- switch (bin->type) {
- case TYPE_OR:
- bin->match = bin->leftmatch || bin->rightmatch;
+static boolean
+nexus_match(struct field *f, int msgnum, struct nexus *n)
+{
+ switch (n->t) {
+ case and_t:
+ n->match = nexus_match(f, msgnum, n->data.b.left);
+ n->match = nexus_match(f, msgnum, n->data.b.right) && n->match;
+ break;
+ case or_t:
+ n->match = nexus_match(f, msgnum, n->data.b.left);
+ n->match = nexus_match(f, msgnum, n->data.b.right) || n->match;
+ break;
+ case not_t:
+ n->match = !nexus_match(f, msgnum, n->data.b.left);
+ break;
+ case date_t:
+ if (n->match) {
+ return n->match;
+ }
+ n->match = match_date(f, msgnum, &n->data.d);
break;
- case TYPE_AND:
- bin->match = bin->leftmatch && bin->rightmatch;
+ case grep_t:
+ if (n->match) {
+ return n->match;
+ }
+ n->match = match_grep(f, &n->data.g);
break;
default:
- adios(EX_SOFTWARE, NULL, "unknown nexus type: %d\n", bin->type);
+ adios(EX_SOFTWARE, NULL, "nexus tree contains a unknown nexus_type (%d)", n->t);
}
+ return n->match;
+}
- return bin->match;
+static void
+nexus_debug(struct nexus *n, size_t level)
+{
+ struct date_data *dd;
+ print_debug_level(level);
+ switch (n->t) {
+ case and_t:
+ fputs("AND\n", stderr);
+ nexus_debug(n->data.b.left, level+1);
+ nexus_debug(n->data.b.right, level+1);
+ break;
+ case or_t:
+ fputs("OR\n", stderr);
+ nexus_debug(n->data.b.left, level+1);
+ nexus_debug(n->data.b.right, level+1);
+ break;
+ case not_t:
+ fputs("NOT\n", stderr);
+ nexus_debug(n->data.b.left, level+1);
+ break;
+ case grep_t:
+ nexus_debug_grep(&n->data.g);
+ break;
+ case date_t:
+ dd = &n->data.d;
+ fprintf(stderr, "TEMPORAL(%s) %s: %s\n",dd->after ? "after" : "before", dd->datef, dasctime(&dd->tws));
+ break;
+ default:
+ adios(EX_SOFTWARE, NULL, "nexus tree contains a unknown nexus_type (%d)", n->t);
+ }
}
static void
-BINfree(struct nexus **n)
+nexus_debug_grep(struct grep_data *gd)
{
- struct nexus *bin = *n;
- struct bin_data *bd = bin->data;
+ char *buf, *buf2, *pbuf, *pbuf2;
- if (bd->left && bd->left->free) {
- bd->left->free(&bd->left);
- } else {
- advise(NULL, "BUG: can't free left child");
+ pbuf = pbuf2 = mh_xstrdup(gd->pattern);
+
+ for (;*pbuf2; pbuf2++) {
+ *pbuf2 = tolower(*pbuf2);
}
- if (bd->right && bd->right->free) {
- bd->right->free(&bd->right);
+ if (gd->header) {
+ buf = buf2 = mh_xstrdup(gd->header);
+ for (;*buf2; buf2++) {
+ *buf2 = tolower(*buf2);
+ }
+ fprintf(stderr, "PETTERN(%s) %s\n", buf, pbuf);
} else {
- advise(NULL, "BUG: can't free right child");
+ fprintf(stderr, "PETTERN(BODY) %s\n", pbuf);
}
+ mh_free0(&buf);
+ mh_free0(&pbuf);
+}
+static void
+nexus_free(struct nexus **n)
+{
+ if (!(*n)) {
+ return;
+ }
+ switch((*n)->t) {
+ case and_t:
+ case or_t:
+ nexus_free(&(*n)->data.b.right);
+ /* FALL */
+ case not_t:
+ nexus_free(&(*n)->data.b.left);
+ break;
+ case grep_t:
+ mh_free0(&(*n)->data.g.header);
+ mh_free0(&(*n)->data.g.pattern);
+ regfree((*n)->data.g.preg);
+ case date_t:
+ break;
+ default:
+ advise(NULL, "Unknown nexus_type (%d) to free", (*n)->t);
+ }
mh_free0(n);
}
+static void
+print_debug_level(size_t level)
+{
+ size_t i;
+
+ for (i = 0; i < level; i++) {
+ fputs("| ", stderr);
+ }
+}
+
static int
gcompile(struct grep_data *g, const char *astr)
{
}
-static boolean
-GREPaction(struct field *f, int msgnum, void *data)
-{
- struct grep_data *g = data;
- int ret;
- char *buf;
-
- /* check for the write field */
- if (g->header && *g->header && mh_strcasecmp(g->header, f->name)) {
- return FALSE;
- }
-
- if (!g->header && *f->name) {
- return FALSE;
- }
-
- ret = regexec(g->preg, f->value, 0, NULL, 0) == REG_NOMATCH;
- switch (ret) {
- case 0:
- return TRUE;
- case REG_NOMATCH:
- return FALSE;
- default:
- buf = mh_xcalloc(BUFSIZ, sizeof(char));
- regerror(ret, g->preg, buf, BUFSIZ*sizeof(char));
- fprintf(stderr, "%s\n", buf);
- return FALSE;
- }
-
-}
-
-static void
-GREPfree(struct nexus **n)
-{
- struct grep_data *gd = (*n)->data;
- mh_free0(&gd->header);
- regfree(gd->preg);
- mh_free0(n);
-}
-
-static void
-GREPdebug(void *data, size_t level)
-{
- struct grep_data *gd = data;
- char *buf, *buf2, *pbuf, *pbuf2;
-
- pbuf = pbuf2 = mh_xstrdup(gd->pattern);
-
- for (;*pbuf2; pbuf2++) {
- *pbuf2 = tolower(*pbuf2);
- }
-
- print_debug_level(level);
-
- if (gd->header) {
- buf = buf2 = mh_xstrdup(gd->header);
- for (;*buf2; buf2++) {
- *buf2 = tolower(*buf2);
- }
- fprintf(stderr, "PETTERN(%s) %s\n", buf, pbuf);
- } else {
- fprintf(stderr, "PETTERN(BODY) %s\n", pbuf);
- }
- mh_free0(&buf);
- mh_free0(&pbuf);
-}
-
static int
tcompile(char *ap, struct tws *tb, int isafter)
{
}
-static boolean
-DATEaction(struct field *f, int msgnum, void *data)
+static struct nexus *
+createpickthread(char *msgs)
{
- struct date_data *dd = data;
- boolean state = FALSE;
- char *bp;
- struct tws *tw;
+ char *folder = NULL;
+ struct msgs_array msgarray = {0};
+ struct msgs_array files = {0};
+ struct nexus *ret = NULL;
+ struct nexus *c;
+ struct nexus *or;
+ struct bin_data *bd;
+ char *buf;
+ char **cp = brkstring(msgs, " \t", NULL);
+ int i;
- if (mh_strcasecmp(f->name, dd->datef)!=0) {
- return FALSE;
+ for (; cp && *cp; cp++) {
+ switch (**cp) {
+ case '@':
+ case '+':
+ if (folder) {
+ advise("","");
+ break;
+ }
+ folder = mh_xstrdup(*cp);
+ break;
+ default:
+ app_msgarg(&msgarray, mh_xstrdup(*cp));
+ }
}
- bp = mh_xstrdup(f->value);
- if ((tw = dparsetime(bp)) == NULL) {
- advise(NULL, "unable to parse %s field in message %d, not matching...", dd->datef, msgnum);
- state = FALSE;
- } else if (dd->after) {
- state = twsort(tw, &dd->tws) > 0;
- } else {
- state = twsort(tw, &dd->tws) < 0;
+
+ parse_msgs(&msgarray, folder, &files);
+
+ for (i = 0; i < files.size; i++) {
+ buf = getthreadid(files.msgs[i]);
+ if (!buf) {
+ adios(EX_DATAERR, NULL, "message %s is not part of a thread", basename(files.msgs[i]));
+ continue;
+ }
+
+ c = createonethread(buf);
+
+ if (!ret) {
+ ret = c;
+ continue;
+ }
+
+
+ or = newnexus(or_t);
+ bd = &or->data.b;
+ bd->right = ret;
+ bd->left = c;
+ ret = or;
}
- mh_free0(&bp);
+ mh_free0(&(files.msgs));
+ mh_free0(&(msgarray.msgs));
- return state;
+ return ret;
}
-static void
-DATEfree(struct nexus **n)
+static struct nexus *
+createonethread(char *c)
{
- struct date_data *dd = (*n)->data;
+ struct nexus *ret = newnexus(or_t);
+ struct nexus *left = newnexus(grep_t);
+ struct nexus *right = newnexus(grep_t);
+ char buf[BUFSIZ];
- mh_free0(&dd->datef);
- mh_free0(n);
-}
+ ret->data.b.left = left;
+ ret->data.b.right = right;
+ left->data.g.header = mh_xstrdup("message-id");
-static void
-DATEdebug(void *data, size_t level)
-{
- struct date_data *dd = data;
- print_debug_level(level);
- fprintf(stderr, "TEMPORAL(%s) %s: %s\n",dd->after ? "after" : "before", dd->datef, dasctime(&dd->tws));
+
+ snprintf(buf, sizeof(buf), "^[ \t]*<%s>", c);
+ if(!gcompile(&left->data.g, buf)) {
+ padvise(NULL, "pattern error %s", c);
+ goto error;
+ }
+
+ right->data.g.header = mh_xstrdup("references");
+
+ snprintf(buf, sizeof(buf), "^[ \t]*<%s>", c);
+ if(!gcompile(&right->data.g, buf)) {
+ padvise(NULL, "pattern error in %s", c);
+ goto error;
+ }
+
+ return ret;
+
+error:
+ nexus_free(&ret);
+ return NULL;
+
}