]> ncurses.scripts.mit.edu Git - ncurses.git/blob - progs/infocmp.c
ncurses 6.4 - patch 20230128
[ncurses.git] / progs / infocmp.c
1 /****************************************************************************
2  * Copyright 2020-2021,2022 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey                        1996-on                 *
34  ****************************************************************************/
35
36 /*
37  *      infocmp.c -- decompile an entry, or compare two entries
38  *              written by Eric S. Raymond
39  *              and Thomas E Dickey
40  */
41
42 #include <progs.priv.h>
43
44 #include <dump_entry.h>
45
46 MODULE_ID("$Id: infocmp.c,v 1.156 2022/09/24 10:13:06 tom Exp $")
47
48 #define MAX_STRING      1024    /* maximum formatted string */
49
50 const char *_nc_progname = "infocmp";
51
52 typedef char path[PATH_MAX];
53
54 /***************************************************************************
55  *
56  * The following control variables, together with the contents of the
57  * terminfo entries, completely determine the actions of the program.
58  *
59  ***************************************************************************/
60
61 static ENTRY *entries;          /* terminfo entries */
62 static int termcount;           /* count of terminal entries */
63
64 static bool limited = TRUE;     /* "-r" option is not set */
65 static bool quiet = FALSE;
66 static bool literal = FALSE;
67 static const char *bool_sep = ":";
68 static const char *s_absent = "NULL";
69 static const char *s_cancel = "NULL";
70 static const char *tversion;    /* terminfo version selected */
71 static unsigned itrace;         /* trace flag for debugging */
72 static int mwidth = 60;
73 static int mheight = 65535;
74 static int numbers = 0;         /* format "%'char'" to/from "%{number}" */
75 static int outform = F_TERMINFO;        /* output format */
76 static int sortmode;            /* sort_mode */
77
78 /* main comparison mode */
79 static int compare;
80 #define C_DEFAULT       0       /* don't force comparison mode */
81 #define C_DIFFERENCE    1       /* list differences between two terminals */
82 #define C_COMMON        2       /* list common capabilities */
83 #define C_NAND          3       /* list capabilities in neither terminal */
84 #define C_USEALL        4       /* generate relative use-form entry */
85 static bool ignorepads;         /* ignore pad prefixes when diffing */
86
87 #if NO_LEAKS
88
89 typedef struct {
90     ENTRY *head;
91     ENTRY *tail;
92 } ENTERED;
93
94 static ENTERED *entered;
95
96 #undef ExitProgram
97 static GCC_NORETURN void ExitProgram(int code);
98 /* prototype is to get gcc to accept the noreturn attribute */
99 static void
100 ExitProgram(int code)
101 {
102     int n;
103
104     for (n = 0; n < termcount; ++n) {
105         ENTRY *new_head = _nc_head;
106         ENTRY *new_tail = _nc_tail;
107         _nc_head = entered[n].head;
108         _nc_tail = entered[n].tail;
109         _nc_free_entries(entered[n].head);
110         _nc_head = new_head;
111         _nc_tail = new_tail;
112     }
113     _nc_leaks_dump_entry();
114     free(entries);
115     free(entered);
116     _nc_free_tic(code);
117 }
118 #endif
119
120 static void
121 failed(const char *s)
122 {
123     perror(s);
124     ExitProgram(EXIT_FAILURE);
125 }
126
127 static void
128 canonical_name(char *source, char *target)
129 /* extract the terminal type's primary name */
130 {
131     int limit = NAMESIZE;
132
133     while (--limit > 0) {
134         char ch = *source++;
135         if (ch == '|')
136             break;
137         *target++ = ch;
138     }
139     *target = '\0';
140 }
141
142 static bool
143 no_boolean(int value)
144 {
145     bool result = (value == ABSENT_BOOLEAN);
146     if (!strcmp(s_absent, s_cancel))
147         result = !VALID_BOOLEAN(value);
148     return result;
149 }
150
151 static bool
152 no_numeric(int value)
153 {
154     bool result = (value == ABSENT_NUMERIC);
155     if (!strcmp(s_absent, s_cancel))
156         result = !VALID_NUMERIC(value);
157     return result;
158 }
159
160 static bool
161 no_string(char *value)
162 {
163     bool result = (value == ABSENT_STRING);
164     if (!strcmp(s_absent, s_cancel))
165         result = !VALID_STRING(value);
166     return result;
167 }
168
169 /***************************************************************************
170  *
171  * Predicates for dump function
172  *
173  ***************************************************************************/
174
175 static int
176 capcmp(PredIdx idx, const char *s, const char *t)
177 /* capability comparison function */
178 {
179     if (!VALID_STRING(s) && !VALID_STRING(t))
180         return (s != t);
181     else if (!VALID_STRING(s) || !VALID_STRING(t))
182         return (1);
183
184     if ((idx == acs_chars_index) || !ignorepads)
185         return (strcmp(s, t));
186     else
187         return (_nc_capcmp(s, t));
188 }
189
190 static int
191 use_predicate(unsigned type, PredIdx idx)
192 /* predicate function to use for use decompilation */
193 {
194     ENTRY *ep;
195
196     switch (type) {
197     case BOOLEAN:
198         {
199             int is_set = FALSE;
200
201             /*
202              * This assumes that multiple use entries are supposed
203              * to contribute the logical or of their boolean capabilities.
204              * This is true if we take the semantics of multiple uses to
205              * be 'each capability gets the first non-default value found
206              * in the sequence of use entries'.
207              *
208              * Note that cancelled or absent booleans are stored as FALSE,
209              * unlike numbers and strings, whose cancelled/absent state is
210              * recorded in the terminfo database.
211              */
212             for (ep = &entries[1]; ep < entries + termcount; ep++)
213                 if (ep->tterm.Booleans[idx] == TRUE) {
214                     is_set = entries[0].tterm.Booleans[idx];
215                     break;
216                 }
217             if (is_set != entries[0].tterm.Booleans[idx])
218                 return (!is_set);
219             else
220                 return (FAIL);
221         }
222
223     case NUMBER:
224         {
225             int value = ABSENT_NUMERIC;
226
227             /*
228              * We take the semantics of multiple uses to be 'each
229              * capability gets the first non-default value found
230              * in the sequence of use entries'.
231              */
232             for (ep = &entries[1]; ep < entries + termcount; ep++)
233                 if (VALID_NUMERIC(ep->tterm.Numbers[idx])) {
234                     value = ep->tterm.Numbers[idx];
235                     break;
236                 }
237
238             if (value != entries[0].tterm.Numbers[idx])
239                 return (value != ABSENT_NUMERIC);
240             else
241                 return (FAIL);
242         }
243
244     case STRING:
245         {
246             char *termstr, *usestr = ABSENT_STRING;
247
248             termstr = entries[0].tterm.Strings[idx];
249
250             /*
251              * We take the semantics of multiple uses to be 'each
252              * capability gets the first non-default value found
253              * in the sequence of use entries'.
254              */
255             for (ep = &entries[1]; ep < entries + termcount; ep++)
256                 if (ep->tterm.Strings[idx]) {
257                     usestr = ep->tterm.Strings[idx];
258                     break;
259                 }
260
261             if (usestr == ABSENT_STRING && termstr == ABSENT_STRING)
262                 return (FAIL);
263             else if (!usestr || !termstr || capcmp(idx, usestr, termstr))
264                 return (TRUE);
265             else
266                 return (FAIL);
267         }
268     }
269
270     return (FALSE);             /* pacify compiler */
271 }
272
273 static bool
274 useeq(ENTRY * e1, ENTRY * e2)
275 /* are the use references in two entries equivalent? */
276 {
277     unsigned i, j;
278
279     if (e1->nuses != e2->nuses)
280         return (FALSE);
281
282     /* Ugh...this is quadratic again */
283     for (i = 0; i < e1->nuses; i++) {
284         bool foundmatch = FALSE;
285
286         /* search second entry for given use reference */
287         for (j = 0; j < e2->nuses; j++)
288             if (!strcmp(e1->uses[i].name, e2->uses[j].name)) {
289                 foundmatch = TRUE;
290                 break;
291             }
292
293         if (!foundmatch)
294             return (FALSE);
295     }
296
297     return (TRUE);
298 }
299
300 static bool
301 entryeq(TERMTYPE2 *t1, TERMTYPE2 *t2)
302 /* are two entries equivalent? */
303 {
304     unsigned i;
305
306     for (i = 0; i < NUM_BOOLEANS(t1); i++)
307         if (t1->Booleans[i] != t2->Booleans[i])
308             return (FALSE);
309
310     for (i = 0; i < NUM_NUMBERS(t1); i++)
311         if (t1->Numbers[i] != t2->Numbers[i])
312             return (FALSE);
313
314     for (i = 0; i < NUM_STRINGS(t1); i++)
315         if (capcmp((PredIdx) i, t1->Strings[i], t2->Strings[i]))
316             return (FALSE);
317
318     return (TRUE);
319 }
320
321 #define TIC_EXPAND(result) _nc_tic_expand(result, outform==F_TERMINFO, numbers)
322
323 static void
324 print_uses(ENTRY * ep, FILE *fp)
325 /* print an entry's use references */
326 {
327     if (!ep->nuses) {
328         fputs("NULL", fp);
329     } else {
330         unsigned i;
331
332         for (i = 0; i < ep->nuses; i++) {
333             fputs(ep->uses[i].name, fp);
334             if (i < ep->nuses - 1)
335                 fputs(" ", fp);
336         }
337     }
338 }
339
340 static const char *
341 dump_boolean(int val)
342 /* display the value of a boolean capability */
343 {
344     switch (val) {
345     case ABSENT_BOOLEAN:
346         return (s_absent);
347     case CANCELLED_BOOLEAN:
348         return (s_cancel);
349     case FALSE:
350         return ("F");
351     case TRUE:
352         return ("T");
353     default:
354         return ("?");
355     }
356 }
357
358 static void
359 dump_numeric(int val, char *buf)
360 /* display the value of a numeric capability */
361 {
362     switch (val) {
363     case ABSENT_NUMERIC:
364         _nc_STRCPY(buf, s_absent, MAX_STRING);
365         break;
366     case CANCELLED_NUMERIC:
367         _nc_STRCPY(buf, s_cancel, MAX_STRING);
368         break;
369     default:
370         _nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING) "%d", val);
371         break;
372     }
373 }
374
375 static void
376 dump_string(char *val, char *buf)
377 /* display the value of a string capability */
378 {
379     if (val == ABSENT_STRING)
380         _nc_STRCPY(buf, s_absent, MAX_STRING);
381     else if (val == CANCELLED_STRING)
382         _nc_STRCPY(buf, s_cancel, MAX_STRING);
383     else {
384         _nc_SPRINTF(buf, _nc_SLIMIT(MAX_STRING)
385                     "'%.*s'", MAX_STRING - 3, TIC_EXPAND(val));
386     }
387 }
388
389 /*
390  * Show "comparing..." message for the given terminal names.
391  */
392 static void
393 show_comparing(char **names)
394 {
395     if (itrace) {
396         switch (compare) {
397         case C_DIFFERENCE:
398             (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname);
399             break;
400
401         case C_COMMON:
402             (void) fprintf(stderr, "%s: dumping common capabilities\n", _nc_progname);
403             break;
404
405         case C_NAND:
406             (void) fprintf(stderr, "%s: dumping differences\n", _nc_progname);
407             break;
408         }
409     }
410     if (*names) {
411         printf("comparing %s", *names++);
412         if (*names) {
413             printf(" to %s", *names++);
414             while (*names) {
415                 printf(", %s", *names++);
416             }
417         }
418         printf(".\n");
419     }
420 }
421
422 /*
423  * ncurses stores two types of non-standard capabilities:
424  * a) capabilities listed past the "STOP-HERE" comment in the Caps file.
425  *    These are used in the terminfo source file to provide data for termcaps,
426  *    e.g., when there is no equivalent capability in terminfo, as well as for
427  *    widely-used non-standard capabilities.
428  * b) user-definable capabilities, via "tic -x".
429  *
430  * However, if "-x" is omitted from the tic command, both types of
431  * non-standard capability are not loaded into the terminfo database.  This
432  * macro is used for limit-checks against the symbols that tic uses to omit
433  * the two types of non-standard entry.
434  */
435 #if NCURSES_XNAMES
436 #define check_user_definable(n,limit) if (!_nc_user_definable && (n) > (limit)) break
437 #else
438 #define check_user_definable(n,limit) if ((n) > (limit)) break
439 #endif
440
441 /*
442  * Use these macros to simplify loops on C_COMMON and C_NAND:
443  */
444 #define for_each_entry() while (entries[extra].tterm.term_names)
445 #define next_entry           (&(entries[extra++].tterm))
446
447 static void
448 compare_predicate(PredType type, PredIdx idx, const char *name)
449 /* predicate function to use for entry difference reports */
450 {
451     ENTRY *e1 = &entries[0];
452     ENTRY *e2 = &entries[1];
453     char buf1[MAX_STRING];
454     char buf2[MAX_STRING];
455     int b1, b2;
456     int n1, n2;
457     char *s1, *s2;
458     bool found;
459     int extra = 1;
460
461     switch (type) {
462     case CMP_BOOLEAN:
463         check_user_definable(idx, BOOLWRITE);
464         b1 = e1->tterm.Booleans[idx];
465         switch (compare) {
466         case C_DIFFERENCE:
467             b2 = next_entry->Booleans[idx];
468             if (!(no_boolean(b1) && no_boolean(b2)) && (b1 != b2))
469                 (void) printf("\t%s: %s%s%s.\n",
470                               name,
471                               dump_boolean(b1),
472                               bool_sep,
473                               dump_boolean(b2));
474             break;
475
476         case C_COMMON:
477             if (b1 != ABSENT_BOOLEAN) {
478                 found = TRUE;
479                 for_each_entry() {
480                     b2 = next_entry->Booleans[idx];
481                     if (b1 != b2) {
482                         found = FALSE;
483                         break;
484                     }
485                 }
486                 if (found) {
487                     (void) printf("\t%s= %s.\n", name, dump_boolean(b1));
488                 }
489             }
490             break;
491
492         case C_NAND:
493             if (b1 == ABSENT_BOOLEAN) {
494                 found = TRUE;
495                 for_each_entry() {
496                     b2 = next_entry->Booleans[idx];
497                     if (b1 != b2) {
498                         found = FALSE;
499                         break;
500                     }
501                 }
502                 if (found) {
503                     (void) printf("\t!%s.\n", name);
504                 }
505             }
506             break;
507         }
508         break;
509
510     case CMP_NUMBER:
511         check_user_definable(idx, NUMWRITE);
512         n1 = e1->tterm.Numbers[idx];
513         switch (compare) {
514         case C_DIFFERENCE:
515             n2 = next_entry->Numbers[idx];
516             if (!(no_numeric(n1) && no_numeric(n2)) && n1 != n2) {
517                 dump_numeric(n1, buf1);
518                 dump_numeric(n2, buf2);
519                 (void) printf("\t%s: %s, %s.\n", name, buf1, buf2);
520             }
521             break;
522
523         case C_COMMON:
524             if (n1 != ABSENT_NUMERIC) {
525                 found = TRUE;
526                 for_each_entry() {
527                     n2 = next_entry->Numbers[idx];
528                     if (n1 != n2) {
529                         found = FALSE;
530                         break;
531                     }
532                 }
533                 if (found) {
534                     dump_numeric(n1, buf1);
535                     (void) printf("\t%s= %s.\n", name, buf1);
536                 }
537             }
538             break;
539
540         case C_NAND:
541             if (n1 == ABSENT_NUMERIC) {
542                 found = TRUE;
543                 for_each_entry() {
544                     n2 = next_entry->Numbers[idx];
545                     if (n1 != n2) {
546                         found = FALSE;
547                         break;
548                     }
549                 }
550                 if (found) {
551                     (void) printf("\t!%s.\n", name);
552                 }
553             }
554             break;
555         }
556         break;
557
558     case CMP_STRING:
559         check_user_definable(idx, STRWRITE);
560         s1 = e1->tterm.Strings[idx];
561         switch (compare) {
562         case C_DIFFERENCE:
563             s2 = next_entry->Strings[idx];
564             if (!(no_string(s1) && no_string(s2)) && capcmp(idx, s1, s2)) {
565                 dump_string(s1, buf1);
566                 dump_string(s2, buf2);
567                 if (strcmp(buf1, buf2))
568                     (void) printf("\t%s: %s, %s.\n", name, buf1, buf2);
569             }
570             break;
571
572         case C_COMMON:
573             if (s1 != ABSENT_STRING) {
574                 found = TRUE;
575                 for_each_entry() {
576                     s2 = next_entry->Strings[idx];
577                     if (capcmp(idx, s1, s2) != 0) {
578                         found = FALSE;
579                         break;
580                     }
581                 }
582                 if (found) {
583                     (void) printf("\t%s= '%s'.\n", name, TIC_EXPAND(s1));
584                 }
585             }
586             break;
587
588         case C_NAND:
589             if (s1 == ABSENT_STRING) {
590                 found = TRUE;
591                 for_each_entry() {
592                     s2 = next_entry->Strings[idx];
593                     if (s2 != s1) {
594                         found = FALSE;
595                         break;
596                     }
597                 }
598                 if (found) {
599                     (void) printf("\t!%s.\n", name);
600                 }
601             }
602             break;
603         }
604         break;
605
606     case CMP_USE:
607         /* unlike the other modes, this compares *all* use entries */
608         switch (compare) {
609         case C_DIFFERENCE:
610             if (!useeq(e1, e2)) {
611                 (void) fputs("\tuse: ", stdout);
612                 print_uses(e1, stdout);
613                 fputs(", ", stdout);
614                 print_uses(e2, stdout);
615                 fputs(".\n", stdout);
616             }
617             break;
618
619         case C_COMMON:
620             if (e1->nuses) {
621                 found = TRUE;
622                 for_each_entry() {
623                     e2 = &entries[extra++];
624                     if (e2->nuses != e1->nuses || !useeq(e1, e2)) {
625                         found = FALSE;
626                         break;
627                     }
628                 }
629                 if (found) {
630                     (void) fputs("\tuse: ", stdout);
631                     print_uses(e1, stdout);
632                     fputs(".\n", stdout);
633                 }
634             }
635             break;
636
637         case C_NAND:
638             if (!e1->nuses) {
639                 found = TRUE;
640                 for_each_entry() {
641                     e2 = &entries[extra++];
642                     if (e2->nuses != e1->nuses) {
643                         found = FALSE;
644                         break;
645                     }
646                 }
647                 if (found) {
648                     (void) printf("\t!use.\n");
649                 }
650             }
651             break;
652         }
653     }
654 }
655
656 /***************************************************************************
657  *
658  * Init string analysis
659  *
660  ***************************************************************************/
661
662 #define DATA(from, to) { { from }, { to } }
663 #define DATAX()        DATA("", "")
664
665 typedef struct {
666     const char from[4];
667     const char to[12];
668 } assoc;
669
670 static const assoc std_caps[] =
671 {
672     /* these are specified by X.364 and iBCS2 */
673     DATA("\033c", "RIS"),       /* full reset */
674     DATA("\0337", "SC"),        /* save cursor */
675     DATA("\0338", "RC"),        /* restore cursor */
676     DATA("\033[r", "RSR"),      /* not an X.364 mnemonic */
677     DATA("\033[m", "SGR0"),     /* not an X.364 mnemonic */
678     DATA("\033[2J", "ED2"),     /* clear page */
679
680     /* this group is specified by ISO 2022 */
681     DATA("\033(0", "ISO DEC G0"),       /* enable DEC graphics for G0 */
682     DATA("\033(A", "ISO UK G0"),        /* enable UK chars for G0 */
683     DATA("\033(B", "ISO US G0"),        /* enable US chars for G0 */
684     DATA("\033)0", "ISO DEC G1"),       /* enable DEC graphics for G1 */
685     DATA("\033)A", "ISO UK G1"),        /* enable UK chars for G1 */
686     DATA("\033)B", "ISO US G1"),        /* enable US chars for G1 */
687
688     /* these are DEC private controls widely supported by emulators */
689     DATA("\033=", "DECPAM"),    /* application keypad mode */
690     DATA("\033>", "DECPNM"),    /* normal keypad mode */
691     DATA("\033<", "DECANSI"),   /* enter ANSI mode */
692     DATA("\033[!p", "DECSTR"),  /* soft reset */
693     DATA("\033 F", "S7C1T"),    /* 7-bit controls */
694
695     DATAX()
696 };
697
698 static const assoc std_modes[] =
699 /* ECMA \E[ ... [hl] modes recognized by many emulators */
700 {
701     DATA("2", "AM"),            /* keyboard action mode */
702     DATA("4", "IRM"),           /* insert/replace mode */
703     DATA("12", "SRM"),          /* send/receive mode */
704     DATA("20", "LNM"),          /* linefeed mode */
705     DATAX()
706 };
707
708 static const assoc private_modes[] =
709 /* DEC \E[ ... [hl] modes recognized by many emulators */
710 {
711     DATA("1", "CKM"),           /* application cursor keys */
712     DATA("2", "ANM"),           /* set VT52 mode */
713     DATA("3", "COLM"),          /* 132-column mode */
714     DATA("4", "SCLM"),          /* smooth scroll */
715     DATA("5", "SCNM"),          /* reverse video mode */
716     DATA("6", "OM"),            /* origin mode */
717     DATA("7", "AWM"),           /* wraparound mode */
718     DATA("8", "ARM"),           /* auto-repeat mode */
719     DATAX()
720 };
721
722 static const assoc ecma_highlights[] =
723 /* recognize ECMA attribute sequences */
724 {
725     DATA("0", "NORMAL"),        /* normal */
726     DATA("1", "+BOLD"),         /* bold on */
727     DATA("2", "+DIM"),          /* dim on */
728     DATA("3", "+ITALIC"),       /* italic on */
729     DATA("4", "+UNDERLINE"),    /* underline on */
730     DATA("5", "+BLINK"),        /* blink on */
731     DATA("6", "+FASTBLINK"),    /* fastblink on */
732     DATA("7", "+REVERSE"),      /* reverse on */
733     DATA("8", "+INVISIBLE"),    /* invisible on */
734     DATA("9", "+DELETED"),      /* deleted on */
735     DATA("10", "MAIN-FONT"),    /* select primary font */
736     DATA("11", "ALT-FONT-1"),   /* select alternate font 1 */
737     DATA("12", "ALT-FONT-2"),   /* select alternate font 2 */
738     DATA("13", "ALT-FONT-3"),   /* select alternate font 3 */
739     DATA("14", "ALT-FONT-4"),   /* select alternate font 4 */
740     DATA("15", "ALT-FONT-5"),   /* select alternate font 5 */
741     DATA("16", "ALT-FONT-6"),   /* select alternate font 6 */
742     DATA("17", "ALT-FONT-7"),   /* select alternate font 7 */
743     DATA("18", "ALT-FONT-1"),   /* select alternate font 1 */
744     DATA("19", "ALT-FONT-1"),   /* select alternate font 1 */
745     DATA("20", "FRAKTUR"),      /* Fraktur font */
746     DATA("21", "DOUBLEUNDER"),  /* double underline */
747     DATA("22", "-DIM"),         /* dim off */
748     DATA("23", "-ITALIC"),      /* italic off */
749     DATA("24", "-UNDERLINE"),   /* underline off */
750     DATA("25", "-BLINK"),       /* blink off */
751     DATA("26", "-FASTBLINK"),   /* fastblink off */
752     DATA("27", "-REVERSE"),     /* reverse off */
753     DATA("28", "-INVISIBLE"),   /* invisible off */
754     DATA("29", "-DELETED"),     /* deleted off */
755     DATAX()
756 };
757
758 #undef DATA
759
760 static int
761 skip_csi(const char *cap)
762 {
763     int result = 0;
764     if (cap[0] == '\033' && cap[1] == '[')
765         result = 2;
766     else if (UChar(cap[0]) == 0233)
767         result = 1;
768     return result;
769 }
770
771 static bool
772 same_param(const char *table, const char *param, size_t length)
773 {
774     bool result = FALSE;
775     if (strncmp(table, param, length) == 0) {
776         result = !isdigit(UChar(param[length]));
777     }
778     return result;
779 }
780
781 static char *
782 lookup_params(const assoc * table, char *dst, char *src)
783 {
784     char *result = 0;
785     const char *ep = strtok(src, ";");
786
787     if (ep != 0) {
788         const assoc *ap;
789
790         do {
791             bool found = FALSE;
792
793             for (ap = table; ap->from[0]; ap++) {
794                 size_t tlen = strlen(ap->from);
795
796                 if (same_param(ap->from, ep, tlen)) {
797                     _nc_STRCAT(dst, ap->to, MAX_TERMINFO_LENGTH);
798                     found = TRUE;
799                     break;
800                 }
801             }
802
803             if (!found)
804                 _nc_STRCAT(dst, ep, MAX_TERMINFO_LENGTH);
805             _nc_STRCAT(dst, ";", MAX_TERMINFO_LENGTH);
806         } while
807             ((ep = strtok((char *) 0, ";")));
808
809         dst[strlen(dst) - 1] = '\0';
810
811         result = dst;
812     }
813     return result;
814 }
815
816 static void
817 analyze_string(const char *name, const char *cap, TERMTYPE2 *tp)
818 {
819     char buf2[MAX_TERMINFO_LENGTH];
820     const char *sp;
821     const assoc *ap;
822     int tp_lines = tp->Numbers[2];
823
824     if (!VALID_STRING(cap))
825         return;
826     (void) printf("%s: ", name);
827
828     for (sp = cap; *sp; sp++) {
829         int i;
830         int csi;
831         size_t len = 0;
832         size_t next;
833         const char *expansion = 0;
834         char buf3[MAX_TERMINFO_LENGTH];
835
836         /* first, check other capabilities in this entry */
837         for (i = 0; i < STRCOUNT; i++) {
838             char *cp = tp->Strings[i];
839
840             /* don't use function-key capabilities */
841             if (strnames[i] == NULL)
842                 continue;
843             if (strnames[i][0] == 'k' && strnames[i][1] == 'f')
844                 continue;
845
846             if (VALID_STRING(cp) &&
847                 cp[0] != '\0' &&
848                 cp != cap) {
849                 len = strlen(cp);
850                 _nc_STRNCPY(buf2, sp, len);
851                 buf2[len] = '\0';
852
853                 if (_nc_capcmp(cp, buf2))
854                     continue;
855
856 #define ISRS(s) (!strncmp((s), "is", (size_t) 2) || !strncmp((s), "rs", (size_t) 2))
857                 /*
858                  * Theoretically we just passed the test for translation
859                  * (equality once the padding is stripped).  However, there
860                  * are a few more hoops that need to be jumped so that
861                  * identical pairs of initialization and reset strings
862                  * don't just refer to each other.
863                  */
864                 if (ISRS(name) || ISRS(strnames[i]))
865                     if (cap < cp)
866                         continue;
867 #undef ISRS
868
869                 expansion = strnames[i];
870                 break;
871             }
872         }
873
874         /* now check the standard capabilities */
875         if (!expansion) {
876             csi = skip_csi(sp);
877             for (ap = std_caps; ap->from[0]; ap++) {
878                 size_t adj = (size_t) (csi ? 2 : 0);
879
880                 len = strlen(ap->from);
881                 if (csi && skip_csi(ap->from) != csi)
882                     continue;
883                 if (len > adj
884                     && strncmp(ap->from + adj, sp + csi, len - adj) == 0) {
885                     expansion = ap->to;
886                     len -= adj;
887                     len += (size_t) csi;
888                     break;
889                 }
890             }
891         }
892
893         /* now check for standard-mode sequences */
894         if (!expansion
895             && (csi = skip_csi(sp)) != 0
896             && (len = (strspn) (sp + csi, "0123456789;"))
897             && (len < sizeof(buf3))
898             && (next = (size_t) csi + len)
899             && ((sp[next] == 'h') || (sp[next] == 'l'))) {
900
901             _nc_STRCPY(buf2,
902                        ((sp[next] == 'h')
903                         ? "ECMA+"
904                         : "ECMA-"),
905                        sizeof(buf2));
906             _nc_STRNCPY(buf3, sp + csi, len);
907             buf3[len] = '\0';
908
909             expansion = lookup_params(std_modes, buf2, buf3);
910         }
911
912         /* now check for private-mode sequences */
913         if (!expansion
914             && (csi = skip_csi(sp)) != 0
915             && sp[csi] == '?'
916             && (len = (strspn) (sp + csi + 1, "0123456789;"))
917             && (len < sizeof(buf3))
918             && (next = (size_t) csi + 1 + len)
919             && ((sp[next] == 'h') || (sp[next] == 'l'))) {
920
921             _nc_STRCPY(buf2,
922                        ((sp[next] == 'h')
923                         ? "DEC+"
924                         : "DEC-"),
925                        sizeof(buf2));
926             _nc_STRNCPY(buf3, sp + csi + 1, len);
927             buf3[len] = '\0';
928
929             expansion = lookup_params(private_modes, buf2, buf3);
930         }
931
932         /* now check for ECMA highlight sequences */
933         if (!expansion
934             && (csi = skip_csi(sp)) != 0
935             && (len = (strspn) (sp + csi, "0123456789;")) != 0
936             && (len < sizeof(buf3))
937             && (next = (size_t) csi + len)
938             && sp[next] == 'm') {
939
940             _nc_STRCPY(buf2, "SGR:", sizeof(buf2));
941             _nc_STRNCPY(buf3, sp + csi, len);
942             buf3[len] = '\0';
943             len += (size_t) csi + 1;
944
945             expansion = lookup_params(ecma_highlights, buf2, buf3);
946         }
947
948         if (!expansion
949             && (csi = skip_csi(sp)) != 0
950             && sp[csi] == 'm') {
951             len = (size_t) csi + 1;
952             _nc_STRCPY(buf2, "SGR:", sizeof(buf2));
953             _nc_STRCAT(buf2, ecma_highlights[0].to, sizeof(buf2));
954             expansion = buf2;
955         }
956
957         /* now check for scroll region reset */
958         if (!expansion
959             && (csi = skip_csi(sp)) != 0) {
960             if (sp[csi] == 'r') {
961                 expansion = "RSR";
962                 len = 1;
963             } else {
964                 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "1;%dr", tp_lines);
965                 len = strlen(buf2);
966                 if (strncmp(buf2, sp + csi, len) == 0)
967                     expansion = "RSR";
968             }
969             len += (size_t) csi;
970         }
971
972         /* now check for home-down */
973         if (!expansion
974             && (csi = skip_csi(sp)) != 0) {
975             _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%d;1H", tp_lines);
976             len = strlen(buf2);
977             if (strncmp(buf2, sp + csi, len) == 0) {
978                 expansion = "LL";
979             } else {
980                 _nc_SPRINTF(buf2, _nc_SLIMIT(sizeof(buf2)) "%dH", tp_lines);
981                 len = strlen(buf2);
982                 if (strncmp(buf2, sp + csi, len) == 0) {
983                     expansion = "LL";
984                 }
985             }
986             len += (size_t) csi;
987         }
988
989         /* now look at the expansion we got, if any */
990         if (expansion) {
991             printf("{%s}", expansion);
992             sp += len - 1;
993         } else {
994             /* couldn't match anything */
995             buf2[0] = *sp;
996             buf2[1] = '\0';
997             fputs(TIC_EXPAND(buf2), stdout);
998         }
999     }
1000     putchar('\n');
1001 }
1002
1003 /***************************************************************************
1004  *
1005  * File comparison
1006  *
1007  ***************************************************************************/
1008
1009 static void
1010 file_comparison(int argc, char *argv[])
1011 {
1012 #define MAXCOMPARE      2
1013     /* someday we may allow comparisons on more files */
1014     int filecount = 0;
1015     ENTRY *heads[MAXCOMPARE];
1016     ENTRY *qp, *rp;
1017     int i, n;
1018
1019     memset(heads, 0, sizeof(heads));
1020     dump_init((char *) 0, F_LITERAL, S_TERMINFO,
1021               FALSE, 0, 65535, itrace, FALSE, FALSE, FALSE);
1022
1023     for (n = 0; n < argc && n < MAXCOMPARE; n++) {
1024         if (freopen(argv[n], "r", stdin) == 0)
1025             _nc_err_abort("Can't open %s", argv[n]);
1026
1027 #if NO_LEAKS
1028         entered[n].head = _nc_head;
1029         entered[n].tail = _nc_tail;
1030 #endif
1031         _nc_head = _nc_tail = 0;
1032
1033         /* parse entries out of the source file */
1034         _nc_set_source(argv[n]);
1035         _nc_read_entry_source(stdin, NULL, TRUE, literal, NULLHOOK);
1036
1037         if (itrace)
1038             (void) fprintf(stderr, "Resolving file %d...\n", n - 0);
1039
1040         /* maybe do use resolution */
1041         if (!_nc_resolve_uses2(!limited, literal)) {
1042             (void) fprintf(stderr,
1043                            "There are unresolved use entries in %s:\n",
1044                            argv[n]);
1045             for_entry_list(qp) {
1046                 if (qp->nuses) {
1047                     (void) fputs(qp->tterm.term_names, stderr);
1048                     (void) fputc('\n', stderr);
1049                 }
1050             }
1051             ExitProgram(EXIT_FAILURE);
1052         }
1053
1054         heads[filecount] = _nc_head;
1055         filecount++;
1056     }
1057
1058     /* OK, all entries are in core.  Ready to do the comparison */
1059     if (itrace)
1060         (void) fprintf(stderr, "Entries are now in core...\n");
1061
1062     /* The entry-matching loop. Sigh, this is intrinsically quadratic. */
1063     for (qp = heads[0]; qp; qp = qp->next) {
1064         for (rp = heads[1]; rp; rp = rp->next)
1065             if (_nc_entry_match(qp->tterm.term_names, rp->tterm.term_names)) {
1066                 if (qp->ncrosslinks < MAX_CROSSLINKS)
1067                     qp->crosslinks[qp->ncrosslinks] = rp;
1068                 qp->ncrosslinks++;
1069
1070                 if (rp->ncrosslinks < MAX_CROSSLINKS)
1071                     rp->crosslinks[rp->ncrosslinks] = qp;
1072                 rp->ncrosslinks++;
1073             }
1074     }
1075
1076     /* now we have two circular lists with crosslinks */
1077     if (itrace)
1078         (void) fprintf(stderr, "Name matches are done...\n");
1079
1080     for (qp = heads[0]; qp; qp = qp->next) {
1081         if (qp->ncrosslinks > 1) {
1082             (void) fprintf(stderr,
1083                            "%s in file 1 (%s) has %d matches in file 2 (%s):\n",
1084                            _nc_first_name(qp->tterm.term_names),
1085                            argv[0],
1086                            qp->ncrosslinks,
1087                            argv[1]);
1088             for (i = 0; i < qp->ncrosslinks; i++)
1089                 (void) fprintf(stderr,
1090                                "\t%s\n",
1091                                _nc_first_name((qp->crosslinks[i])->tterm.term_names));
1092         }
1093     }
1094
1095     for (rp = heads[1]; rp; rp = rp->next) {
1096         if (rp->ncrosslinks > 1) {
1097             (void) fprintf(stderr,
1098                            "%s in file 2 (%s) has %d matches in file 1 (%s):\n",
1099                            _nc_first_name(rp->tterm.term_names),
1100                            argv[1],
1101                            rp->ncrosslinks,
1102                            argv[0]);
1103             for (i = 0; i < rp->ncrosslinks; i++)
1104                 (void) fprintf(stderr,
1105                                "\t%s\n",
1106                                _nc_first_name((rp->crosslinks[i])->tterm.term_names));
1107         }
1108     }
1109
1110     (void) printf("In file 1 (%s) only:\n", argv[0]);
1111     for (qp = heads[0]; qp; qp = qp->next)
1112         if (qp->ncrosslinks == 0)
1113             (void) printf("\t%s\n",
1114                           _nc_first_name(qp->tterm.term_names));
1115
1116     (void) printf("In file 2 (%s) only:\n", argv[1]);
1117     for (rp = heads[1]; rp; rp = rp->next)
1118         if (rp->ncrosslinks == 0)
1119             (void) printf("\t%s\n",
1120                           _nc_first_name(rp->tterm.term_names));
1121
1122     (void) printf("The following entries are equivalent:\n");
1123     for (qp = heads[0]; qp; qp = qp->next) {
1124         if (qp->ncrosslinks == 1) {
1125             rp = qp->crosslinks[0];
1126
1127             repair_acsc(&qp->tterm);
1128             repair_acsc(&rp->tterm);
1129 #if NCURSES_XNAMES
1130             _nc_align_termtype(&qp->tterm, &rp->tterm);
1131 #endif
1132             if (entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp)) {
1133                 char name1[NAMESIZE], name2[NAMESIZE];
1134
1135                 canonical_name(qp->tterm.term_names, name1);
1136                 canonical_name(rp->tterm.term_names, name2);
1137
1138                 (void) printf("%s = %s\n", name1, name2);
1139             }
1140         }
1141     }
1142
1143     (void) printf("Differing entries:\n");
1144     termcount = 2;
1145     for (qp = heads[0]; qp; qp = qp->next) {
1146
1147         if (qp->ncrosslinks == 1) {
1148             rp = qp->crosslinks[0];
1149 #if NCURSES_XNAMES
1150             /* sorry - we have to do this on each pass */
1151             _nc_align_termtype(&qp->tterm, &rp->tterm);
1152 #endif
1153             if (!(entryeq(&qp->tterm, &rp->tterm) && useeq(qp, rp))) {
1154                 char name1[NAMESIZE], name2[NAMESIZE];
1155                 char *names[3];
1156
1157                 names[0] = name1;
1158                 names[1] = name2;
1159                 names[2] = 0;
1160
1161                 entries[0] = *qp;
1162                 entries[1] = *rp;
1163
1164                 canonical_name(qp->tterm.term_names, name1);
1165                 canonical_name(rp->tterm.term_names, name2);
1166
1167                 switch (compare) {
1168                 case C_DIFFERENCE:
1169                     show_comparing(names);
1170                     compare_entry(compare_predicate, &entries->tterm, quiet);
1171                     break;
1172
1173                 case C_COMMON:
1174                     show_comparing(names);
1175                     compare_entry(compare_predicate, &entries->tterm, quiet);
1176                     break;
1177
1178                 case C_NAND:
1179                     show_comparing(names);
1180                     compare_entry(compare_predicate, &entries->tterm, quiet);
1181                     break;
1182
1183                 }
1184             }
1185         }
1186     }
1187 }
1188
1189 static void
1190 usage(void)
1191 {
1192 #define DATA(s) s "\n"
1193     static const char head[] =
1194     {
1195         DATA("Usage: infocmp [options] [-A directory] [-B directory] [termname...]")
1196         DATA("")
1197         DATA("Options:")
1198     };
1199 #undef DATA
1200     /* length is given here so the compiler can make everything readonly */
1201 #define DATA(s) s
1202     static const char options[][46] =
1203     {
1204         "  -0    print single-row"
1205         ,"  -1    print single-column"
1206         ,"  -C    use termcap-names"
1207         ,"  -D    print database locations"
1208         ,"  -E    format output as C tables"
1209         ,"  -F    compare terminfo-files"
1210         ,"  -G    format %{number} to %'char'"
1211         ,"  -I    use terminfo-names"
1212         ,"  -K    use termcap-names and BSD syntax"
1213         ,"  -L    use long names"
1214         ,"  -R subset (see manpage)"
1215         ,"  -T    eliminate size limits (test)"
1216         ,"  -U    do not post-process entries"
1217         ,"  -V    print version"
1218         ,"  -W    wrap long strings per -w[n]"
1219 #if NCURSES_XNAMES
1220         ,"  -a    with -F, list commented-out caps"
1221 #endif
1222         ,"  -c    list common capabilities"
1223         ,"  -d    list different capabilities"
1224         ,"  -e    format output for C initializer"
1225         ,"  -f    with -1, format complex strings"
1226         ,"  -g    format %'char' to %{number}"
1227         ,"  -i    analyze initialization/reset"
1228         ,"  -l    output terminfo names"
1229         ,"  -n    list capabilities in neither"
1230         ,"  -p    ignore padding specifiers"
1231         ,"  -Q number  dump compiled description"
1232         ,"  -q    brief listing, removes headers"
1233         ,"  -r    with -C, output in termcap form"
1234         ,"  -r    with -F, resolve use-references"
1235         ,"  -s [d|i|l|c] sort fields"
1236 #if NCURSES_XNAMES
1237         ,"  -t    suppress commented-out capabilities"
1238 #endif
1239         ,"  -u    produce source with 'use='"
1240         ,"  -v number  (verbose)"
1241         ,"  -w number  (width)"
1242 #if NCURSES_XNAMES
1243         ,"  -x    unknown capabilities are user-defined"
1244 #endif
1245     };
1246 #undef DATA
1247     const size_t last = SIZEOF(options);
1248     const size_t left = (last + 1) / 2;
1249     size_t n;
1250
1251     fputs(head, stderr);
1252     for (n = 0; n < left; n++) {
1253         size_t m = n + left;
1254         if (m < last)
1255             fprintf(stderr, "%-40.40s%s\n", options[n], options[m]);
1256         else
1257             fprintf(stderr, "%s\n", options[n]);
1258     }
1259     ExitProgram(EXIT_FAILURE);
1260 }
1261
1262 static char *
1263 any_initializer(const char *fmt, const char *type)
1264 {
1265     static char *initializer;
1266     static size_t need;
1267     char *s;
1268
1269     if (initializer == 0) {
1270         need = (strlen(entries->tterm.term_names)
1271                 + strlen(type)
1272                 + strlen(fmt));
1273         initializer = (char *) malloc(need + 1);
1274         if (initializer == 0)
1275             failed("any_initializer");
1276     }
1277
1278     _nc_STRCPY(initializer, entries->tterm.term_names, need);
1279     for (s = initializer; *s != 0 && *s != '|'; s++) {
1280         if (!isalnum(UChar(*s)))
1281             *s = '_';
1282     }
1283     *s = 0;
1284     _nc_SPRINTF(s, _nc_SLIMIT(need) fmt, type);
1285     return initializer;
1286 }
1287
1288 static char *
1289 name_initializer(const char *type)
1290 {
1291     return any_initializer("_%s_data", type);
1292 }
1293
1294 static char *
1295 string_variable(const char *type)
1296 {
1297     return any_initializer("_s_%s", type);
1298 }
1299
1300 /* dump C initializers for the terminal type */
1301 static void
1302 dump_initializers(TERMTYPE2 *term)
1303 {
1304     unsigned n;
1305     const char *str = 0;
1306
1307     printf("\nstatic char %s[] = \"%s\";\n\n",
1308            name_initializer("alias"), entries->tterm.term_names);
1309
1310     for_each_string(n, term) {
1311         if (VALID_STRING(term->Strings[n])) {
1312             char buf[MAX_STRING], *sp, *tp;
1313
1314             tp = buf;
1315 #define TP_LIMIT        ((MAX_STRING - 5) - (size_t)(tp - buf))
1316             *tp++ = '"';
1317             for (sp = term->Strings[n];
1318                  *sp != 0 && TP_LIMIT > 2;
1319                  sp++) {
1320                 if (isascii(UChar(*sp))
1321                     && isprint(UChar(*sp))
1322                     && *sp != '\\'
1323                     && *sp != '"')
1324                     *tp++ = *sp;
1325                 else {
1326                     _nc_SPRINTF(tp, _nc_SLIMIT(TP_LIMIT) "\\%03o", UChar(*sp));
1327                     tp += 4;
1328                 }
1329             }
1330             *tp++ = '"';
1331             *tp = '\0';
1332             (void) printf("static char %-20s[] = %s;\n",
1333                           string_variable(ExtStrname(term, (int) n, strnames)),
1334                           buf);
1335         }
1336     }
1337     printf("\n");
1338
1339     (void) printf("static char %s[] = %s\n", name_initializer("bool"), L_CURL);
1340
1341     for_each_boolean(n, term) {
1342         switch ((int) (term->Booleans[n])) {
1343         case TRUE:
1344             str = "TRUE";
1345             break;
1346
1347         case FALSE:
1348             str = "FALSE";
1349             break;
1350
1351         case ABSENT_BOOLEAN:
1352             str = "ABSENT_BOOLEAN";
1353             break;
1354
1355         case CANCELLED_BOOLEAN:
1356             str = "CANCELLED_BOOLEAN";
1357             break;
1358         }
1359         (void) printf("\t/* %3u: %-8s */\t%s,\n",
1360                       n, ExtBoolname(term, (int) n, boolnames), str);
1361     }
1362     (void) printf("%s;\n", R_CURL);
1363
1364     (void) printf("static short %s[] = %s\n", name_initializer("number"), L_CURL);
1365
1366     for_each_number(n, term) {
1367         char buf[BUFSIZ];
1368         switch (term->Numbers[n]) {
1369         case ABSENT_NUMERIC:
1370             str = "ABSENT_NUMERIC";
1371             break;
1372         case CANCELLED_NUMERIC:
1373             str = "CANCELLED_NUMERIC";
1374             break;
1375         default:
1376             _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf)) "%d", term->Numbers[n]);
1377             str = buf;
1378             break;
1379         }
1380         (void) printf("\t/* %3u: %-8s */\t%s,\n", n,
1381                       ExtNumname(term, (int) n, numnames), str);
1382     }
1383     (void) printf("%s;\n", R_CURL);
1384
1385     (void) printf("static char * %s[] = %s\n", name_initializer("string"), L_CURL);
1386
1387     for_each_string(n, term) {
1388
1389         if (term->Strings[n] == ABSENT_STRING)
1390             str = "ABSENT_STRING";
1391         else if (term->Strings[n] == CANCELLED_STRING)
1392             str = "CANCELLED_STRING";
1393         else {
1394             str = string_variable(ExtStrname(term, (int) n, strnames));
1395         }
1396         (void) printf("\t/* %3u: %-8s */\t%s,\n", n,
1397                       ExtStrname(term, (int) n, strnames), str);
1398     }
1399     (void) printf("%s;\n", R_CURL);
1400
1401 #if NCURSES_XNAMES
1402     if ((NUM_BOOLEANS(term) != BOOLCOUNT)
1403         || (NUM_NUMBERS(term) != NUMCOUNT)
1404         || (NUM_STRINGS(term) != STRCOUNT)) {
1405         (void) printf("static char * %s[] = %s\n",
1406                       name_initializer("string_ext"), L_CURL);
1407         for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
1408             (void) printf("\t/* %3u: bool */\t\"%s\",\n",
1409                           n, ExtBoolname(term, (int) n, boolnames));
1410         }
1411         for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
1412             (void) printf("\t/* %3u: num */\t\"%s\",\n",
1413                           n, ExtNumname(term, (int) n, numnames));
1414         }
1415         for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
1416             (void) printf("\t/* %3u: str */\t\"%s\",\n",
1417                           n, ExtStrname(term, (int) n, strnames));
1418         }
1419         (void) printf("%s;\n", R_CURL);
1420     }
1421 #endif
1422 }
1423
1424 /* dump C initializers for the terminal type */
1425 static void
1426 dump_termtype(TERMTYPE2 *term)
1427 {
1428     (void) printf("\t%s\n\t\t%s,\n", L_CURL, name_initializer("alias"));
1429     (void) printf("\t\t(char *)0,\t/* pointer to string table */\n");
1430
1431     (void) printf("\t\t%s,\n", name_initializer("bool"));
1432     (void) printf("\t\t%s,\n", name_initializer("number"));
1433
1434     (void) printf("\t\t%s,\n", name_initializer("string"));
1435
1436 #if NCURSES_XNAMES
1437     (void) printf("#if NCURSES_XNAMES\n");
1438     (void) printf("\t\t(char *)0,\t/* pointer to extended string table */\n");
1439     (void) printf("\t\t%s,\t/* ...corresponding names */\n",
1440                   ((NUM_BOOLEANS(term) != BOOLCOUNT)
1441                    || (NUM_NUMBERS(term) != NUMCOUNT)
1442                    || (NUM_STRINGS(term) != STRCOUNT))
1443                   ? name_initializer("string_ext")
1444                   : "(char **)0");
1445
1446     (void) printf("\t\t%d,\t\t/* count total Booleans */\n", NUM_BOOLEANS(term));
1447     (void) printf("\t\t%d,\t\t/* count total Numbers */\n", NUM_NUMBERS(term));
1448     (void) printf("\t\t%d,\t\t/* count total Strings */\n", NUM_STRINGS(term));
1449
1450     (void) printf("\t\t%d,\t\t/* count extensions to Booleans */\n",
1451                   NUM_BOOLEANS(term) - BOOLCOUNT);
1452     (void) printf("\t\t%d,\t\t/* count extensions to Numbers */\n",
1453                   NUM_NUMBERS(term) - NUMCOUNT);
1454     (void) printf("\t\t%d,\t\t/* count extensions to Strings */\n",
1455                   NUM_STRINGS(term) - STRCOUNT);
1456
1457     (void) printf("#endif /* NCURSES_XNAMES */\n");
1458 #else
1459     (void) term;
1460 #endif /* NCURSES_XNAMES */
1461     (void) printf("\t%s\n", R_CURL);
1462 }
1463
1464 static int
1465 optarg_to_number(void)
1466 {
1467     char *temp = 0;
1468     long value = strtol(optarg, &temp, 0);
1469
1470     if (temp == 0 || temp == optarg || *temp != 0) {
1471         fprintf(stderr, "Expected a number, not \"%s\"\n", optarg);
1472         ExitProgram(EXIT_FAILURE);
1473     }
1474     return (int) value;
1475 }
1476
1477 static char *
1478 terminal_env(void)
1479 {
1480     char *terminal;
1481
1482     if ((terminal = getenv("TERM")) == 0) {
1483         (void) fprintf(stderr,
1484                        "%s: environment variable TERM not set\n",
1485                        _nc_progname);
1486         exit(EXIT_FAILURE);
1487     }
1488     return terminal;
1489 }
1490
1491 /*
1492  * Show the databases that infocmp knows about.  The location to which it writes is
1493  */
1494 static void
1495 show_databases(void)
1496 {
1497     DBDIRS state;
1498     int offset;
1499     const char *path2;
1500
1501     _nc_first_db(&state, &offset);
1502     while ((path2 = _nc_next_db(&state, &offset)) != 0) {
1503         printf("%s\n", path2);
1504     }
1505     _nc_last_db();
1506 }
1507
1508 /***************************************************************************
1509  *
1510  * Main sequence
1511  *
1512  ***************************************************************************/
1513
1514 #if NO_LEAKS
1515 #define MAIN_LEAKS() \
1516     _nc_free_termtype2(&entries[0].tterm); \
1517     _nc_free_termtype2(&entries[1].tterm); \
1518     free(myargv); \
1519     free(tfile); \
1520     free(tname)
1521 #else
1522 #define MAIN_LEAKS()            /* nothing */
1523 #endif
1524
1525 int
1526 main(int argc, char *argv[])
1527 {
1528     /* Avoid "local data >32k" error with mwcc */
1529     /* Also avoid overflowing smaller stacks on systems like AmigaOS */
1530     path *tfile = 0;
1531     char **tname = 0;
1532     size_t maxterms;
1533
1534     char **myargv;
1535
1536     char *firstdir, *restdir;
1537     int c;
1538     bool formatted = FALSE;
1539     bool filecompare = FALSE;
1540     int initdump = 0;
1541     bool init_analyze = FALSE;
1542     bool suppress_untranslatable = FALSE;
1543     int quickdump = 0;
1544     bool wrap_strings = FALSE;
1545
1546     /* where is the terminfo database location going to default to? */
1547     restdir = firstdir = 0;
1548
1549 #if NCURSES_XNAMES
1550     use_extended_names(FALSE);
1551 #endif
1552     _nc_strict_bsd = 0;
1553
1554     _nc_progname = _nc_rootname(argv[0]);
1555
1556     /* make sure we have enough space to add two terminal entries */
1557     myargv = typeCalloc(char *, (size_t) (argc + 3));
1558     if (myargv == 0)
1559         failed("myargv");
1560
1561     memcpy(myargv, argv, (sizeof(char *) * (size_t) argc));
1562     argv = myargv;
1563
1564     while ((c = getopt(argc,
1565                        argv,
1566                        "01A:aB:CcDdEeFfGgIiKLlnpQ:qR:rs:TtUuVv:Ww:x")) != -1) {
1567         switch (c) {
1568         case '0':
1569             mwidth = 65535;
1570             mheight = 1;
1571             break;
1572
1573         case '1':
1574             mwidth = 0;
1575             break;
1576
1577         case 'A':
1578             firstdir = optarg;
1579             break;
1580
1581 #if NCURSES_XNAMES
1582         case 'a':
1583             _nc_disable_period = TRUE;
1584             use_extended_names(TRUE);
1585             break;
1586 #endif
1587         case 'B':
1588             restdir = optarg;
1589             break;
1590
1591         case 'K':
1592             _nc_strict_bsd = 1;
1593             /* FALLTHRU */
1594         case 'C':
1595             outform = F_TERMCAP;
1596             tversion = "BSD";
1597             if (sortmode == S_DEFAULT)
1598                 sortmode = S_TERMCAP;
1599             break;
1600
1601         case 'D':
1602             show_databases();
1603             ExitProgram(EXIT_SUCCESS);
1604             break;
1605
1606         case 'c':
1607             compare = C_COMMON;
1608             break;
1609
1610         case 'd':
1611             compare = C_DIFFERENCE;
1612             break;
1613
1614         case 'E':
1615             initdump |= 2;
1616             break;
1617
1618         case 'e':
1619             initdump |= 1;
1620             break;
1621
1622         case 'F':
1623             filecompare = TRUE;
1624             break;
1625
1626         case 'f':
1627             formatted = TRUE;
1628             break;
1629
1630         case 'G':
1631             numbers = 1;
1632             break;
1633
1634         case 'g':
1635             numbers = -1;
1636             break;
1637
1638         case 'I':
1639             outform = F_TERMINFO;
1640             if (sortmode == S_DEFAULT)
1641                 sortmode = S_VARIABLE;
1642             tversion = 0;
1643             break;
1644
1645         case 'i':
1646             init_analyze = TRUE;
1647             break;
1648
1649         case 'L':
1650             outform = F_VARIABLE;
1651             if (sortmode == S_DEFAULT)
1652                 sortmode = S_VARIABLE;
1653             break;
1654
1655         case 'l':
1656             outform = F_TERMINFO;
1657             break;
1658
1659         case 'n':
1660             compare = C_NAND;
1661             break;
1662
1663         case 'p':
1664             ignorepads = TRUE;
1665             break;
1666
1667         case 'Q':
1668             quickdump = optarg_to_number();
1669             break;
1670
1671         case 'q':
1672             quiet = TRUE;
1673             s_absent = "-";
1674             s_cancel = "@";
1675             bool_sep = ", ";
1676             break;
1677
1678         case 'R':
1679             tversion = optarg;
1680             break;
1681
1682         case 'r':
1683             tversion = 0;
1684             break;
1685
1686         case 's':
1687             if (*optarg == 'd')
1688                 sortmode = S_NOSORT;
1689             else if (*optarg == 'i')
1690                 sortmode = S_TERMINFO;
1691             else if (*optarg == 'l')
1692                 sortmode = S_VARIABLE;
1693             else if (*optarg == 'c')
1694                 sortmode = S_TERMCAP;
1695             else {
1696                 (void) fprintf(stderr,
1697                                "%s: unknown sort mode\n",
1698                                _nc_progname);
1699                 ExitProgram(EXIT_FAILURE);
1700             }
1701             break;
1702
1703         case 'T':
1704             limited = FALSE;
1705             break;
1706
1707 #if NCURSES_XNAMES
1708         case 't':
1709             _nc_disable_period = FALSE;
1710             suppress_untranslatable = TRUE;
1711             break;
1712 #endif
1713
1714         case 'U':
1715             literal = TRUE;
1716             break;
1717
1718         case 'u':
1719             compare = C_USEALL;
1720             break;
1721
1722         case 'V':
1723             puts(curses_version());
1724             ExitProgram(EXIT_SUCCESS);
1725
1726         case 'v':
1727             itrace = (unsigned) optarg_to_number();
1728             use_verbosity(itrace);
1729             break;
1730
1731         case 'W':
1732             wrap_strings = TRUE;
1733             break;
1734
1735         case 'w':
1736             mwidth = optarg_to_number();
1737             break;
1738
1739 #if NCURSES_XNAMES
1740         case 'x':
1741             use_extended_names(TRUE);
1742             break;
1743 #endif
1744
1745         default:
1746             usage();
1747         }
1748     }
1749
1750     maxterms = (size_t) (argc + 2 - optind);
1751     if ((tfile = typeMalloc(path, maxterms)) == 0)
1752         failed("tfile");
1753     if ((tname = typeCalloc(char *, maxterms)) == 0)
1754           failed("tname");
1755     if ((entries = typeCalloc(ENTRY, maxterms)) == 0)
1756         failed("entries");
1757 #if NO_LEAKS
1758     if ((entered = typeCalloc(ENTERED, maxterms)) == 0)
1759         failed("entered");
1760 #endif
1761
1762     if (tfile == 0
1763         || tname == 0
1764         || entries == 0) {
1765         fprintf(stderr, "%s: not enough memory\n", _nc_progname);
1766         ExitProgram(EXIT_FAILURE);
1767     }
1768
1769     /* by default, sort by terminfo name */
1770     if (sortmode == S_DEFAULT)
1771         sortmode = S_TERMINFO;
1772
1773     /* make sure we have at least one terminal name to work with */
1774     if (optind >= argc)
1775         argv[argc++] = terminal_env();
1776
1777     /* if user is after a comparison, make sure we have two entries */
1778     if (compare != C_DEFAULT && optind >= argc - 1)
1779         argv[argc++] = terminal_env();
1780
1781     /* exactly one terminal name with no options means display it */
1782     /* exactly two terminal names with no options means do -d */
1783     if (compare == C_DEFAULT) {
1784         switch (argc - optind) {
1785         default:
1786             fprintf(stderr, "%s: too many names to compare\n", _nc_progname);
1787             ExitProgram(EXIT_FAILURE);
1788         case 1:
1789             break;
1790         case 2:
1791             compare = C_DIFFERENCE;
1792             break;
1793         }
1794     }
1795
1796     /* set up for display */
1797     dump_init(tversion, outform, sortmode,
1798               wrap_strings, mwidth, mheight, itrace,
1799               formatted, FALSE, quickdump);
1800
1801     if (!filecompare) {
1802         /* grab the entries */
1803         termcount = 0;
1804         for (; optind < argc; optind++) {
1805             const char *directory = termcount ? restdir : firstdir;
1806             int status;
1807
1808             tname[termcount] = argv[optind];
1809
1810             if (directory) {
1811 #if NCURSES_USE_DATABASE
1812 #if MIXEDCASE_FILENAMES
1813 #define LEAF_FMT "%c"
1814 #else
1815 #define LEAF_FMT "%02x"
1816 #endif
1817                 _nc_SPRINTF(tfile[termcount],
1818                             _nc_SLIMIT(sizeof(path))
1819                             "%s/" LEAF_FMT "/%s",
1820                             directory,
1821                             UChar(*argv[optind]), argv[optind]);
1822                 if (itrace)
1823                     (void) fprintf(stderr,
1824                                    "%s: reading entry %s from file %s\n",
1825                                    _nc_progname,
1826                                    argv[optind], tfile[termcount]);
1827
1828                 status = _nc_read_file_entry(tfile[termcount],
1829                                              &entries[termcount].tterm);
1830 #else
1831                 (void) fprintf(stderr, "%s: terminfo files not supported\n",
1832                                _nc_progname);
1833                 MAIN_LEAKS();
1834                 ExitProgram(EXIT_FAILURE);
1835 #endif
1836             } else {
1837                 if (itrace)
1838                     (void) fprintf(stderr,
1839                                    "%s: reading entry %s from database\n",
1840                                    _nc_progname,
1841                                    tname[termcount]);
1842
1843                 status = _nc_read_entry2(tname[termcount],
1844                                          tfile[termcount],
1845                                          &entries[termcount].tterm);
1846             }
1847
1848             if (status <= 0) {
1849                 (void) fprintf(stderr,
1850                                "%s: couldn't open terminfo file %s.\n",
1851                                _nc_progname,
1852                                tfile[termcount]);
1853                 MAIN_LEAKS();
1854                 ExitProgram(EXIT_FAILURE);
1855             }
1856             repair_acsc(&entries[termcount].tterm);
1857             termcount++;
1858         }
1859
1860 #if NCURSES_XNAMES
1861         if (termcount > 1)
1862             _nc_align_termtype(&entries[0].tterm, &entries[1].tterm);
1863 #endif
1864
1865         /* dump as C initializer for the terminal type */
1866         if (initdump) {
1867             if (initdump & 1)
1868                 dump_termtype(&entries[0].tterm);
1869             if (initdump & 2)
1870                 dump_initializers(&entries[0].tterm);
1871         }
1872
1873         /* analyze the init strings */
1874         else if (init_analyze) {
1875 #undef CUR
1876 #define CUR     entries[0].tterm.
1877             analyze_string("is1", init_1string, &entries[0].tterm);
1878             analyze_string("is2", init_2string, &entries[0].tterm);
1879             analyze_string("is3", init_3string, &entries[0].tterm);
1880             analyze_string("rs1", reset_1string, &entries[0].tterm);
1881             analyze_string("rs2", reset_2string, &entries[0].tterm);
1882             analyze_string("rs3", reset_3string, &entries[0].tterm);
1883             analyze_string("smcup", enter_ca_mode, &entries[0].tterm);
1884             analyze_string("rmcup", exit_ca_mode, &entries[0].tterm);
1885             analyze_string("smkx", keypad_xmit, &entries[0].tterm);
1886             analyze_string("rmkx", keypad_local, &entries[0].tterm);
1887 #undef CUR
1888         } else {
1889             int i;
1890             int len;
1891
1892             /*
1893              * Here's where the real work gets done
1894              */
1895             switch (compare) {
1896             case C_DEFAULT:
1897                 if (itrace)
1898                     (void) fprintf(stderr,
1899                                    "%s: about to dump %s\n",
1900                                    _nc_progname,
1901                                    tname[0]);
1902                 if (!quiet)
1903                     (void)
1904                         printf("#\tReconstructed via infocmp from file: %s\n",
1905                                tfile[0]);
1906                 dump_entry(&entries[0].tterm,
1907                            suppress_untranslatable,
1908                            limited,
1909                            numbers,
1910                            NULL);
1911                 len = show_entry();
1912                 if (itrace)
1913                     (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len);
1914                 break;
1915
1916             case C_DIFFERENCE:
1917                 show_comparing(tname);
1918                 compare_entry(compare_predicate, &entries->tterm, quiet);
1919                 break;
1920
1921             case C_COMMON:
1922                 show_comparing(tname);
1923                 compare_entry(compare_predicate, &entries->tterm, quiet);
1924                 break;
1925
1926             case C_NAND:
1927                 show_comparing(tname);
1928                 compare_entry(compare_predicate, &entries->tterm, quiet);
1929                 break;
1930
1931             case C_USEALL:
1932                 if (itrace)
1933                     (void) fprintf(stderr, "%s: dumping use entry\n", _nc_progname);
1934                 dump_entry(&entries[0].tterm,
1935                            suppress_untranslatable,
1936                            limited,
1937                            numbers,
1938                            use_predicate);
1939                 for (i = 1; i < termcount; i++)
1940                     dump_uses(tname[i], !(outform == F_TERMCAP
1941                                           || outform == F_TCONVERR));
1942                 len = show_entry();
1943                 if (itrace)
1944                     (void) fprintf(stderr, "%s: length %d\n", _nc_progname, len);
1945                 break;
1946             }
1947         }
1948     } else if (compare == C_USEALL) {
1949         (void) fprintf(stderr, "Sorry, -u doesn't work with -F\n");
1950     } else if (compare == C_DEFAULT) {
1951         (void) fprintf(stderr, "Use `tic -[CI] <file>' for this.\n");
1952     } else if (argc - optind != 2) {
1953         (void) fprintf(stderr,
1954                        "File comparison needs exactly two file arguments.\n");
1955     } else {
1956         file_comparison(argc - optind, argv + optind);
1957     }
1958
1959     MAIN_LEAKS();
1960     ExitProgram(EXIT_SUCCESS);
1961 }
1962
1963 /* infocmp.c ends here */