regex: updates from neatvi
[neatmail.git] / mk.c
blob49b19f5adafe4e5383c788cd7245417656f5858b
1 #include <ctype.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <time.h>
6 #include "mail.h"
8 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
10 static int uc_len(char *s)
12 int c = (unsigned char) s[0];
13 if (~c & 0xc0) /* ASCII or invalid */
14 return c > 0;
15 if (~c & 0x20)
16 return 2;
17 if (~c & 0x10)
18 return 3;
19 if (~c & 0x08)
20 return 4;
21 return 1;
24 static int uc_wid(char *s)
26 return 1;
29 static char *msg_dec(char *msg, long msz, char *hdr)
31 char *val = msg_get(msg, msz, hdr);
32 char *buf, *ret;
33 int val_len;
34 if (!val)
35 return NULL;
36 val_len = hdrlen(val, msg + msz - val) - 1;
37 buf = malloc(val_len + 1);
38 memcpy(buf, val, val_len);
39 buf[val_len] = '\0';
40 ret = msg_hdrdec(buf);
41 free(buf);
42 return ret;
45 static int msg_stat(char *msg, long msz, int pos, int def)
47 char *val = msg_get(msg, msz, "status:");
48 if (!val)
49 return def;
50 val += strlen("status:");
51 while (val + pos < msg + msz && isspace((unsigned char) val[0]))
52 val++;
53 return isalpha((unsigned char) val[pos]) ? val[pos] : def;
56 static int datedate(char *s);
58 static char *mk_field(char *msg, long msz, char *hdr, int wid)
60 char tbuf[128];
61 struct sbuf *dst;
62 int dst_wid;
63 char *val, *val0, *end;
64 val0 = msg_dec(msg, msz, hdr[0] == '~' ? hdr + 1 : hdr);
65 if (val0) {
66 val = val0 + strlen(hdr) - (hdr[0] == '~');
67 } else {
68 val0 = malloc(1);
69 val0[0] = '\0';
70 val = val0;
72 end = strchr(val, '\0');
73 dst = sbuf_make();
74 while (val < end && isspace((unsigned char) *val))
75 val++;
76 dst_wid = 0;
77 if (!strcmp("~subject:", hdr)) {
78 while (startswith(val, "re:") || startswith(val, "fwd:")) {
79 sbuf_chr(dst, '+');
80 dst_wid++;
81 val = strchr(val, ':') + 1;
82 while (val < end && isspace((unsigned char) *val))
83 val++;
86 if (!strcmp("~date:", hdr)) {
87 time_t ts = datedate(val);
88 strftime(tbuf, sizeof(tbuf), "%d %b %Y %H:%M:%S", localtime(&ts));
89 val = tbuf;
90 end = strchr(tbuf, '\0');
92 if (!strcmp("~size:", hdr)) {
93 char fmt[16];
94 sprintf(fmt, "%%%dd", wid);
95 snprintf(tbuf, sizeof(tbuf), fmt, msz);
96 val = tbuf;
97 end = strchr(tbuf, '\0');
99 while (val < end && (wid <= 0 || dst_wid < wid)) {
100 int l = uc_len(val);
101 if (l == 1) {
102 int c = (unsigned char) *val;
103 sbuf_chr(dst, isblank(c) || !isprint(c) ? ' ' : c);
104 } else {
105 sbuf_mem(dst, val, l);
107 dst_wid += uc_wid(val);
108 val += l;
110 if (wid > 0)
111 while (dst_wid++ < wid)
112 sbuf_chr(dst, ' ');
113 free(val0);
114 return sbuf_done(dst);
117 static char *mk_msgid(int idx, char *box, int flg1, int flg2, int idxwid, int boxwid)
119 static char num[32];
120 char fmt[32], fmtbox[32], numbox[32] = "";
121 sprintf(fmtbox, "%c%%-%ds", box ? '@' : ' ', boxwid);
122 if (boxwid > 0)
123 snprintf(numbox, sizeof(numbox), fmtbox, box ? box : "");
124 numbox[boxwid + 1] = '\0';
125 sprintf(fmt, "%c%c%%0%dd%%s", flg1, flg2, idxwid);
126 snprintf(num, sizeof(num), fmt, idx, numbox);
127 return num;
130 static void mk_sum(struct mbox *mbox)
132 int stats[128] = {0};
133 char *msg;
134 long msz;
135 int i, st;
136 for (i = 0; i < mbox_len(mbox); i++) {
137 mbox_get(mbox, i, &msg, &msz);
138 st = msg_stat(msg, msz, 0, 'N');
139 if (st >= 'A' && st <= 'Z')
140 stats[st - 'A']++;
142 for (i = 0; i < LEN(stats); i++)
143 if (stats[i])
144 fprintf(stderr, "%c%04d ", 'A' + i, stats[i]);
145 fprintf(stderr, "\n");
148 static char *segment(char *d, char *s, int m)
150 while (*s)
151 if ((*d++ = *s++) == m)
152 break;
153 *d = '\0';
154 return s;
157 static char *usage =
158 "usage: neatmail mk [options] [mbox]\n\n"
159 "options:\n"
160 " -0 fmt \tmessage first line format (e.g., 20from:40subject:)\n"
161 " -1 fmt \tmessage second line format\n"
162 " -sd \tsort by receiving date\n"
163 " -st \tsort by threads\n"
164 " -r \tprint a summary of status flags\n"
165 " -f n \tthe first message to list\n"
166 " -n n \tmessage index field width\n"
167 " -m n \tmessage file field width\n";
169 static int sort_mails(struct mbox *mbox, int *mids, int *levs);
171 int mk(char *argv[])
173 int *mids, *levs;
174 struct mbox *mbox;
175 char *ln[4] = {"18from:40~subject:"};
176 int i, j, k;
177 int beg = 0;
178 int sort = 0;
179 int sum = 0;
180 int idxwid = 4;
181 int boxwid = 0;
182 char *path[16] = {NULL};
183 int path_n = 0;
184 for (i = 0; argv[i] && argv[i][0] == '-'; i++) {
185 if (argv[i][1] == 'f') {
186 beg = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
187 continue;
189 if (argv[i][1] == 'r') {
190 sum = 1;
191 continue;
193 if (argv[i][1] == 's') {
194 char t = (argv[i][2] ? argv[i] + 2 : argv[++i])[0];
195 sort = t == 't' ? 2 : 1;
196 continue;
198 if (argv[i][1] == 'b') {
199 path[path_n++] = argv[i][2] ? argv[i] + 2 : argv[++i];
200 continue;
202 if (argv[i][1] == '0' || argv[i][1] == '1') {
203 int idx = argv[i][1] - '0';
204 ln[idx] = argv[i][2] ? argv[i] + 2 : argv[++i];
205 continue;
207 if (argv[i][1] == 'n') {
208 idxwid = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
209 continue;
211 if (argv[i][1] == 'm') {
212 boxwid = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
213 continue;
216 if (!path[0] && !argv[i]) {
217 printf("%s", usage);
218 return 1;
220 for (; argv[i]; i++)
221 path[path_n++] = argv[i];
222 mbox = mbox_open(path);
223 if (!mbox) {
224 fprintf(stderr, "neatmail: cannot open <%s>\n", path[0]);
225 return 1;
227 mids = malloc(mbox_len(mbox) * sizeof(mids[0]));
228 levs = malloc(mbox_len(mbox) * sizeof(levs[0]));
229 for (i = 0; i < mbox_len(mbox); i++)
230 mids[i] = i;
231 for (i = 0; i < mbox_len(mbox); i++)
232 levs[i] = 0;
233 if (sort)
234 sort_mails(mbox, mids, sort == 2 ? levs : NULL);
235 for (i = beg; i < mbox_len(mbox); i++) {
236 char *msg;
237 long msz;
238 int idx;
239 int tag = mbox_pos(mbox, mids[i], &idx);
240 mbox_get(mbox, mids[i], &msg, &msz);
241 printf("%s", mk_msgid(idx, tag > 0 ? path[tag] : NULL,
242 msg_stat(msg, msz, 0, 'N'),
243 msg_stat(msg, msz, 1, '0'),
244 idxwid, boxwid));
245 for (j = 0; ln[j]; j++) {
246 char *cln = ln[j];
247 char *tok = malloc(strlen(ln[j]) + 1);
248 if (j)
249 printf("\n");
250 while ((cln = segment(tok, cln, ':')) && tok[0]) {
251 char *hdr = tok;
252 char *val;
253 int wid = 0;
254 while (*hdr >= '0' && *hdr <= '9')
255 wid = wid * 10 + (*hdr++ - '0');
256 printf("\t");
257 if (!strcmp("~subject:", hdr)) {
258 for (k = 0; k < levs[i]; k++)
259 if (wid < 0 || k + 1 < wid)
260 printf(" ");
261 if (wid > 0)
262 wid = wid > k ? wid - k : 1;
264 val = mk_field(msg, msz, hdr, wid);
265 printf("[%s]", val);
266 free(val);
268 free(tok);
270 printf("\n");
272 free(mids);
273 free(levs);
274 if (sum)
275 mk_sum(mbox);
276 mbox_free(mbox);
277 return 0;
280 /* sorting messages */
282 struct msg {
283 char *msg;
284 long msglen;
285 char *id; /* message-id header value */
286 int id_len; /* message-id length */
287 char *rply; /* reply-to header value */
288 int rply_len; /* reply-to length */
289 int date; /* message receiving date */
290 int depth; /* depth of message in the thread */
291 int oidx; /* the original index of the message */
292 struct msg *parent;
293 struct msg *head;
294 struct msg *tail;
295 struct msg *next;
298 static int id_cmp(char *i1, int l1, char *i2, int l2)
300 if (l1 != l2)
301 return l2 - l1;
302 return strncmp(i1, i2, l1);
305 static int msgcmp_id(void *v1, void *v2)
307 struct msg *t1 = *(struct msg **) v1;
308 struct msg *t2 = *(struct msg **) v2;
309 return id_cmp(t1->id, t1->id_len, t2->id, t2->id_len);
312 static int msgcmp_date(void *v1, void *v2)
314 struct msg *t1 = *(struct msg **) v1;
315 struct msg *t2 = *(struct msg **) v2;
316 return t1->date == t2->date ? t1->oidx - t2->oidx : t1->date - t2->date;
319 static char *months[] = {
320 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
321 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
324 static char *readtok(char *s, char *d)
326 while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '(' || *s == ':') {
327 if (*s == '(')
328 while (*s && *s != ')')
329 s++;
330 if (*s)
331 s++;
333 while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '(' && *s != ':')
334 *d++ = *s++;
335 *d = '\0';
336 return s;
339 static int fromdate(char *s)
341 char tok[128];
342 int year, mon, day, hour, min, sec = 0;
343 int i;
344 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
345 s = readtok(s, tok); /* From */
346 s = readtok(s, tok); /* username */
347 s = readtok(s, tok); /* day of week */
348 s = readtok(s, tok); /* month name */
349 mon = 0;
350 for (i = 0; i < LEN(months); i++)
351 if (!strcmp(months[i], tok))
352 mon = i;
353 s = readtok(s, tok); /* day of month */
354 day = atoi(tok);
355 s = readtok(s, tok); /* hour */
356 hour = atoi(tok);
357 s = readtok(s, tok); /* minute */
358 min = atoi(tok);
359 s = readtok(s, tok); /* seconds (optional) */
360 sec = atoi(tok);
361 if (sec < 60)
362 s = readtok(s, tok); /* year */
363 year = atoi(tok);
364 return ((year - 1970) * 400 + mon * 31 + day) * 24 * 3600 +
365 hour * 3600 + min * 60 + sec;
368 static int datedate(char *s)
370 char tok[128];
371 struct tm tm = {0};
372 int ts, tz, i;
373 /* parsing "Fri, 25 Dec 2015 20:26:18 +0100" */
374 s = readtok(s, tok); /* day of week (optional) */
375 if (strchr(tok, ','))
376 s = readtok(s, tok); /* day of month */
377 tm.tm_mday = atoi(tok);
378 s = readtok(s, tok); /* month name */
379 for (i = 0; i < LEN(months); i++)
380 if (!strcmp(months[i], tok))
381 tm.tm_mon = i;
382 s = readtok(s, tok); /* year */
383 tm.tm_year = atoi(tok) - 1900;
384 s = readtok(s, tok); /* hour */
385 tm.tm_hour = atoi(tok);
386 s = readtok(s, tok); /* minute */
387 tm.tm_min = atoi(tok);
388 s = readtok(s, tok); /* seconds (optional) */
389 if (tok[0] != '+' && tok[0] != '-') {
390 tm.tm_sec = atoi(tok);
391 s = readtok(s, tok); /* time-zone */
393 tz = atoi(tok);
394 ts = mktime(&tm);
395 if (tz >= 0)
396 ts -= (tz / 100) * 3600 + (tz % 100) * 60;
397 else
398 ts += (-tz / 100) * 3600 + (-tz % 100) * 60;
399 return ts;
402 static struct msg *msg_byid(struct msg **msgs, int n, char *id, int len)
404 int l = 0;
405 int h = n;
406 while (l < h) {
407 int m = (l + h) / 2;
408 int d = id_cmp(id, len, msgs[m]->id, msgs[m]->id_len);
409 if (!d)
410 return msgs[m];
411 if (d < 0)
412 h = m;
413 else
414 l = m + 1;
416 return NULL;
419 static void msgs_tree(struct msg **all, struct msg **sorted_id, int n)
421 int i;
422 for (i = 0; i < n; i++) {
423 struct msg *msg = all[i];
424 struct msg *dad;
425 if (!msg->rply)
426 continue;
427 dad = msg_byid(sorted_id, n, msg->rply, msg->rply_len);
428 if (dad && dad->date < msg->date) {
429 msg->parent = dad;
430 msg->depth = dad->depth + 1;
431 if (!msg->parent->head)
432 msg->parent->head = msg;
433 else
434 msg->parent->tail->next = msg;
435 msg->parent->tail = msg;
440 static void msg_init(struct msg *msg)
442 char *id_hdr = msg_get(msg->msg, msg->msglen, "Message-ID:");
443 char *rply_hdr = msg_get(msg->msg, msg->msglen, "In-Reply-To:");
444 char *date_hdr = msg_get(msg->msg, msg->msglen, "Date:");
445 char *end = msg->msg + msg->msglen;
446 if (id_hdr) {
447 int len = hdrlen(id_hdr, end - id_hdr);
448 char *beg = memchr(id_hdr, '<', len);
449 char *end = beg ? memchr(id_hdr, '>', len) : NULL;
450 if (beg && end) {
451 while (*beg == '<')
452 beg++;
453 msg->id = beg;
454 msg->id_len = end - beg;
457 if (rply_hdr) {
458 int len = hdrlen(rply_hdr, end - rply_hdr);
459 char *beg = memchr(rply_hdr, '<', len);
460 char *end = beg ? memchr(rply_hdr, '>', len) : NULL;
461 if (beg && end) {
462 while (*beg == '<')
463 beg++;
464 msg->rply = beg;
465 msg->rply_len = end - beg;
468 msg->date = date_hdr ? datedate(date_hdr + 5) : fromdate(msg->msg);
471 static struct msg **put_msg(struct msg **sorted, struct msg *msg)
473 struct msg *cur = msg->head;
474 *sorted++ = msg;
475 while (cur) {
476 sorted = put_msg(sorted, cur);
477 cur = cur->next;
479 return sorted;
482 static void msgs_sort(struct msg **sorted, struct msg **msgs, int n)
484 int i;
485 for (i = 0; i < n; i++)
486 if (!msgs[i]->parent)
487 sorted = put_msg(sorted, msgs[i]);
490 static int sort_mails(struct mbox *mbox, int *mids, int *levs)
492 int n = mbox_len(mbox);
493 struct msg *msgs = malloc(n * sizeof(*msgs));
494 struct msg **sorted_date = malloc(n * sizeof(*sorted_date));
495 struct msg **sorted_id = malloc(n * sizeof(*sorted_id));
496 struct msg **sorted = malloc(n * sizeof(*sorted));
497 int i;
498 if (!msgs || !sorted_date || !sorted_id) {
499 free(msgs);
500 free(sorted_date);
501 free(sorted_id);
502 free(sorted);
503 return 1;
505 memset(msgs, 0, n * sizeof(*msgs));
506 for (i = 0; i < n; i++) {
507 struct msg *msg = &msgs[i];
508 msg->oidx = i;
509 mbox_get(mbox, i, &msg->msg, &msg->msglen);
510 sorted_id[i] = msg;
511 sorted_date[i] = msg;
512 msg_init(msg);
514 qsort(sorted_date, n, sizeof(*sorted_date), (void *) msgcmp_date);
515 qsort(sorted_id, n, sizeof(*sorted_id), (void *) msgcmp_id);
516 if (levs)
517 msgs_tree(sorted_date, sorted_id, n);
518 msgs_sort(sorted, sorted_date, n);
519 for (i = 0; i < n; i++)
520 mids[i] = sorted[i]->oidx;
521 if (levs)
522 for (i = 0; i < n; i++)
523 levs[i] = sorted[i]->depth;
524 free(msgs);
525 free(sorted_date);
526 free(sorted_id);
527 free(sorted);
528 return 0;