regex: updates from neatvi
[neatmail.git] / ex.c
blobc2556fb749dd6d8089e3c916715d3760da767909
1 #include <ctype.h>
2 #include <fcntl.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <sys/stat.h>
8 #include "mail.h"
9 #include "regex.h"
11 #define EXLEN 512
13 static struct mbox *mbox;
14 static int pos;
15 static char kwd[EXLEN];
16 static char kwd_hdr[EXLEN];
17 static regex_t kwd_re;
19 static char *ex_loc(char *s, char *loc)
21 while (*s == ':' || isspace((unsigned char) *s))
22 s++;
23 while (*s && !isalpha((unsigned char) *s)) {
24 if (*s == '/' || *s == '?') {
25 int d = *s;
26 *loc++ = *s++;
27 while (*s && *s != d) {
28 if (*s == '\\' && s[1])
29 *loc++ = *s++;
30 *loc++ = *s++;
33 if (*s)
34 *loc++ = *s++;
36 *loc = '\0';
37 return s;
40 static char *ex_cmd(char *s, char *cmd)
42 while (isspace((unsigned char) *s))
43 s++;
44 while (isalpha((unsigned char) *s))
45 *cmd++ = *s++;
46 *cmd = '\0';
47 return s;
50 static char *ex_arg(char *s, char *arg)
52 while (isspace((unsigned char) *s))
53 s++;
54 if (*s == '"') {
55 s++;
56 while (*s && *s != '"') {
57 if (*s == '\\' && s[1])
58 s++;
59 *arg++ = *s++;
61 if (*s)
62 s++;
63 } else {
64 while (*s && !isspace((unsigned char) *s)) {
65 if (*s == '\\' && s[1])
66 s++;
67 *arg++ = *s++;
70 *arg = '\0';
71 return s;
74 static int ex_eol(char *s)
76 while (isspace((unsigned char) *s))
77 s++;
78 return !*s;
81 static int ex_keyword(char *pat)
83 struct sbuf *sb;
84 char *b = pat;
85 char *e = b;
86 sb = sbuf_make();
87 while (*++e) {
88 if (*e == *pat)
89 break;
90 if (*e == '\\' && e[1])
91 e++;
92 sbuf_chr(sb, (unsigned char) *e);
94 if (sbuf_len(sb) && strcmp(kwd, sbuf_buf(sb))) {
95 if (kwd[0])
96 regfree(&kwd_re);
97 snprintf(kwd, sizeof(kwd), "%s", sbuf_buf(sb));
98 if (regcomp(&kwd_re, kwd, REG_EXTENDED | REG_ICASE))
99 kwd[0] = '\0';
100 if (kwd[0] == '^' && isalpha((unsigned char) (kwd[1])) &&
101 strchr(kwd, ':')) {
102 int len = strchr(kwd, ':') - kwd;
103 memcpy(kwd_hdr, kwd + 1, len);
104 kwd_hdr[len] = '\0';
105 } else {
106 strcpy(kwd_hdr, "subject:");
109 sbuf_free(sb);
110 return !kwd[0];
113 static int ex_match(int i)
115 char *msg;
116 long msglen;
117 char *hdr;
118 char *buf;
119 int len, ret;
120 if (mbox_get(mbox, i, &msg, &msglen))
121 return 1;
122 hdr = msg_get(msg, msglen, kwd_hdr);
123 if (!hdr)
124 return 1;
125 len = hdrlen(hdr, msg + msglen - hdr);
126 buf = malloc(len + 1);
127 memcpy(buf, hdr, len);
128 buf[len] = '\0';
129 ret = regexec(&kwd_re, buf, 0, NULL, 0);
130 free(buf);
131 return ret != 0;
134 static int ex_search(char *pat)
136 int dir = *pat == '/' ? +1 : -1;
137 int i = pos + dir;
138 if (ex_keyword(pat))
139 return 1;
140 while (i >= 0 && i < mbox_len(mbox)) {
141 if (!ex_match(i))
142 return i;
143 i += dir;
145 return pos;
148 static int ex_lineno(char *num)
150 int n = pos;
151 if (!num[0] || num[0] == '.')
152 n = pos;
153 if (isdigit(num[0]))
154 n = atoi(num);
155 if (num[0] == '$')
156 n = mbox_len(mbox) - 1;
157 if (num[0] == '-')
158 n = pos - (num[1] ? ex_lineno(num + 1) : 1);
159 if (num[0] == '+')
160 n = pos + (num[1] ? ex_lineno(num + 1) : 1);
161 if (num[0] == '/' && num[1])
162 n = ex_search(num);
163 if (num[0] == '?' && num[1])
164 n = ex_search(num);
165 return n;
168 static int ex_region(char *loc, int *beg, int *end)
170 if (loc[0] == '%') {
171 *beg = 0;
172 *end = mbox_len(mbox);
173 return 0;
175 if (!*loc || loc[0] == ';') {
176 *beg = pos;
177 *end = pos == mbox_len(mbox) ? pos : pos + 1;
178 return 0;
180 *beg = ex_lineno(loc);
181 while (*loc && *loc != ',' && *loc != ';')
182 loc++;
183 if (*loc == ',')
184 *end = ex_lineno(++loc) + 1;
185 else
186 *end = *beg == mbox_len(mbox) ? *beg : *beg + 1;
187 if (*beg < 0 || *beg >= mbox_len(mbox))
188 return 1;
189 if (*end < *beg || *end > mbox_len(mbox))
190 return 1;
191 return 0;
194 static int ec_print(char *arg)
196 printf("%d\n", pos);
197 return 0;
200 static int ec_rm(char *arg)
202 return mbox_set(mbox, pos, "", 0);
205 static int ec_cp(char *arg)
207 char box[EXLEN];
208 char *msg;
209 long msz;
210 int fd;
211 arg = ex_arg(arg, box);
212 if (mbox_get(mbox, pos, &msg, &msz))
213 return 1;
214 if ((fd = open(box, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR)) < 0)
215 return 1;
216 xwrite(fd, msg, msz);
217 close(fd);
218 return 0;
221 static int ec_mv(char *arg)
223 ec_cp(arg);
224 ec_rm("");
225 return 0;
228 static int ec_hd(char *arg)
230 char hdr[EXLEN], val[EXLEN];
231 char hln[EXLEN];
232 char *msg, *mod;
233 long msglen, modlen;
234 arg = ex_arg(arg, hdr);
235 if (mbox_get(mbox, pos, &msg, &msglen))
236 return 1;
237 hln[0] = '\0';
238 if (!ex_eol(arg)) {
239 arg = ex_arg(arg, val);
240 snprintf(hln, sizeof(hln), "%s %s\n", hdr, val);
242 if (msg_set(msg, msglen, &mod, &modlen, hdr, hln))
243 return 1;
244 mbox_set(mbox, pos, mod, modlen);
245 free(mod);
246 return 0;
249 static int ec_ft(char *arg)
251 char cmd[EXLEN];
252 char *msg, *mod;
253 long msglen, modlen;
254 arg = ex_arg(arg, cmd);
255 if (mbox_get(mbox, pos, &msg, &msglen))
256 return 1;
257 if (xpipe(cmd, msg, msglen, &mod, &modlen))
258 return 1;
259 mbox_set(mbox, pos, mod, modlen);
260 free(mod);
261 return 0;
264 static int ec_threadjoin(char *arg)
266 char *th, *msg, *mod;
267 long thlen, msglen, modlen;
268 struct sbuf *sb;
269 char *id, *id_end;
270 if (mbox_get(mbox, atoi(arg), &th, &thlen))
271 return 1;
272 if (mbox_get(mbox, pos, &msg, &msglen))
273 return 1;
274 id = msg_get(th, thlen, "message-id:");
275 if (!id)
276 return 1;
277 id_end = id + hdrlen(id, th + thlen - id);
278 id = strchr(id, ':') + 1;
279 sb = sbuf_make();
280 sbuf_str(sb, "In-Reply-To:");
281 sbuf_mem(sb, id, id_end - id);
282 if (msg_set(msg, msglen, &mod, &modlen, "in-reply-to:", sbuf_done(sb)))
283 return 1;
284 mbox_set(mbox, pos, mod, modlen);
285 free(mod);
286 return 0;
289 static int ec_chop(char *arg)
291 char *msg, *mod;
292 long msglen, modlen;
293 struct sbuf *sb;
294 long newlen = arg ? atoi(arg) * 1024 : 0;
295 long end, beg = 0;
296 if (mbox_get(mbox, pos, &msg, &msglen))
297 return 1;
298 while (beg + 2 < msglen && (msg[beg] != '\n' || msg[beg + 1] != '\n'))
299 beg++;
300 end = beg + 1 + newlen;
301 while (end < msglen && msg[end] != '\n')
302 end++;
303 if (end >= msglen)
304 return 0;
305 sb = sbuf_make();
306 sbuf_mem(sb, msg, end);
307 sbuf_str(sb, "\nCHOPPED...\n\n");
308 modlen = sbuf_len(sb);
309 mod = sbuf_done(sb);
310 mbox_set(mbox, pos, mod, modlen);
311 free(mod);
312 return 0;
315 static int ec_wr(char *arg)
317 char box[EXLEN];
318 arg = ex_arg(arg, box);
319 if (box[0])
320 mbox_copy(mbox, box);
321 else
322 mbox_save(mbox);
323 return 0;
326 static int ex_exec(char *ec);
328 static int ec_g(char *arg, int not)
330 while (isspace((unsigned char) *arg))
331 arg++;
332 if (arg[0] != '/' || ex_keyword(arg))
333 return 1;
334 arg++;
335 while (arg[0] && arg[0] != '/')
336 arg++;
337 if (kwd[0] && arg[0] == '/') {
338 arg++;
339 if (!ex_match(pos) == !not)
340 ex_exec(arg);
341 return 0;
343 return 1;
346 static int ex_exec(char *ec)
348 char cmd[EXLEN];
349 char *arg = ex_cmd(ec, cmd);
350 if (!strcmp("rm", cmd))
351 return ec_rm(arg);
352 if (!strcmp("cp", cmd))
353 return ec_cp(arg);
354 if (!strcmp("mv", cmd))
355 return ec_mv(arg);
356 if (!strcmp("hd", cmd) || !strcmp("set", cmd))
357 return ec_hd(arg);
358 if (!strcmp("ft", cmd) || !strcmp("filt", cmd))
359 return ec_ft(arg);
360 if (!strcmp("w", cmd))
361 return ec_wr(arg);
362 if (!strcmp("g", cmd))
363 return ec_g(arg, 0);
364 if (!strcmp("g!", cmd))
365 return ec_g(arg, 1);
366 if (!strcmp("tj", cmd))
367 return ec_threadjoin(arg);
368 if (!strcmp("ch", cmd) || !strcmp("chop", cmd))
369 return ec_chop(arg);
370 if (!strcmp("p", cmd))
371 return ec_print(arg);
372 return 1;
375 static int ec_stat(char *ec)
377 char *val;
378 char newval[16];
379 int c0 = ec[0] >= 'A' && ec[0] <= 'Z' ? ec[0] : 0;
380 int c1 = ec[1] >= 'A' && ec[1] <= 'Z' ? ec[1] : 0;
381 int s0 = 'N';
382 int s1 = 0;
383 int i = 1 + (c1 != 0);
384 int n = 0;
385 char *msg, *mod;
386 long msglen, modlen;
387 while (ec[i] >= '0' && ec[i] <= '9')
388 n = n * 10 + ec[i++] - '0';
389 if (ec[i] == '@') /* message not in the main mbox */
390 return 1;
391 if (mbox_get(mbox, n, &msg, &msglen))
392 return 1;
393 pos = n;
394 val = msg_get(msg, msglen, "status:");
395 if (val) {
396 val += strlen("status:");
397 while (val + 1 < msg + msglen && isspace((unsigned char) val[0]))
398 val++;
399 s0 = !isspace(val[0]) ? (unsigned char) val[0] : s0;
400 s1 = !isspace(val[1]) ? (unsigned char) val[1] : s1;
402 if (s0 == c0 && s1 == c1)
403 return 0;
404 if (c1)
405 sprintf(newval, "Status: %c%c\n", c0, c1);
406 else
407 sprintf(newval, "Status: %c\n", c0);
408 if (msg_set(msg, msglen, &mod, &modlen, "status:", newval))
409 return 1;
410 mbox_set(mbox, pos, mod, modlen);
411 free(mod);
412 return 0;
415 int ex(char *argv[])
417 char ec[EXLEN];
418 char loc[EXLEN];
419 int beg, end, i;
420 char *cmd;
421 char *path[16] = {NULL};
422 int path_n = 0;
423 if (!argv[0]) {
424 printf("usage: neatmail ex [mbox] <cmds\n");
425 return 1;
427 for (i = 0; argv[i] && argv[i][0] == '-'; i++) {
428 if (argv[i][1] == 'b')
429 path[path_n++] = argv[i][2] ? argv[i] + 2 : argv[++i];
431 for (; argv[i]; i++)
432 path[path_n++] = argv[i];
433 mbox = mbox_open(path);
434 if (!mbox) {
435 fprintf(stderr, "neatmail: cannot open <%s>\n", path[0]);
436 return 1;
438 while (fgets(ec, sizeof(ec), stdin)) {
439 char *cur = loc;
440 if (isupper((unsigned char) ec[0]) && ec[1])
441 ec_stat(ec);
442 if (ec[0] != ':')
443 continue;
444 cmd = ex_loc(ec, loc);
445 do {
446 if (!ex_region(cur, &beg, &end)) {
447 for (i = beg; i < end; i++) {
448 pos = i;
449 ex_exec(cmd);
452 while (*cur && *cur != ';')
453 cur++;
454 if (*cur == ';')
455 cur++;
456 } while (*cur);
458 mbox_free(mbox);
459 if (kwd[0])
460 regfree(&kwd_re);
461 return 0;