addrutil: Re-indent.
[wk-misc.git] / readgnusmarks.c
1 /* readgnusmarks.c - Helper to convert nnml to Maildir
2  *      Copyright (C) 2009 Werner Koch
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 3 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16  */
17
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <stddef.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <stdarg.h>
25 #include <assert.h>
26 #include <ctype.h>
27 #include <time.h>
28
29 #define PGM           "readgnusmarks"
30 #define PGM_VERSION   "0.0"
31 #define PGM_BUGREPORT "wk@gnupg.org"
32
33 /* Option flags. */
34 static int verbose;
35 static int debug;
36 static const char *outputdir;
37
38 /* Error counter.  */
39 static int any_error;
40
41
42 typedef struct
43 {
44   unsigned int in_use:1;
45   unsigned int seen:1;
46   unsigned int replied:1;
47   unsigned int passed:1;
48   unsigned int flagged:1;
49 } flags_t;
50
51 /* Number of flags we currently support.  If one ever needs to process
52    messages with nnnml file number higher an additional data structure
53    is required.  */
54 #define MAX_FLAGS 300000
55
56 /* An array of flags and a value indicating the allocated numer of
57    elements.  */
58 static flags_t *flagarray;
59 static size_t flagarraysize;
60
61
62
63 /* Print diagnostic message and exit with failure. */
64 static void
65 die (const char *format, ...)
66 {
67   va_list arg_ptr;
68
69   fflush (stdout);
70   fprintf (stderr, "%s: ", PGM);
71
72   va_start (arg_ptr, format);
73   vfprintf (stderr, format, arg_ptr);
74   va_end (arg_ptr);
75   putc ('\n', stderr);
76
77   exit (1);
78 }
79
80
81 /* Print diagnostic message. */
82 static void
83 err (const char *format, ...)
84 {
85   va_list arg_ptr;
86
87   any_error = 1;
88
89   fflush (stdout);
90   fprintf (stderr, "%s: ", PGM);
91
92   va_start (arg_ptr, format);
93   vfprintf (stderr, format, arg_ptr);
94   va_end (arg_ptr);
95   putc ('\n', stderr);
96 }
97
98 /* Print an info message. */
99 static void
100 inf (const char *format, ...)
101 {
102   va_list arg_ptr;
103
104   if (!verbose)
105     return;
106
107   fflush (stdout);
108   fprintf (stderr, "%s: ", PGM);
109
110   va_start (arg_ptr, format);
111   vfprintf (stderr, format, arg_ptr);
112   va_end (arg_ptr);
113   putc ('\n', stderr);
114 }
115
116
117
118 static void
119 set_mark (int num, int action)
120 {
121   if (!action)
122     return;
123
124   if (num < 0 || (size_t)num >= flagarraysize)
125     die ("nnml file number %d too high; maximum is %lu", 
126          num, (unsigned long)flagarraysize);
127
128   flagarray[num].in_use = 1;
129   switch (action)
130     {
131     case 'S': flagarray[num].seen = 1; break;
132     case 'R': flagarray[num].replied = 1; break;
133     case 'P': flagarray[num].passed = 1; break;
134     case 'F': flagarray[num].flagged = 1; break;
135     }
136 }
137
138
139
140
141
142 static void
143 read_marks (const char *fname)
144 {
145   FILE *fp;
146   int c;
147   int level = 0;
148   int wait_for_action = 0;
149   char token[50];
150   size_t tokenidx = 0;
151   int action = 0;
152   int num, n_from, n_to, cons_state;
153
154   fp = fopen (fname, "r");
155   if (!fp)
156     die ("failed to open `%s': %s", fname, strerror (errno));
157
158   while ((c = getc (fp)) != EOF)
159     {
160       if (!isascii (c))
161         die ("non ascii character found in `%s' - can't proceed", fname);
162       if (isspace (c))
163         ;
164       else if (level < 0)
165         die ("garbage at end of `%s' - can't proceed", fname);
166       else if (c == ')')
167         ;
168       else if (c == '(')
169         {
170           level++;
171           if (level > 3)
172             die ("nesting too deep in `%s' - can't proceed", fname);
173           if (wait_for_action && level == 3)
174             die ("action missing in `%s' - can't proceed", fname);
175           wait_for_action = (level == 2);
176           if (level == 3)
177             cons_state = n_from = n_to = 0;
178           tokenidx = 0;
179         }
180       else
181         {
182           if (tokenidx+1 >= sizeof token)
183             die ("token too long in `%s' - can't proceed", fname);
184           token[tokenidx++] = c;
185           continue;
186         }
187       
188       token[tokenidx] = 0;
189       if (tokenidx)
190         {
191           tokenidx = 0;
192           if (wait_for_action)
193             {
194               wait_for_action = 0;
195               inf ("got action token `%s'", token);
196               if (!strcmp (token, "read"))
197                 action = 'S';
198               else if (!strcmp (token, "reply"))
199                 action = 'R';
200               else if (!strcmp (token, "forward"))
201                 action = 'P';
202               else if (!strcmp (token, "tick"))
203                 action = 'F';
204               else if (!strcmp (token, "save")
205                        ||!strcmp (token, "dormant")
206                        ||!strcmp (token, "killed"))
207                 {
208                   inf ("action '%s' in `%s' - ignored", token, fname);
209                   action = 0;
210                 }
211               else 
212                 {
213                   err ("unknown action '%s' in `%s' - skipped", token, fname);
214                   action = 0;
215                 }
216             }
217           else if (level == 2)
218             {
219               num = atoi (token);
220               if (num < 1)
221                 err ("bad number `%s' in `%s' - skipped", token, fname);
222               else
223                 set_mark (num, action);
224             }
225           else if (level == 3)
226             {
227               if (*token == '.' && !token[1] && cons_state == 1)
228                 cons_state++;
229               else
230                 {
231                   num = atoi (token);
232                   if (num < 1)
233                     err ("bad number `%s' in `%s' - skipped", token, fname);
234                   else if (!cons_state)
235                     {
236                       cons_state++;
237                       n_from = num;
238                     }
239                   else if (cons_state == 2)
240                     {
241                       cons_state++;
242                       n_to = num;
243                       if (n_to < n_from)
244                         err ("invalid range in `%s'", fname);
245                       for (num = n_from; num <= n_to; num++)
246                         set_mark (num, action);
247                     }
248                   else
249                     err ("too many numbers in cons `%s' - skipped", fname);
250                 }
251             }
252         }
253       if (c == ')')
254         level--;
255     }
256   if (ferror (fp))
257     die ("failed to read `%s': %s", fname, strerror (errno));
258
259   fclose (fp);
260 }
261
262
263 static void
264 process_input (void)
265 {
266   char oldname[1024];
267   char newname[1024+50];
268   size_t n;
269   const char *s;
270   char *endp;
271   int num;
272   int counter = 0;
273
274   while (fgets (oldname, sizeof oldname, stdin))
275     {
276       n = strlen (oldname);
277       if (n && oldname[n-1] == '\n')
278         oldname[--n] = 0;
279       s = oldname;
280       num = (int)strtol (s, &endp, 10);
281       if (num < 1 || *endp != '\0')
282         {
283           err ("bad file name structure '%s' - skipped", oldname);
284           continue;
285         }
286       if (num < 0 || (size_t)num >= flagarraysize)
287         {
288           err ("nnml file number %d in `%s' too high - skipped", num, oldname);
289           continue;
290         }
291
292       snprintf (newname, sizeof newname-50, "%s/cur/%lu.%d-%d", 
293                 outputdir,
294                 (unsigned long)time (NULL), num, ++counter);
295
296       strcat (newname, ":2,");
297       if (flagarray[num].in_use)
298         {
299            if (flagarray[num].flagged)
300              strcat (newname, "F");
301            if (flagarray[num].passed)
302              strcat (newname, "P");
303            if (flagarray[num].replied)
304              strcat (newname, "R");
305            if (flagarray[num].seen)
306              strcat (newname, "S");
307         }
308       if ( !strcmp (oldname, newname) )
309         {
310           inf ("file `%s' not moved", oldname);
311           continue;
312         }
313       printf ("mv '%s' '%s'\n", oldname, newname); 
314     }
315   if (ferror (stdin))
316     die ("error reading from stdin: %s", strerror (errno));
317 }
318
319
320
321
322 static int
323 show_usage (int ex)
324 {
325   fputs ("Usage: " PGM " <MARKSFILE> [OUTDIR]\n"
326          "Read an nnml .marks file and rename Maildir files from stdin\n\n"
327          "  --verbose      enable extra informational output\n"
328          "  --debug        enable additional debug output\n"
329          "  --help         display this help and exit\n\n"
330          "This tool is used with the mv-nnml2maildir script.\n"
331          "Report bugs to " PGM_BUGREPORT ".\n",
332          ex? stderr:stdout);
333   exit (ex);
334 }
335
336
337 int 
338 main (int argc, char **argv)
339 {
340   int last_argc = -1;
341
342   if (argc)
343     {
344       argc--; argv++;
345     }
346   while (argc && last_argc != argc )
347     {
348       last_argc = argc;
349       if (!strcmp (*argv, "--"))
350         {
351           argc--; argv++;
352           break;
353         }
354       else if (!strcmp (*argv, "--version"))
355         {
356           fputs (PGM " " PGM_VERSION "\n", stdout);
357           exit (0);
358         }
359       else if (!strcmp (*argv, "--help"))
360         {
361           show_usage (0);
362         }
363       else if (!strcmp (*argv, "--verbose"))
364         {
365           verbose = 1;
366           argc--; argv++;
367         }
368       else if (!strcmp (*argv, "--debug"))
369         {
370           verbose = debug = 1;
371           argc--; argv++;
372         }
373       else if (!strncmp (*argv, "--", 2))
374         show_usage (1);
375     }          
376
377   if (argc < 1 || argc > 2 )
378     show_usage (1);
379   if (argc == 2)
380     outputdir = argv[1];
381   else
382     outputdir = ".";
383
384   flagarraysize = MAX_FLAGS;
385   flagarray = calloc (flagarraysize, sizeof *flagarray);
386   if (!flagarray)
387     die ("out of core: %s", strerror (errno));
388
389   read_marks (*argv);
390   
391   process_input ();
392
393   free (flagarray);
394
395   return any_error? 1:0;
396 }
397
398
399 /*
400 Local Variables:
401 compile-command: "gcc -Wall -W -O2 -g -o readgnusmarks readgnusmarks.c"
402 End:
403 */