README: omit -i option in examples
[neatmail.git] / mk.c
blob9f9cce1869f90919bd7c79f0fc6bf036211e9bd9
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 " -b path \tmbox path\n"
161 " -0 fmt \tmessage first line format (e.g., 20from:40subject:)\n"
162 " -1 fmt \tmessage second line format\n"
163 " -sd \tsort by receiving date\n"
164 " -st \tsort by threads\n"
165 " -r \tprint a summary of status flags\n"
166 " -f n \tthe first message to list\n"
167 " -n n \tmessage index field width\n"
168 " -m n \tmessage file field width\n";
170 static int sort_mails(struct mbox *mbox, int *mids, int *levs);
172 int mk(char *argv[])
174 int *mids, *levs;
175 struct mbox *mbox;
176 char *ln[4] = {"18from:40~subject:"};
177 int i, j, k;
178 int beg = 0;
179 int sort = 0;
180 int sum = 0;
181 int idxwid = 4;
182 int boxwid = 0;
183 char *path[16] = {NULL};
184 int path_n = 0;
185 for (i = 0; argv[i] && argv[i][0] == '-'; i++) {
186 if (argv[i][1] == 'f') {
187 beg = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
188 continue;
190 if (argv[i][1] == 'r') {
191 sum = 1;
192 continue;
194 if (argv[i][1] == 's') {
195 char t = (argv[i][2] ? argv[i] + 2 : argv[++i])[0];
196 sort = t == 't' ? 2 : 1;
197 continue;
199 if (argv[i][1] == 'b') {
200 path[path_n++] = argv[i][2] ? argv[i] + 2 : argv[++i];
201 continue;
203 if (argv[i][1] == '0' || argv[i][1] == '1') {
204 int idx = argv[i][1] - '0';
205 ln[idx] = argv[i][2] ? argv[i] + 2 : argv[++i];
206 continue;
208 if (argv[i][1] == 'n') {
209 idxwid = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
210 continue;
212 if (argv[i][1] == 'm') {
213 boxwid = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
214 continue;
217 if (!path[0] && !argv[i]) {
218 printf("%s", usage);
219 return 1;
221 for (; argv[i]; i++)
222 path[path_n++] = argv[i];
223 mbox = mbox_open(path);
224 if (!mbox) {
225 fprintf(stderr, "neatmail: cannot open <%s>\n", path[0]);
226 return 1;
228 mids = malloc(mbox_len(mbox) * sizeof(mids[0]));
229 levs = malloc(mbox_len(mbox) * sizeof(levs[0]));
230 for (i = 0; i < mbox_len(mbox); i++)
231 mids[i] = i;
232 for (i = 0; i < mbox_len(mbox); i++)
233 levs[i] = 0;
234 if (sort)
235 sort_mails(mbox, mids, sort == 2 ? levs : NULL);
236 for (i = beg; i < mbox_len(mbox); i++) {
237 char *msg;
238 long msz;
239 int idx;
240 int tag = mbox_pos(mbox, mids[i], &idx);
241 mbox_get(mbox, mids[i], &msg, &msz);
242 printf("%s", mk_msgid(idx, tag > 0 ? path[tag] : NULL,
243 msg_stat(msg, msz, 0, 'N'),
244 msg_stat(msg, msz, 1, '0'),
245 idxwid, boxwid));
246 for (j = 0; ln[j]; j++) {
247 char *cln = ln[j];
248 char *tok = malloc(strlen(ln[j]) + 1);
249 if (j)
250 printf("\n");
251 while ((cln = segment(tok, cln, ':')) && tok[0]) {
252 char *hdr = tok;
253 char *val;
254 int wid = 0;
255 while (*hdr >= '0' && *hdr <= '9')
256 wid = wid * 10 + (*hdr++ - '0');
257 printf("\t");
258 if (!strcmp("~subject:", hdr)) {
259 for (k = 0; k < levs[i]; k++)
260 if (wid < 0 || k + 1 < wid)
261 printf(" ");
262 if (wid > 0)
263 wid = wid > k ? wid - k : 1;
265 val = mk_field(msg, msz, hdr, wid);
266 printf("[%s]", val);
267 free(val);
269 free(tok);
271 printf("\n");
273 free(mids);
274 free(levs);
275 if (sum)
276 mk_sum(mbox);
277 mbox_free(mbox);
278 return 0;
281 /* sorting messages */
283 struct msg {
284 char *msg;
285 long msglen;
286 char *id; /* message-id header value */
287 int id_len; /* message-id length */
288 char *rply; /* reply-to header value */
289 int rply_len; /* reply-to length */
290 int date; /* message receiving date */
291 int depth; /* depth of message in the thread */
292 int oidx; /* the original index of the message */
293 struct msg *parent;
294 struct msg *head;
295 struct msg *tail;
296 struct msg *next;
299 static int id_cmp(char *i1, int l1, char *i2, int l2)
301 if (l1 != l2)
302 return l2 - l1;
303 return strncmp(i1, i2, l1);
306 static int msgcmp_id(void *v1, void *v2)
308 struct msg *t1 = *(struct msg **) v1;
309 struct msg *t2 = *(struct msg **) v2;
310 return id_cmp(t1->id, t1->id_len, t2->id, t2->id_len);
313 static int msgcmp_date(void *v1, void *v2)
315 struct msg *t1 = *(struct msg **) v1;
316 struct msg *t2 = *(struct msg **) v2;
317 return t1->date == t2->date ? t1->oidx - t2->oidx : t1->date - t2->date;
320 static char *months[] = {
321 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
322 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
325 static char *readtok(char *s, char *d)
327 while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '(' || *s == ':') {
328 if (*s == '(')
329 while (*s && *s != ')')
330 s++;
331 if (*s)
332 s++;
334 while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '(' && *s != ':')
335 *d++ = *s++;
336 *d = '\0';
337 return s;
340 static int fromdate(char *s)
342 char tok[128];
343 int year, mon, day, hour, min, sec = 0;
344 int i;
345 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
346 s = readtok(s, tok); /* From */
347 s = readtok(s, tok); /* username */
348 s = readtok(s, tok); /* day of week */
349 s = readtok(s, tok); /* month name */
350 mon = 0;
351 for (i = 0; i < LEN(months); i++)
352 if (!strcmp(months[i], tok))
353 mon = i;
354 s = readtok(s, tok); /* day of month */
355 day = atoi(tok);
356 s = readtok(s, tok); /* hour */
357 hour = atoi(tok);
358 s = readtok(s, tok); /* minute */
359 min = atoi(tok);
360 s = readtok(s, tok); /* seconds (optional) */
361 sec = atoi(tok);
362 if (sec < 60)
363 s = readtok(s, tok); /* year */
364 year = atoi(tok);
365 return ((year - 1970) * 400 + mon * 31 + day) * 24 * 3600 +
366 hour * 3600 + min * 60 + sec;
369 static int datedate(char *s)
371 char tok[128];
372 struct tm tm = {0};
373 int ts, tz, i;
374 /* parsing "Fri, 25 Dec 2015 20:26:18 +0100" */
375 s = readtok(s, tok); /* day of week (optional) */
376 if (strchr(tok, ','))
377 s = readtok(s, tok); /* day of month */
378 tm.tm_mday = atoi(tok);
379 s = readtok(s, tok); /* month name */
380 for (i = 0; i < LEN(months); i++)
381 if (!strcmp(months[i], tok))
382 tm.tm_mon = i;
383 s = readtok(s, tok); /* year */
384 tm.tm_year = atoi(tok) - 1900;
385 s = readtok(s, tok); /* hour */
386 tm.tm_hour = atoi(tok);
387 s = readtok(s, tok); /* minute */
388 tm.tm_min = atoi(tok);
389 s = readtok(s, tok); /* seconds (optional) */
390 if (tok[0] != '+' && tok[0] != '-') {
391 tm.tm_sec = atoi(tok);
392 s = readtok(s, tok); /* time-zone */
394 tz = atoi(tok);
395 ts = mktime(&tm);
396 if (tz >= 0)
397 ts -= (tz / 100) * 3600 + (tz % 100) * 60;
398 else
399 ts += (-tz / 100) * 3600 + (-tz % 100) * 60;
400 return ts;
403 static struct msg *msg_byid(struct msg **msgs, int n, char *id, int len)
405 int l = 0;
406 int h = n;
407 while (l < h) {
408 int m = (l + h) / 2;
409 int d = id_cmp(id, len, msgs[m]->id, msgs[m]->id_len);
410 if (!d)
411 return msgs[m];
412 if (d < 0)
413 h = m;
414 else
415 l = m + 1;
417 return NULL;
420 static void msgs_tree(struct msg **all, struct msg **sorted_id, int n)
422 int i;
423 for (i = 0; i < n; i++) {
424 struct msg *msg = all[i];
425 struct msg *dad;
426 if (!msg->rply)
427 continue;
428 dad = msg_byid(sorted_id, n, msg->rply, msg->rply_len);
429 if (dad && dad->date < msg->date) {
430 msg->parent = dad;
431 msg->depth = dad->depth + 1;
432 if (!msg->parent->head)
433 msg->parent->head = msg;
434 else
435 msg->parent->tail->next = msg;
436 msg->parent->tail = msg;
441 static void msg_init(struct msg *msg)
443 char *id_hdr = msg_get(msg->msg, msg->msglen, "Message-ID:");
444 char *rply_hdr = msg_get(msg->msg, msg->msglen, "In-Reply-To:");
445 char *date_hdr = msg_get(msg->msg, msg->msglen, "Date:");
446 char *end = msg->msg + msg->msglen;
447 if (id_hdr) {
448 int len = hdrlen(id_hdr, end - id_hdr);
449 char *beg = memchr(id_hdr, '<', len);
450 char *end = beg ? memchr(id_hdr, '>', len) : NULL;
451 if (beg && end) {
452 while (*beg == '<')
453 beg++;
454 msg->id = beg;
455 msg->id_len = end - beg;
458 if (rply_hdr) {
459 int len = hdrlen(rply_hdr, end - rply_hdr);
460 char *beg = memchr(rply_hdr, '<', len);
461 char *end = beg ? memchr(rply_hdr, '>', len) : NULL;
462 if (beg && end) {
463 while (*beg == '<')
464 beg++;
465 msg->rply = beg;
466 msg->rply_len = end - beg;
469 msg->date = date_hdr ? datedate(date_hdr + 5) : fromdate(msg->msg);
472 static struct msg **put_msg(struct msg **sorted, struct msg *msg)
474 struct msg *cur = msg->head;
475 *sorted++ = msg;
476 while (cur) {
477 sorted = put_msg(sorted, cur);
478 cur = cur->next;
480 return sorted;
483 static void msgs_sort(struct msg **sorted, struct msg **msgs, int n)
485 int i;
486 for (i = 0; i < n; i++)
487 if (!msgs[i]->parent)
488 sorted = put_msg(sorted, msgs[i]);
491 static int sort_mails(struct mbox *mbox, int *mids, int *levs)
493 int n = mbox_len(mbox);
494 struct msg *msgs = malloc(n * sizeof(*msgs));
495 struct msg **sorted_date = malloc(n * sizeof(*sorted_date));
496 struct msg **sorted_id = malloc(n * sizeof(*sorted_id));
497 struct msg **sorted = malloc(n * sizeof(*sorted));
498 int i;
499 if (!msgs || !sorted_date || !sorted_id) {
500 free(msgs);
501 free(sorted_date);
502 free(sorted_id);
503 free(sorted);
504 return 1;
506 memset(msgs, 0, n * sizeof(*msgs));
507 for (i = 0; i < n; i++) {
508 struct msg *msg = &msgs[i];
509 msg->oidx = i;
510 mbox_get(mbox, i, &msg->msg, &msg->msglen);
511 sorted_id[i] = msg;
512 sorted_date[i] = msg;
513 msg_init(msg);
515 qsort(sorted_date, n, sizeof(*sorted_date), (void *) msgcmp_date);
516 qsort(sorted_id, n, sizeof(*sorted_id), (void *) msgcmp_id);
517 if (levs)
518 msgs_tree(sorted_date, sorted_id, n);
519 msgs_sort(sorted, sorted_date, n);
520 for (i = 0; i < n; i++)
521 mids[i] = sorted[i]->oidx;
522 if (levs)
523 for (i = 0; i < n; i++)
524 levs[i] = sorted[i]->depth;
525 free(msgs);
526 free(sorted_date);
527 free(sorted_id);
528 free(sorted);
529 return 0;