Renamed r1bindex() to mhbasename(), to make its function becomes clear.
[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", 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 if (!drft) {
178                         drft = cp;
179                 }
180
181         if (!drft)
182                 adios(NULL, "usage: %s [switches] file", invo_name);
183         if ((in = fopen(drft, "r")) == NULL)
184                 adios(drft, "unable to open");
185
186         tfile = m_mktemp2(NULL, invo_name, NULL, &out);
187         if (tfile == NULL)
188                 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),
257                                 in)) {
258                         case FLD:
259                         case FLDEOF:
260                         case FLDPLUS:
261                                 /*
262                                 ** Check if the value of field contains
263                                 ** anything 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 = 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 && (i = getln(field, sizeof(field))) >= 0);
306                                                 if (i == -1)
307                                                         goto abort;
308                                         }
309                                 }
310
311                                 if (state == FLDEOF) {  /* moby hack */
312                                         fprintf(out, "--------\n");
313                                         printf("--------\n");
314                                         if (!body)
315                                                 break;
316                                         goto no_body;
317                                 }
318                                 continue;
319
320                         case BODY:
321                         case BODYEOF:
322                         case FILEEOF:
323                                 if (!body)
324                                         break;
325                                 fprintf(out, "--------\n");
326                                 if (field[0] == 0 || !prepend)
327                                         printf("--------\n");
328                                 if (field[0]) {
329                                         if (prepend && body) {
330                                                 printf("\n--------Enter initial text\n\n");
331                                                 fflush(stdout);
332                                                 for (;;) {
333                                                         getln(buffer, sizeof(buffer));
334                                                         if (doteof && buffer[0] == '.' && buffer[1] == '\n')
335                                                                 break;
336                                                         if (buffer[0] == 0)
337                                                                 break;
338                                                         fprintf(out, "%s", buffer);
339                                                 }
340                                         }
341
342                                         do {
343                                                 fprintf(out, "%s", field);
344                                                 if (!rapid && !sigint)
345                                                         printf("%s", field);
346                                         } while (state == BODY &&
347                                                         (state = m_getfld(state, name, field, sizeof(field), in)));
348                                         if (prepend || !body)
349                                                 break;
350                                         else
351                                                 printf ("\n--------Enter additional text\n\n");
352                                 }
353 no_body:
354                                 fflush(stdout);
355                                 for (;;) {
356                                         getln(field, sizeof(field));
357                                         if (doteof && field[0] == '.' && field[1] == '\n')
358                                                 break;
359                                         if (field[0] == 0)
360                                                 break;
361                                          fprintf(out, "%s", field);
362                                 }
363                                 break;
364
365                         default:
366                                 adios(NULL, "skeleton is poorly formatted");
367                 }
368                 break;
369         }
370
371         if (body)
372                 printf("--------\n");
373
374         fflush(stdout);
375         fclose(in);
376         fclose(out);
377         SIGNAL(SIGINT, SIG_IGN);
378
379         if (killp || erasep) {
380 #ifdef HAVE_TERMIOS_H
381                  tcsetattr(0, TCSADRAIN, &tio);
382 #else
383 # ifdef HAVE_TERMIO_H
384                 ioctl(0, TCSETAW, &tio);
385 # else
386                 ioctl(0, TIOCSETN, (char *) &tio);
387 # endif
388 #endif
389         }
390
391         if ((fdi = open(tmpfil, O_RDONLY)) == NOTOK)
392                 adios(tmpfil, "unable to re-open");
393         if ((fdo = creat(drft, m_gmprot())) == NOTOK)
394                 adios(drft, "unable to write");
395         cpydata(fdi, fdo, tmpfil, drft);
396         close(fdi);
397         close(fdo);
398         unlink(tmpfil);
399
400         context_save();  /* save the context file */
401         done(0);
402         return 1;
403 }
404
405
406 int
407 getln(char *buffer, int n)
408 {
409         int c;
410         char *cp;
411
412         cp = buffer;
413         *cp = 0;
414
415         switch (setjmp(sigenv)) {
416                 case OK:
417                         wtuser = 1;
418                         break;
419
420                 case DONE:
421                         wtuser = 0;
422                         return 0;
423
424                 default:
425                         wtuser = 0;
426                         return NOTOK;
427         }
428
429         for (;;) {
430                 switch (c = getchar()) {
431                         case EOF:
432                                 clearerr(stdin);
433                                 longjmp(sigenv, DONE);
434
435                         case '\n':
436                                 if (cp[-1] == QUOTE) {
437                                         cp[-1] = c;
438                                         wtuser = 0;
439                                         return 1;
440                                 }
441                                 *cp++ = c;
442                                 *cp = 0;
443                                 wtuser = 0;
444                                 return 0;
445
446                         default:
447                                 if (cp < buffer + n)
448                                         *cp++ = c;
449                                 *cp = 0;
450                 }
451         }
452 }
453
454
455 static RETSIGTYPE
456 intrser(int i)
457 {
458 #ifndef RELIABLE_SIGNALS
459         SIGNAL(SIGINT, intrser);
460 #endif
461
462         if (wtuser)
463                 longjmp(sigenv, NOTOK);
464         sigint++;
465 }
466
467
468 static int
469 chrcnv(char *cp)
470 {
471         return (*cp != QUOTE ? *cp : m_atoi(++cp));
472 }
473
474
475 static void
476 chrdsp(char *s, char c)
477 {
478         printf("%s ", s);
479         if (c < ' ' || c == 0177)
480                 printf("^%c", c ^ 0100);
481         else
482                 printf("%c", c);
483 }