8 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
10 static int uc_len(char *s
)
12 int c
= (unsigned char) s
[0];
28 static int uc_wid(char *s
)
33 static char *msg_dec(char *msg
, long msz
, char *hdr
)
35 char *val
= msg_get(msg
, msz
, hdr
);
40 val_len
= hdrlen(val
, msg
+ msz
- val
) - 1;
41 buf
= malloc(val_len
+ 1);
42 memcpy(buf
, val
, val_len
);
44 ret
= msg_hdrdec(buf
);
49 static int msg_stat(char *msg
, long msz
)
51 char *val
= msg_get(msg
, msz
, "status:");
54 val
+= strlen("status:");
55 while (isspace((unsigned char) val
[0]))
60 static int datedate(char *s
);
62 static char *fieldformat(char *msg
, long msz
, char *hdr
, int wid
)
67 char *val
, *val0
, *end
;
68 val
= msg_dec(msg
, msz
, hdr
[0] == '~' ? hdr
+ 1 : hdr
);
74 end
= strchr(val
, '\0');
77 while (val
< end
&& isspace((unsigned char) *val
))
80 if (!strcmp("~subject:", hdr
)) {
81 while (startswith(val
, "re:") || startswith(val
, "fwd:")) {
84 val
= strchr(val
, ':') + 1;
85 while (val
< end
&& isspace((unsigned char) *val
))
89 if (!strcmp("~date:", hdr
)) {
90 time_t ts
= datedate(val
);
91 strftime(tbuf
, sizeof(tbuf
), "%d %b %Y %H:%M:%S", localtime(&ts
));
93 end
= strchr(tbuf
, '\0');
95 while (val
< end
&& (wid
<= 0 || dst_wid
< wid
)) {
98 int c
= (unsigned char) *val
;
99 sbuf_chr(dst
, isblank(c
) || !isprint(c
) ? ' ' : c
);
101 sbuf_mem(dst
, val
, l
);
103 dst_wid
+= uc_wid(val
);
107 while (dst_wid
++ < wid
)
110 return sbuf_done(dst
);
113 static void mk_sum(struct mbox
*mbox
)
115 int stats
[128] = {0};
119 for (i
= 0; i
< mbox_len(mbox
); i
++) {
120 mbox_get(mbox
, i
, &msg
, &msz
);
121 st
= msg_stat(msg
, msz
);
122 if (st
>= 'A' && st
<= 'Z')
125 for (i
= 0; i
< LEN(stats
); i
++)
127 fprintf(stderr
, "%c%04d ", 'A' + i
, stats
[i
]);
128 fprintf(stderr
, "\n");
131 static char *segment(char *d
, char *s
, int m
)
133 char *r
= strchr(s
, m
);
134 char *e
= r
? r
+ 1 : strchr(s
, '\0');
141 "usage: neatmail mk [options] mbox\n\n"
143 " -0 fmt \tmessage first line format (e.g., 20from:40subject:)\n"
144 " -1 fmt \tmessage second line format\n"
145 " -sd \tsort by receiving date\n"
146 " -st \tsort by threads\n"
147 " -r \tprint a summary of status flags\n"
148 " -f n \tthe first message to list\n";
150 static int sort_mails(struct mbox
*mbox
, int *mids
, int *levs
);
156 char *ln
[4] = {"18from:40~subject:"};
161 for (i
= 0; argv
[i
] && argv
[i
][0] == '-'; i
++) {
162 if (argv
[i
][1] == 'f') {
163 beg
= atoi(argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
]);
166 if (argv
[i
][1] == 'r') {
170 if (argv
[i
][1] == 's') {
171 char t
= (argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
])[0];
172 sort
= t
== 't' ? 2 : 1;
175 if (argv
[i
][1] == '0' || argv
[i
][1] == '1') {
176 int idx
= argv
[i
][1] - '0';
177 ln
[idx
] = argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
];
185 mbox
= mbox_open(argv
[i
]);
187 fprintf(stderr
, "neatmail: cannot open <%s>\n", argv
[i
]);
190 mids
= malloc(mbox_len(mbox
) * sizeof(mids
[0]));
191 levs
= malloc(mbox_len(mbox
) * sizeof(levs
[0]));
192 for (i
= 0; i
< mbox_len(mbox
); i
++)
194 for (i
= 0; i
< mbox_len(mbox
); i
++)
197 sort_mails(mbox
, mids
, sort
== 2 ? levs
: NULL
);
198 for (i
= beg
; i
< mbox_len(mbox
); i
++) {
201 mbox_get(mbox
, mids
[i
], &msg
, &msz
);
202 printf("%c%04d", msg_stat(msg
, msz
), mids
[i
]);
203 for (j
= 0; ln
[j
]; j
++) {
205 char *tok
= malloc(strlen(ln
[j
]) + 1);
208 while ((cln
= segment(tok
, cln
, ':')) && tok
[0]) {
213 while (isdigit((unsigned char) *hdr
))
216 if (!strcmp("~subject:", hdr
))
217 for (k
= 0; k
< levs
[i
]; k
++, wid
--)
219 val
= fieldformat(msg
, msz
, hdr
, wid
);
235 /* sorting messages */
240 char *id
; /* message-id header value */
241 int id_len
; /* message-id length */
242 char *rply
; /* reply-to header value */
243 int rply_len
; /* reply-to length */
244 int date
; /* message receiving date */
245 int depth
; /* depth of message in the thread */
246 int oidx
; /* the original index of the message */
253 static int id_cmp(char *i1
, int l1
, char *i2
, int l2
)
257 return strncmp(i1
, i2
, l1
);
260 static int msgcmp_id(void *v1
, void *v2
)
262 struct msg
*t1
= *(struct msg
**) v1
;
263 struct msg
*t2
= *(struct msg
**) v2
;
264 return id_cmp(t1
->id
, t1
->id_len
, t2
->id
, t2
->id_len
);
267 static int msgcmp_date(void *v1
, void *v2
)
269 struct msg
*t1
= *(struct msg
**) v1
;
270 struct msg
*t2
= *(struct msg
**) v2
;
271 return t1
->date
== t2
->date
? t1
->oidx
- t2
->oidx
: t1
->date
- t2
->date
;
274 static char *months
[] = {
275 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
276 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
279 static char *readtok(char *s
, char *d
)
281 while (*s
== ' ' || *s
== ':')
283 while (!strchr(" \t\r\n:", *s
))
289 static int fromdate(char *s
)
292 int year
, mon
, day
, hour
, min
, sec
;
294 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
295 s
= readtok(s
, tok
); /* From */
296 s
= readtok(s
, tok
); /* username */
297 s
= readtok(s
, tok
); /* day of week */
298 s
= readtok(s
, tok
); /* month name */
300 for (i
= 0; i
< LEN(months
); i
++)
301 if (!strcmp(months
[i
], tok
))
303 s
= readtok(s
, tok
); /* day of month */
305 s
= readtok(s
, tok
); /* hour */
307 s
= readtok(s
, tok
); /* minute */
309 s
= readtok(s
, tok
); /* seconds */
311 s
= readtok(s
, tok
); /* year */
313 return ((year
- 1970) * 400 + mon
* 31 + day
) * 24 * 3600 +
314 hour
* 3600 + min
* 60 + sec
;
317 static int datedate(char *s
)
322 /* parsing "Fri, 25 Dec 2015 20:26:18 +0100" */
323 s
= readtok(s
, tok
); /* day of week */
324 s
= readtok(s
, tok
); /* day of month */
325 tm
.tm_mday
= atoi(tok
);
326 s
= readtok(s
, tok
); /* month name */
327 for (i
= 0; i
< LEN(months
); i
++)
328 if (!strcmp(months
[i
], tok
))
330 s
= readtok(s
, tok
); /* year */
331 tm
.tm_year
= atoi(tok
) - 1900;
332 s
= readtok(s
, tok
); /* hour */
333 tm
.tm_hour
= atoi(tok
);
334 s
= readtok(s
, tok
); /* minute */
335 tm
.tm_min
= atoi(tok
);
336 s
= readtok(s
, tok
); /* seconds */
337 tm
.tm_sec
= atoi(tok
);
338 s
= readtok(s
, tok
); /* time-zone */
342 ts
-= (tz
/ 100) * 3600 + (tz
% 100) * 60;
344 ts
+= (-tz
/ 100) * 3600 + (-tz
% 100) * 60;
348 static struct msg
*msg_byid(struct msg
**msgs
, int n
, char *id
, int len
)
354 int d
= id_cmp(id
, len
, msgs
[m
]->id
, msgs
[m
]->id_len
);
365 static void msgs_tree(struct msg
**all
, struct msg
**sorted_id
, int n
)
368 for (i
= 0; i
< n
; i
++) {
369 struct msg
*msg
= all
[i
];
373 dad
= msg_byid(sorted_id
, n
, msg
->rply
, msg
->rply_len
);
377 msg
->depth
= dad
->depth
+ 1;
378 if (!msg
->parent
->head
)
379 msg
->parent
->head
= msg
;
381 msg
->parent
->tail
->next
= msg
;
382 msg
->parent
->tail
= msg
;
386 static void msg_init(struct msg
*msg
)
388 char *id_hdr
= msg_get(msg
->msg
, msg
->msglen
, "Message-ID:");
389 char *rply_hdr
= msg_get(msg
->msg
, msg
->msglen
, "In-Reply-To:");
390 char *end
= msg
->msg
+ msg
->msglen
;
392 int len
= hdrlen(id_hdr
, end
- id_hdr
);
393 char *beg
= memchr(id_hdr
, '<', len
);
394 char *end
= beg
? memchr(id_hdr
, '>', len
) : NULL
;
399 msg
->id_len
= end
- beg
;
403 int len
= hdrlen(rply_hdr
, end
- rply_hdr
);
404 char *beg
= memchr(rply_hdr
, '<', len
);
405 char *end
= beg
? memchr(rply_hdr
, '>', len
) : NULL
;
410 msg
->rply_len
= end
- beg
;
413 msg
->date
= fromdate(msg
->msg
);
416 static struct msg
**put_msg(struct msg
**sorted
, struct msg
*msg
)
418 struct msg
*cur
= msg
->head
;
421 sorted
= put_msg(sorted
, cur
);
427 static void msgs_sort(struct msg
**sorted
, struct msg
**msgs
, int n
)
430 for (i
= 0; i
< n
; i
++)
431 if (!msgs
[i
]->parent
)
432 sorted
= put_msg(sorted
, msgs
[i
]);
435 static int sort_mails(struct mbox
*mbox
, int *mids
, int *levs
)
437 int n
= mbox_len(mbox
);
438 struct msg
*msgs
= malloc(n
* sizeof(*msgs
));
439 struct msg
**sorted_date
= malloc(n
* sizeof(*sorted_date
));
440 struct msg
**sorted_id
= malloc(n
* sizeof(*sorted_id
));
441 struct msg
**sorted
= malloc(n
* sizeof(*sorted
));
443 if (!msgs
|| !sorted_date
|| !sorted_id
) {
450 memset(msgs
, 0, n
* sizeof(*msgs
));
451 for (i
= 0; i
< n
; i
++) {
452 struct msg
*msg
= &msgs
[i
];
454 mbox_get(mbox
, i
, &msg
->msg
, &msg
->msglen
);
456 sorted_date
[i
] = msg
;
459 qsort(sorted_date
, n
, sizeof(*sorted_date
), (void *) msgcmp_date
);
460 qsort(sorted_id
, n
, sizeof(*sorted_id
), (void *) msgcmp_id
);
462 msgs_tree(sorted_date
, sorted_id
, n
);
463 msgs_sort(sorted
, sorted_date
, n
);
464 for (i
= 0; i
< n
; i
++)
465 mids
[i
] = sorted
[i
]->oidx
;
467 for (i
= 0; i
< n
; i
++)
468 levs
[i
] = sorted
[i
]->depth
;