mk: sort based on the date header
[neatmail.git] / mk.c
blob1105a5c87ee2e002fd46b0a72b24d48f5506cb2b
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 & 0x80) /* ASCII */
14 return c > 0;
15 if (~c & 0x40) /* invalid UTF-8 */
16 return 1;
17 if (~c & 0x20)
18 return 2;
19 if (~c & 0x10)
20 return 3;
21 if (~c & 0x08)
22 return 4;
23 return 1;
26 static int uc_wid(char *s)
28 return 1;
31 static char *msg_dec(char *msg, long msz, char *hdr)
33 char *val = msg_get(msg, msz, hdr);
34 char *buf, *ret;
35 int val_len;
36 if (!val)
37 return NULL;
38 val_len = hdrlen(val, msg + msz - val) - 1;
39 buf = malloc(val_len + 1);
40 memcpy(buf, val, val_len);
41 buf[val_len] = '\0';
42 ret = msg_hdrdec(buf);
43 free(buf);
44 return ret;
47 static int msg_stat(char *msg, long msz)
49 char *val = msg_get(msg, msz, "status:");
50 if (!val)
51 return 'N';
52 val += strlen("status:");
53 while (isspace((unsigned char) val[0]))
54 val++;
55 return val[0];
58 static int datedate(char *s);
60 static char *fieldformat(char *msg, long msz, char *hdr, int wid)
62 char tbuf[128];
63 struct sbuf *dst;
64 int dst_wid;
65 char *val, *val0, *end;
66 val = msg_dec(msg, msz, hdr[0] == '~' ? hdr + 1 : hdr);
67 if (!val) {
68 val = malloc(1);
69 val[0] = '\0';
71 val0 = val;
72 end = strchr(val, '\0');
73 dst = sbuf_make();
74 val += strlen(hdr);
75 while (val < end && isspace((unsigned char) *val))
76 val++;
77 dst_wid = 0;
78 if (!strcmp("~subject:", hdr)) {
79 while (startswith(val, "re:") || startswith(val, "fwd:")) {
80 sbuf_chr(dst, '+');
81 dst_wid++;
82 val = strchr(val, ':') + 1;
83 while (val < end && isspace((unsigned char) *val))
84 val++;
87 if (!strcmp("~date:", hdr)) {
88 time_t ts = datedate(val);
89 strftime(tbuf, sizeof(tbuf), "%d %b %Y %H:%M:%S", localtime(&ts));
90 val = tbuf;
91 end = strchr(tbuf, '\0');
93 if (!strcmp("~size:", hdr)) {
94 char fmt[16];
95 sprintf(fmt, "%%%dd", wid);
96 snprintf(tbuf, sizeof(tbuf), fmt, msz);
97 val = tbuf;
98 end = strchr(tbuf, '\0');
100 while (val < end && (wid <= 0 || dst_wid < wid)) {
101 int l = uc_len(val);
102 if (l == 1) {
103 int c = (unsigned char) *val;
104 sbuf_chr(dst, isblank(c) || !isprint(c) ? ' ' : c);
105 } else {
106 sbuf_mem(dst, val, l);
108 dst_wid += uc_wid(val);
109 val += l;
111 if (wid > 0)
112 while (dst_wid++ < wid)
113 sbuf_chr(dst, ' ');
114 free(val0);
115 return sbuf_done(dst);
118 static void mk_sum(struct mbox *mbox)
120 int stats[128] = {0};
121 char *msg;
122 long msz;
123 int i, st;
124 for (i = 0; i < mbox_len(mbox); i++) {
125 mbox_get(mbox, i, &msg, &msz);
126 st = msg_stat(msg, msz);
127 if (st >= 'A' && st <= 'Z')
128 stats[st - 'A']++;
130 for (i = 0; i < LEN(stats); i++)
131 if (stats[i])
132 fprintf(stderr, "%c%04d ", 'A' + i, stats[i]);
133 fprintf(stderr, "\n");
136 static char *segment(char *d, char *s, int m)
138 char *r = strchr(s, m);
139 char *e = r ? r + 1 : strchr(s, '\0');
140 memcpy(d, s, e - s);
141 d[e - s] = '\0';
142 return e;
145 static char *usage =
146 "usage: neatmail mk [options] mbox\n\n"
147 "options:\n"
148 " -0 fmt \tmessage first line format (e.g., 20from:40subject:)\n"
149 " -1 fmt \tmessage second line format\n"
150 " -sd \tsort by receiving date\n"
151 " -st \tsort by threads\n"
152 " -r \tprint a summary of status flags\n"
153 " -f n \tthe first message to list\n";
155 static int sort_mails(struct mbox *mbox, int *mids, int *levs);
157 int mk(char *argv[])
159 int *mids, *levs;
160 struct mbox *mbox;
161 char *ln[4] = {"18from:40~subject:"};
162 int i, j, k;
163 int beg = 0;
164 int sort = 0;
165 int sum = 0;
166 for (i = 0; argv[i] && argv[i][0] == '-'; i++) {
167 if (argv[i][1] == 'f') {
168 beg = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
169 continue;
171 if (argv[i][1] == 'r') {
172 sum = 1;
173 continue;
175 if (argv[i][1] == 's') {
176 char t = (argv[i][2] ? argv[i] + 2 : argv[++i])[0];
177 sort = t == 't' ? 2 : 1;
178 continue;
180 if (argv[i][1] == '0' || argv[i][1] == '1') {
181 int idx = argv[i][1] - '0';
182 ln[idx] = argv[i][2] ? argv[i] + 2 : argv[++i];
183 continue;
186 if (!argv[i]) {
187 printf("%s", usage);
188 return 1;
190 mbox = mbox_open(argv[i]);
191 if (!mbox) {
192 fprintf(stderr, "neatmail: cannot open <%s>\n", argv[i]);
193 return 1;
195 mids = malloc(mbox_len(mbox) * sizeof(mids[0]));
196 levs = malloc(mbox_len(mbox) * sizeof(levs[0]));
197 for (i = 0; i < mbox_len(mbox); i++)
198 mids[i] = i;
199 for (i = 0; i < mbox_len(mbox); i++)
200 levs[i] = 0;
201 if (sort)
202 sort_mails(mbox, mids, sort == 2 ? levs : NULL);
203 for (i = beg; i < mbox_len(mbox); i++) {
204 char *msg;
205 long msz;
206 mbox_get(mbox, mids[i], &msg, &msz);
207 printf("%c%04d", msg_stat(msg, msz), mids[i]);
208 for (j = 0; ln[j]; j++) {
209 char *cln = ln[j];
210 char *tok = malloc(strlen(ln[j]) + 1);
211 if (j)
212 printf("\n");
213 while ((cln = segment(tok, cln, ':')) && tok[0]) {
214 char *fmt = tok;
215 char *hdr = tok;
216 char *val;
217 int wid = atoi(fmt);
218 while (isdigit((unsigned char) *hdr))
219 hdr++;
220 printf("\t");
221 if (!strcmp("~subject:", hdr))
222 for (k = 0; k < levs[i]; k++, wid--)
223 printf(" ");
224 val = fieldformat(msg, msz, hdr, wid);
225 printf("[%s]", val);
226 free(val);
228 free(tok);
230 printf("\n");
232 free(mids);
233 free(levs);
234 if (sum)
235 mk_sum(mbox);
236 mbox_free(mbox);
237 return 0;
240 /* sorting messages */
242 struct msg {
243 char *msg;
244 long msglen;
245 char *id; /* message-id header value */
246 int id_len; /* message-id length */
247 char *rply; /* reply-to header value */
248 int rply_len; /* reply-to length */
249 int date; /* message receiving date */
250 int depth; /* depth of message in the thread */
251 int oidx; /* the original index of the message */
252 struct msg *parent;
253 struct msg *head;
254 struct msg *tail;
255 struct msg *next;
258 static int id_cmp(char *i1, int l1, char *i2, int l2)
260 if (l1 != l2)
261 return l2 - l1;
262 return strncmp(i1, i2, l1);
265 static int msgcmp_id(void *v1, void *v2)
267 struct msg *t1 = *(struct msg **) v1;
268 struct msg *t2 = *(struct msg **) v2;
269 return id_cmp(t1->id, t1->id_len, t2->id, t2->id_len);
272 static int msgcmp_date(void *v1, void *v2)
274 struct msg *t1 = *(struct msg **) v1;
275 struct msg *t2 = *(struct msg **) v2;
276 return t1->date == t2->date ? t1->oidx - t2->oidx : t1->date - t2->date;
279 static char *months[] = {
280 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
281 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
284 static char *readtok(char *s, char *d)
286 while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '(' || *s == ':') {
287 if (*s == '(')
288 while (*s && *s != ')')
289 s++;
290 if (*s)
291 s++;
293 while (*s != ' ' && *s != '\t' && *s != '\n' && *s != '(' && *s != ':')
294 *d++ = *s++;
295 *d = '\0';
296 return s;
299 static int fromdate(char *s)
301 char tok[128];
302 int year, mon, day, hour, min, sec = 0;
303 int i;
304 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
305 s = readtok(s, tok); /* From */
306 s = readtok(s, tok); /* username */
307 s = readtok(s, tok); /* day of week */
308 s = readtok(s, tok); /* month name */
309 mon = 0;
310 for (i = 0; i < LEN(months); i++)
311 if (!strcmp(months[i], tok))
312 mon = i;
313 s = readtok(s, tok); /* day of month */
314 day = atoi(tok);
315 s = readtok(s, tok); /* hour */
316 hour = atoi(tok);
317 s = readtok(s, tok); /* minute */
318 min = atoi(tok);
319 s = readtok(s, tok); /* seconds (optional) */
320 if (sec < 60) {
321 sec = atoi(tok);
322 s = readtok(s, tok); /* year */
324 year = atoi(tok);
325 return ((year - 1970) * 400 + mon * 31 + day) * 24 * 3600 +
326 hour * 3600 + min * 60 + sec;
329 static int datedate(char *s)
331 char tok[128];
332 struct tm tm = {0};
333 int ts, tz, i;
334 /* parsing "Fri, 25 Dec 2015 20:26:18 +0100" */
335 s = readtok(s, tok); /* day of week (optional) */
336 if (strchr(tok, ','))
337 s = readtok(s, tok); /* day of month */
338 tm.tm_mday = atoi(tok);
339 s = readtok(s, tok); /* month name */
340 for (i = 0; i < LEN(months); i++)
341 if (!strcmp(months[i], tok))
342 tm.tm_mon = i;
343 s = readtok(s, tok); /* year */
344 tm.tm_year = atoi(tok) - 1900;
345 s = readtok(s, tok); /* hour */
346 tm.tm_hour = atoi(tok);
347 s = readtok(s, tok); /* minute */
348 tm.tm_min = atoi(tok);
349 s = readtok(s, tok); /* seconds (optional) */
350 if (tok[0] != '+' && tok[0] != '-') {
351 tm.tm_sec = atoi(tok);
352 s = readtok(s, tok); /* time-zone */
354 tz = atoi(tok);
355 ts = mktime(&tm);
356 if (tz >= 0)
357 ts -= (tz / 100) * 3600 + (tz % 100) * 60;
358 else
359 ts += (-tz / 100) * 3600 + (-tz % 100) * 60;
360 return ts;
363 static struct msg *msg_byid(struct msg **msgs, int n, char *id, int len)
365 int l = 0;
366 int h = n;
367 while (l < h) {
368 int m = (l + h) / 2;
369 int d = id_cmp(id, len, msgs[m]->id, msgs[m]->id_len);
370 if (!d)
371 return msgs[m];
372 if (d < 0)
373 h = m;
374 else
375 l = m + 1;
377 return NULL;
380 static void msgs_tree(struct msg **all, struct msg **sorted_id, int n)
382 int i;
383 for (i = 0; i < n; i++) {
384 struct msg *msg = all[i];
385 struct msg *dad;
386 if (!msg->rply)
387 continue;
388 dad = msg_byid(sorted_id, n, msg->rply, msg->rply_len);
389 if (dad && dad->date < msg->date) {
390 msg->parent = dad;
391 msg->depth = dad->depth + 1;
392 if (!msg->parent->head)
393 msg->parent->head = msg;
394 else
395 msg->parent->tail->next = msg;
396 msg->parent->tail = msg;
401 static void msg_init(struct msg *msg)
403 char *id_hdr = msg_get(msg->msg, msg->msglen, "Message-ID:");
404 char *rply_hdr = msg_get(msg->msg, msg->msglen, "In-Reply-To:");
405 char *date_hdr = msg_get(msg->msg, msg->msglen, "Date:");
406 char *end = msg->msg + msg->msglen;
407 if (id_hdr) {
408 int len = hdrlen(id_hdr, end - id_hdr);
409 char *beg = memchr(id_hdr, '<', len);
410 char *end = beg ? memchr(id_hdr, '>', len) : NULL;
411 if (beg && end) {
412 while (*beg == '<')
413 beg++;
414 msg->id = beg;
415 msg->id_len = end - beg;
418 if (rply_hdr) {
419 int len = hdrlen(rply_hdr, end - rply_hdr);
420 char *beg = memchr(rply_hdr, '<', len);
421 char *end = beg ? memchr(rply_hdr, '>', len) : NULL;
422 if (beg && end) {
423 while (*beg == '<')
424 beg++;
425 msg->rply = beg;
426 msg->rply_len = end - beg;
429 msg->date = date_hdr ? datedate(date_hdr + 5) : fromdate(msg->msg);
432 static struct msg **put_msg(struct msg **sorted, struct msg *msg)
434 struct msg *cur = msg->head;
435 *sorted++ = msg;
436 while (cur) {
437 sorted = put_msg(sorted, cur);
438 cur = cur->next;
440 return sorted;
443 static void msgs_sort(struct msg **sorted, struct msg **msgs, int n)
445 int i;
446 for (i = 0; i < n; i++)
447 if (!msgs[i]->parent)
448 sorted = put_msg(sorted, msgs[i]);
451 static int sort_mails(struct mbox *mbox, int *mids, int *levs)
453 int n = mbox_len(mbox);
454 struct msg *msgs = malloc(n * sizeof(*msgs));
455 struct msg **sorted_date = malloc(n * sizeof(*sorted_date));
456 struct msg **sorted_id = malloc(n * sizeof(*sorted_id));
457 struct msg **sorted = malloc(n * sizeof(*sorted));
458 int i;
459 if (!msgs || !sorted_date || !sorted_id) {
460 free(msgs);
461 free(sorted_date);
462 free(sorted_id);
463 free(sorted);
464 return 1;
466 memset(msgs, 0, n * sizeof(*msgs));
467 for (i = 0; i < n; i++) {
468 struct msg *msg = &msgs[i];
469 msg->oidx = i;
470 mbox_get(mbox, i, &msg->msg, &msg->msglen);
471 sorted_id[i] = msg;
472 sorted_date[i] = msg;
473 msg_init(msg);
475 qsort(sorted_date, n, sizeof(*sorted_date), (void *) msgcmp_date);
476 qsort(sorted_id, n, sizeof(*sorted_id), (void *) msgcmp_id);
477 if (levs)
478 msgs_tree(sorted_date, sorted_id, n);
479 msgs_sort(sorted, sorted_date, n);
480 for (i = 0; i < n; i++)
481 mids[i] = sorted[i]->oidx;
482 if (levs)
483 for (i = 0; i < n; i++)
484 levs[i] = sorted[i]->depth;
485 free(msgs);
486 free(sorted_date);
487 free(sorted_id);
488 free(sorted);
489 return 0;