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