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 " -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
);
176 char *ln
[4] = {"18from:40~subject:"};
183 char *path
[16] = {NULL
};
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
]);
190 if (argv
[i
][1] == 'r') {
194 if (argv
[i
][1] == 's') {
195 char t
= (argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
])[0];
196 sort
= t
== 't' ? 2 : 1;
199 if (argv
[i
][1] == 'b') {
200 path
[path_n
++] = argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
];
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
];
208 if (argv
[i
][1] == 'n') {
209 idxwid
= atoi(argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
]);
212 if (argv
[i
][1] == 'm') {
213 boxwid
= atoi(argv
[i
][2] ? argv
[i
] + 2 : argv
[++i
]);
217 if (!path
[0] && !argv
[i
]) {
222 path
[path_n
++] = argv
[i
];
223 mbox
= mbox_open(path
);
225 fprintf(stderr
, "neatmail: cannot open <%s>\n", path
[0]);
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
++)
232 for (i
= 0; i
< mbox_len(mbox
); i
++)
235 sort_mails(mbox
, mids
, sort
== 2 ? levs
: NULL
);
236 for (i
= beg
; i
< mbox_len(mbox
); i
++) {
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'),
246 for (j
= 0; ln
[j
]; j
++) {
248 char *tok
= malloc(strlen(ln
[j
]) + 1);
251 while ((cln
= segment(tok
, cln
, ':')) && tok
[0]) {
255 while (*hdr
>= '0' && *hdr
<= '9')
256 wid
= wid
* 10 + (*hdr
++ - '0');
258 if (!strcmp("~subject:", hdr
)) {
259 for (k
= 0; k
< levs
[i
]; k
++)
260 if (wid
< 0 || k
+ 1 < wid
)
263 wid
= wid
> k
? wid
- k
: 1;
265 val
= mk_field(msg
, msz
, hdr
, wid
);
281 /* sorting messages */
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 */
299 static int id_cmp(char *i1
, int l1
, char *i2
, int l2
)
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
== ':') {
329 while (*s
&& *s
!= ')')
334 while (*s
&& *s
!= ' ' && *s
!= '\t' && *s
!= '\n' && *s
!= '(' && *s
!= ':')
340 static int fromdate(char *s
)
343 int year
, mon
, day
, hour
, min
, sec
= 0;
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 */
351 for (i
= 0; i
< LEN(months
); i
++)
352 if (!strcmp(months
[i
], tok
))
354 s
= readtok(s
, tok
); /* day of month */
356 s
= readtok(s
, tok
); /* hour */
358 s
= readtok(s
, tok
); /* minute */
360 s
= readtok(s
, tok
); /* seconds (optional) */
363 s
= readtok(s
, tok
); /* year */
365 return ((year
- 1970) * 400 + mon
* 31 + day
) * 24 * 3600 +
366 hour
* 3600 + min
* 60 + sec
;
369 static int datedate(char *s
)
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
))
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 */
397 ts
-= (tz
/ 100) * 3600 + (tz
% 100) * 60;
399 ts
+= (-tz
/ 100) * 3600 + (-tz
% 100) * 60;
403 static struct msg
*msg_byid(struct msg
**msgs
, int n
, char *id
, int len
)
409 int d
= id_cmp(id
, len
, msgs
[m
]->id
, msgs
[m
]->id_len
);
420 static void msgs_tree(struct msg
**all
, struct msg
**sorted_id
, int n
)
423 for (i
= 0; i
< n
; i
++) {
424 struct msg
*msg
= all
[i
];
428 dad
= msg_byid(sorted_id
, n
, msg
->rply
, msg
->rply_len
);
429 if (dad
&& dad
->date
< msg
->date
) {
431 msg
->depth
= dad
->depth
+ 1;
432 if (!msg
->parent
->head
)
433 msg
->parent
->head
= msg
;
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
;
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
;
455 msg
->id_len
= end
- beg
;
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
;
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
;
477 sorted
= put_msg(sorted
, cur
);
483 static void msgs_sort(struct msg
**sorted
, struct msg
**msgs
, int n
)
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
));
499 if (!msgs
|| !sorted_date
|| !sorted_id
) {
506 memset(msgs
, 0, n
* sizeof(*msgs
));
507 for (i
= 0; i
< n
; i
++) {
508 struct msg
*msg
= &msgs
[i
];
510 mbox_get(mbox
, i
, &msg
->msg
, &msg
->msglen
);
512 sorted_date
[i
] = msg
;
515 qsort(sorted_date
, n
, sizeof(*sorted_date
), (void *) msgcmp_date
);
516 qsort(sorted_id
, n
, sizeof(*sorted_id
), (void *) msgcmp_id
);
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
;
523 for (i
= 0; i
< n
; i
++)
524 levs
[i
] = sorted
[i
]->depth
;