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