ncurses 4.2
[ncurses.git] / progs / infocmp.c
1 /****************************************************************************
2  * Copyright (c) 1998 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  ****************************************************************************/
33
34
35 /*
36  *      infocmp.c -- decompile an entry, or compare two entries
37  *              written by Eric S. Raymond
38  */
39
40 #include <progs.priv.h>
41
42 #include <term_entry.h>
43 #include <dump_entry.h>
44
45 MODULE_ID("$Id: infocmp.c,v 1.34 1998/02/11 12:14:03 tom Exp $")
46
47 #define L_CURL "{"
48 #define R_CURL "}"
49
50 #define MAXTERMS        32      /* max # terminal arguments we can handle */
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 char *tname[MAXTERMS];   /* terminal type names */
64 static TERMTYPE term[MAXTERMS]; /* terminfo entries */
65 static int termcount;           /* count of terminal entries */
66
67 static const char *tversion;    /* terminfo version selected */
68 static int outform;             /* output format */
69 static int sortmode;            /* sort_mode */
70 static int itrace;              /* trace flag for debugging */
71 static int mwidth = 60;
72
73 /* main comparison mode */
74 static int compare;
75 #define C_DEFAULT       0       /* don't force comparison mode */
76 #define C_DIFFERENCE    1       /* list differences between two terminals */
77 #define C_COMMON        2       /* list common capabilities */
78 #define C_NAND          3       /* list capabilities in neither terminal */
79 #define C_USEALL        4       /* generate relative use-form entry */
80 static bool ignorepads;         /* ignore pad prefixes when diffing */
81
82 #if NO_LEAKS
83 #undef ExitProgram
84 static void ExitProgram(int code) GCC_NORETURN;
85 static void ExitProgram(int code)
86 {
87         while (termcount-- > 0)
88                 _nc_free_termtype(&term[termcount], FALSE);
89         _nc_leaks_dump_entry();
90         _nc_free_and_exit(code);
91 }
92 #endif
93
94 static char *canonical_name(char *ptr, char *buf)
95 /* extract the terminal type's primary name */
96 {
97     char        *bp;
98
99     (void) strcpy(buf, ptr);
100     if ((bp = strchr(buf, '|')) != (char *)NULL)
101         *bp = '\0';
102
103     return(buf);
104 }
105
106 /***************************************************************************
107  *
108  * Predicates for dump function
109  *
110  ***************************************************************************/
111
112 static int capcmp(const char *s, const char *t)
113 /* capability comparison function */
114 {
115     if (!VALID_STRING(s) && !VALID_STRING(t))
116         return(0);
117     else if (!VALID_STRING(s) || !VALID_STRING(t))
118         return(1);
119
120     if (ignorepads)
121         return(_nc_capcmp(s, t));
122     else
123         return(strcmp(s, t));
124 }
125
126 static int use_predicate(int type, int idx)
127 /* predicate function to use for use decompilation */
128 {
129         TERMTYPE *tp;
130
131         switch(type)
132         {
133         case BOOLEAN: {
134                 int is_set = FALSE;
135
136                 /*
137                  * This assumes that multiple use entries are supposed
138                  * to contribute the logical or of their boolean capabilities.
139                  * This is true if we take the semantics of multiple uses to
140                  * be 'each capability gets the first non-default value found
141                  * in the sequence of use entries'.
142                  */
143                 for (tp = &term[1]; tp < term + termcount; tp++)
144                         if (tp->Booleans[idx]) {
145                                 is_set = TRUE;
146                                 break;
147                         }
148                         if (is_set != term->Booleans[idx])
149                                 return(!is_set);
150                         else
151                                 return(FAIL);
152                 }
153
154         case NUMBER: {
155                 int     value = ABSENT_NUMERIC;
156
157                 /*
158                  * We take the semantics of multiple uses to be 'each
159                  * capability gets the first non-default value found
160                  * in the sequence of use entries'.
161                  */
162                 for (tp = &term[1]; tp < term + termcount; tp++)
163                         if (tp->Numbers[idx] >= 0) {
164                                 value = tp->Numbers[idx];
165                                 break;
166                         }
167
168                 if (value != term->Numbers[idx])
169                         return(value != ABSENT_NUMERIC);
170                 else
171                         return(FAIL);
172                 }
173
174         case STRING: {
175                 char *termstr, *usestr = ABSENT_STRING;
176
177                 termstr = term->Strings[idx];
178
179                 /*
180                  * We take the semantics of multiple uses to be 'each
181                  * capability gets the first non-default value found
182                  * in the sequence of use entries'.
183                  */
184                 for (tp = &term[1]; tp < term + termcount; tp++)
185                         if (tp->Strings[idx])
186                         {
187                                 usestr = tp->Strings[idx];
188                                 break;
189                         }
190
191                 if (usestr == ABSENT_STRING && termstr == ABSENT_STRING)
192                         return(FAIL);
193                 else if (!usestr || !termstr || capcmp(usestr, termstr))
194                         return(TRUE);
195                 else
196                         return(FAIL);
197             }
198         }
199
200         return(FALSE);  /* pacify compiler */
201 }
202
203 static bool entryeq(TERMTYPE *t1, TERMTYPE *t2)
204 /* are two terminal types equal */
205 {
206     int i;
207
208     for (i = 0; i < BOOLCOUNT; i++)
209         if (t1->Booleans[i] != t2->Booleans[i])
210             return(FALSE);
211
212     for (i = 0; i < NUMCOUNT; i++)
213         if (t1->Numbers[i] != t2->Numbers[i])
214             return(FALSE);
215
216     for (i = 0; i < STRCOUNT; i++)
217         if (capcmp(t1->Strings[i], t2->Strings[i]))
218             return(FALSE);
219
220     return(TRUE);
221 }
222
223 static void compare_predicate(int type, int idx, const char *name)
224 /* predicate function to use for entry difference reports */
225 {
226         register TERMTYPE *t1 = &term[0];
227         register TERMTYPE *t2 = &term[1];
228         char *s1, *s2;
229
230         switch(type)
231         {
232         case BOOLEAN:
233                 switch(compare)
234                 {
235                 case C_DIFFERENCE:
236                         if (t1->Booleans[idx] != t2->Booleans[idx])
237                         (void) printf("\t%s: %c:%c.\n",
238                                           name,
239                                           t1->Booleans[idx] ? 'T' : 'F',
240                                           t2->Booleans[idx] ? 'T' : 'F');
241                         break;
242
243                 case C_COMMON:
244                         if (t1->Booleans[idx] && t2->Booleans[idx])
245                         (void) printf("\t%s= T.\n", name);
246                         break;
247
248                 case C_NAND:
249                         if (!t1->Booleans[idx] && !t2->Booleans[idx])
250                         (void) printf("\t!%s.\n", name);
251                         break;
252                 }
253                 break;
254
255         case NUMBER:
256                 switch(compare)
257                 {
258                 case C_DIFFERENCE:
259                         if (t1->Numbers[idx] != t2->Numbers[idx])
260                         (void) printf("\t%s: %d:%d.\n",
261                                           name, t1->Numbers[idx], t2->Numbers[idx]);
262                         break;
263
264                 case C_COMMON:
265                         if (t1->Numbers[idx]!=-1 && t2->Numbers[idx]!=-1
266                                 && t1->Numbers[idx] == t2->Numbers[idx])
267                         (void) printf("\t%s= %d.\n", name, t1->Numbers[idx]);
268                         break;
269
270                 case C_NAND:
271                         if (t1->Numbers[idx]==-1 && t2->Numbers[idx] == -1)
272                         (void) printf("\t!%s.\n", name);
273                         break;
274                 }
275         break;
276
277         case STRING:
278                 s1 = t1->Strings[idx];
279                 s2 = t2->Strings[idx];
280                 switch(compare)
281                 {
282                 case C_DIFFERENCE:
283                         if (capcmp(s1, s2))
284                         {
285                                 char    buf1[BUFSIZ], buf2[BUFSIZ];
286
287                                 if (s1 == (char *)NULL)
288                                         (void) strcpy(buf1, "NULL");
289                                 else
290                                 {
291                                         (void) strcpy(buf1, "'");
292                                         (void) strcat(buf1, _nc_tic_expand(s1, outform==F_TERMINFO));
293                                         (void) strcat(buf1, "'");
294                                 }
295
296                                 if (s2 == (char *)NULL)
297                                         (void) strcpy(buf2, "NULL");
298                                 else
299                                 {
300                                         (void) strcpy(buf2, "'");
301                                         (void) strcat(buf2, _nc_tic_expand(s2, outform==F_TERMINFO));
302                                         (void) strcat(buf2, "'");
303                                 }
304
305                                 (void) printf("\t%s: %s, %s.\n",
306                                               name, buf1, buf2);
307                         }
308                         break;
309
310                 case C_COMMON:
311                         if (s1 && s2 && !capcmp(s1, s2))
312                                 (void) printf("\t%s= '%s'.\n", name, _nc_tic_expand(s1, outform==F_TERMINFO));
313                         break;
314
315                 case C_NAND:
316                         if (!s1 && !s2)
317                                 (void) printf("\t!%s.\n", name);
318                         break;
319                 }
320                 break;
321         }
322
323 }
324
325 /***************************************************************************
326  *
327  * Init string analysis
328  *
329  ***************************************************************************/
330
331 typedef struct {const char *from; const char *to;} assoc;
332
333 static const assoc std_caps[] =
334 {
335     /* these are specified by X.364 and iBCS2 */
336     {"\033c",   "RIS"},         /* full reset */
337     {"\0337",   "SC"},          /* save cursor */
338     {"\0338",   "RC"},          /* restore cursor */
339     {"\033[r",  "RSR"},         /* not an X.364 mnemonic */
340     {"\033[m",  "SGR0"},        /* not an X.364 mnemonic */
341     {"\033[2J", "ED2"},         /* clear page */
342
343     /* this group is specified by ISO 2022 */
344     {"\033(0",  "ISO DEC G0"},  /* enable DEC graphics for G0 */
345     {"\033(A",  "ISO UK G0"},   /* enable UK chars for G0 */
346     {"\033(B",  "ISO US G0"},   /* enable US chars for G0 */
347     {"\033)0",  "ISO DEC G1"},  /* enable DEC graphics for G1 */
348     {"\033)A",  "ISO UK G1"},   /* enable UK chars for G1 */
349     {"\033)B",  "ISO US G1"},   /* enable US chars for G1 */
350
351     /* these are DEC private modes widely supported by emulators */
352     {"\033=",   "DECPAM"},      /* application keypad mode */
353     {"\033>",   "DECPNM"},      /* normal keypad mode */
354     {"\033<",   "DECANSI"},     /* enter ANSI mode */
355
356     { (char *)0, (char *)0}
357 };
358
359 static const assoc private_modes[] =
360 /* DEC \E[ ... [hl] modes recognized by many emulators */
361 {
362     {"1",       "CKM"},         /* application cursor keys */
363     {"2",       "ANM"},         /* set VT52 mode */
364     {"3",       "COLM"},        /* 132-column mode */
365     {"4",       "SCLM"},        /* smooth scroll */
366     {"5",       "SCNM"},        /* reverse video mode */
367     {"6",       "OM"},          /* origin mode */
368     {"7",       "AWM"},         /* wraparound mode */
369     {"8",       "ARM"},         /* auto-repeat mode */
370     {(char *)0, (char *)0}
371 };
372
373 static const assoc ecma_highlights[] =
374 /* recognize ECMA attribute sequences */
375 {
376     {"0",       "NORMAL"},      /* normal */
377     {"1",       "+BOLD"},       /* bold on */
378     {"2",       "+DIM"},        /* dim on */
379     {"3",       "+ITALIC"},     /* italic on */
380     {"4",       "+UNDERLINE"},  /* underline on */
381     {"5",       "+BLINK"},      /* blink on */
382     {"6",       "+FASTBLINK"},  /* fastblink on */
383     {"7",       "+REVERSE"},    /* reverse on */
384     {"8",       "+INVISIBLE"},  /* invisible on */
385     {"9",       "+DELETED"},    /* deleted on */
386     {"10",      "MAIN-FONT"},   /* select primary font */
387     {"11",      "ALT-FONT-1"},  /* select alternate font 1 */
388     {"12",      "ALT-FONT-2"},  /* select alternate font 2 */
389     {"13",      "ALT-FONT-3"},  /* select alternate font 3 */
390     {"14",      "ALT-FONT-4"},  /* select alternate font 4 */
391     {"15",      "ALT-FONT-5"},  /* select alternate font 5 */
392     {"16",      "ALT-FONT-6"},  /* select alternate font 6 */
393     {"17",      "ALT-FONT-7"},  /* select alternate font 7 */
394     {"18",      "ALT-FONT-1"},  /* select alternate font 1 */
395     {"19",      "ALT-FONT-1"},  /* select alternate font 1 */
396     {"20",      "FRAKTUR"},     /* Fraktur font */
397     {"21",      "DOUBLEUNDER"}, /* double underline */
398     {"22",      "-DIM"},        /* dim off */
399     {"23",      "-ITALIC"},     /* italic off */
400     {"24",      "-UNDERLINE"},  /* underline off */
401     {"25",      "-BLINK"},      /* blink off */
402     {"26",      "-FASTBLINK"},  /* fastblink off */
403     {"27",      "-REVERSE"},    /* reverse off */
404     {"28",      "-INVISIBLE"},  /* invisible off */
405     {"29",      "-DELETED"},    /* deleted off */
406     {(char *)0, (char *)0}
407 };
408
409 static void analyze_string(const char *name, const char *cap, TERMTYPE *tp)
410 {
411     char        buf[MAX_TERMINFO_LENGTH];
412     char        buf2[MAX_TERMINFO_LENGTH];
413     const char  *sp, *ep;
414     const assoc *ap;
415
416     if (cap == ABSENT_STRING || cap == CANCELLED_STRING)
417         return;
418     (void) printf("%s: ", name);
419
420     buf[0] = '\0';
421     for (sp = cap; *sp; sp++)
422     {
423         int     i;
424         size_t  len = 0;
425         const char *expansion = 0;
426
427         /* first, check other capabilities in this entry */
428         for (i = 0; i < STRCOUNT; i++)
429         {
430             char        *cp = tp->Strings[i];
431
432             /* don't use soft-key capabilities */
433             if (strnames[i][0] == 'k' && strnames[i][0] == 'f')
434                 continue;
435
436
437             if (cp != ABSENT_STRING && cp != CANCELLED_STRING && cp[0] && cp != cap)
438             {
439                 len = strlen(cp);
440                 (void) strncpy(buf2, sp, len);
441                 buf2[len] = '\0';
442
443                 if (_nc_capcmp(cp, buf2))
444                     continue;
445
446 #define ISRS(s) (!strncmp((s), "is", 2) || !strncmp((s), "rs", 2))
447                 /*
448                  * Theoretically we just passed the test for translation
449                  * (equality once the padding is stripped).  However, there
450                  * are a few more hoops that need to be jumped so that
451                  * identical pairs of initialization and reset strings
452                  * don't just refer to each other.
453                  */
454                 if (ISRS(name) || ISRS(strnames[i]))
455                     if (cap < cp)
456                         continue;
457 #undef ISRS
458
459                 expansion = strnames[i];
460                 break;
461             }
462         }
463
464         /* now check the standard capabilities */
465         if (!expansion)
466             for (ap = std_caps; ap->from; ap++)
467             {
468                 len = strlen(ap->from);
469
470                 if (strncmp(ap->from, sp, len) == 0)
471                 {
472                     expansion = ap->to;
473                     break;
474                 }
475             }
476
477         /* now check for private-mode sequences */
478         if (!expansion
479                     && sp[0] == '\033' && sp[1] == '[' && sp[2] == '?'
480                     && (len = strspn(sp + 3, "0123456789;"))
481                     && ((sp[3 + len] == 'h') || (sp[3 + len] == 'l')))
482         {
483             char        buf3[MAX_TERMINFO_LENGTH];
484
485             (void) strcpy(buf2, (sp[3 + len] == 'h') ? "DEC+" : "DEC-");
486             (void) strncpy(buf3, sp + 3, len);
487             len += 4;
488             buf3[len] = '\0';
489
490             ep = strtok(buf3, ";");
491             do {
492                    bool found = FALSE;
493
494                    for (ap = private_modes; ap->from; ap++)
495                    {
496                        size_t tlen = strlen(ap->from);
497
498                        if (strncmp(ap->from, ep, tlen) == 0)
499                        {
500                            (void) strcat(buf2, ap->to);
501                            found = TRUE;
502                            break;
503                        }
504                    }
505
506                    if (!found)
507                        (void) strcat(buf2, ep);
508                    (void) strcat(buf2, ";");
509                } while
510                    ((ep = strtok((char *)NULL, ";")));
511             buf2[strlen(buf2) - 1] = '\0';
512             expansion = buf2;
513         }
514
515         /* now check for ECMA highlight sequences */
516         if (!expansion
517                     && sp[0] == '\033' && sp[1] == '['
518                     && (len = strspn(sp + 2, "0123456789;"))
519                     && sp[2 + len] == 'm')
520         {
521             char        buf3[MAX_TERMINFO_LENGTH];
522
523             (void) strcpy(buf2, "SGR:");
524             (void) strncpy(buf3, sp + 2, len);
525             len += 3;
526             buf3[len] = '\0';
527
528             ep = strtok(buf3, ";");
529             do {
530                    bool found = FALSE;
531
532                    for (ap = ecma_highlights; ap->from; ap++)
533                    {
534                        size_t tlen = strlen(ap->from);
535
536                        if (strncmp(ap->from, ep, tlen) == 0)
537                        {
538                            (void) strcat(buf2, ap->to);
539                            found = TRUE;
540                            break;
541                        }
542                    }
543
544                    if (!found)
545                        (void) strcat(buf2, ep);
546                    (void) strcat(buf2, ";");
547                } while
548                    ((ep = strtok((char *)NULL, ";")));
549
550             buf2[strlen(buf2) - 1] = '\0';
551             expansion = buf2;
552         }
553         /* now check for scroll region reset */
554         if (!expansion)
555         {
556             (void) sprintf(buf2, "\033[1;%dr", tp->Numbers[2]);
557             len = strlen(buf2);
558             if (strncmp(buf2, sp, len) == 0)
559                 expansion = "RSR";
560         }
561
562         /* now check for home-down */
563         if (!expansion)
564         {
565             (void) sprintf(buf2, "\033[%d;1H", tp->Numbers[2]);
566             len = strlen(buf2);
567             if (strncmp(buf2, sp, len) == 0)
568                     expansion = "LL";
569         }
570
571         /* now look at the expansion we got, if any */
572         if (expansion)
573         {
574             (void) sprintf(buf + strlen(buf), "{%s}", expansion);
575             sp += len - 1;
576             continue;
577         }
578         else
579         {
580             /* couldn't match anything */
581             buf2[0] = *sp;
582             buf2[1] = '\0';
583             (void) strcat(buf, _nc_tic_expand(buf2, outform==F_TERMINFO));
584         }
585     }
586     (void) printf("%s\n", buf);
587 }
588
589 /***************************************************************************
590  *
591  * File comparison
592  *
593  ***************************************************************************/
594
595 static void file_comparison(int argc, char *argv[])
596 {
597 #define MAXCOMPARE      2
598     /* someday we may allow comparisons on more files */
599     int filecount = 0;
600     ENTRY       *heads[MAXCOMPARE];
601     ENTRY       *tails[MAXCOMPARE];
602     ENTRY       *qp, *rp;
603     int         i, n;
604
605     dump_init((char *)NULL, F_LITERAL, S_TERMINFO, 0, itrace);
606
607     for (n = 0; n < argc && n < MAXCOMPARE; n++)
608     {
609         if (freopen(argv[n], "r", stdin) == NULL)
610             _nc_err_abort("Can't open %s", argv[n]);
611
612         _nc_head = _nc_tail = (ENTRY *)NULL;
613
614         /* parse entries out of the source file */
615         _nc_set_source(argv[n]);
616         _nc_read_entry_source(stdin, NULL, TRUE, FALSE, NULLHOOK);
617
618         if (itrace)
619             (void) fprintf(stderr, "Resolving file %d...\n", n-0);
620
621         /* do use resolution */
622         if (!_nc_resolve_uses())
623         {
624             (void) fprintf(stderr,
625                            "There are unresolved use entries in %s:\n",
626                            argv[n]);
627             for_entry_list(qp)
628                 if (qp->nuses)
629                 {
630                     (void) fputs(qp->tterm.term_names, stderr);
631                     (void) fputc('\n', stderr);
632                 }
633             exit(EXIT_FAILURE);
634         }
635
636         heads[filecount] = _nc_head;
637         tails[filecount] = _nc_tail;
638         filecount++;
639     }
640
641     /* OK, all entries are in core.  Ready to do the comparison */
642     if (itrace)
643         (void) fprintf(stderr, "Entries are now in core...\n");
644
645     /*
646      * The entry-matching loop.  We're not using the use[]
647      * slots any more (they got zeroed out by resolve_uses) so
648      * we stash each entry's matches in the other file there.
649      * Sigh, this is intrinsically quadratic.
650      */
651     for (qp = heads[0]; qp; qp = qp->next)
652     {
653         for (rp = heads[1]; rp; rp = rp->next)
654             if (_nc_entry_match(qp->tterm.term_names, rp->tterm.term_names))
655             {
656                 /*
657                  * This is why the uses structure parent element is
658                  * (void *) -- so we can have either (char *) for
659                  * names or entry structure pointers in them and still
660                  * be type-safe.
661                  */
662                 if (qp->nuses < MAX_USES)
663                     qp->uses[qp->nuses].parent = (void *)rp;
664                 qp->nuses++;
665
666                 if (rp->nuses < MAX_USES)
667                     rp->uses[rp->nuses].parent = (void *)qp;
668                 rp->nuses++;
669             }
670     }
671
672     /* now we have two circular lists with crosslinks */
673     if (itrace)
674         (void) fprintf(stderr, "Name matches are done...\n");
675
676     for (qp = heads[0]; qp; qp = qp->next)
677         if (qp->nuses > 1)
678         {
679             (void) fprintf(stderr,
680                            "%s in file 1 (%s) has %d matches in file 2 (%s):\n",
681                            _nc_first_name(qp->tterm.term_names),
682                            argv[0],
683                            qp->nuses,
684                            argv[1]);
685             for (i = 0; i < qp->nuses; i++)
686                 (void) fprintf(stderr,
687                                "\t%s\n",
688                                _nc_first_name(((ENTRY *)qp->uses[i].parent)->tterm.term_names));
689         }
690     for (rp = heads[1]; rp; rp = rp->next)
691         if (rp->nuses > 1)
692         {
693             (void) fprintf(stderr,
694                            "%s in file 2 (%s) has %d matches in file 1 (%s):\n",
695                            _nc_first_name(rp->tterm.term_names),
696                            argv[1],
697                            rp->nuses,
698                            argv[0]);
699             for (i = 0; i < rp->nuses; i++)
700                 (void) fprintf(stderr,
701                                "\t%s\n",
702                                _nc_first_name(((ENTRY *)rp->uses[i].parent)->tterm.term_names));
703         }
704
705     (void) printf("In file 1 (%s) only:\n", argv[0]);
706     for (qp = heads[0]; qp; qp = qp->next)
707         if (qp->nuses == 0)
708             (void) printf("\t%s\n",
709                           _nc_first_name(qp->tterm.term_names));
710
711     (void) printf("In file 2 (%s) only:\n", argv[1]);
712     for (rp = heads[1]; rp; rp = rp->next)
713         if (rp->nuses == 0)
714             (void) printf("\t%s\n",
715                           _nc_first_name(rp->tterm.term_names));
716
717     (void) printf("The following entries are equivalent:\n");
718     for (qp = heads[0]; qp; qp = qp->next)
719     {
720         rp = (ENTRY *)qp->uses[0].parent;
721
722         if (qp->nuses == 1 && entryeq(&qp->tterm, &rp->tterm))
723         {
724             char name1[NAMESIZE], name2[NAMESIZE];
725
726             (void) canonical_name(qp->tterm.term_names, name1);
727             (void) canonical_name(rp->tterm.term_names, name2);
728
729             (void) printf("%s = %s\n", name1, name2);
730         }
731     }
732
733     (void) printf("Differing entries:\n");
734     termcount = 2;
735     for (qp = heads[0]; qp; qp = qp->next)
736     {
737         rp = (ENTRY *)qp->uses[0].parent;
738
739         if (qp->nuses == 1 && !entryeq(&qp->tterm, &rp->tterm))
740         {
741             char name1[NAMESIZE], name2[NAMESIZE];
742
743             memcpy(&term[0], &qp->tterm, sizeof(TERMTYPE));
744             memcpy(&term[1], &rp->tterm, sizeof(TERMTYPE));
745
746             (void) canonical_name(qp->tterm.term_names, name1);
747             (void) canonical_name(rp->tterm.term_names, name2);
748
749             switch (compare)
750             {
751             case C_DIFFERENCE:
752                 if (itrace)
753                     (void)fprintf(stderr, "infocmp: dumping differences\n");
754                 (void) printf("comparing %s to %s.\n", name1, name2);
755                 compare_entry(compare_predicate);
756                 break;
757
758             case C_COMMON:
759                 if (itrace)
760                     (void) fprintf(stderr,
761                                    "infocmp: dumping common capabilities\n");
762                 (void) printf("comparing %s to %s.\n", name1, name2);
763                 compare_entry(compare_predicate);
764                 break;
765
766             case C_NAND:
767                 if (itrace)
768                     (void) fprintf(stderr,
769                                    "infocmp: dumping differences\n");
770                 (void) printf("comparing %s to %s.\n", name1, name2);
771                 compare_entry(compare_predicate);
772                 break;
773
774             }
775         }
776     }
777 }
778
779 static void usage(void)
780 {
781         fprintf(stderr,
782 "usage: infocmp [-dcnILCuvV1T] [-s d| i| l| c] [-w width] [-A directory] [-B directory] [termname...]\n");
783         exit(EXIT_FAILURE);
784 }
785
786 /***************************************************************************
787  *
788  * Main sequence
789  *
790  ***************************************************************************/
791
792 int main(int argc, char *argv[])
793 {
794         char *terminal, *firstdir, *restdir;
795         /* Avoid "local data >32k" error with mwcc */
796         /* Also avoid overflowing smaller stacks on systems like AmigaOS */
797         path *tfile = malloc(sizeof(path)*MAXTERMS);
798         int c, i, len;
799         bool filecompare = FALSE;
800         bool initdump = FALSE;
801         bool init_analyze = FALSE;
802         bool limited = TRUE;
803
804         if ((terminal = getenv("TERM")) == NULL)
805         {
806                 (void) fprintf(stderr,
807                         "infocmp: environment variable TERM not set\n");
808                 return EXIT_FAILURE;
809         }
810
811         /* where is the terminfo database location going to default to? */
812         restdir = firstdir = 0;
813
814         while ((c = getopt(argc, argv, "decCFIinlLprR:s:uv:Vw:A:B:1T")) != EOF)
815                 switch (c)
816                 {
817                 case 'd':
818                         compare = C_DIFFERENCE;
819                         break;
820
821                 case 'e':
822                         initdump = TRUE;
823                         break;
824
825                 case 'c':
826                         compare = C_COMMON;
827                         break;
828
829                 case 'C':
830                         outform = F_TERMCAP;
831                         tversion = "BSD";
832                         if (sortmode == S_DEFAULT)
833                             sortmode = S_TERMCAP;
834                         break;
835
836                 case 'F':
837                         filecompare = TRUE;
838                         break;
839
840                 case 'I':
841                         outform = F_TERMINFO;
842                         if (sortmode == S_DEFAULT)
843                             sortmode = S_VARIABLE;
844                         tversion = 0;
845                         break;
846
847                 case 'i':
848                         init_analyze = TRUE;
849                         break;
850
851                 case 'l':
852                         outform = F_TERMINFO;
853                         break;
854
855                 case 'L':
856                         outform = F_VARIABLE;
857                         if (sortmode == S_DEFAULT)
858                             sortmode = S_VARIABLE;
859                         break;
860
861                 case 'n':
862                         compare = C_NAND;
863                         break;
864
865                 case 'p':
866                         ignorepads = TRUE;
867                         break;
868
869                 case 'r':
870                         tversion = 0;
871                         break;
872
873                 case 'R':
874                         tversion = optarg;
875                         break;
876
877                 case 's':
878                         if (*optarg == 'd')
879                                 sortmode = S_NOSORT;
880                         else if (*optarg == 'i')
881                                 sortmode = S_TERMINFO;
882                         else if (*optarg == 'l')
883                                 sortmode = S_VARIABLE;
884                         else if (*optarg == 'c')
885                                 sortmode = S_TERMCAP;
886                         else
887                         {
888                                 (void) fprintf(stderr,
889                                                "infocmp: unknown sort mode\n");
890                                 return EXIT_FAILURE;
891                         }
892                         break;
893
894                 case 'u':
895                         compare = C_USEALL;
896                         break;
897
898                 case 'v':
899                         itrace = atoi(optarg);
900                         _nc_tracing = (1 << itrace) - 1;
901                         break;
902
903                 case 'V':
904                         (void) fputs(NCURSES_VERSION, stdout);
905                         putchar('\n');
906                         ExitProgram(EXIT_SUCCESS);
907
908                 case 'w':
909                         mwidth = atoi(optarg);
910                         break;
911
912                 case 'A':
913                         firstdir = optarg;
914                         break;
915
916                 case 'B':
917                         restdir = optarg;
918                         break;
919
920                 case '1':
921                         mwidth = 0;
922                         break;
923                 case 'T':
924                         limited = FALSE;
925                         break;
926                 default:
927                         usage();
928                 }
929
930         /* by default, sort by terminfo name */
931         if (sortmode == S_DEFAULT)
932                 sortmode = S_TERMINFO;
933
934         /* set up for display */
935         dump_init(tversion, outform, sortmode, mwidth, itrace);
936
937         /* make sure we have at least one terminal name to work with */
938         if (optind >= argc)
939                 argv[argc++] = terminal;
940
941         /* if user is after a comparison, make sure we have two entries */
942         if (compare != C_DEFAULT && optind >= argc - 1)
943                 argv[argc++] = terminal;
944
945         /* exactly two terminal names with no options means do -d */
946         if (argc - optind == 2 && compare == C_DEFAULT)
947                 compare = C_DIFFERENCE;
948
949         if (!filecompare)
950         {
951             /* grab the entries */
952             termcount = 0;
953             for (; optind < argc; optind++)
954             {
955                 if (termcount >= MAXTERMS)
956                 {
957                     (void) fprintf(stderr,
958                            "infocmp: too many terminal type arguments\n");
959                     return EXIT_FAILURE;
960                 }
961                 else
962                 {
963                     const char  *directory = termcount ? restdir : firstdir;
964                     int         status;
965
966                     tname[termcount] = argv[optind];
967
968                     if (directory)
969                     {
970                         (void) sprintf(tfile[termcount], "%s/%c/%s",
971                                        directory,
972                                        *argv[optind], argv[optind]);
973                         if (itrace)
974                             (void) fprintf(stderr,
975                                            "infocmp: reading entry %s from file %s\n",
976                                            argv[optind], tfile[termcount]);
977
978                         status = _nc_read_file_entry(tfile[termcount],
979                                                      &term[termcount]);
980                     }
981                     else
982                     {
983                         if (itrace)
984                             (void) fprintf(stderr,
985                                            "infocmp: reading entry %s from system directories %s\n",
986                                            argv[optind], tname[termcount]);
987
988                         status = _nc_read_entry(tname[termcount],
989                                                 tfile[termcount],
990                                                 &term[termcount]);
991                         directory = TERMINFO;   /* for error message */
992                     }
993
994                     if (status <= 0)
995                     {
996                         (void) fprintf(stderr,
997                                        "infocmp: couldn't open terminfo file %s.\n",
998                                        tfile[termcount]);
999                         return EXIT_FAILURE;
1000                     }
1001                     termcount++;
1002                 }
1003             }
1004
1005             /* dump as C initializer for the terminal type */
1006             if (initdump)
1007             {
1008                 int     n;
1009                 const char *str = 0;
1010                 int     size;
1011
1012                 (void) printf("\t%s\n\t\t\"%s\",\n",
1013                               L_CURL, term->term_names);
1014                 (void) printf("\t\t(char *)0,\n");
1015
1016                 (void) printf("\t\t%s /* BOOLEANS */\n", L_CURL);
1017                 for (n = 0; n < BOOLCOUNT; n++)
1018                 {
1019                     switch((int)(term->Booleans[n]))
1020                     {
1021                     case TRUE:
1022                         str = "TRUE";
1023                         break;
1024
1025                     case FALSE:
1026                         str = "FALSE";
1027                         break;
1028
1029                     case ABSENT_BOOLEAN:
1030                         str = "ABSENT_BOOLEAN";
1031                         break;
1032
1033                     case CANCELLED_BOOLEAN:
1034                         str = "CANCELLED_BOOLEAN";
1035                         break;
1036                     }
1037                     (void) printf("\t\t/* %s */\t%s%s,\n",
1038                                   boolnames[n], str,
1039                                   n == BOOLCOUNT-1 ? R_CURL : "");
1040                 }
1041
1042                 (void) printf("\t\t%s /* NUMERICS */\n", L_CURL);
1043                 for (n = 0; n < NUMCOUNT; n++)
1044                 {
1045                     char        buf[BUFSIZ];
1046                     switch (term->Numbers[n])
1047                     {
1048                     case ABSENT_NUMERIC:
1049                         str = "ABSENT_NUMERIC";
1050                         break;
1051                     case CANCELLED_NUMERIC:
1052                         str = "CANCELLED_NUMERIC";
1053                         break;
1054                     default:
1055                         sprintf(buf, "%d", term->Numbers[n]);
1056                         str = buf;
1057                         break;
1058                     }
1059                     (void) printf("\t\t/* %s */\t%s%s,\n",
1060                         numnames[n], str,
1061                         n == NUMCOUNT-1 ? R_CURL : "");
1062                 }
1063
1064                 size = sizeof(TERMTYPE)
1065                     + (BOOLCOUNT * sizeof(term->Booleans[0]))
1066                     + (NUMCOUNT * sizeof(term->Numbers[0]));
1067
1068                 (void) printf("\t\t%s /* STRINGS */\n", L_CURL);
1069                 for (n = 0; n < STRCOUNT; n++)
1070                 {
1071                     char        buf[BUFSIZ], *sp, *tp;
1072
1073                     if (term->Strings[n] == ABSENT_STRING)
1074                         str = "ABSENT_STRING";
1075                     else if (term->Strings[n] == CANCELLED_STRING)
1076                         str = "CANCELLED_STRING";
1077                     else
1078                     {
1079                         tp = buf;
1080                         *tp++ = '"';
1081                         for (sp = term->Strings[n]; *sp; sp++)
1082                         {
1083                             if (isascii(*sp) && isprint(*sp) && *sp !='\\' && *sp != '"')
1084                                 *tp++ = *sp;
1085                             else
1086                             {
1087                                 (void) sprintf(tp, "\\%03o", *sp & 0xff);
1088                                 tp += 4;
1089                             }
1090                         }
1091                         *tp++ = '"';
1092                         *tp = '\0';
1093                         size += (strlen(term->Strings[n]) + 1);
1094                         str = buf;
1095                     }
1096                     (void) printf("\t\t/* %s */\t%s%s%s\n",
1097                         strnames[n], str,
1098                         n == STRCOUNT-1 ? R_CURL : "",
1099                         n == STRCOUNT-1 ? ""     : ",");
1100                 }
1101                 (void) printf("\t%s /* size = %d */\n", R_CURL, size);
1102                 ExitProgram(EXIT_SUCCESS);
1103             }
1104
1105             /* analyze the init strings */
1106             if (init_analyze)
1107             {
1108 #undef CUR
1109 #define CUR     term[0].
1110                 analyze_string("is1", init_1string, &term[0]);
1111                 analyze_string("is2", init_2string, &term[0]);
1112                 analyze_string("is3", init_3string, &term[0]);
1113                 analyze_string("rs1", reset_1string, &term[0]);
1114                 analyze_string("rs2", reset_2string, &term[0]);
1115                 analyze_string("rs3", reset_3string, &term[0]);
1116                 analyze_string("smcup", enter_ca_mode, &term[0]);
1117                 analyze_string("rmcup", exit_ca_mode, &term[0]);
1118 #undef CUR
1119                 ExitProgram(EXIT_SUCCESS);
1120             }
1121
1122             /*
1123              * Here's where the real work gets done
1124              */
1125             switch (compare)
1126             {
1127             case C_DEFAULT:
1128                 if (itrace)
1129                     (void) fprintf(stderr,
1130                                    "infocmp: about to dump %s\n",
1131                                    tname[0]);
1132                 (void) printf("#\tReconstructed via infocmp from file: %s\n",
1133                               tfile[0]);
1134                 len = dump_entry(&term[0], limited, NULL);
1135                 putchar('\n');
1136                 if (itrace)
1137                     (void)fprintf(stderr, "infocmp: length %d\n", len);
1138                 break;
1139
1140             case C_DIFFERENCE:
1141                 if (itrace)
1142                     (void)fprintf(stderr, "infocmp: dumping differences\n");
1143                 (void) printf("comparing %s to %s.\n", tname[0], tname[1]);
1144                 compare_entry(compare_predicate);
1145                 break;
1146
1147             case C_COMMON:
1148                 if (itrace)
1149                     (void) fprintf(stderr,
1150                                    "infocmp: dumping common capabilities\n");
1151                 (void) printf("comparing %s to %s.\n", tname[0], tname[1]);
1152                 compare_entry(compare_predicate);
1153                 break;
1154
1155             case C_NAND:
1156                 if (itrace)
1157                     (void) fprintf(stderr,
1158                                    "infocmp: dumping differences\n");
1159                 (void) printf("comparing %s to %s.\n", tname[0], tname[1]);
1160                 compare_entry(compare_predicate);
1161                 break;
1162
1163             case C_USEALL:
1164                 if (itrace)
1165                     (void) fprintf(stderr, "infocmp: dumping use entry\n");
1166                 len = dump_entry(&term[0], limited, use_predicate);
1167                 for (i = 1; i < termcount; i++)
1168                     len += dump_uses(tname[i], !(outform==F_TERMCAP || outform==F_TCONVERR));
1169                 putchar('\n');
1170                 if (itrace)
1171                     (void)fprintf(stderr, "infocmp: length %d\n", len);
1172                 break;
1173             }
1174         }
1175         else if (compare == C_USEALL)
1176             (void) fprintf(stderr, "Sorry, -u doesn't work with -F\n");
1177         else if (compare == C_DEFAULT)
1178             (void) fprintf(stderr, "Use `tic -[CI] <file>' for this.\n");
1179         else if (argc - optind != 2)
1180             (void) fprintf(stderr,
1181                 "File comparison needs exactly two file arguments.\n");
1182         else
1183             file_comparison(argc-optind, argv+optind);
1184
1185         ExitProgram(EXIT_SUCCESS);
1186 }
1187
1188 /* infocmp.c ends here */