Return type of (*done)() changed to void. default_done() replaced by
[mmh] / uip / prompter.c
1
2 /*
3  * prompter.c -- simple prompting editor front-end
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 #include <h/mh.h>
13 #include <fcntl.h>
14 #include <h/signals.h>
15 #include <errno.h>
16 #include <signal.h>
17 #include <setjmp.h>
18
19 #ifdef HAVE_TERMIOS_H
20 # include <termios.h>
21 #else
22 # ifdef HAVE_TERMIO_H
23 #  include <termio.h>
24 # else
25 #  include <sgtty.h>
26 # endif
27 #endif
28
29 #define QUOTE '\\'
30
31 #ifndef CKILL
32 # define CKILL '@'
33 #endif
34
35 #ifndef CERASE
36 # define CERASE '#'
37 #endif
38
39 static struct swit switches[] = {
40 #define ERASESW 0
41     { "erase chr", 0 },
42 #define KILLSW  1
43     { "kill chr", 0 },
44 #define PREPSW  2
45     { "prepend", 0 },
46 #define NPREPSW 3
47     { "noprepend", 0 }, 
48 #define RAPDSW  4
49     { "rapid", 0 },
50 #define NRAPDSW 5
51     { "norapid", 0 },
52 #define BODYSW  6
53     { "body", -4 },
54 #define NBODYSW 7
55     { "nobody", -6 },
56 #define DOTSW   8
57     { "doteof", 0 },
58 #define NDOTSW  9
59     { "nodoteof", 0 },
60 #define VERSIONSW 10
61     { "version", 0 },
62 #define HELPSW  11
63     { "help", 0 },
64     { NULL, 0 }
65 };
66
67
68 #ifdef HAVE_TERMIOS_H
69 static struct termios tio;
70 # define ERASE tio.c_cc[VERASE]
71 # define KILL  tio.c_cc[VKILL]
72 # define INTR  tio.c_cc[VINTR]
73 #else
74 # ifdef HAVE_TERMIO_H
75 static struct termio tio;
76 #  define ERASE tio.c_cc[VERASE]
77 #  define KILL  tio.c_cc[VKILL]
78 #  define INTR  tio.c_cc[VINTR]
79 # else
80 static struct sgttyb tio;
81 static struct tchars tc;
82 #  define ERASE tio.sg_erase
83 #  define KILL  tio.sg_kill
84 #  define INTR  tc.t_intrc
85 # endif
86 #endif
87
88 static int wtuser = 0;
89 static int sigint = 0;
90 static jmp_buf sigenv;
91
92 /*
93  * prototypes
94  */
95 int getln (char *, int);
96 static int chrcnv (char *);
97 static void chrdsp (char *, char);
98 static RETSIGTYPE intrser (int);
99
100
101 int
102 main (int argc, char **argv)
103 {
104     int body = 1, prepend = 1, rapid = 0;
105     int doteof = 0, fdi, fdo, i, state;
106     char *cp, *drft = NULL, *erasep = NULL;
107     char *killp = NULL, name[NAMESZ], field[BUFSIZ];
108     char buffer[BUFSIZ], tmpfil[BUFSIZ];
109     char **arguments, **argp;
110     FILE *in, *out;
111
112 #ifdef LOCALE
113     setlocale(LC_ALL, "");
114 #endif
115     invo_name = r1bindex (argv[0], '/');
116
117     /* read user profile/context */
118     context_read();
119
120     arguments = getarguments (invo_name, argc, argv, 1);
121     argp = arguments;
122
123     while ((cp = *argp++))
124         if (*cp == '-') {
125             switch (smatch (++cp, switches)) {
126                 case AMBIGSW: 
127                     ambigsw (cp, switches);
128                     done (1);
129                 case UNKWNSW: 
130                     adios (NULL, "-%s unknown", cp);
131
132                 case HELPSW: 
133                     snprintf (buffer, sizeof(buffer), "%s [switches] file",
134                         invo_name);
135                     print_help (buffer, switches, 1);
136                     done (1);
137                 case VERSIONSW:
138                     print_version(invo_name);
139                     done (1);
140
141                 case ERASESW: 
142                     if (!(erasep = *argp++) || *erasep == '-')
143                         adios (NULL, "missing argument to %s", argp[-2]);
144                     continue;
145                 case KILLSW: 
146                     if (!(killp = *argp++) || *killp == '-')
147                         adios (NULL, "missing argument to %s", argp[-2]);
148                     continue;
149
150                 case PREPSW: 
151                     prepend++;
152                     continue;
153                 case NPREPSW: 
154                     prepend = 0;
155                     continue;
156
157                 case RAPDSW: 
158                     rapid++;
159                     continue;
160                 case NRAPDSW: 
161                     rapid = 0;
162                     continue;
163
164                 case BODYSW: 
165                     body++;
166                     continue;
167                 case NBODYSW: 
168                     body = 0;
169                     continue;
170
171                 case DOTSW: 
172                     doteof++;
173                     continue;
174                 case NDOTSW: 
175                     doteof = 0;
176                     continue;
177             }
178         } else {
179             if (!drft)
180                 drft = cp;
181         }
182
183     if (!drft)
184         adios (NULL, "usage: %s [switches] file", invo_name);
185     if ((in = fopen (drft, "r")) == NULL)
186         adios (drft, "unable to open");
187
188     strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil));
189     if ((out = fopen (tmpfil, "w")) == NULL)
190         adios (tmpfil, "unable to create");
191     chmod (tmpfil, 0600);
192
193     /*
194      * Are we changing the kill or erase character?
195      */
196     if (killp || erasep) {
197 #ifdef HAVE_TERMIOS_H
198         cc_t save_erase, save_kill;
199 #else
200         int save_erase, save_kill;
201 #endif
202
203         /* get the current terminal attributes */
204 #ifdef HAVE_TERMIOS_H
205         tcgetattr(0, &tio);
206 #else
207 # ifdef HAVE_TERMIO_H
208         ioctl(0, TCGETA, &tio);
209 # else
210         ioctl (0, TIOCGETP, (char *) &tio);
211         ioctl (0, TIOCGETC, (char *) &tc);
212 # endif
213 #endif
214
215         /* save original kill, erase character for later */
216         save_kill = KILL;
217         save_erase = ERASE;
218
219         /* set new kill, erase character in terminal structure */
220         KILL = killp ? chrcnv (killp) : save_kill;
221         ERASE = erasep ? chrcnv (erasep) : save_erase;
222
223         /* set the new terminal attributes */
224 #ifdef HAVE_TERMIOS_H
225          tcsetattr(0, TCSADRAIN, &tio);
226 #else
227 # ifdef HAVE_TERMIO_H
228         ioctl(0, TCSETAW, &tio);
229 # else
230         ioctl (0, TIOCSETN, (char *) &tio);
231 # endif
232 #endif
233
234         /* print out new kill erase characters */
235         chrdsp ("erase", ERASE);
236         chrdsp (", kill", KILL);
237         chrdsp (", intr", INTR);
238         putchar ('\n');
239         fflush (stdout);
240
241         /*
242          * We set the kill and erase character back to original
243          * setup in terminal structure so we can easily
244          * restore it upon exit.
245          */
246         KILL = save_kill;
247         ERASE = save_erase;
248     }
249
250     sigint = 0;
251     SIGNAL2 (SIGINT, intrser);
252
253     /*
254      * Loop through the lines of the draft skeleton.
255      */
256     for (state = FLD;;) {
257         switch (state = m_getfld (state, name, field, sizeof(field), in)) {
258             case FLD: 
259             case FLDEOF: 
260             case FLDPLUS: 
261                 /*
262                  * Check if the value of field contains anything
263                  * other than space or tab.
264                  */
265                 for (cp = field; *cp; cp++)
266                     if (*cp != ' ' && *cp != '\t')
267                         break;
268
269                 /* If so, just add header line to draft */
270                 if (*cp++ != '\n' || *cp != 0) {
271                     printf ("%s:%s", name, field);
272                     fprintf (out, "%s:%s", name, field);
273                     while (state == FLDPLUS) {
274                         state =
275                             m_getfld (state, name, field, sizeof(field), in);
276                         printf ("%s", field);
277                         fprintf (out, "%s", field);
278                     }
279                 } else {
280                     /* Else, get value of header field */
281                     printf ("%s: ", name);
282                     fflush (stdout);
283                     i = getln (field, sizeof(field));
284                     if (i == -1) {
285 abort:
286                         if (killp || erasep) {
287 #ifdef HAVE_TERMIOS_H
288                             tcsetattr(0, TCSADRAIN, &tio);
289 #else
290 # ifdef HAVE_TERMIO
291                             ioctl (0, TCSETA, &tio);
292 # else
293                             ioctl (0, TIOCSETN, (char *) &tio);
294 # endif
295 #endif
296                         }
297                         unlink (tmpfil);
298                         done (1);
299                     }
300                     if (i != 0 || (field[0] != '\n' && field[0] != 0)) {
301                         fprintf (out, "%s:", name);
302                         do {
303                             if (field[0] != ' ' && field[0] != '\t')
304                                 putc (' ', out);
305                             fprintf (out, "%s", field);
306                         } while (i == 1
307                                     && (i = getln (field, sizeof(field))) >= 0);
308                         if (i == -1)
309                             goto abort;
310                     }
311                 }
312
313                 if (state == FLDEOF) {  /* moby hack */
314                     fprintf (out, "--------\n");
315                     printf ("--------\n");
316                     if (!body)
317                         break;
318                     goto no_body;
319                 }
320                 continue;
321
322             case BODY: 
323             case BODYEOF:
324             case FILEEOF: 
325                 if (!body)
326                     break;
327                 fprintf (out, "--------\n");
328                 if (field[0] == 0 || !prepend)
329                     printf ("--------\n");
330                 if (field[0]) {
331                     if (prepend && body) {
332                         printf ("\n--------Enter initial text\n\n");
333                         fflush (stdout);
334                         for (;;) {
335                             getln (buffer, sizeof(buffer));
336                             if (doteof && buffer[0] == '.' && buffer[1] == '\n')
337                                 break;
338                             if (buffer[0] == 0)
339                                 break;
340                             fprintf (out, "%s", buffer);
341                         }
342                     }
343
344                     do {
345                         fprintf (out, "%s", field);
346                         if (!rapid && !sigint)
347                             printf ("%s", field);
348                     } while (state == BODY &&
349                             (state = m_getfld (state, name, field, sizeof(field), in)));
350                     if (prepend || !body)
351                         break;
352                     else
353                         printf ("\n--------Enter additional text\n\n");
354                 }
355 no_body:
356                 fflush (stdout);
357                 for (;;) {
358                     getln (field, sizeof(field));
359                     if (doteof && field[0] == '.' && field[1] == '\n')
360                         break;
361                     if (field[0] == 0)
362                         break;
363                     fprintf (out, "%s", field);
364                 }
365                 break;
366
367             default: 
368                 adios (NULL, "skeleton is poorly formatted");
369         }
370         break;
371     }
372
373     if (body)
374         printf ("--------\n");
375
376     fflush (stdout);
377     fclose (in);
378     fclose (out);
379     SIGNAL (SIGINT, SIG_IGN);
380
381     if (killp || erasep) {
382 #ifdef HAVE_TERMIOS_H
383          tcsetattr(0, TCSADRAIN, &tio);
384 #else
385 # ifdef HAVE_TERMIO_H
386         ioctl (0, TCSETAW, &tio);
387 # else
388         ioctl (0, TIOCSETN, (char *) &tio);
389 # endif
390 #endif
391     }
392
393     if ((fdi = open (tmpfil, O_RDONLY)) == NOTOK)
394         adios (tmpfil, "unable to re-open");
395     if ((fdo = creat (drft, m_gmprot ())) == NOTOK)
396         adios (drft, "unable to write");
397     cpydata (fdi, fdo, tmpfil, drft);
398     close (fdi);
399     close (fdo);
400     unlink (tmpfil);
401
402     context_save ();    /* save the context file */
403     done (0);
404     return 1;
405 }
406
407
408 int
409 getln (char *buffer, int n)
410 {
411     int c;
412     char *cp;
413
414     cp = buffer;
415     *cp = 0;
416
417     switch (setjmp (sigenv)) {
418         case OK: 
419             wtuser = 1;
420             break;
421
422         case DONE: 
423             wtuser = 0;
424             return 0;
425
426         default: 
427             wtuser = 0;
428             return NOTOK;
429     }
430
431     for (;;) {
432         switch (c = getchar ()) {
433             case EOF: 
434                 clearerr (stdin);
435                 longjmp (sigenv, DONE);
436
437             case '\n': 
438                 if (cp[-1] == QUOTE) {
439                     cp[-1] = c;
440                     wtuser = 0;
441                     return 1;
442                 }
443                 *cp++ = c;
444                 *cp = 0;
445                 wtuser = 0;
446                 return 0;
447
448             default: 
449                 if (cp < buffer + n)
450                     *cp++ = c;
451                 *cp = 0;
452         }
453     }
454 }
455
456
457 static RETSIGTYPE
458 intrser (int i)
459 {
460 #ifndef RELIABLE_SIGNALS
461     SIGNAL (SIGINT, intrser);
462 #endif
463
464     if (wtuser)
465         longjmp (sigenv, NOTOK);
466     sigint++;
467 }
468
469
470 static int
471 chrcnv (char *cp)
472 {
473     return (*cp != QUOTE ? *cp : m_atoi (++cp));
474 }
475
476
477 static void
478 chrdsp (char *s, char c)
479 {
480     printf ("%s ", s);
481     if (c < ' ' || c == 0177)
482         printf ("^%c", c ^ 0100);
483     else
484         printf ("%c", c);
485 }