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