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