mk: two-character status
[neatmail.git] / mk.c
blob20cefd9d6afa4ed26fd943da156bf053b8c198d2
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, int pos, int def)
49 char *val = msg_get(msg, msz, "status:");
50 if (!val)
51 return def;
52 val += strlen("status:");
53 while (val + pos < msg + msz && isspace((unsigned char) val[0]))
54 val++;
55 return isalpha((unsigned char) val[pos]) ? val[pos] : def;
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, 0, 'N');
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%c%03d", msg_stat(msg, msz, 0, 'N'),
208 msg_stat(msg, msz, 1, '0'), mids[i]);
209 for (j = 0; ln[j]; j++) {
210 char *cln = ln[j];
211 char *tok = malloc(strlen(ln[j]) + 1);
212 if (j)
213 printf("\n");
214 while ((cln = segment(tok, cln, ':')) && tok[0]) {
215 char *fmt = tok;
216 char *hdr = tok;
217 char *val;
218 int wid = atoi(fmt);
219 while (isdigit((unsigned char) *hdr))
220 hdr++;
221 printf("\t");
222 if (!strcmp("~subject:", hdr))
223 for (k = 0; k < levs[i]; k++, wid--)
224 printf(" ");
225 val = fieldformat(msg, msz, hdr, wid);
226 printf("[%s]", val);
227 free(val);
229 free(tok);
231 printf("\n");
233 free(mids);
234 free(levs);
235 if (sum)
236 mk_sum(mbox);
237 mbox_free(mbox);
238 return 0;
241 /* sorting messages */
243 struct msg {
244 char *msg;
245 long msglen;
246 char *id; /* message-id header value */
247 int id_len; /* message-id length */
248 char *rply; /* reply-to header value */
249 int rply_len; /* reply-to length */
250 int date; /* message receiving date */
251 int depth; /* depth of message in the thread */
252 int oidx; /* the original index of the message */
253 struct msg *parent;
254 struct msg *head;
255 struct msg *tail;
256 struct msg *next;
259 static int id_cmp(char *i1, int l1, char *i2, int l2)
261 if (l1 != l2)
262 return l2 - l1;
263 return strncmp(i1, i2, l1);
266 static int msgcmp_id(void *v1, void *v2)
268 struct msg *t1 = *(struct msg **) v1;
269 struct msg *t2 = *(struct msg **) v2;
270 return id_cmp(t1->id, t1->id_len, t2->id, t2->id_len);
273 static int msgcmp_date(void *v1, void *v2)
275 struct msg *t1 = *(struct msg **) v1;
276 struct msg *t2 = *(struct msg **) v2;
277 return t1->date == t2->date ? t1->oidx - t2->oidx : t1->date - t2->date;
280 static char *months[] = {
281 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
282 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
285 static char *readtok(char *s, char *d)
287 while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '(' || *s == ':') {
288 if (*s == '(')
289 while (*s && *s != ')')
290 s++;
291 if (*s)
292 s++;
294 while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '(' && *s != ':')
295 *d++ = *s++;
296 *d = '\0';
297 return s;
300 static int fromdate(char *s)
302 char tok[128];
303 int year, mon, day, hour, min, sec = 0;
304 int i;
305 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
306 s = readtok(s, tok); /* From */
307 s = readtok(s, tok); /* username */
308 s = readtok(s, tok); /* day of week */
309 s = readtok(s, tok); /* month name */
310 mon = 0;
311 for (i = 0; i < LEN(months); i++)
312 if (!strcmp(months[i], tok))
313 mon = i;
314 s = readtok(s, tok); /* day of month */
315 day = atoi(tok);
316 s = readtok(s, tok); /* hour */
317 hour = atoi(tok);
318 s = readtok(s, tok); /* minute */
319 min = atoi(tok);
320 s = readtok(s, tok); /* seconds (optional) */
321 if (sec < 60) {
322 sec = atoi(tok);
323 s = readtok(s, tok); /* year */
325 year = atoi(tok);
326 return ((year - 1970) * 400 + mon * 31 + day) * 24 * 3600 +
327 hour * 3600 + min * 60 + sec;
330 static int datedate(char *s)
332 char tok[128];
333 struct tm tm = {0};
334 int ts, tz, i;
335 /* parsing "Fri, 25 Dec 2015 20:26:18 +0100" */
336 s = readtok(s, tok); /* day of week (optional) */
337 if (strchr(tok, ','))
338 s = readtok(s, tok); /* day of month */
339 tm.tm_mday = atoi(tok);
340 s = readtok(s, tok); /* month name */
341 for (i = 0; i < LEN(months); i++)
342 if (!strcmp(months[i], tok))
343 tm.tm_mon = i;
344 s = readtok(s, tok); /* year */
345 tm.tm_year = atoi(tok) - 1900;
346 s = readtok(s, tok); /* hour */
347 tm.tm_hour = atoi(tok);
348 s = readtok(s, tok); /* minute */
349 tm.tm_min = atoi(tok);
350 s = readtok(s, tok); /* seconds (optional) */
351 if (tok[0] != '+' && tok[0] != '-') {
352 tm.tm_sec = atoi(tok);
353 s = readtok(s, tok); /* time-zone */
355 tz = atoi(tok);
356 ts = mktime(&tm);
357 if (tz >= 0)
358 ts -= (tz / 100) * 3600 + (tz % 100) * 60;
359 else
360 ts += (-tz / 100) * 3600 + (-tz % 100) * 60;
361 return ts;
364 static struct msg *msg_byid(struct msg **msgs, int n, char *id, int len)
366 int l = 0;
367 int h = n;
368 while (l < h) {
369 int m = (l + h) / 2;
370 int d = id_cmp(id, len, msgs[m]->id, msgs[m]->id_len);
371 if (!d)
372 return msgs[m];
373 if (d < 0)
374 h = m;
375 else
376 l = m + 1;
378 return NULL;
381 static void msgs_tree(struct msg **all, struct msg **sorted_id, int n)
383 int i;
384 for (i = 0; i < n; i++) {
385 struct msg *msg = all[i];
386 struct msg *dad;
387 if (!msg->rply)
388 continue;
389 dad = msg_byid(sorted_id, n, msg->rply, msg->rply_len);
390 if (dad && dad->date < msg->date) {
391 msg->parent = dad;
392 msg->depth = dad->depth + 1;
393 if (!msg->parent->head)
394 msg->parent->head = msg;
395 else
396 msg->parent->tail->next = msg;
397 msg->parent->tail = msg;
402 static void msg_init(struct msg *msg)
404 char *id_hdr = msg_get(msg->msg, msg->msglen, "Message-ID:");
405 char *rply_hdr = msg_get(msg->msg, msg->msglen, "In-Reply-To:");
406 char *date_hdr = msg_get(msg->msg, msg->msglen, "Date:");
407 char *end = msg->msg + msg->msglen;
408 if (id_hdr) {
409 int len = hdrlen(id_hdr, end - id_hdr);
410 char *beg = memchr(id_hdr, '<', len);
411 char *end = beg ? memchr(id_hdr, '>', len) : NULL;
412 if (beg && end) {
413 while (*beg == '<')
414 beg++;
415 msg->id = beg;
416 msg->id_len = end - beg;
419 if (rply_hdr) {
420 int len = hdrlen(rply_hdr, end - rply_hdr);
421 char *beg = memchr(rply_hdr, '<', len);
422 char *end = beg ? memchr(rply_hdr, '>', len) : NULL;
423 if (beg && end) {
424 while (*beg == '<')
425 beg++;
426 msg->rply = beg;
427 msg->rply_len = end - beg;
430 msg->date = date_hdr ? datedate(date_hdr + 5) : fromdate(msg->msg);
433 static struct msg **put_msg(struct msg **sorted, struct msg *msg)
435 struct msg *cur = msg->head;
436 *sorted++ = msg;
437 while (cur) {
438 sorted = put_msg(sorted, cur);
439 cur = cur->next;
441 return sorted;
444 static void msgs_sort(struct msg **sorted, struct msg **msgs, int n)
446 int i;
447 for (i = 0; i < n; i++)
448 if (!msgs[i]->parent)
449 sorted = put_msg(sorted, msgs[i]);
452 static int sort_mails(struct mbox *mbox, int *mids, int *levs)
454 int n = mbox_len(mbox);
455 struct msg *msgs = malloc(n * sizeof(*msgs));
456 struct msg **sorted_date = malloc(n * sizeof(*sorted_date));
457 struct msg **sorted_id = malloc(n * sizeof(*sorted_id));
458 struct msg **sorted = malloc(n * sizeof(*sorted));
459 int i;
460 if (!msgs || !sorted_date || !sorted_id) {
461 free(msgs);
462 free(sorted_date);
463 free(sorted_id);
464 free(sorted);
465 return 1;
467 memset(msgs, 0, n * sizeof(*msgs));
468 for (i = 0; i < n; i++) {
469 struct msg *msg = &msgs[i];
470 msg->oidx = i;
471 mbox_get(mbox, i, &msg->msg, &msg->msglen);
472 sorted_id[i] = msg;
473 sorted_date[i] = msg;
474 msg_init(msg);
476 qsort(sorted_date, n, sizeof(*sorted_date), (void *) msgcmp_date);
477 qsort(sorted_id, n, sizeof(*sorted_id), (void *) msgcmp_id);
478 if (levs)
479 msgs_tree(sorted_date, sorted_id, n);
480 msgs_sort(sorted, sorted_date, n);
481 for (i = 0; i < n; i++)
482 mids[i] = sorted[i]->oidx;
483 if (levs)
484 for (i = 0; i < n; i++)
485 levs[i] = sorted[i]->depth;
486 free(msgs);
487 free(sorted_date);
488 free(sorted_id);
489 free(sorted);
490 return 0;