credit where credit is due
[claws.git] / src / quote_fmt_parse.y
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 %{
21
22 #include "defs.h"
23
24 #include <glib.h>
25 #include <ctype.h>
26
27 #include "procmsg.h"
28 #include "procmime.h"
29 #include "utils.h"
30 #include "procheader.h"
31
32 #include "quote_fmt.h"
33 #include "quote_fmt_lex.h"
34
35 /* decl */
36 /*
37 flex quote_fmt.l
38 bison -p quote_fmt quote_fmt.y
39 */
40
41 int yylex(void);
42
43 static MsgInfo *msginfo = NULL;
44 static gboolean *visible = NULL;
45 static gboolean dry_run = FALSE;
46 static gint maxsize = 0;
47 static gint stacksize = 0;
48
49 static gchar *buffer = NULL;
50 static gint bufmax = 0;
51 static gint bufsize = 0;
52 static const gchar *quote_str = NULL;
53 static const gchar *body = NULL;
54 static gint error = 0;
55
56 static gint cursor_pos  = -1;
57
58 extern int quote_fmt_firsttime;
59
60 static void add_visibility(gboolean val)
61 {
62         stacksize++;
63         if (maxsize < stacksize) {
64                 maxsize += 128;
65                 visible = g_realloc(visible, maxsize * sizeof(gboolean));
66                 if (visible == NULL)
67                         maxsize = 0;
68         }
69
70         visible[stacksize - 1] = val;
71 }
72
73 static void remove_visibility(void)
74 {
75         stacksize--;
76 }
77
78 static void add_buffer(const gchar *s)
79 {
80         gint len;
81
82         len = strlen(s);
83         if (bufsize + len + 1 > bufmax) {
84                 if (bufmax == 0)
85                         bufmax = 128;
86                 while (bufsize + len + 1 > bufmax)
87                         bufmax *= 2;
88                 buffer = g_realloc(buffer, bufmax);
89         }
90         strcpy(buffer + bufsize, s);
91         bufsize += len;
92 }
93
94 gchar *quote_fmt_get_buffer(void)
95 {
96         if (error != 0)
97                 return NULL;
98         else
99                 return buffer;
100 }
101
102 gint quote_fmt_get_cursor_pos(void)
103 {
104         return cursor_pos;      
105 }
106
107 #define INSERT(buf) \
108         if (stacksize != 0 && visible[stacksize - 1]) \
109                 add_buffer(buf)
110
111 #define INSERT_CHARACTER(chr) \
112         if (stacksize != 0 && visible[stacksize - 1]) { \
113                 gchar tmp[2]; \
114                 tmp[0] = (chr); \
115                 tmp[1] = '\0'; \
116                 add_buffer(tmp); \
117         }
118
119 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
120                     const gchar *my_body, gboolean my_dry_run)
121 {
122         quote_str = my_quote_str;
123         body = my_body;
124         msginfo = info;
125         dry_run = my_dry_run;
126         stacksize = 0;
127         add_visibility(TRUE);
128         if (buffer != NULL)
129                 *buffer = 0;
130         bufsize = 0;
131         error = 0;
132         /*
133          * force LEX initialization
134          */
135         quote_fmt_firsttime = 1;
136         cursor_pos = -1;
137 }
138
139 void quote_fmterror(char *str)
140 {
141         g_warning("Error: %s\n", str);
142         error = 1;
143 }
144
145 int quote_fmtwrap(void)
146 {
147         return 1;
148 }
149
150 static int isseparator(int ch)
151 {
152         return g_ascii_isspace(ch) || ch == '.' || ch == '-';
153 }
154
155 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
156 {
157         char  result[100];
158         char *rptr;
159         char  zone[6];
160         struct tm lt;
161         const char *fptr;
162         const char *zptr;
163
164         if (!msginfo->date)
165                 return;
166         
167         /* 
168          * ALF - GNU C's strftime() has a nice format specifier 
169          * for time zone offset (%z). Non-standard however, so 
170          * emulate it.
171          */
172
173 #define RLEFT (sizeof result) - (rptr - result) 
174 #define STR_SIZE(x) (sizeof (x) - 1)
175
176         zone[0] = 0;
177
178         if (procheader_date_parse_to_tm(msginfo->date, &lt, zone)) {
179                 /*
180                  * break up format string in tiny bits delimited by valid %z's and 
181                  * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
182                  */
183                 for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
184                         int         perc;
185                         const char *p;
186                         char       *tmp;
187                         
188                         if (NULL != (zptr = strstr(fptr, "%z"))) {
189                                 /*
190                                  * count nr. of prepended percent chars
191                                  */
192                                 for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
193                                         ;
194                                 /*
195                                  * feed to strftime()
196                                  */
197                                 tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : STR_SIZE("%z")));
198                                 if (tmp) {
199                                         rptr += strftime(rptr, RLEFT, tmp, &lt);
200                                         g_free(tmp);
201                                 }
202                                 /*
203                                  * append time zone offset
204                                  */
205                                 if (zone[0] && perc % 2) 
206                                         rptr += g_snprintf(rptr, RLEFT, "%s", zone);
207                                 fptr = zptr + STR_SIZE("%z");
208                         } else {
209                                 rptr += strftime(rptr, RLEFT, fptr, &lt);
210                                 fptr  = NULL;
211                         }
212                 }
213                 
214                 INSERT(result);
215         }
216 #undef STR_SIZE                 
217 #undef RLEFT                    
218 }               
219
220 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
221 {
222         guchar *p;
223         gchar *str;
224
225         if (!msginfo->fromname)
226                 return; 
227         
228         p = strchr(msginfo->fromname, ',');
229         if (p != NULL) {
230                 /* fromname is like "Duck, Donald" */
231                 p++;
232                 while (*p && isspace(*p)) p++;
233                 str = alloca(strlen(p) + 1);
234                 if (str != NULL) {
235                         strcpy(str, p);
236                         INSERT(str);
237                 }
238         } else {
239                 /* fromname is like "Donald Duck" */
240                 str = alloca(strlen(msginfo->fromname) + 1);
241                 if (str != NULL) {
242                         strcpy(str, msginfo->fromname);
243                         p = str;
244                         while (*p && !isspace(*p)) p++;
245                         *p = '\0';
246                         INSERT(str);
247                 }
248         }
249 }
250
251 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
252 {
253         gchar *p;
254         gchar *str;
255
256         /* This probably won't work together very well with Middle
257            names and the like - thth */
258         if (!msginfo->fromname) 
259                 return;
260
261         str = alloca(strlen(msginfo->fromname) + 1);
262         if (str != NULL) {
263                 strcpy(str, msginfo->fromname);
264                 p = strchr(str, ',');
265                 if (p != NULL) {
266                         /* fromname is like "Duck, Donald" */
267                         *p = '\0';
268                         INSERT(str);
269                 } else {
270                         /* fromname is like "Donald Duck" */
271                         p = str;
272                         while (*p && !isspace(*p)) p++;
273                         if (*p) {
274                             /* We found a space. Get first 
275                              none-space char and insert
276                              rest of string from there. */
277                             while (*p && isspace(*p)) p++;
278                             if (*p) {
279                                 INSERT(p);
280                             } else {
281                                 /* If there is no none-space 
282                                  char, just insert whole 
283                                  fromname. */
284                                 INSERT(str);
285                             }
286                         } else {
287                             /* If there is no space, just 
288                              insert whole fromname. */
289                             INSERT(str);
290                         }
291                 }
292         }
293 }
294
295 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
296 {
297 #define MAX_SENDER_INITIAL 20
298         gchar tmp[MAX_SENDER_INITIAL];
299         guchar *p;
300         gchar *cur;
301         gint len = 0;
302
303         if (!msginfo->fromname) 
304                 return;
305
306         p = msginfo->fromname;
307         cur = tmp;
308         while (*p) {
309                 if (*p && g_utf8_validate(p, 1, NULL)) {
310                         *cur = toupper(*p);
311                                 cur++;
312                         len++;
313                         if (len >= MAX_SENDER_INITIAL - 1)
314                                 break;
315                 } else
316                         break;
317                 while (*p && !isseparator(*p)) p++;
318                 while (*p && isseparator(*p)) p++;
319         }
320         *cur = '\0';
321         INSERT(tmp);
322 }
323
324 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
325                                gboolean quoted, gboolean signature,
326                                const gchar *quote_str)
327 {
328         gchar buf[BUFFSIZE];
329         FILE *fp;
330
331         if (!(msginfo->folder || body))
332                 return;
333
334         if (body)
335                 fp = str_open_as_stream(body);
336         else {
337                 if (procmime_msginfo_is_encrypted(msginfo))
338                         fp = procmime_get_first_encrypted_text_content(msginfo);
339                 else
340                         fp = procmime_get_first_text_content(msginfo);
341         }
342
343         if (fp == NULL)
344                 g_warning("Can't get text part\n");
345         else {
346                 while (fgets(buf, sizeof(buf), fp) != NULL) {
347                         strcrchomp(buf);
348                         
349                         if (!signature && strncmp(buf, "-- \n", 4) == 0)
350                                 break;
351                 
352                         if (quoted && quote_str)
353                                 INSERT(quote_str);
354                         
355                         INSERT(buf);
356                 }
357                 fclose(fp);
358         }
359 }
360
361 static void quote_fmt_insert_file(const gchar *filename)
362 {
363         FILE *file;
364         char buffer[256];
365         
366         if ((file = g_fopen(filename, "rb")) != NULL) {
367                 while (fgets(buffer, sizeof(buffer), file)) {
368                         INSERT(buffer);
369                 }
370                 fclose(file);
371         }
372
373 }
374
375 static void quote_fmt_insert_program_output(const gchar *progname)
376 {
377         FILE *file;
378         char buffer[256];
379
380         if ((file = popen(progname, "r")) != NULL) {
381                 while (fgets(buffer, sizeof(buffer), file)) {
382                         INSERT(buffer);
383                 }
384                 pclose(file);
385         }
386 }
387
388 %}
389
390 %union {
391         char chr;
392         char str[256];
393 }
394
395 %token SHOW_NEWSGROUPS
396 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
397 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
398 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
399 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB
400 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
401 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
402 %token QUERY_DATE QUERY_FROM
403 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
404 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
405 %token INSERT_FILE INSERT_PROGRAMOUTPUT
406 %token OPARENT CPARENT
407 %token CHARACTER
408 %token SHOW_DATE_EXPR
409 %token SET_CURSOR_POS
410
411 %start quote_fmt
412
413 %token <chr> CHARACTER
414 %type <chr> character
415 %type <str> string
416
417 %%
418
419 quote_fmt:
420         character_or_special_or_insert_or_query_list;
421
422 character_or_special_or_insert_or_query_list:
423         character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
424         | character_or_special_or_insert_or_query ;
425
426 character_or_special_or_insert_or_query:
427         special
428         | character
429         {
430                 INSERT_CHARACTER($1);
431         }
432         | query
433         | insert ;
434
435 character:
436         CHARACTER
437         ;
438
439 string:
440         CHARACTER
441         {
442                 $$[0] = $1;
443                 $$[1] = '\0';
444         }
445         | string CHARACTER
446         {
447                 int len;
448                 
449                 strncpy($$, $1, sizeof($$));
450                 $$[sizeof($$) - 1] = '\0';
451                 len = strlen($$);
452                 if (len + 1 < sizeof($$)) {
453                         $$[len + 1] = '\0';
454                         $$[len] = $2;
455                 }
456         };
457
458 special:
459         SHOW_NEWSGROUPS
460         {
461                 if (msginfo->newsgroups)
462                         INSERT(msginfo->newsgroups);
463         }
464         | SHOW_DATE_EXPR OPARENT string CPARENT
465         {
466                 quote_fmt_show_date(msginfo, $3);
467         }
468         | SHOW_DATE
469         {
470                 if (msginfo->date)
471                         INSERT(msginfo->date);
472         }
473         | SHOW_FROM
474         {
475                 if (msginfo->from)
476                         INSERT(msginfo->from);
477         }
478         | SHOW_FULLNAME
479         {
480                 if (msginfo->fromname)
481                         INSERT(msginfo->fromname);
482         }
483         | SHOW_FIRST_NAME
484         {
485                 quote_fmt_show_first_name(msginfo);
486         }
487         | SHOW_LAST_NAME
488         {
489                 quote_fmt_show_last_name(msginfo);
490         }
491         | SHOW_SENDER_INITIAL
492         {
493                 quote_fmt_show_sender_initial(msginfo);
494         }
495         | SHOW_SUBJECT
496         {
497                 if (msginfo->subject)
498                         INSERT(msginfo->subject);
499         }
500         | SHOW_TO
501         {
502                 if (msginfo->to)
503                         INSERT(msginfo->to);
504         }
505         | SHOW_MESSAGEID
506         {
507                 if (msginfo->msgid)
508                         INSERT(msginfo->msgid);
509         }
510         | SHOW_PERCENT
511         {
512                 INSERT("%");
513         }
514         | SHOW_CC
515         {
516                 if (msginfo->cc)
517                         INSERT(msginfo->cc);
518         }
519         | SHOW_REFERENCES
520         {
521                 /* CLAWS: use in reply to header */
522                 /* if (msginfo->references)
523                         INSERT(msginfo->references); */
524         }
525         | SHOW_MESSAGE
526         {
527                 quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
528         }
529         | SHOW_QUOTED_MESSAGE
530         {
531                 quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
532         }
533         | SHOW_MESSAGE_NO_SIGNATURE
534         {
535                 quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
536         }
537         | SHOW_QUOTED_MESSAGE_NO_SIGNATURE
538         {
539                 quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
540         }
541         | SHOW_BACKSLASH
542         {
543                 INSERT("\\");
544         }
545         | SHOW_TAB
546         {
547                 INSERT("\t");
548         }
549         | SHOW_EOL
550         {
551                 INSERT("\n");
552         }
553         | SHOW_QUESTION_MARK
554         {
555                 INSERT("?");
556         }
557         | SHOW_PIPE
558         {
559                 INSERT("|");
560         }
561         | SHOW_OPARENT
562         {
563                 INSERT("{");
564         }
565         | SHOW_CPARENT
566         {
567                 INSERT("}");
568         }
569         | SET_CURSOR_POS
570         {
571                 cursor_pos = bufsize;
572         };
573
574 query:
575         QUERY_DATE
576         {
577                 add_visibility(msginfo->date != NULL);
578         }
579         OPARENT quote_fmt CPARENT
580         {
581                 remove_visibility();
582         }
583         | QUERY_FROM
584         {
585                 add_visibility(msginfo->from != NULL);
586         }
587         OPARENT quote_fmt CPARENT
588         {
589                 remove_visibility();
590         }
591         | QUERY_FULLNAME
592         {
593                 add_visibility(msginfo->fromname != NULL);
594         }
595         OPARENT quote_fmt CPARENT
596         {
597                 remove_visibility();
598         }
599         | QUERY_SUBJECT
600         {
601                 add_visibility(msginfo->subject != NULL);
602         }
603         OPARENT quote_fmt CPARENT
604         {
605                 remove_visibility();
606         }
607         | QUERY_TO
608         {
609                 add_visibility(msginfo->to != NULL);
610         }
611         OPARENT quote_fmt CPARENT
612         {
613                 remove_visibility();
614         }
615         | QUERY_NEWSGROUPS
616         {
617                 add_visibility(msginfo->newsgroups != NULL);
618         }
619         OPARENT quote_fmt CPARENT
620         {
621                 remove_visibility();
622         }
623         | QUERY_MESSAGEID
624         {
625                 add_visibility(msginfo->msgid != NULL);
626         }
627         OPARENT quote_fmt CPARENT
628         {
629                 remove_visibility();
630         }
631         | QUERY_CC
632         {
633                 add_visibility(msginfo->cc != NULL);
634         }
635         OPARENT quote_fmt CPARENT
636         {
637                 remove_visibility();
638         }
639         | QUERY_REFERENCES
640         {
641                 /* CLAWS: use in-reply-to header */
642                 /* add_visibility(msginfo->references != NULL); */
643         }
644         OPARENT quote_fmt CPARENT
645         {
646                 remove_visibility();
647         };
648
649 insert:
650         INSERT_FILE OPARENT string CPARENT
651         {
652                 if (!dry_run) {
653                         quote_fmt_insert_file($3);
654                 }
655         }
656         | INSERT_PROGRAMOUTPUT OPARENT string CPARENT
657         {
658                 if (!dry_run) {
659                         quote_fmt_insert_program_output($3);
660                 }
661         };