use ',' for the next unread message
[mailx.git] / mailx.c
blobcffc7c482abf86af093b6b329caa9537cfb28efe
1 #include <ctype.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8 #include "config.h"
9 #include "mbox.h"
10 #include "sort.h"
12 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
13 #define MIN(a, b) ((a) < (b) ? (a) : (b))
14 #define MAXLINE (1 << 7)
15 #define BUFSIZE (1 << 12)
17 static struct mbox *mbox;
18 static struct sort *sort;
19 static int cur;
21 static int read_line(char *dst, int size)
23 static char buf[BUFSIZE];
24 static int cur;
25 static int len;
26 int nw = 0;
27 while (1) {
28 int cur_len = MIN(len - cur, size - nw - 1);
29 char *nl = memchr(buf + cur, '\n', cur_len);
30 int nr = nl ? nl - buf - cur + 1 : cur_len;
31 if (nr) {
32 memcpy(dst + nw, buf + cur, nr);
33 nw += nr;
34 cur += nr;
35 dst[nw] = '\0';
37 if (nl || nw == size - 1)
38 return nw;
39 cur = 0;
40 if ((len = read(STDIN_FILENO, buf, BUFSIZE)) <= 0)
41 return -1;
43 return nw;
46 static char *till_eol(char *r, int len, char *s)
48 char *d = r + len;
49 while (r < d && *s && *s != '\r' && *s != '\n')
50 *r++ = *s++;
51 return r;
54 static char *cut_word(char *dst, char *s)
56 while (isspace(*s))
57 s++;
58 while (!isspace(*s))
59 *dst++ = *s++;
60 *dst = '\0';
61 return s;
64 static int msg_num(char *num)
66 int n = -1;
67 if (!*num || !strcmp(".", num))
68 n = cur;
69 if (isdigit(*num))
70 n = atoi(num);
71 if (!strcmp("$", num))
72 n = mbox->n - 1;
73 if (!strcmp(",", num)) {
74 int i = cur;
75 while (++i < sort->n) {
76 if (!(sort->mails[i]->stat & STAT_READ)) {
77 n = i;
78 break;
82 if (n < 0 || n >= mbox->n)
83 return -1;
84 cur = n;
85 return n;
88 static void cmd_page(char *args)
90 struct mail *mail;
91 pid_t pid;
92 int fds[2];
93 char head[BUFSIZE];
94 char num[MAXLINE];
95 int hdr_len;
96 args = cut_word(num, args);
97 if (msg_num(num) == -1)
98 return;
99 mail = sort->mails[cur];
100 mail->stat |= STAT_READ;
101 pipe(fds);
102 if (!(pid = fork())) {
103 char *args[] = {PAGER, NULL};
104 close(fds[1]);
105 dup2(fds[0], STDIN_FILENO);
106 execvp(PAGER, args);
107 exit(1);
109 close(fds[0]);
110 hdr_len = mail_head(mail, head, sizeof(head),
111 hdr_filt, ARRAY_SIZE(hdr_filt));
112 write(fds[1], head, hdr_len);
113 write(fds[1], mail->body, mail->body_len);
114 close(fds[1]);
115 waitpid(pid, NULL, 0);
118 static char *cut_cmd(char *dst, char *s)
120 while (isalpha(*s)) {
121 *dst++ = *s++;
123 *dst = '\0';
124 return s;
127 static char *put_int(char *s, int n, int w)
129 int i;
130 for (i = 0; i < w; i++) {
131 s[w - i - 1] = n || !i ? '0' + n % 10 : ' ';
132 n = n / 10;
134 return s + w;
137 static char *put_str(char *dst, char *src)
139 int len = strchr(src, '\0') - src;
140 memcpy(dst, src, len + 1);
141 return dst + len;
144 static char *put_hdr(struct mail *mail, char *name, int w, char *s)
146 char *hdr = mail_hdr(mail, name);
147 return till_eol(s, w, hdr ? hdr + strlen(name) + 1 : NULL);
150 static void cmd_head(char *args)
152 char num[MAXLINE];
153 int beg, end;
154 int i;
155 args = cut_word(num, args);
156 if (msg_num(num) == -1)
157 return;
158 beg = cur / NHEAD * NHEAD;
159 end = MIN(beg + NHEAD, mbox->n);
160 for (i = beg; i < end; i++) {
161 struct mail *mail = sort->mails[i];
162 char fmt[MAXLINE];
163 char *s = fmt;
164 *s++ = i == cur ? '>' : ' ';
165 if (mail->stat & STAT_READ)
166 *s++ = ' ';
167 else
168 *s++ = mail->stat & STAT_NEW ? 'N' : 'O';
169 s = put_int(s, i, DIGITS);
170 *s++ = ' ';
171 *s++ = ' ';
172 s = put_hdr(mail, "From:", 16, s);
173 *s++ = ' ';
174 *s++ = ' ';
175 s = sort_draw(sort, i, s, WIDTH - (s - fmt));
176 s = put_hdr(mail, "Subject:", WIDTH - (s - fmt), s);
177 *s++ = '\n';
178 write(STDOUT_FILENO, fmt, s - fmt);
182 static void cmd_z(char *args)
184 char num[MAXLINE];
185 int page = -1;
186 cut_word(num, args);
187 if (!*num)
188 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
189 if (*num == '+' || *num == '-') {
190 int d = (*num + 1) ? (*num == '-' ? -1 : 1) : atoi(num);
191 page = cur / NHEAD + d;
193 if (*num == '$')
194 page = mbox->n / NHEAD;
195 if (isdigit(*num))
196 page = atoi(num);
197 if (page >= 0 && page * NHEAD < mbox->n) {
198 cur = page * NHEAD;
199 cmd_head("");
203 static void print(char *s)
205 write(STDOUT_FILENO, s, strlen(s));
208 static void open_mbox(char *filename)
210 mbox = mbox_alloc(filename);
211 sort = sort_alloc(mbox);
212 cur = 0;
215 static void close_mbox(void)
217 sort_free(sort);
218 mbox_free(mbox);
221 static int is_mbox(char *filename)
223 struct stat st;
224 stat(filename, &st);
225 return S_ISREG(st.st_mode);
228 static void sum_mbox(void)
230 char msg[MAXLINE];
231 char *s = put_int(msg, mbox->n, DIGITS);
232 int new = 0;
233 int unread = 0;
234 int i;
235 s = put_str(s, " messages ");
236 for (i = 0; i < mbox->n; i++)
237 if (mbox->mails[i].stat & STAT_NEW)
238 new++;
239 else if (!(mbox->mails[i].stat & STAT_READ))
240 unread++;
241 if (new) {
242 s = put_int(s, new, DIGITS);
243 s = put_str(s, " new ");
245 if (unread) {
246 s = put_int(s, unread, DIGITS);
247 s = put_str(s, " unread ");
249 s = put_str(s, "\n");
250 print(msg);
253 static int has_mail(char *path)
255 struct stat st;
256 if (stat(path, &st) == -1)
257 return 0;
258 return st.st_mtime > st.st_atime;
261 static int mbox_path(char *path, char *addr)
263 char *s = path;
264 int i;
265 if (*addr == '+') {
266 s = put_str(s, FOLDER);
267 s = put_str(s, addr + 1);
269 if (*addr == ',') {
270 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
271 if (has_mail(boxes[i])) {
272 s = put_str(s, boxes[i]);
273 break;
277 if (!strcmp(".", addr) && mbox)
278 s = put_str(s, mbox->path);
279 if (s == path && *addr)
280 s = put_str(s, addr);
281 return s - path;
284 static void cmd_fold(char *args)
286 char filename[MAXLINE];
287 args = cut_word(filename, args);
288 if (*filename) {
289 char path[MAXPATHLEN];
290 if (mbox_path(path, filename) && is_mbox(path)) {
291 close_mbox();
292 open_mbox(filename);
294 } else {
295 sum_mbox();
299 static void cmd_inc(char *args)
301 char msg[MAXLINE];
302 int new = mbox_inc(mbox);
303 char *s;
304 int i;
305 if (new) {
306 sort_inc(sort);
307 s = put_int(msg, new, DIGITS);
308 s = put_str(s, " new\n");
309 print(msg);
311 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
312 if (has_mail(boxes[i])) {
313 char *box = strrchr(boxes[i], '/');
314 s = msg;
315 box = box ? box + 1 : boxes[i];
316 s = put_str(s, "mbox: ");
317 s = put_str(s, box);
318 s = put_str(s, "\n");
319 print(msg);
324 static void prompt(void)
326 write(STDOUT_FILENO, "? ", 2);
329 static void loop(void)
331 char line[MAXLINE];
332 char cmd[MAXLINE];
333 prompt();
334 while (read_line(line, sizeof(line)) > 0) {
335 char *args = cut_cmd(cmd, line);
336 int len = strlen(cmd);
337 if (!len) {
338 if (cur + 1 < mbox->n) {
339 cur++;
340 cmd_page(args);
341 } else {
342 print("EOF\n");
344 prompt();
345 continue;
347 if (!strncmp("page", cmd, len))
348 cmd_page(args);
349 if (!strncmp("header", cmd, len))
350 cmd_head(args);
351 if (!strncmp("file", cmd, len) || !strncmp("folder", cmd, len))
352 cmd_fold(args);
353 if (!strncmp("inc", cmd, len))
354 cmd_inc(args);
355 if (!strncmp("z", cmd, len))
356 cmd_z(args);
357 if (!strncmp("quit", cmd, len)) {
358 mbox_write(mbox);
359 return;
361 if (!strncmp("xit", cmd, len) || !strncmp("exit", cmd, len))
362 return;
363 prompt();
367 int main(int argc, char *argv[])
369 int i = 0;
370 char *filename = NULL;
371 while (++i < argc)
372 if (!strcmp("-f", argv[i]))
373 filename = argv[++i];
374 if (filename) {
375 char path[MAXPATHLEN];
376 if (mbox_path(path, filename) && is_mbox(path)) {
377 open_mbox(path);
378 loop();
379 close_mbox();
382 return 0;