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 */
24 static int uc_wid(char *s
)
29 static char *msg_dec(char *msg
, long msz
, char *hdr
)
31 char *val
= msg_get(msg
, msz
, hdr
);
36 val_len
= hdrlen(val
, msg
+ msz
- val
) - 1;
37 buf
= malloc(val_len
+ 1);
38 memcpy(buf
, val
, val_len
);
40 ret
= msg_hdrdec(buf
);
45 static int msg_stat(char *msg
, long msz
, int pos
, int def
)
47 char *val
= msg_get(msg
, msz
, "status:");
50 val
+= strlen("status:");
51 while (val
+ pos
< msg
+ msz
&& isspace((unsigned char) val
[0]))
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
)
63 char *val
, *val0
, *end
;
64 val0
= msg_dec(msg
, msz
, hdr
[0] == '~' ? hdr
+ 1 : hdr
);
66 val
= val0
+ strlen(hdr
) - (hdr
[0] == '~');
72 end
= strchr(val
, '\0');
74 while (val
< end
&& isspace((unsigned char) *val
))
77 if (!strcmp("~subject:", hdr
)) {
78 while (startswith(val
, "re:") || startswith(val
, "fwd:")) {
81 val
= strchr(val
, ':') + 1;
82 while (val
< end
&& isspace((unsigned char) *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
));
90 end
= strchr(tbuf
, '\0');
92 if (!strcmp("~size:", hdr
)) {
94 sprintf(fmt
, "%%%dd", wid
);
95 snprintf(tbuf
, sizeof(tbuf
), fmt
, msz
);
97 end
= strchr(tbuf
, '\0');
99 while (val
< end
&& (wid
<= 0 || dst_wid
< wid
)) {
102 int c
= (unsigned char) *val
;
103 sbuf_chr(dst
, isblank(c
) || !isprint(c
) ? ' ' : c
);
105 sbuf_mem(dst
, val
, l
);
107 dst_wid
+= uc_wid(val
);
111 while (dst_wid
++ < wid
)
114 return sbuf_done(dst
);
117 static char *mk_msgid(int idx
, char *box
, int flg1
, int flg2
, int idxwid
, int boxwid
)
120 char fmt
[32], fmtbox
[32], numbox
[32] = "";
121 sprintf(fmtbox
, "%c%%-%ds", box
? '@' : ' ', boxwid
);
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
);
130 static void mk_sum(struct mbox
*mbox
)
132 int stats
[128] = {0};
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')
142 for (i
= 0; i
< LEN(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
)
151 if ((*d
++ = *s
++) == m
)
158 "usage: neatmail mk [options] [mbox]\n\n"
160 " -0 fmt \tmessage first line format (e.g., 20from:40subject:)\n"
161 " -1 fmt \tmessage second line format\n"
162 " -sd \tsort by receiving date\n"
163 " -st \tsort by threads\n"
164 " -r \tprint a summary of status flags\n"
165 " -f n \tthe first message to list\n"
166 " -n n \tmessage index field width\n"
167 " -m n \tmessage file field width\n";
169 static int sort_mails(struct mbox
*mbox
, int *mids
, int *levs
);
175 char *ln
[4] = {"18from:40~subject:"};
182 char *path
[16] = {NULL
};
184 for (i
= 0; argv
[i
] && argv
[i
][0] == '-'; i
++) {
185 if (argv
[i
][1] == 'f') {
186 beg
= atoi(argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
]);
189 if (argv
[i
][1] == 'r') {
193 if (argv
[i
][1] == 's') {
194 char t
= (argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
])[0];
195 sort
= t
== 't' ? 2 : 1;
198 if (argv
[i
][1] == 'b') {
199 path
[path_n
++] = argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
];
202 if (argv
[i
][1] == '0' || argv
[i
][1] == '1') {
203 int idx
= argv
[i
][1] - '0';
204 ln
[idx
] = argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
];
207 if (argv
[i
][1] == 'n') {
208 idxwid
= atoi(argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
]);
211 if (argv
[i
][1] == 'm') {
212 boxwid
= atoi(argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
]);
216 if (!path
[0] && !argv
[i
]) {
221 path
[path_n
++] = argv
[i
];
222 mbox
= mbox_open(path
);
224 fprintf(stderr
, "neatmail: cannot open <%s>\n", path
[0]);
227 mids
= malloc(mbox_len(mbox
) * sizeof(mids
[0]));
228 levs
= malloc(mbox_len(mbox
) * sizeof(levs
[0]));
229 for (i
= 0; i
< mbox_len(mbox
); i
++)
231 for (i
= 0; i
< mbox_len(mbox
); i
++)
234 sort_mails(mbox
, mids
, sort
== 2 ? levs
: NULL
);
235 for (i
= beg
; i
< mbox_len(mbox
); i
++) {
239 int tag
= mbox_pos(mbox
, mids
[i
], &idx
);
240 mbox_get(mbox
, mids
[i
], &msg
, &msz
);
241 printf("%s", mk_msgid(idx
, tag
> 0 ? path
[tag
] : NULL
,
242 msg_stat(msg
, msz
, 0, 'N'),
243 msg_stat(msg
, msz
, 1, '0'),
245 for (j
= 0; ln
[j
]; j
++) {
247 char *tok
= malloc(strlen(ln
[j
]) + 1);
250 while ((cln
= segment(tok
, cln
, ':')) && tok
[0]) {
254 while (*hdr
>= '0' && *hdr
<= '9')
255 wid
= wid
* 10 + (*hdr
++ - '0');
257 if (!strcmp("~subject:", hdr
)) {
258 for (k
= 0; k
< levs
[i
]; k
++)
259 if (wid
< 0 || k
+ 1 < wid
)
262 wid
= wid
> k
? wid
- k
: 1;
264 val
= mk_field(msg
, msz
, hdr
, wid
);
280 /* sorting messages */
285 char *id
; /* message-id header value */
286 int id_len
; /* message-id length */
287 char *rply
; /* reply-to header value */
288 int rply_len
; /* reply-to length */
289 int date
; /* message receiving date */
290 int depth
; /* depth of message in the thread */
291 int oidx
; /* the original index of the message */
298 static int id_cmp(char *i1
, int l1
, char *i2
, int l2
)
302 return strncmp(i1
, i2
, l1
);
305 static int msgcmp_id(void *v1
, void *v2
)
307 struct msg
*t1
= *(struct msg
**) v1
;
308 struct msg
*t2
= *(struct msg
**) v2
;
309 return id_cmp(t1
->id
, t1
->id_len
, t2
->id
, t2
->id_len
);
312 static int msgcmp_date(void *v1
, void *v2
)
314 struct msg
*t1
= *(struct msg
**) v1
;
315 struct msg
*t2
= *(struct msg
**) v2
;
316 return t1
->date
== t2
->date
? t1
->oidx
- t2
->oidx
: t1
->date
- t2
->date
;
319 static char *months
[] = {
320 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
321 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
324 static char *readtok(char *s
, char *d
)
326 while (*s
== ' ' || *s
== '\t' || *s
== '\n' || *s
== '(' || *s
== ':') {
328 while (*s
&& *s
!= ')')
333 while (*s
&& *s
!= ' ' && *s
!= '\t' && *s
!= '\n' && *s
!= '(' && *s
!= ':')
339 static int fromdate(char *s
)
342 int year
, mon
, day
, hour
, min
, sec
= 0;
344 /* parsing "From ali Tue Apr 16 20:18:40 2013" */
345 s
= readtok(s
, tok
); /* From */
346 s
= readtok(s
, tok
); /* username */
347 s
= readtok(s
, tok
); /* day of week */
348 s
= readtok(s
, tok
); /* month name */
350 for (i
= 0; i
< LEN(months
); i
++)
351 if (!strcmp(months
[i
], tok
))
353 s
= readtok(s
, tok
); /* day of month */
355 s
= readtok(s
, tok
); /* hour */
357 s
= readtok(s
, tok
); /* minute */
359 s
= readtok(s
, tok
); /* seconds (optional) */
362 s
= readtok(s
, tok
); /* year */
364 return ((year
- 1970) * 400 + mon
* 31 + day
) * 24 * 3600 +
365 hour
* 3600 + min
* 60 + sec
;
368 static int datedate(char *s
)
373 /* parsing "Fri, 25 Dec 2015 20:26:18 +0100" */
374 s
= readtok(s
, tok
); /* day of week (optional) */
375 if (strchr(tok
, ','))
376 s
= readtok(s
, tok
); /* day of month */
377 tm
.tm_mday
= atoi(tok
);
378 s
= readtok(s
, tok
); /* month name */
379 for (i
= 0; i
< LEN(months
); i
++)
380 if (!strcmp(months
[i
], tok
))
382 s
= readtok(s
, tok
); /* year */
383 tm
.tm_year
= atoi(tok
) - 1900;
384 s
= readtok(s
, tok
); /* hour */
385 tm
.tm_hour
= atoi(tok
);
386 s
= readtok(s
, tok
); /* minute */
387 tm
.tm_min
= atoi(tok
);
388 s
= readtok(s
, tok
); /* seconds (optional) */
389 if (tok
[0] != '+' && tok
[0] != '-') {
390 tm
.tm_sec
= atoi(tok
);
391 s
= readtok(s
, tok
); /* time-zone */
396 ts
-= (tz
/ 100) * 3600 + (tz
% 100) * 60;
398 ts
+= (-tz
/ 100) * 3600 + (-tz
% 100) * 60;
402 static struct msg
*msg_byid(struct msg
**msgs
, int n
, char *id
, int len
)
408 int d
= id_cmp(id
, len
, msgs
[m
]->id
, msgs
[m
]->id_len
);
419 static void msgs_tree(struct msg
**all
, struct msg
**sorted_id
, int n
)
422 for (i
= 0; i
< n
; i
++) {
423 struct msg
*msg
= all
[i
];
427 dad
= msg_byid(sorted_id
, n
, msg
->rply
, msg
->rply_len
);
428 if (dad
&& dad
->date
< msg
->date
) {
430 msg
->depth
= dad
->depth
+ 1;
431 if (!msg
->parent
->head
)
432 msg
->parent
->head
= msg
;
434 msg
->parent
->tail
->next
= msg
;
435 msg
->parent
->tail
= msg
;
440 static void msg_init(struct msg
*msg
)
442 char *id_hdr
= msg_get(msg
->msg
, msg
->msglen
, "Message-ID:");
443 char *rply_hdr
= msg_get(msg
->msg
, msg
->msglen
, "In-Reply-To:");
444 char *date_hdr
= msg_get(msg
->msg
, msg
->msglen
, "Date:");
445 char *end
= msg
->msg
+ msg
->msglen
;
447 int len
= hdrlen(id_hdr
, end
- id_hdr
);
448 char *beg
= memchr(id_hdr
, '<', len
);
449 char *end
= beg
? memchr(id_hdr
, '>', len
) : NULL
;
454 msg
->id_len
= end
- beg
;
458 int len
= hdrlen(rply_hdr
, end
- rply_hdr
);
459 char *beg
= memchr(rply_hdr
, '<', len
);
460 char *end
= beg
? memchr(rply_hdr
, '>', len
) : NULL
;
465 msg
->rply_len
= end
- beg
;
468 msg
->date
= date_hdr
? datedate(date_hdr
+ 5) : fromdate(msg
->msg
);
471 static struct msg
**put_msg(struct msg
**sorted
, struct msg
*msg
)
473 struct msg
*cur
= msg
->head
;
476 sorted
= put_msg(sorted
, cur
);
482 static void msgs_sort(struct msg
**sorted
, struct msg
**msgs
, int n
)
485 for (i
= 0; i
< n
; i
++)
486 if (!msgs
[i
]->parent
)
487 sorted
= put_msg(sorted
, msgs
[i
]);
490 static int sort_mails(struct mbox
*mbox
, int *mids
, int *levs
)
492 int n
= mbox_len(mbox
);
493 struct msg
*msgs
= malloc(n
* sizeof(*msgs
));
494 struct msg
**sorted_date
= malloc(n
* sizeof(*sorted_date
));
495 struct msg
**sorted_id
= malloc(n
* sizeof(*sorted_id
));
496 struct msg
**sorted
= malloc(n
* sizeof(*sorted
));
498 if (!msgs
|| !sorted_date
|| !sorted_id
) {
505 memset(msgs
, 0, n
* sizeof(*msgs
));
506 for (i
= 0; i
< n
; i
++) {
507 struct msg
*msg
= &msgs
[i
];
509 mbox_get(mbox
, i
, &msg
->msg
, &msg
->msglen
);
511 sorted_date
[i
] = msg
;
514 qsort(sorted_date
, n
, sizeof(*sorted_date
), (void *) msgcmp_date
);
515 qsort(sorted_id
, n
, sizeof(*sorted_id
), (void *) msgcmp_id
);
517 msgs_tree(sorted_date
, sorted_id
, n
);
518 msgs_sort(sorted
, sorted_date
, n
);
519 for (i
= 0; i
< n
; i
++)
520 mids
[i
] = sorted
[i
]->oidx
;
522 for (i
= 0; i
< n
; i
++)
523 levs
[i
] = sorted
[i
]->depth
;