Annotation of src/usr.bin/tic/tic.c, Revision 1.3
1.3 ! millert 1: /* $OpenBSD: tic.c,v 1.2 1998/11/03 21:59:53 millert Exp $ */
1.1 millert 2:
3: /****************************************************************************
4: * Copyright (c) 1998 Free Software Foundation, Inc. *
5: * *
6: * Permission is hereby granted, free of charge, to any person obtaining a *
7: * copy of this software and associated documentation files (the *
8: * "Software"), to deal in the Software without restriction, including *
9: * without limitation the rights to use, copy, modify, merge, publish, *
10: * distribute, distribute with modifications, sublicense, and/or sell *
11: * copies of the Software, and to permit persons to whom the Software is *
12: * furnished to do so, subject to the following conditions: *
13: * *
14: * The above copyright notice and this permission notice shall be included *
15: * in all copies or substantial portions of the Software. *
16: * *
17: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
18: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
19: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
20: * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
21: * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
22: * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
23: * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
24: * *
25: * Except as contained in this notice, the name(s) of the above copyright *
26: * holders shall not be used in advertising or otherwise to promote the *
27: * sale, use or other dealings in this Software without prior written *
28: * authorization. *
29: ****************************************************************************/
30:
31: /****************************************************************************
32: * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 *
33: * and: Eric S. Raymond <esr@snark.thyrsus.com> *
34: ****************************************************************************/
35:
36: /*
37: * tic.c --- Main program for terminfo compiler
38: * by Eric S. Raymond
39: *
40: */
41:
42: #include <progs.priv.h>
43:
44: #include <dump_entry.h>
45: #include <term_entry.h>
46:
1.2 millert 47: MODULE_ID("$From: tic.c,v 1.38 1998/10/18 00:27:14 tom Exp $")
1.1 millert 48:
49: const char *_nc_progname = "tic";
50:
51: static FILE *log_fp;
1.2 millert 52: static FILE *tmp_fp;
1.1 millert 53: static bool showsummary = FALSE;
1.2 millert 54: static const char *to_remove;
1.1 millert 55:
56: static const char usage_string[] = "[-h] [-v[n]] [-e names] [-CILNRTcfrsw1] source-file\n";
57:
1.2 millert 58: static void cleanup(void)
59: {
1.3 ! millert 60: if (tmp_fp != 0)
! 61: fclose(tmp_fp);
1.2 millert 62: if (to_remove != 0)
63: remove(to_remove);
64: }
65:
66: static void failed(const char *msg)
67: {
68: perror(msg);
69: cleanup();
70: exit(EXIT_FAILURE);
71: }
72:
1.1 millert 73: static void usage(void)
74: {
75: static const char *const tbl[] = {
76: "Options:",
77: " -1 format translation output one capability per line",
78: " -C translate entries to termcap source form",
79: " -I translate entries to terminfo source form",
80: " -L translate entries to full terminfo source form",
81: " -N disable smart defaults for source translation",
82: " -R restrict translation to given terminfo/termcap version",
83: " -T remove size-restrictions on compiled description",
84: " -c check only, validate input without compiling or translating",
85: " -f format complex strings for readability",
1.2 millert 86: " -g format %'char' to %{number}",
1.1 millert 87: " -e<names> translate/compile only entries named by comma-separated list",
88: " -o<dir> set output directory for compiled entry writes",
89: " -r force resolution of all use entries in source translation",
90: " -s print summary statistics",
91: " -v[n] set verbosity level",
92: " -w[n] set format width for translation output",
93: "",
94: "Parameters:",
95: " <file> file to translate or compile"
96: };
97: size_t j;
98:
99: printf("Usage: %s %s\n", _nc_progname, usage_string);
100: for (j = 0; j < sizeof(tbl)/sizeof(tbl[0]); j++)
101: puts(tbl[j]);
102: exit(EXIT_FAILURE);
103: }
104:
1.2 millert 105: #define L_BRACE '{'
106: #define R_BRACE '}'
107: #define S_QUOTE '\'';
108:
109: static void write_it(ENTRY *ep)
110: {
111: unsigned n;
112: int ch;
113: char *s, *d, *t;
114: char result[MAX_ENTRY_SIZE];
115:
116: /*
117: * Look for strings that contain %{number}, convert them to %'char',
118: * which is shorter and runs a little faster.
119: */
120: for (n = 0; n < STRCOUNT; n++) {
121: s = ep->tterm.Strings[n];
122: if (VALID_STRING(s)
123: && strchr(s, L_BRACE) != 0) {
124: d = result;
125: t = s;
126: while ((ch = *t++) != 0) {
127: *d++ = ch;
128: if (ch == '\\') {
129: *d++ = *t++;
130: } else if ((ch == '%')
131: && (*t == L_BRACE)) {
132: char *v = 0;
133: long value = strtol(t+1, &v, 0);
134: if (v != 0
135: && *v == R_BRACE
136: && value > 0
137: && value != '\\' /* FIXME */
138: && value < 127
139: && isprint((int)value)) {
140: *d++ = S_QUOTE;
141: *d++ = (int)value;
142: *d++ = S_QUOTE;
143: t = (v + 1);
144: }
145: }
146: }
147: *d = 0;
148: if (strlen(result) < strlen(s))
149: strcpy(s, result);
150: }
151: }
152:
153: _nc_set_type(_nc_first_name(ep->tterm.term_names));
154: _nc_curr_line = ep->startline;
155: _nc_write_entry(&ep->tterm);
156: }
157:
1.1 millert 158: static bool immedhook(ENTRY *ep)
159: /* write out entries with no use capabilities immediately to save storage */
160: {
161: #ifndef HAVE_BIG_CORE
162: /*
163: * This is strictly a core-economy kluge. The really clean way to handle
164: * compilation is to slurp the whole file into core and then do all the
165: * name-collision checks and entry writes in one swell foop. But the
166: * terminfo master file is large enough that some core-poor systems swap
167: * like crazy when you compile it this way...there have been reports of
168: * this process taking *three hours*, rather than the twenty seconds or
169: * less typical on my development box.
170: *
171: * So. This hook *immediately* writes out the referenced entry if it
172: * has no use capabilities. The compiler main loop refrains from
173: * adding the entry to the in-core list when this hook fires. If some
174: * other entry later needs to reference an entry that got written
175: * immediately, that's OK; the resolution code will fetch it off disk
176: * when it can't find it in core.
177: *
178: * Name collisions will still be detected, just not as cleanly. The
179: * write_entry() code complains before overwriting an entry that
180: * postdates the time of tic's first call to write_entry(). Thus
181: * it will complain about overwriting entries newly made during the
182: * tic run, but not about overwriting ones that predate it.
183: *
184: * The reason this is a hook, and not in line with the rest of the
185: * compiler code, is that the support for termcap fallback cannot assume
186: * it has anywhere to spool out these entries!
187: *
188: * The _nc_set_type() call here requires a compensating one in
189: * _nc_parse_entry().
190: *
191: * If you define HAVE_BIG_CORE, you'll disable this kluge. This will
192: * make tic a bit faster (because the resolution code won't have to do
193: * disk I/O nearly as often).
194: */
195: if (ep->nuses == 0)
196: {
197: int oldline = _nc_curr_line;
198:
1.2 millert 199: write_it(ep);
1.1 millert 200: _nc_curr_line = oldline;
201: free(ep->tterm.str_table);
202: return(TRUE);
203: }
204: else
205: #endif /* HAVE_BIG_CORE */
206: return(FALSE);
207: }
208:
209: static void put_translate(int c)
210: /* emit a comment char, translating terminfo names to termcap names */
211: {
212: static bool in_name = FALSE;
213: static char namebuf[132], suffix[132], *sp;
214:
215: if (!in_name)
216: {
217: if (c == '<')
218: {
219: in_name = TRUE;
220: sp = namebuf;
221: }
222: else
223: putchar(c);
224: }
225: else if (c == '\n' || c == '@')
226: {
227: *sp++ = '\0';
228: (void) putchar('<');
229: (void) fputs(namebuf, stdout);
230: putchar(c);
231: in_name = FALSE;
232: }
233: else if (c != '>')
234: *sp++ = c;
235: else /* ah! candidate name! */
236: {
237: char *up;
238: NCURSES_CONST char *tp;
239:
240: *sp++ = '\0';
241: in_name = FALSE;
242:
243: suffix[0] = '\0';
244: if ((up = strchr(namebuf, '#')) != 0
245: || (up = strchr(namebuf, '=')) != 0
246: || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>'))
247: {
248: (void) strcpy(suffix, up);
249: *up = '\0';
250: }
251:
252: if ((tp = nametrans(namebuf)) != 0)
253: {
254: (void) putchar(':');
255: (void) fputs(tp, stdout);
256: (void) fputs(suffix, stdout);
257: (void) putchar(':');
258: }
259: else
260: {
261: /* couldn't find a translation, just dump the name */
262: (void) putchar('<');
263: (void) fputs(namebuf, stdout);
264: (void) fputs(suffix, stdout);
265: (void) putchar('>');
266: }
267:
268: }
269: }
270:
271: /* Returns a string, stripped of leading/trailing whitespace */
272: static char *stripped(char *src)
273: {
274: while (isspace(*src))
275: src++;
276: if (*src != '\0') {
277: char *dst = strcpy(malloc(strlen(src)+1), src);
278: size_t len = strlen(dst);
279: while (--len != 0 && isspace(dst[len]))
280: dst[len] = '\0';
281: return dst;
282: }
283: return 0;
284: }
285:
286: /* Parse the "-e" option-value into a list of names */
287: static const char **make_namelist(char *src)
288: {
289: const char **dst = 0;
290:
291: char *s, *base;
1.2 millert 292: unsigned pass, n, nn;
1.1 millert 293: char buffer[BUFSIZ];
294:
1.2 millert 295: if (src == 0) {
296: /* EMPTY */;
297: } else if (strchr(src, '/') != 0) { /* a filename */
1.1 millert 298: FILE *fp = fopen(src, "r");
1.2 millert 299: if (fp == 0)
300: failed(src);
301:
1.1 millert 302: for (pass = 1; pass <= 2; pass++) {
303: nn = 0;
304: while (fgets(buffer, sizeof(buffer), fp) != 0) {
305: if ((s = stripped(buffer)) != 0) {
306: if (dst != 0)
307: dst[nn] = s;
308: nn++;
309: }
310: }
311: if (pass == 1) {
312: dst = (const char **)calloc(nn+1, sizeof(*dst));
313: rewind(fp);
314: }
315: }
316: fclose(fp);
317: } else { /* literal list of names */
318: for (pass = 1; pass <= 2; pass++) {
319: for (n = nn = 0, base = src; ; n++) {
320: int mark = src[n];
321: if (mark == ',' || mark == '\0') {
322: if (pass == 1) {
323: nn++;
324: } else {
325: src[n] = '\0';
326: if ((s = stripped(base)) != 0)
327: dst[nn++] = s;
328: base = &src[n+1];
329: }
330: }
331: if (mark == '\0')
332: break;
333: }
334: if (pass == 1)
335: dst = (const char **)calloc(nn+1, sizeof(*dst));
336: }
337: }
338: if (showsummary) {
339: fprintf(log_fp, "Entries that will be compiled:\n");
340: for (n = 0; dst[n] != 0; n++)
341: fprintf(log_fp, "%d:%s\n", n+1, dst[n]);
342: }
343: return dst;
344: }
345:
346: static bool matches(const char **needle, const char *haystack)
347: /* does entry in needle list match |-separated field in haystack? */
348: {
349: bool code = FALSE;
350: size_t n;
351:
352: if (needle != 0)
353: {
354: for (n = 0; needle[n] != 0; n++)
355: {
356: if (_nc_name_match(haystack, needle[n], "|"))
357: {
358: code = TRUE;
359: break;
360: }
361: }
362: }
363: else
364: code = TRUE;
365: return(code);
366: }
367:
368: int main (int argc, char *argv[])
369: {
1.2 millert 370: char my_tmpname[PATH_MAX];
1.1 millert 371: int v_opt = -1, debug_level;
372: int smart_defaults = TRUE;
373: char *termcap;
374: ENTRY *qp;
375:
376: int this_opt, last_opt = '?';
377:
378: int outform = F_TERMINFO; /* output format */
379: int sortmode = S_TERMINFO; /* sort_mode */
380:
1.2 millert 381: int fd;
1.1 millert 382: int width = 60;
383: bool formatted = FALSE; /* reformat complex strings? */
1.2 millert 384: bool numbers = TRUE; /* format "%'char'" to "%{number}" */
1.1 millert 385: bool infodump = FALSE; /* running as captoinfo? */
386: bool capdump = FALSE; /* running as infotocap? */
387: bool forceresolve = FALSE; /* force resolution */
388: bool limited = TRUE;
389: char *tversion = (char *)NULL;
390: const char *source_file = "terminfo";
391: const char **namelst = 0;
392: char *outdir = (char *)NULL;
393: bool check_only = FALSE;
394:
395: log_fp = stderr;
396:
397: if ((_nc_progname = strrchr(argv[0], '/')) == NULL)
398: _nc_progname = argv[0];
399: else
400: _nc_progname++;
401:
402: infodump = (strcmp(_nc_progname, "captoinfo") == 0);
403: capdump = (strcmp(_nc_progname, "infotocap") == 0);
404:
405: /*
406: * Processing arguments is a little complicated, since someone made a
407: * design decision to allow the numeric values for -w, -v options to
408: * be optional.
409: */
1.2 millert 410: while ((this_opt = getopt(argc, argv, "0123456789CILNR:TVce:fgo:rsvw")) != EOF) {
1.1 millert 411: if (isdigit(this_opt)) {
412: switch (last_opt) {
413: case 'v':
414: v_opt = (v_opt * 10) + (this_opt - '0');
415: break;
416: case 'w':
417: width = (width * 10) + (this_opt - '0');
418: break;
419: default:
420: if (this_opt != '1')
421: usage();
422: last_opt = this_opt;
423: width = 0;
424: }
425: continue;
426: }
427: switch (this_opt) {
428: case 'C':
429: capdump = TRUE;
430: outform = F_TERMCAP;
431: sortmode = S_TERMCAP;
432: break;
433: case 'I':
434: infodump = TRUE;
435: outform = F_TERMINFO;
436: sortmode = S_TERMINFO;
437: break;
438: case 'L':
439: infodump = TRUE;
440: outform = F_VARIABLE;
441: sortmode = S_VARIABLE;
442: break;
443: case 'N':
444: smart_defaults = FALSE;
445: break;
446: case 'R':
447: tversion = optarg;
448: break;
449: case 'T':
450: limited = FALSE;
451: break;
452: case 'V':
453: puts(NCURSES_VERSION);
454: return EXIT_SUCCESS;
455: case 'c':
456: check_only = TRUE;
457: break;
458: case 'e':
459: namelst = make_namelist(optarg);
460: break;
461: case 'f':
462: formatted = TRUE;
463: break;
1.2 millert 464: case 'g':
465: numbers = FALSE;
466: break;
1.1 millert 467: case 'o':
468: outdir = optarg;
469: break;
470: case 'r':
471: forceresolve = TRUE;
472: break;
473: case 's':
474: showsummary = TRUE;
475: break;
476: case 'v':
477: v_opt = 0;
478: break;
479: case 'w':
480: width = 0;
481: break;
482: default:
483: usage();
484: }
485: last_opt = this_opt;
486: }
487:
488: debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
489: _nc_tracing = (1 << debug_level) - 1;
490:
491: if (optind < argc) {
492: source_file = argv[optind++];
493: if (optind < argc) {
494: fprintf (stderr,
495: "%s: Too many file names. Usage:\n\t%s %s",
496: _nc_progname,
497: _nc_progname,
498: usage_string);
499: return EXIT_FAILURE;
500: }
501: } else {
502: if (infodump == TRUE) {
503: /* captoinfo's no-argument case */
1.2 millert 504: source_file = "/usr/share/misc/termcap";
505: if ((termcap = getenv("TERMCAP")) != 0
506: && (namelst = make_namelist(getenv("TERM"))) != 0) {
1.1 millert 507: if (access(termcap, F_OK) == 0) {
508: /* file exists */
509: source_file = termcap;
1.2 millert 510: } else
511: if (strcpy(my_tmpname, "/tmp/tic.XXXXXXXX")
512: && (fd = mkstemp(my_tmpname)) != -1
513: && (tmp_fp = fdopen(fd, "w")) != 0) {
514: fprintf(tmp_fp, "%s\n", termcap);
515: fclose(tmp_fp);
516: tmp_fp = fopen(source_file, "r");
517: to_remove = source_file;
518: } else {
519: failed("mkstemp");
1.1 millert 520: }
521: }
522: } else {
523: /* tic */
524: fprintf (stderr,
525: "%s: File name needed. Usage:\n\t%s %s",
526: _nc_progname,
527: _nc_progname,
528: usage_string);
1.2 millert 529: cleanup();
1.1 millert 530: return EXIT_FAILURE;
531: }
532: }
533:
1.2 millert 534: if (tmp_fp == 0
535: && (tmp_fp = fopen(source_file, "r")) == 0) {
1.1 millert 536: fprintf (stderr, "%s: Can't open %s\n", _nc_progname, source_file);
537: return EXIT_FAILURE;
538: }
539:
540: if (infodump)
541: dump_init(tversion,
542: smart_defaults
543: ? outform
544: : F_LITERAL,
545: sortmode, width, debug_level, formatted);
546: else if (capdump)
547: dump_init(tversion,
548: outform,
549: sortmode, width, debug_level, FALSE);
550:
551: /* parse entries out of the source file */
552: _nc_set_source(source_file);
553: #ifndef HAVE_BIG_CORE
554: if (!(check_only || infodump || capdump))
555: _nc_set_writedir(outdir);
556: #endif /* HAVE_BIG_CORE */
1.2 millert 557: _nc_read_entry_source(tmp_fp, (char *)NULL,
1.1 millert 558: !smart_defaults, FALSE,
559: (check_only || infodump || capdump) ? NULLHOOK : immedhook);
560:
561: /* do use resolution */
1.2 millert 562: if (check_only || (!infodump && !capdump) || forceresolve) {
563: if (!_nc_resolve_uses() && !check_only) {
564: cleanup();
1.1 millert 565: return EXIT_FAILURE;
1.2 millert 566: }
567: }
1.1 millert 568:
569: #ifndef HAVE_BIG_CORE
570: /*
571: * Aaargh! immedhook seriously hoses us!
572: *
573: * One problem with immedhook is it means we can't do -e. Problem
574: * is that we can't guarantee that for each terminal listed, all the
575: * terminals it depends on will have been kept in core for reference
576: * resolution -- in fact it's certain the primitive types at the end
577: * of reference chains *won't* be in core unless they were explicitly
578: * in the select list themselves.
579: */
580: if (namelst && (!infodump && !capdump))
581: {
582: (void) fprintf(stderr,
583: "Sorry, -e can't be used without -I or -C\n");
1.2 millert 584: cleanup();
1.1 millert 585: return EXIT_FAILURE;
586: }
587: #endif /* HAVE_BIG_CORE */
588:
589: /* length check */
590: if (check_only && (capdump || infodump))
591: {
592: for_entry_list(qp)
593: {
594: if (matches(namelst, qp->tterm.term_names))
595: {
1.2 millert 596: int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump, numbers);
1.1 millert 597:
598: if (len>(infodump?MAX_TERMINFO_LENGTH:MAX_TERMCAP_LENGTH))
599: (void) fprintf(stderr,
600: "warning: resolved %s entry is %d bytes long\n",
601: _nc_first_name(qp->tterm.term_names),
602: len);
603: }
604: }
605: }
606:
607: /* write or dump all entries */
608: if (!check_only)
609: {
610: if (!infodump && !capdump)
611: {
612: _nc_set_writedir(outdir);
613: for_entry_list(qp)
614: if (matches(namelst, qp->tterm.term_names))
1.2 millert 615: write_it(qp);
1.1 millert 616: }
617: else
618: {
619: /* this is in case infotocap() generates warnings */
620: _nc_curr_col = _nc_curr_line = -1;
621:
622: for_entry_list(qp)
623: if (matches(namelst, qp->tterm.term_names))
624: {
625: int j = qp->cend - qp->cstart;
626: int len = 0;
627:
628: /* this is in case infotocap() generates warnings */
629: _nc_set_type(_nc_first_name(qp->tterm.term_names));
630:
1.2 millert 631: (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
1.1 millert 632: while (j-- )
633: if (infodump)
1.2 millert 634: (void) putchar(fgetc(tmp_fp));
1.1 millert 635: else
1.2 millert 636: put_translate(fgetc(tmp_fp));
1.1 millert 637:
1.2 millert 638: len = dump_entry(&qp->tterm, limited, numbers, NULL);
1.1 millert 639: for (j = 0; j < qp->nuses; j++)
640: len += dump_uses((char *)(qp->uses[j].parent), infodump);
641: (void) putchar('\n');
642: if (debug_level != 0 && !limited)
643: printf("# length=%d\n", len);
644: }
645: if (!namelst)
646: {
647: int c, oldc = '\0';
648: bool in_comment = FALSE;
649: bool trailing_comment = FALSE;
650:
1.2 millert 651: (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
652: while ((c = fgetc(tmp_fp)) != EOF)
1.1 millert 653: {
654: if (oldc == '\n') {
655: if (c == '#') {
656: trailing_comment = TRUE;
657: in_comment = TRUE;
658: } else {
659: in_comment = FALSE;
660: }
661: }
662: if (trailing_comment
663: && (in_comment || (oldc == '\n' && c == '\n')))
664: putchar(c);
665: oldc = c;
666: }
667: }
668: }
669: }
670:
671: /* Show the directory into which entries were written, and the total
672: * number of entries
673: */
674: if (showsummary
675: && (!(check_only || infodump || capdump))) {
676: int total = _nc_tic_written();
677: if (total != 0)
678: fprintf(log_fp, "%d entries written to %s\n",
679: total,
680: _nc_tic_dir((char *)0));
681: else
682: fprintf(log_fp, "No entries written\n");
683: }
1.2 millert 684: cleanup();
1.1 millert 685: return(EXIT_SUCCESS);
686: }