5921b34038f0005f79091e1efca9c2f550f066e9
[ncurses.git] / progs / tic.c
1
2 /***************************************************************************
3 *                            COPYRIGHT NOTICE                              *
4 ****************************************************************************
5 *                ncurses is copyright (C) 1992-1995                        *
6 *                          Zeyd M. Ben-Halim                               *
7 *                          zmbenhal@netcom.com                             *
8 *                          Eric S. Raymond                                 *
9 *                          esr@snark.thyrsus.com                           *
10 *                                                                          *
11 *        Permission is hereby granted to reproduce and distribute ncurses  *
12 *        by any means and for any fee, whether alone or as part of a       *
13 *        larger distribution, in source or in binary form, PROVIDED        *
14 *        this notice is included with any such distribution, and is not    *
15 *        removed from any of its header files. Mention of ncurses in any   *
16 *        applications linked with it is highly appreciated.                *
17 *                                                                          *
18 *        ncurses comes AS IS with no warranty, implied or expressed.       *
19 *                                                                          *
20 ***************************************************************************/
21
22 /*
23  *      tic.c --- Main program for terminfo compiler
24  *                      by Eric S. Raymond
25  *
26  */
27
28 #include <progs.priv.h>
29
30 #include <ctype.h>
31
32 #include <dump_entry.h>
33 #include <term_entry.h>
34
35 MODULE_ID("$Id: tic.c,v 1.21 1996/12/30 02:24:15 tom Exp $")
36
37 const char *_nc_progname = "tic";
38
39 static  FILE    *log_fp;
40 static  bool    showsummary = FALSE;
41
42 static  const   char usage_string[] = "[-hc] [-v[n]] [-e names] [-CILNRTrsw1] source-file\n";
43
44 static void usage(void)
45 {
46         static const char *const tbl[] = {
47         "Options:",
48         "  -1         format translation output one capability per line",
49         "  -C         translate entries to termcap source form",
50         "  -I         translate entries to terminfo source form",
51         "  -L         translate entries to full terminfo source form",
52         "  -N         disable smart defaults for source translation",
53         "  -R         restrict translation to given terminfo/termcap version",
54         "  -T         remove size-restrictions on compiled description",
55         "  -c         check only, validate input without compiling or translating",
56         "  -e<names>  translate/compile only entries named by comma-separated list",
57         "  -o<dir>    set output directory for compiled entry writes",
58         "  -r         force resolution of all use entries in source translation",
59         "  -s         print summary statistics",
60         "  -v[n]      set verbosity level",
61         "  -w[n]      set format width for translation output",
62         "",
63         "Parameters:",
64         "  <file>     file to translate or compile"
65         };
66         size_t j;
67
68         printf("Usage: %s %s\n", _nc_progname, usage_string);
69         for (j = 0; j < sizeof(tbl)/sizeof(tbl[0]); j++)
70                 puts(tbl[j]);
71         exit(EXIT_FAILURE);
72 }
73
74 static bool immedhook(ENTRY *ep)
75 /* write out entries with no use capabilities immediately to save storage */
76 {
77 #ifndef HAVE_BIG_CORE
78     /*
79      * This is strictly a core-economy kluge.  The really clean way to handle
80      * compilation is to slurp the whole file into core and then do all the
81      * name-collision checks and entry writes in one swell foop.  But the
82      * terminfo master file is large enough that some core-poor systems swap
83      * like crazy when you compile it this way...there have been reports of
84      * this process taking *three hours*, rather than the twenty seconds or
85      * less typical on my development box.
86      *
87      * So.  This hook *immediately* writes out the referenced entry if it
88      * has no use capabilities.  The compiler main loop refrains from
89      * adding the entry to the in-core list when this hook fires.  If some
90      * other entry later needs to reference an entry that got written
91      * immediately, that's OK; the resolution code will fetch it off disk
92      * when it can't find it in core.
93      *
94      * Name collisions will still be detected, just not as cleanly.  The
95      * write_entry() code complains before overwriting an entry that
96      * postdates the time of tic's first call to write_entry().  Thus
97      * it will complain about overwriting entries newly made during the
98      * tic run, but not about overwriting ones that predate it.
99      *
100      * The reason this is a hook, and not in line with the rest of the
101      * compiler code, is that the support for termcap fallback cannot assume
102      * it has anywhere to spool out these entries!
103      *
104      * The _nc_set_type() call here requires a compensating one in
105      * _nc_parse_entry().
106      *
107      * If you define HAVE_BIG_CORE, you'll disable this kluge.  This will
108      * make tic a bit faster (because the resolution code won't have to do
109      * disk I/O nearly as often).
110      */
111     if (ep->nuses == 0)
112     {
113         int     oldline = _nc_curr_line;
114
115         _nc_set_type(_nc_first_name(ep->tterm.term_names));
116         _nc_curr_line = ep->startline;
117         _nc_write_entry(&ep->tterm);
118         _nc_curr_line = oldline;
119         free(ep->tterm.str_table);
120         return(TRUE);
121     }
122     else
123 #endif /* HAVE_BIG_CORE */
124         return(FALSE);
125 }
126
127 static void put_translate(int c)
128 /* emit a comment char, translating terminfo names to termcap names */
129 {
130     static bool in_name = FALSE;
131     static char namebuf[132], suffix[132], *sp;
132
133     if (!in_name)
134     {
135         if (c == '<')
136         {
137             in_name = TRUE;
138             sp = namebuf;
139         }
140         else
141             putchar(c);
142     }
143     else if (c == '\n' || c == '@')
144     {
145         *sp++ = '\0';
146         (void) putchar('<');
147         (void) fputs(namebuf, stdout);
148         putchar(c);
149         in_name = FALSE;
150     }
151     else if (c != '>')
152         *sp++ = c;
153     else                /* ah! candidate name! */
154     {
155         char    *up, *tp;
156
157         *sp++ = '\0';
158         in_name = FALSE;
159
160         suffix[0] = '\0';
161         if ((up = strchr(namebuf, '#')) != 0
162          || (up = strchr(namebuf, '=')) != 0
163          || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>'))
164         {
165             (void) strcpy(suffix, up);
166             *up = '\0';
167         }
168
169         if ((tp = nametrans(namebuf)) != (char *)NULL)
170         {
171             (void) putchar(':');
172             (void) fputs(tp, stdout);
173             (void) fputs(suffix, stdout);
174             (void) putchar(':');
175         }
176         else
177         {
178             /* couldn't find a translation, just dump the name */
179             (void) putchar('<');
180             (void) fputs(namebuf, stdout);
181             (void) fputs(suffix, stdout);
182             (void) putchar('>');
183         }
184
185     }
186 }
187
188 /* Returns a string, stripped of leading/trailing whitespace */
189 static char *stripped(char *src)
190 {
191         while (isspace(*src))
192                 src++;
193         if (*src != '\0') {
194                 char *dst = strcpy(malloc(strlen(src)+1), src);
195                 size_t len = strlen(dst);
196                 while (--len != 0 && isspace(dst[len]))
197                         dst[len] = '\0';
198                 return dst;
199         }
200         return 0;
201 }
202
203 /* Parse the "-e" option-value into a list of names */
204 static const char **make_namelist(char *src)
205 {
206         const char **dst = 0;
207
208         char *s, *base;
209         size_t pass, n, nn;
210         char buffer[BUFSIZ];
211
212         if (strchr(src, '/') != 0) {    /* a filename */
213                 FILE *fp = fopen(src, "r");
214                 if (fp == 0) {
215                         perror(src);
216                         exit(EXIT_FAILURE);
217                 }
218                 for (pass = 1; pass <= 2; pass++) {
219                         nn = 0;
220                         while (fgets(buffer, sizeof(buffer), fp) != 0) {
221                                 if ((s = stripped(buffer)) != 0) {
222                                         if (dst != 0)
223                                                 dst[nn] = s;
224                                         nn++;
225                                 }
226                         }
227                         if (pass == 1) {
228                                 dst = (const char **)calloc(nn+1, sizeof(*dst));
229                                 rewind(fp);
230                         }
231                 }
232                 fclose(fp);
233         } else {                        /* literal list of names */
234                 for (pass = 1; pass <= 2; pass++) {
235                         for (n = nn = 0, base = src; ; n++) {
236                                 int mark = src[n];
237                                 if (mark == ',' || mark == '\0') {
238                                         if (pass == 1) {
239                                                 nn++;
240                                         } else {
241                                                 src[n] = '\0';
242                                                 if ((s = stripped(base)) != 0)
243                                                         dst[nn++] = s;
244                                                 base = &src[n+1];
245                                         }
246                                 }
247                                 if (mark == '\0')
248                                         break;
249                         }
250                         if (pass == 1)
251                                 dst = (const char **)calloc(nn+1, sizeof(*dst));
252                 }
253         }
254         if (showsummary) {
255                 fprintf(log_fp, "Entries that will be compiled:\n");
256                 for (n = 0; dst[n] != 0; n++)
257                         fprintf(log_fp, "%d:%s\n", n+1, dst[n]);
258         }
259         return dst;
260 }
261
262 static bool matches(const char **needle, const char *haystack)
263 /* does entry in needle list match |-separated field in haystack? */
264 {
265         int code = FALSE;
266         size_t n;
267
268         if (needle != 0)
269         {
270                 for (n = 0; needle[n] != 0; n++)
271                 {
272                         if (_nc_name_match(haystack, needle[n], "|"))
273                         {
274                                 code = TRUE;
275                                 break;
276                         }
277                 }
278         }
279         else
280                 code = TRUE;
281         return(code);
282 }
283
284 int main (int argc, char *argv[])
285 {
286 int     v_opt = -1, debug_level;
287 int     smart_defaults = TRUE;
288 char    *termcap;
289 ENTRY   *qp;
290
291 int     this_opt, last_opt = '?';
292
293 int     outform = F_TERMINFO;   /* output format */
294 int     sortmode = S_TERMINFO;  /* sort_mode */
295
296 int     width = 60;
297 bool    infodump = FALSE;       /* running as captoinfo? */
298 bool    capdump = FALSE;        /* running as infotocap? */
299 bool    forceresolve = FALSE;   /* force resolution */
300 bool    limited = TRUE;
301 char    *tversion = (char *)NULL;
302 const   char    *source_file = "terminfo";
303 const   char    **namelst = 0;
304 char    *outdir = (char *)NULL;
305 bool    check_only = FALSE;
306
307         log_fp = stderr;
308
309         if ((_nc_progname = strrchr(argv[0], '/')) == NULL)
310                 _nc_progname = argv[0];
311         else
312                 _nc_progname++;
313
314         infodump = (strcmp(_nc_progname, "captoinfo") == 0);
315         capdump = (strcmp(_nc_progname, "infotocap") == 0);
316
317         /*
318          * Processing arguments is a little complicated, since someone made a
319          * design decision to allow the numeric values for -w, -v options to
320          * be optional.
321          */
322         while ((this_opt = getopt(argc, argv, "0123456789CILNR:TVce:orsvw")) != EOF) {
323                 if (isdigit(this_opt)) {
324                         switch (last_opt) {
325                         case 'v':
326                                 v_opt = (v_opt * 10) + (this_opt - '0');
327                                 break;
328                         case 'w':
329                                 width = (width * 10) + (this_opt - '0');
330                                 break;
331                         default:
332                                 if (this_opt != '1')
333                                         usage();
334                                 last_opt = this_opt;
335                                 width = 0;
336                         }
337                         continue;
338                 }
339                 switch (this_opt) {
340                 case 'C':
341                         capdump  = TRUE;
342                         outform  = F_TERMCAP;
343                         sortmode = S_TERMCAP;
344                         break;
345                 case 'I':
346                         infodump = TRUE;
347                         outform  = F_TERMINFO;
348                         sortmode = S_TERMINFO;
349                         break;
350                 case 'L':
351                         infodump = TRUE;
352                         outform  = F_VARIABLE;
353                         sortmode = S_VARIABLE;
354                         break;
355                 case 'N':
356                         smart_defaults = FALSE;
357                         break;
358                 case 'R':
359                         tversion = optarg;
360                         break;
361                 case 'T':
362                         limited = FALSE;
363                         break;
364                 case 'V':
365                         puts(NCURSES_VERSION);
366                         return EXIT_SUCCESS;
367                 case 'c':
368                         check_only = TRUE;
369                         break;
370                 case 'e':
371                         namelst = make_namelist(optarg);
372                         break;
373                 case 'o':
374                         outdir = optarg;
375                         break;
376                 case 'r':
377                         forceresolve = TRUE;
378                         break;
379                 case 's':
380                         showsummary = TRUE;
381                         break;
382                 case 'v':
383                         v_opt = 0;
384                         break;
385                 case 'w':
386                         width = 0;
387                         break;
388                 default:
389                         usage();
390                 }
391                 last_opt = this_opt;
392         }
393
394         debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
395         _nc_tracing = (1 << debug_level) - 1;
396
397         if (optind < argc) {
398                 source_file = argv[optind++];
399                 if (optind < argc) {
400                         fprintf (stderr,
401                                 "%s: Too many file names.  Usage:\n\t%s %s",
402                                 _nc_progname,
403                                 _nc_progname,
404                                 usage_string);
405                         return EXIT_FAILURE;
406                 }
407         } else {
408                 if (infodump == TRUE) {
409                         /* captoinfo's no-argument case */
410                         source_file = "/etc/termcap";
411                         if ((termcap = getenv("TERMCAP")) != NULL) {
412                                 if (access(termcap, F_OK) == 0) {
413                                         /* file exists */
414                                         source_file = termcap;
415                                 }
416                         }
417                 } else {
418                 /* tic */
419                         fprintf (stderr,
420                                 "%s: File name needed.  Usage:\n\t%s %s",
421                                 _nc_progname,
422                                 _nc_progname,
423                                 usage_string);
424                         return EXIT_FAILURE;
425                 }
426         }
427
428         if (freopen(source_file, "r", stdin) == NULL) {
429                 fprintf (stderr, "%s: Can't open %s\n", _nc_progname, source_file);
430                 return EXIT_FAILURE;
431         }
432
433         if (infodump)
434                 dump_init(tversion,
435                           smart_defaults
436                                 ? outform
437                                 : F_LITERAL,
438                           sortmode, width, debug_level);
439         else if (capdump)
440                 dump_init(tversion,
441                           outform,
442                           sortmode, width, debug_level);
443
444         /* parse entries out of the source file */
445         _nc_set_source(source_file);
446 #ifndef HAVE_BIG_CORE
447         if (!(check_only || infodump || capdump))
448             _nc_set_writedir(outdir);
449 #endif /* HAVE_BIG_CORE */
450         _nc_read_entry_source(stdin, (char *)NULL,
451                               !smart_defaults, FALSE,
452                               (check_only || infodump || capdump) ? NULLHOOK : immedhook);
453
454         /* do use resolution */
455         if (check_only || (!infodump && !capdump) || forceresolve)
456             if (!_nc_resolve_uses() && !check_only)
457                 return EXIT_FAILURE;
458
459 #ifndef HAVE_BIG_CORE
460         /*
461          * Aaargh! immedhook seriously hoses us!
462          *
463          * One problem with immedhook is it means we can't do -e.  Problem
464          * is that we can't guarantee that for each terminal listed, all the
465          * terminals it depends on will have been kept in core for reference
466          * resolution -- in fact it's certain the primitive types at the end
467          * of reference chains *won't* be in core unless they were explicitly
468          * in the select list themselves.
469          */
470         if (namelst && (!infodump && !capdump))
471         {
472             (void) fprintf(stderr,
473                            "Sorry, -e can't be used without -I or -C\n");
474             return EXIT_FAILURE;
475         }
476 #endif /* HAVE_BIG_CORE */
477
478         /* length check */
479         if (check_only && (capdump || infodump))
480         {
481             for_entry_list(qp)
482             {
483                 if (matches(namelst, qp->tterm.term_names))
484                 {
485                     int len = fmt_entry(&qp->tterm, NULL, TRUE, infodump);
486
487                     if (len>(infodump?MAX_TERMINFO_LENGTH:MAX_TERMCAP_LENGTH))
488                             (void) fprintf(stderr,
489                            "warning: resolved %s entry is %d bytes long\n",
490                            _nc_first_name(qp->tterm.term_names),
491                            len);
492                 }
493             }
494         }
495
496         /* write or dump all entries */
497         if (!check_only)
498         {
499             if (!infodump && !capdump)
500             {
501                 _nc_set_writedir(outdir);
502                 for_entry_list(qp)
503                     if (matches(namelst, qp->tterm.term_names))
504                     {
505                         _nc_set_type(_nc_first_name(qp->tterm.term_names));
506                         _nc_curr_line = qp->startline;
507                         _nc_write_entry(&qp->tterm);
508                     }
509             }
510             else
511             {
512                 bool    trailing_comment = FALSE;
513                 int     c, oldc = '\0';
514
515                 /* this is in case infotocap() generates warnings */
516                 _nc_curr_col = _nc_curr_line = -1;
517
518                 for_entry_list(qp)
519                     if (matches(namelst, qp->tterm.term_names))
520                     {
521                         int     j = qp->cend - qp->cstart;
522                         int     len = 0;
523
524                         /* this is in case infotocap() generates warnings */
525                         _nc_set_type(_nc_first_name(qp->tterm.term_names));
526
527                         (void) fseek(stdin, qp->cstart, SEEK_SET);
528                         while (j-- )
529                             if (infodump)
530                                 (void) putchar(getchar());
531                             else
532                                 put_translate(getchar());
533
534                         len = dump_entry(&qp->tterm, limited, NULL);
535                         for (j = 0; j < qp->nuses; j++)
536                             len += dump_uses((char *)(qp->uses[j].parent), infodump);
537                         (void) putchar('\n');
538                         if (debug_level != 0 && !limited)
539                             printf("# length=%d\n", len);
540                     }
541                 if (!namelst)
542                 {
543                     (void) fseek(stdin, _nc_tail->cend, SEEK_SET);
544                     while ((c = getchar()) != EOF)
545                     {
546                         if (oldc == '\n' && c == '#')
547                             trailing_comment = TRUE;
548                         if (trailing_comment)
549                             putchar(c);
550                         oldc = c;
551                     }
552                 }
553             }
554         }
555
556         /* Show the directory into which entries were written, and the total
557          * number of entries
558          */
559         if (showsummary
560          && (!(check_only || infodump || capdump))) {
561                 int total = _nc_tic_written();
562                 if (total != 0)
563                         fprintf(log_fp, "%d entries written to %s\n",
564                                 total,
565                                 _nc_tic_dir((char *)0));
566                 else
567                         fprintf(log_fp, "No entries written\n");
568         }
569         return(EXIT_SUCCESS);
570 }