Replaced mkinstalldirs with `mkdir -p'.
[mmh] / uip / prompter.c
1 /*
2 ** prompter.c -- simple prompting editor front-end
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <fcntl.h>
11 #include <h/signals.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <setjmp.h>
15
16 #ifdef HAVE_TERMIOS_H
17 # include <termios.h>
18 #else
19 # ifdef HAVE_TERMIO_H
20 #  include <termio.h>
21 # else
22 #  include <sgtty.h>
23 # endif
24 #endif
25
26 #define QUOTE '\\'
27
28 #ifndef CKILL
29 # define CKILL '@'
30 #endif
31
32 #ifndef CERASE
33 # define CERASE '#'
34 #endif
35
36 static struct swit switches[] = {
37 #define ERASESW  0
38         { "erase chr", 0 },
39 #define KILLSW  1
40         { "kill chr", 0 },
41 #define PREPSW  2
42         { "prepend", 0 },
43 #define NPREPSW  3
44         { "noprepend", 0 },
45 #define RAPDSW  4
46         { "rapid", 0 },
47 #define NRAPDSW  5
48         { "norapid", 0 },
49 #define BODYSW  6
50         { "body", -4 },
51 #define NBODYSW  7
52         { "nobody", -6 },
53 #define DOTSW  8
54         { "doteof", 0 },
55 #define NDOTSW  9
56         { "nodoteof", 0 },
57 #define VERSIONSW 10
58         { "version", 0 },
59 #define HELPSW  11
60         { "help", 0 },
61         { NULL, 0 }
62 };
63
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         char *tfile = NULL;
109
110 #ifdef LOCALE
111         setlocale(LC_ALL, "");
112 #endif
113         invo_name = mhbasename(argv[0]);
114
115         /* read user profile/context */
116         context_read();
117
118         arguments = getarguments(invo_name, argc, argv, 1);
119         argp = arguments;
120
121         while ((cp = *argp++))
122                 if (*cp == '-') {
123                         switch (smatch(++cp, switches)) {
124                         case AMBIGSW:
125                                 ambigsw(cp, switches);
126                                 done(1);
127                         case UNKWNSW:
128                                 adios(NULL, "-%s unknown", cp);
129
130                         case HELPSW:
131                                 snprintf(buffer, sizeof(buffer),
132                                                 "%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",
143                                                         argp[-2]);
144                                 continue;
145                         case KILLSW:
146                                 if (!(killp = *argp++) || *killp == '-')
147                                         adios(NULL, "missing argument to %s",
148                                                         argp[-2]);
149                                 continue;
150
151                         case PREPSW:
152                                 prepend++;
153                                 continue;
154                         case NPREPSW:
155                                 prepend = 0;
156                                 continue;
157
158                         case RAPDSW:
159                                 rapid++;
160                                 continue;
161                         case NRAPDSW:
162                                 rapid = 0;
163                                 continue;
164
165                         case BODYSW:
166                                 body++;
167                                 continue;
168                         case NBODYSW:
169                                 body = 0;
170                                 continue;
171
172                         case DOTSW:
173                                 doteof++;
174                                 continue;
175                         case NDOTSW:
176                                 doteof = 0;
177                                 continue;
178                         }
179                 } else 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         tfile = m_mktemp2(NULL, invo_name, NULL, &out);
189         if (tfile == NULL)
190                 adios("prompter", "unable to create temporary file");
191         chmod(tmpfil, 0600);
192         strncpy(tmpfil, tfile, sizeof(tmpfil));
193
194         /*
195         ** Are we changing the kill or erase character?
196         */
197         if (killp || erasep) {
198 #ifdef HAVE_TERMIOS_H
199                 cc_t save_erase, save_kill;
200 #else
201                 int save_erase, save_kill;
202 #endif
203
204                 /* get the current terminal attributes */
205 #ifdef HAVE_TERMIOS_H
206                 tcgetattr(0, &tio);
207 #else
208 # ifdef HAVE_TERMIO_H
209                 ioctl(0, TCGETA, &tio);
210 # else
211                 ioctl(0, TIOCGETP, (char *) &tio);
212                 ioctl(0, TIOCGETC, (char *) &tc);
213 # endif
214 #endif
215
216                 /* save original kill, erase character for later */
217                 save_kill = KILL;
218                 save_erase = ERASE;
219
220                 /* set new kill, erase character in terminal structure */
221                 KILL = killp ? chrcnv(killp) : save_kill;
222                 ERASE = erasep ? chrcnv(erasep) : save_erase;
223
224                 /* set the new terminal attributes */
225 #ifdef HAVE_TERMIOS_H
226                  tcsetattr(0, TCSADRAIN, &tio);
227 #else
228 # ifdef HAVE_TERMIO_H
229                 ioctl(0, TCSETAW, &tio);
230 # else
231                 ioctl(0, TIOCSETN, (char *) &tio);
232 # endif
233 #endif
234
235                 /* print out new kill erase characters */
236                 chrdsp("erase", ERASE);
237                 chrdsp(", kill", KILL);
238                 chrdsp(", intr", INTR);
239                 putchar('\n');
240                 fflush(stdout);
241
242                 /*
243                 ** We set the kill and erase character back to original
244                 ** setup in terminal structure so we can easily
245                 ** restore it upon exit.
246                 */
247                 KILL = save_kill;
248                 ERASE = save_erase;
249         }
250
251         sigint = 0;
252         SIGNAL2(SIGINT, intrser);
253
254         /*
255         ** Loop through the lines of the draft skeleton.
256         */
257         for (state = FLD;;) {
258                 switch (state = m_getfld(state, name, field, sizeof(field),
259                                 in)) {
260                 case FLD:
261                 case FLDEOF:
262                 case FLDPLUS:
263                         /*
264                         ** Check if the value of field contains
265                         ** anything other than space or tab.
266                         */
267                         for (cp = field; *cp; cp++)
268                                 if (*cp != ' ' && *cp != '\t')
269                                         break;
270
271                         /* If so, just add header line to draft */
272                         if (*cp++ != '\n' || *cp != 0) {
273                                 printf("%s:%s", name, field);
274                                 fprintf(out, "%s:%s", name, field);
275                                 while (state == FLDPLUS) {
276                                         state = m_getfld(state, name, field,
277                                                         sizeof(field), in);
278                                         printf("%s", field);
279                                         fprintf(out, "%s", field);
280                                 }
281                         } else {
282                                 /* Else, get value of header field */
283                                 printf("%s: ", name);
284                                 fflush(stdout);
285                                 i = getln(field, sizeof(field));
286                                 if (i == -1) {
287 abort:
288                                         if (killp || erasep) {
289 #ifdef HAVE_TERMIOS_H
290                                                 tcsetattr(0, TCSADRAIN, &tio);
291 #else
292 # ifdef HAVE_TERMIO
293                                                 ioctl(0, TCSETA, &tio);
294 # else
295                                                 ioctl(0, TIOCSETN, (char *) &tio);
296 # endif
297 #endif
298                                         }
299                                         unlink(tmpfil);
300                                         done(1);
301                                 }
302                                 if (i != 0 || (field[0] != '\n' && field[0] != 0)) {
303                                         fprintf(out, "%s:", name);
304                                         do {
305                                                 if (field[0] != ' ' && field[0] != '\t')
306                                                         putc(' ', out);
307                                                 fprintf(out, "%s", field);
308                                         } while (i == 1 && (i = getln(field, sizeof(field))) >= 0);
309                                         if (i == -1)
310                                                 goto abort;
311                                 }
312                         }
313
314                         if (state == FLDEOF) {  /* moby hack */
315                                 fprintf(out, "--------\n");
316                                 printf("--------\n");
317                                 if (!body)
318                                         break;
319                                 goto no_body;
320                         }
321                         continue;
322
323                 case BODY:
324                 case BODYEOF:
325                 case FILEEOF:
326                         if (!body)
327                                 break;
328                         fprintf(out, "--------\n");
329                         if (field[0] == 0 || !prepend)
330                                 printf("--------\n");
331                         if (field[0]) {
332                                 if (prepend && body) {
333                                         printf("\n--------Enter initial text\n\n");
334                                         fflush(stdout);
335                                         for (;;) {
336                                                 getln(buffer, sizeof(buffer));
337                                                 if (doteof && buffer[0] == '.' && buffer[1] == '\n')
338                                                         break;
339                                                 if (buffer[0] == 0)
340                                                         break;
341                                                 fprintf(out, "%s", buffer);
342                                         }
343                                 }
344
345                                 do {
346                                         fprintf(out, "%s", field);
347                                         if (!rapid && !sigint)
348                                                 printf("%s", field);
349                                 } while (state == BODY && (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 }