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