Get AUTHOR_DATE from the email Date: line
[git.git] / mailinfo.c
1 /*
2  * Another stupid program, this one parsing the headers of an
3  * email to figure out authorship and subject
4  */
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <ctype.h>
9
10 static FILE *cmitmsg, *patchfile, *filelist;
11
12 static char line[1000];
13 static char date[1000];
14 static char name[1000];
15 static char email[1000];
16 static char subject[1000];
17
18 static char *sanity_check(char *name, char *email)
19 {
20         int len = strlen(name);
21         if (len < 3 || len > 60)
22                 return email;
23         if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
24                 return email;
25         return name;
26 }
27
28 static int handle_from(char *line)
29 {
30         char *at = strchr(line, '@');
31         char *dst;
32
33         if (!at)
34                 return 0;
35
36         /*
37          * If we already have one email, don't take any confusing lines
38          */
39         if (*email && strchr(at+1, '@'))
40                 return 0;
41
42         while (at > line) {
43                 char c = at[-1];
44                 if (isspace(c) || c == '<')
45                         break;
46                 at--;
47         }
48         dst = email;
49         for (;;) {
50                 unsigned char c = *at;
51                 if (!c || c == '>' || isspace(c))
52                         break;
53                 *at++ = ' ';
54                 *dst++ = c;
55         }
56         *dst++ = 0;
57
58         at = line + strlen(line);
59         while (at > line) {
60                 unsigned char c = *--at;
61                 if (isalnum(c))
62                         break;
63                 *at = 0;
64         }
65
66         at = line;
67         for (;;) {
68                 unsigned char c = *at;
69                 if (!c)
70                         break;
71                 if (isalnum(c))
72                         break;
73                 at++;
74         }
75
76         at = sanity_check(at, email);
77         
78         strcpy(name, at);
79         return 1;
80 }
81
82 static void handle_date(char *line)
83 {
84         strcpy(date, line);
85 }
86
87 static void handle_subject(char *line)
88 {
89         strcpy(subject, line);
90 }
91
92 static void add_subject_line(char *line)
93 {
94         while (isspace(*line))
95                 line++;
96         *--line = ' ';
97         strcat(subject, line);
98 }
99
100 static void check_line(char *line, int len)
101 {
102         static int cont = -1;
103         if (!memcmp(line, "From:", 5) && isspace(line[5])) {
104                 handle_from(line+6);
105                 cont = 0;
106                 return;
107         }
108         if (!memcmp(line, "Date:", 5) && isspace(line[5])) {
109                 handle_date(line+6);
110                 cont = 0;
111                 return;
112         }
113         if (!memcmp(line, "Subject:", 8) && isspace(line[8])) {
114                 handle_subject(line+9);
115                 cont = 1;
116                 return;
117         }
118         if (isspace(*line)) {
119                 switch (cont) {
120                 case 0:
121                         fprintf(stderr, "I don't do 'Date:' or 'From:' line continuations\n");
122                         break;
123                 case 1:
124                         add_subject_line(line);
125                         return;
126                 default:
127                         break;
128                 }
129         }
130         cont = -1;
131 }
132
133 static char * cleanup_subject(char *subject)
134 {
135         for (;;) {
136                 char *p;
137                 int len, remove;
138                 switch (*subject) {
139                 case 'r': case 'R':
140                         if (!memcmp("e:", subject+1, 2)) {
141                                 subject +=3;
142                                 continue;
143                         }
144                         break;
145                 case ' ': case '\t': case ':':
146                         subject++;
147                         continue;
148
149                 case '[':
150                         p = strchr(subject, ']');
151                         if (!p) {
152                                 subject++;
153                                 continue;
154                         }
155                         len = strlen(p);
156                         remove = p - subject;
157                         if (remove <= len *2) {
158                                 subject = p+1;
159                                 continue;
160                         }       
161                         break;
162                 }
163                 return subject;
164         }
165 }                       
166
167 static void cleanup_space(char *buf)
168 {
169         unsigned char c;
170         while ((c = *buf) != 0) {
171                 buf++;
172                 if (isspace(c)) {
173                         buf[-1] = ' ';
174                         c = *buf;
175                         while (isspace(c)) {
176                                 int len = strlen(buf);
177                                 memmove(buf, buf+1, len);
178                                 c = *buf;
179                         }
180                 }
181         }
182 }
183
184 /*
185  * Hacky hacky. This depends not only on -p1, but on
186  * filenames not having some special characters in them,
187  * like tilde.
188  */
189 static void show_filename(char *line)
190 {
191         int len;
192         char *name = strchr(line, '/');
193
194         if (!name || !isspace(*line))
195                 return;
196         name++;
197         len = 0;
198         for (;;) {
199                 unsigned char c = name[len];
200                 switch (c) {
201                 default:
202                         len++;
203                         continue;
204
205                 case 0: case ' ':
206                 case '\t': case '\n':
207                         break;
208
209                 /* patch tends to special-case these things.. */
210                 case '~':
211                         break;
212                 }
213                 break;
214         }
215         /* remove ".orig" from the end - common patch behaviour */
216         if (len > 5 && !memcmp(name+len-5, ".orig", 5))
217                 len -=5;
218         if (!len)
219                 return;
220         fprintf(filelist, "%.*s\n", len, name);
221 }
222
223 static void handle_rest(void)
224 {
225         char *sub = cleanup_subject(subject);
226         cleanup_space(name);
227         cleanup_space(date);
228         cleanup_space(email);
229         cleanup_space(sub);
230         printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", name, email, sub, date);
231         FILE *out = cmitmsg;
232
233         do {
234                 /* Track filename information from the patch.. */
235                 if (!memcmp("---", line, 3)) {
236                         out = patchfile;
237                         show_filename(line+3);
238                 }
239
240                 if (!memcmp("+++", line, 3))
241                         show_filename(line+3);
242
243                 fputs(line, out);
244         } while (fgets(line, sizeof(line), stdin) != NULL);
245
246         if (out == cmitmsg) {
247                 fprintf(stderr, "No patch found\n");
248                 exit(1);
249         }
250
251         fclose(cmitmsg);
252         fclose(patchfile);
253 }
254
255 static int eatspace(char *line)
256 {
257         int len = strlen(line);
258         while (len > 0 && isspace(line[len-1]))
259                 line[--len] = 0;
260         return len;
261 }
262
263 static void handle_body(void)
264 {
265         int has_from = 0;
266
267         /* First line of body can be a From: */
268         while (fgets(line, sizeof(line), stdin) != NULL) {
269                 int len = eatspace(line);
270                 if (!len)
271                         continue;
272                 if (!memcmp("From:", line, 5) && isspace(line[5])) {
273                         if (!has_from && handle_from(line+6)) {
274                                 has_from = 1;
275                                 continue;
276                         }
277                 }
278                 line[len] = '\n';
279                 handle_rest();
280                 break;
281         }
282 }
283
284 static void usage(void)
285 {
286         fprintf(stderr, "mailinfo msg-file path-file filelist-file < email\n");
287         exit(1);
288 }
289
290 int main(int argc, char ** argv)
291 {
292         if (argc != 4)
293                 usage();
294         cmitmsg = fopen(argv[1], "w");
295         if (!cmitmsg) {
296                 perror(argv[1]);
297                 exit(1);
298         }
299         patchfile = fopen(argv[2], "w");
300         if (!patchfile) {
301                 perror(argv[2]);
302                 exit(1);
303         }
304         filelist = fopen(argv[3], "w");
305         if (!filelist) {
306                 perror(argv[3]);
307                 exit(1);
308         }
309         while (fgets(line, sizeof(line), stdin) != NULL) {
310                 int len = eatspace(line);
311                 if (!len) {
312                         handle_body();
313                         break;
314                 }
315                 check_line(line, len);
316         }
317         return 0;
318 }