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