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 */
15 if (~c
& 0x40) /* invalid UTF-8 */
26 static int uc_wid(char *s
)
31 static char *msg_dec(char *msg
, long msz
, char *hdr
)
33 char *val
= msg_get(msg
, msz
, hdr
);
38 val_len
= hdrlen(val
, msg
+ msz
- val
) - 1;
39 buf
= malloc(val_len
+ 1);
40 memcpy(buf
, val
, val_len
);
42 ret
= msg_hdrdec(buf
);
47 static int msg_stat(char *msg
, long msz
)
49 char *val
= msg_get(msg
, msz
, "status:");
52 val
+= strlen("status:");
53 while (isspace((unsigned char) val
[0]))
58 static int datedate(char *s
);
60 static char *fieldformat(char *msg
, long msz
, char *hdr
, int wid
)
65 char *val
, *val0
, *end
;
66 val
= msg_dec(msg
, msz
, hdr
[0] == '~' ? hdr
+ 1 : hdr
);
72 end
= strchr(val
, '\0');
75 while (val
< end
&& isspace((unsigned char) *val
))
78 if (!strcmp("~subject:", hdr
)) {
79 while (startswith(val
, "re:") || startswith(val
, "fwd:")) {
82 val
= strchr(val
, ':') + 1;
83 while (val
< end
&& isspace((unsigned char) *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
));
91 end
= strchr(tbuf
, '\0');
93 if (!strcmp("~size:", hdr
)) {
95 sprintf(fmt
, "%%%dd", wid
);
96 snprintf(tbuf
, sizeof(tbuf
), fmt
, msz
);
98 end
= strchr(tbuf
, '\0');
100 while (val
< end
&& (wid
<= 0 || dst_wid
< wid
)) {
103 int c
= (unsigned char) *val
;
104 sbuf_chr(dst
, isblank(c
) || !isprint(c
) ? ' ' : c
);
106 sbuf_mem(dst
, val
, l
);
108 dst_wid
+= uc_wid(val
);
112 while (dst_wid
++ < wid
)
115 return sbuf_done(dst
);
118 static void mk_sum(struct mbox
*mbox
)
120 int stats
[128] = {0};
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')
130 for (i
= 0; i
< LEN(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');
146 "usage: neatmail mk [options] mbox\n\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
);
161 char *ln
[4] = {"18from:40~subject:"};
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
]);
171 if (argv
[i
][1] == 'r') {
175 if (argv
[i
][1] == 's') {
176 char t
= (argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
])[0];
177 sort
= t
== 't' ? 2 : 1;
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
];
190 mbox
= mbox_open(argv
[i
]);
192 fprintf(stderr
, "neatmail: cannot open <%s>\n", argv
[i
]);
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
++)
199 for (i
= 0; i
< mbox_len(mbox
); i
++)
202 sort_mails(mbox
, mids
, sort
== 2 ? levs
: NULL
);
203 for (i
= beg
; i
< mbox_len(mbox
); i
++) {
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
++) {
210 char *tok
= malloc(strlen(ln
[j
]) + 1);
213 while ((cln
= segment(tok
, cln
, ':')) && tok
[0]) {
218 while (isdigit((unsigned char) *hdr
))
221 if (!strcmp("~subject:", hdr
))
222 for (k
= 0; k
< levs
[i
]; k
++, wid
--)
224 val
= fieldformat(msg
, msz
, hdr
, wid
);
240 /* sorting messages */
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 */
258 static int id_cmp(char *i1
, int l1
, char *i2
, int l2
)
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
== ':') {
288 while (*s
&& *s
!= ')')
293 while (*s
!= ' ' && *s
!= '\t' && *s
!= '\n' && *s
!= '(' && *s
!= ':')
299 static int fromdate(char *s
)
302 int year
, mon
, day
, hour
, min
, sec
= 0;
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 */
310 for (i
= 0; i
< LEN(months
); i
++)
311 if (!strcmp(months
[i
], tok
))
313 s
= readtok(s
, tok
); /* day of month */
315 s
= readtok(s
, tok
); /* hour */
317 s
= readtok(s
, tok
); /* minute */
319 s
= readtok(s
, tok
); /* seconds (optional) */
322 s
= readtok(s
, tok
); /* year */
325 return ((year
- 1970) * 400 + mon
* 31 + day
) * 24 * 3600 +
326 hour
* 3600 + min
* 60 + sec
;
329 static int datedate(char *s
)
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
))
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 */
357 ts
-= (tz
/ 100) * 3600 + (tz
% 100) * 60;
359 ts
+= (-tz
/ 100) * 3600 + (-tz
% 100) * 60;
363 static struct msg
*msg_byid(struct msg
**msgs
, int n
, char *id
, int len
)
369 int d
= id_cmp(id
, len
, msgs
[m
]->id
, msgs
[m
]->id_len
);
380 static void msgs_tree(struct msg
**all
, struct msg
**sorted_id
, int n
)
383 for (i
= 0; i
< n
; i
++) {
384 struct msg
*msg
= all
[i
];
388 dad
= msg_byid(sorted_id
, n
, msg
->rply
, msg
->rply_len
);
389 if (dad
&& dad
->date
< msg
->date
) {
391 msg
->depth
= dad
->depth
+ 1;
392 if (!msg
->parent
->head
)
393 msg
->parent
->head
= msg
;
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
;
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
;
415 msg
->id_len
= end
- beg
;
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
;
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
;
437 sorted
= put_msg(sorted
, cur
);
443 static void msgs_sort(struct msg
**sorted
, struct msg
**msgs
, int n
)
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
));
459 if (!msgs
|| !sorted_date
|| !sorted_id
) {
466 memset(msgs
, 0, n
* sizeof(*msgs
));
467 for (i
= 0; i
< n
; i
++) {
468 struct msg
*msg
= &msgs
[i
];
470 mbox_get(mbox
, i
, &msg
->msg
, &msg
->msglen
);
472 sorted_date
[i
] = msg
;
475 qsort(sorted_date
, n
, sizeof(*sorted_date
), (void *) msgcmp_date
);
476 qsort(sorted_id
, n
, sizeof(*sorted_id
), (void *) msgcmp_id
);
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
;
483 for (i
= 0; i
< n
; i
++)
484 levs
[i
] = sorted
[i
]->depth
;