diff options
-rw-r--r-- | man/mpick.1 | 66 | ||||
-rw-r--r-- | mpick.c | 145 | ||||
-rwxr-xr-x | t/2000-mpick.t | 34 |
3 files changed, 44 insertions, 201 deletions
diff --git a/man/mpick.1 b/man/mpick.1 index bb7d324..007fca6 100644 --- a/man/mpick.1 +++ b/man/mpick.1 @@ -1,4 +1,4 @@ -.Dd July 27, 2016 +.Dd July 30, 2020 .Dt MPICK 1 .Os .Sh NAME @@ -6,10 +6,11 @@ .Nd advanced message filter .Sh SYNOPSIS .Nm +.Op Fl F Ar file .Op Fl T .Op Fl v .Op Fl t Ar test -.Op Ar msglist\ ... +.Op Ar msgs\ ... .Sh DESCRIPTION .Nm prints all matching messages. @@ -23,6 +24,12 @@ will read filenames from the standard input. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl F Ar file +Read expression from +.Ar file +and only show matching messages, see +.Sx TESTS . +.Ar file , .It Fl T Include whole thread. .It Fl t Ar test @@ -33,61 +40,6 @@ see .It Fl v Print how many messages were tested and picked to standard error. .El -.Sh MSGLISTS -.Nm -message lists -.Pq msglist -are mostly compatible with -.Xr mailx 1 . -They are message specifications used as shortened -.Sx TESTS , -and can include: -.Bl -tag -width Ds -.It Ar n -Message number -.Ar n . -.It Ar n Ns Cm \&: Ns Ar m , Ar n Ns Cm \&- Ns Ar m -An inclusive range of message numbers between -.Ar n -and -.Ar m . -.It Ar address -All messages from -.Ar address . -.It Cm \&/ Ns Ar string -All messages with -.Ar string -in the subject line -.Pq case ignored . -.It Cm \&: Ns Ar c -All messages of type -.Ar c , -where -.Ar c -shall be one of: -.Bl -tag -width Ds -.It Cm D -Draft messages. -.It Cm P -Passed -.Pq resent, forwarded or bounced -messages. -.It Cm R -Replied messages. -.It Cm F -Flagged messages. -.It Cm d , Cm T -Deleted messages. -.It Cm n -New messages. -.It Cm o -Old messages. -.It Cm r , Cm S -Read messages. -.It Cm u -Unread messages. -.El -.El .Sh TESTS .Nm tests are given by the following EBNF: diff --git a/mpick.c b/mpick.c index e62604b..bff0cc3 100644 --- a/mpick.c +++ b/mpick.c @@ -1007,108 +1007,6 @@ parse_buf(const char *f, char *s) return e; } -static struct expr * -parse_msglist(char *s) -{ - int64_t n, m; - int r; - struct expr *e1, *e2; - char *d; - enum flags flag; - - switch (*s) { - case '/': - s++; - e1 = mkexpr(EXPR_REGEXI); - 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->c.regex, msg, sizeof msg); - parse_error("invalid regex '%s': %s", s, msg); - } - return e1; - case ':': - 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; - case 'd': /* FALL THROUGH */ - case 'T': flag = FLAG_TRASHED; break; - case 'u': n = 1; /* FALL THROUGH */ - case 'r': /* FALL THROUGH */ - case 'S': flag = FLAG_SEEN; break; - case 'o': n = 1; /* FALL THROUGH */ - case 'n': flag = FLAG_NEW; break; - case 'R': flag = FLAG_REPLIED; break; - default: parse_error("unknown flag at '%s'", 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 = s; - - if (((d = strchr(s, ':')) || (d = strchr(s, '-'))) - && parse_num(&n) && (pos = 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 { - char *disp, *addr; - - blaze822_addr(s, &disp, &addr); - if (!disp && !addr) - parse_error("invalid address '%s'", s); - - d = (disp) ? disp : addr; - - e1 = mkexpr(EXPR_REGEXI); - 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->c.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE); - if (r != 0) { - char msg[256]; - regerror(r, e1->c.regex, msg, sizeof msg); - parse_error("invalid regex '%s': %s", d, msg); - } - - return e1; - } - } - return 0; -} - time_t msg_date(struct mailinfo *m) { @@ -1541,42 +1439,35 @@ main(int argc, char *argv[]) num = 1; vflag = 0; - while ((c = getopt(argc, argv, "Tt:v")) != -1) + while ((c = getopt(argc, argv, "F:Tt:v")) != -1) switch (c) { - case 'T': Tflag = need_thr = 1; 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 (strchr(argv[c], '/') && access(argv[c], R_OK) == 0) { - break; - } - expr = chain(expr, EXPR_AND, parse_msglist(argv[c])); - } - - for (; c < argc; c++) { + case 'F': + { char *s; off_t len; - int r = slurp(argv[c], &s, &len); + int r = slurp(optarg, &s, &len); if (r != 0) { fprintf(stderr, "%s: error opening file '%s': %s\n", - argv0, argv[c], strerror(r)); + argv0, optarg, strerror(r)); exit(1); } - expr = chain(expr, EXPR_AND, parse_buf(argv[c], s)); + expr = chain(expr, EXPR_AND, parse_buf(optarg, s)); free(s); + break; + } + case 'T': Tflag = need_thr = 1; 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] [-F file] [msgs...]\n", argv0); + exit(1); } - } - if (isatty(0)) - i = blaze822_loop1(":", need_thr ? collect : oneline); + void *cb = need_thr ? collect : oneline; + if (argc == optind && isatty(0)) + i = blaze822_loop1(":", cb); else - i = blaze822_loop(0, 0, need_thr ? collect : oneline); + i = blaze822_loop(argc-optind, argv+optind, cb); /* print and free last thread */ if (Tflag && thr) diff --git a/t/2000-mpick.t b/t/2000-mpick.t index 2f530e8..27f784f 100755 --- a/t/2000-mpick.t +++ b/t/2000-mpick.t @@ -19,14 +19,14 @@ touch "inbox/cur/7:2,SR" touch "inbox/cur/8:2,SF" touch "inbox/cur/9:2," -check_same 'flag trashed' 'mlist inbox | mpick :T' 'mlist -T inbox' -check_same 'flag not trashed' 'mlist inbox | mpick -t "!trashed"' 'mlist -t inbox' -check_same 'flag seen' 'mlist inbox | mpick :S' 'mlist -S inbox' +check_same 'flag trashed' 'mlist inbox | mpick -t trashed' 'mlist -T inbox' +check_same 'flag not trashed' 'mlist inbox | mpick -t !trashed' 'mlist -t inbox' +check_same 'flag seen' 'mlist inbox | mpick -t seen' 'mlist -S inbox' check_same 'flag not seen' 'mlist inbox | mpick -t !seen' 'mlist -s inbox' -check_same 'flag seen and trashed' 'mlist inbox | mpick :S :T' 'mlist -ST inbox' -check_same 'flag seen and not trashed' 'mlist inbox | mpick -t "seen && !trashed"' 'mlist -St inbox' -check_same 'flag replied' 'mlist inbox | mpick :R' 'mlist -R inbox' -check_same 'flag forwarded' 'mlist inbox | mpick :F' 'mlist -F inbox' +check_same 'flag seen and trashed' 'mlist inbox | mpick -t seen -t trashed' 'mlist -ST inbox' +check_same 'flag seen and not trashed' 'mlist inbox | mpick -t seen -t !trashed' 'mlist -St inbox' +check_same 'flag replied' 'mlist inbox | mpick -t replied' 'mlist -R inbox' +check_same 'flag forwarded' 'mlist inbox | mpick -t passed' 'mlist -P inbox' cat <<! | mmime >"inbox/cur/1:2,S" @@ -62,14 +62,14 @@ Greetings ! cat <<! >shebang -#!$(command -v mpick) +#!$(command -v mpick) -F 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' +check 'search subject' 'mlist inbox | mpick -t "subject =~~ \"wow\"" | grep -q inbox/cur/9:2,' +check_test 'search addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l' +check_test 'search name' -eq 2 'mlist inbox | mpick -t "from.disp == \"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' @@ -92,7 +92,7 @@ let bar = from.disp == "Peter Example" in foo && bar # another comment ! -check_test 'let expression' -eq 2 'mlist inbox | mpick ./expr | wc -l' +check_test 'let expression' -eq 2 'mlist inbox | mpick -F ./expr | wc -l' cat <<! >expr let foo = from.addr == "peter@example.org" @@ -101,7 +101,7 @@ let bar = from.disp == "Peter Example" in foo && foo ! -check_test 'let expression double free' -eq 2 'mlist inbox | mpick ./expr | wc -l' +check_test 'let expression double free' -eq 2 'mlist inbox | mpick -F ./expr | wc -l' cat <<! >expr let foo = @@ -111,7 +111,7 @@ let foo = in foo ! -check_test 'let expression nested' -eq 2 'mlist inbox | mpick ./expr | wc -l' +check_test 'let expression nested' -eq 2 'mlist inbox | mpick -F ./expr | wc -l' cat <<! >expr let foo = from.addr == "peter@example.org" @@ -119,7 +119,7 @@ let bar = foo && subject =~ "wow" in bar ! -check_test 'let scoping' -eq 1 'mlist inbox | mpick ./expr | wc -l' +check_test 'let scoping' -eq 1 'mlist inbox | mpick -F ./expr | wc -l' cat <<! >expr let foo = from.addr == "peter@example.org" @@ -127,8 +127,8 @@ 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' +check_test 'multi redir' -eq 4 'mlist inbox | mpick -F ./expr | wc -l' +check_test 'multi redir prefixes' -eq 2 'mlist inbox | mpick -F ./expr | cut -d: -f1 | sort -u | wc -l' ) |