1 /****************************************************************************
2 * Copyright (c) 1998-2014,2015 Free Software Foundation, Inc. *
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: *
12 * The above copyright notice and this permission notice shall be included *
13 * in all copies or substantial portions of the Software. *
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. *
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 *
27 ****************************************************************************/
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 ****************************************************************************/
36 * infocmp.c -- decompile an entry, or compare two entries
37 * written by Eric S. Raymond
41 #include <progs.priv.h>
43 #include <dump_entry.h>
45 MODULE_ID("$Id: infocmp.c,v 1.136 2015/09/05 15:49:57 tom Exp $")
50 #define MAX_STRING 1024 /* maximum formatted string */
52 const char *_nc_progname
= "infocmp";
54 typedef char path
[PATH_MAX
];
56 /***************************************************************************
58 * The following control variables, together with the contents of the
59 * terminfo entries, completely determine the actions of the program.
61 ***************************************************************************/
63 static ENTRY
*entries
; /* terminfo entries */
64 static int termcount
; /* count of terminal entries */
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 */
80 /* main comparison mode */
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 */
96 static ENTERED
*entered
;
99 static void ExitProgram(int code
) GCC_NORETURN
;
100 /* prototype is to get gcc to accept the noreturn attribute */
102 ExitProgram(int code
)
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
);
115 _nc_leaks_dump_entry();
123 failed(const char *s
)
126 ExitProgram(EXIT_FAILURE
);
130 canonical_name(char *ptr
, char *buf
)
131 /* extract the terminal type's primary name */
135 _nc_STRCPY(buf
, ptr
, NAMESIZE
);
136 if ((bp
= strchr(buf
, '|')) != 0)
142 /***************************************************************************
144 * Predicates for dump function
146 ***************************************************************************/
149 capcmp(PredIdx idx
, const char *s
, const char *t
)
150 /* capability comparison function */
152 if (!VALID_STRING(s
) && !VALID_STRING(t
))
154 else if (!VALID_STRING(s
) || !VALID_STRING(t
))
157 if ((idx
== acs_chars_index
) || !ignorepads
)
158 return (strcmp(s
, t
));
160 return (_nc_capcmp(s
, t
));
164 use_predicate(unsigned type
, PredIdx idx
)
165 /* predicate function to use for use decompilation */
175 * This assumes that multiple use entries are supposed
176 * to contribute the logical or of their boolean capabilities.
177 * This is true if we take the semantics of multiple uses to
178 * be 'each capability gets the first non-default value found
179 * in the sequence of use entries'.
181 * Note that cancelled or absent booleans are stored as FALSE,
182 * unlike numbers and strings, whose cancelled/absent state is
183 * recorded in the terminfo database.
185 for (ep
= &entries
[1]; ep
< entries
+ termcount
; ep
++)
186 if (ep
->tterm
.Booleans
[idx
] == TRUE
) {
187 is_set
= entries
[0].tterm
.Booleans
[idx
];
190 if (is_set
!= entries
[0].tterm
.Booleans
[idx
])
198 int value
= ABSENT_NUMERIC
;
201 * We take the semantics of multiple uses to be 'each
202 * capability gets the first non-default value found
203 * in the sequence of use entries'.
205 for (ep
= &entries
[1]; ep
< entries
+ termcount
; ep
++)
206 if (VALID_NUMERIC(ep
->tterm
.Numbers
[idx
])) {
207 value
= ep
->tterm
.Numbers
[idx
];
211 if (value
!= entries
[0].tterm
.Numbers
[idx
])
212 return (value
!= ABSENT_NUMERIC
);
219 char *termstr
, *usestr
= ABSENT_STRING
;
221 termstr
= entries
[0].tterm
.Strings
[idx
];
224 * We take the semantics of multiple uses to be 'each
225 * capability gets the first non-default value found
226 * in the sequence of use entries'.
228 for (ep
= &entries
[1]; ep
< entries
+ termcount
; ep
++)
229 if (ep
->tterm
.Strings
[idx
]) {
230 usestr
= ep
->tterm
.Strings
[idx
];
234 if (usestr
== ABSENT_STRING
&& termstr
== ABSENT_STRING
)
236 else if (!usestr
|| !termstr
|| capcmp(idx
, usestr
, termstr
))
243 return (FALSE
); /* pacify compiler */
247 useeq(ENTRY
* e1
, ENTRY
* e2
)
248 /* are the use references in two entries equivalent? */
252 if (e1
->nuses
!= e2
->nuses
)
255 /* Ugh...this is quadratic again */
256 for (i
= 0; i
< e1
->nuses
; i
++) {
257 bool foundmatch
= FALSE
;
259 /* search second entry for given use reference */
260 for (j
= 0; j
< e2
->nuses
; j
++)
261 if (!strcmp(e1
->uses
[i
].name
, e2
->uses
[j
].name
)) {
274 entryeq(TERMTYPE
*t1
, TERMTYPE
*t2
)
275 /* are two entries equivalent? */
279 for (i
= 0; i
< NUM_BOOLEANS(t1
); i
++)
280 if (t1
->Booleans
[i
] != t2
->Booleans
[i
])
283 for (i
= 0; i
< NUM_NUMBERS(t1
); i
++)
284 if (t1
->Numbers
[i
] != t2
->Numbers
[i
])
287 for (i
= 0; i
< NUM_STRINGS(t1
); i
++)
288 if (capcmp((PredIdx
) i
, t1
->Strings
[i
], t2
->Strings
[i
]))
294 #define TIC_EXPAND(result) _nc_tic_expand(result, outform==F_TERMINFO, numbers)
297 print_uses(ENTRY
* ep
, FILE *fp
)
298 /* print an entry's use references */
305 for (i
= 0; i
< ep
->nuses
; i
++) {
306 fputs(ep
->uses
[i
].name
, fp
);
307 if (i
< ep
->nuses
- 1)
313 dump_boolean(int val
)
314 /* display the value of a boolean capability */
319 case CANCELLED_BOOLEAN
:
331 dump_numeric(int val
, char *buf
)
332 /* display the value of a boolean capability */
336 _nc_STRCPY(buf
, s_absent
, MAX_STRING
);
338 case CANCELLED_NUMERIC
:
339 _nc_STRCPY(buf
, s_cancel
, MAX_STRING
);
342 _nc_SPRINTF(buf
, _nc_SLIMIT(MAX_STRING
) "%d", val
);
348 dump_string(char *val
, char *buf
)
349 /* display the value of a string capability */
351 if (val
== ABSENT_STRING
)
352 _nc_STRCPY(buf
, s_absent
, MAX_STRING
);
353 else if (val
== CANCELLED_STRING
)
354 _nc_STRCPY(buf
, s_cancel
, MAX_STRING
);
356 _nc_SPRINTF(buf
, _nc_SLIMIT(MAX_STRING
)
357 "'%.*s'", MAX_STRING
- 3, TIC_EXPAND(val
));
362 * Show "comparing..." message for the given terminal names.
365 show_comparing(char **names
)
370 (void) fprintf(stderr
, "%s: dumping differences\n", _nc_progname
);
374 (void) fprintf(stderr
, "%s: dumping common capabilities\n", _nc_progname
);
378 (void) fprintf(stderr
, "%s: dumping differences\n", _nc_progname
);
383 printf("comparing %s", *names
++);
385 printf(" to %s", *names
++);
387 printf(", %s", *names
++);
395 * ncurses stores two types of non-standard capabilities:
396 * a) capabilities listed past the "STOP-HERE" comment in the Caps file.
397 * These are used in the terminfo source file to provide data for termcaps,
398 * e.g., when there is no equivalent capability in terminfo, as well as for
399 * widely-used non-standard capabilities.
400 * b) user-definable capabilities, via "tic -x".
402 * However, if "-x" is omitted from the tic command, both types of
403 * non-standard capability are not loaded into the terminfo database. This
404 * macro is used for limit-checks against the symbols that tic uses to omit
405 * the two types of non-standard entry.
408 #define check_user_definable(n,limit) if (!_nc_user_definable && (n) > (limit)) break
410 #define check_user_definable(n,limit) if ((n) > (limit)) break
414 * Use these macros to simplify loops on C_COMMON and C_NAND:
416 #define for_each_entry() while (entries[extra].tterm.term_names)
417 #define next_entry (&(entries[extra++].tterm))
420 compare_predicate(PredType type
, PredIdx idx
, const char *name
)
421 /* predicate function to use for entry difference reports */
423 ENTRY
*e1
= &entries
[0];
424 ENTRY
*e2
= &entries
[1];
425 char buf1
[MAX_STRING
];
426 char buf2
[MAX_STRING
];
435 check_user_definable(idx
, BOOLWRITE
);
436 b1
= e1
->tterm
.Booleans
[idx
];
439 b2
= next_entry
->Booleans
[idx
];
440 if (!(b1
== ABSENT_BOOLEAN
&& b2
== ABSENT_BOOLEAN
) && b1
!= b2
)
441 (void) printf("\t%s: %s%s%s.\n",
449 if (b1
!= ABSENT_BOOLEAN
) {
452 b2
= next_entry
->Booleans
[idx
];
459 (void) printf("\t%s= %s.\n", name
, dump_boolean(b1
));
465 if (b1
== ABSENT_BOOLEAN
) {
468 b2
= next_entry
->Booleans
[idx
];
475 (void) printf("\t!%s.\n", name
);
483 check_user_definable(idx
, NUMWRITE
);
484 n1
= e1
->tterm
.Numbers
[idx
];
487 n2
= next_entry
->Numbers
[idx
];
488 if (!((n1
== ABSENT_NUMERIC
&& n2
== ABSENT_NUMERIC
)) && n1
!= n2
) {
489 dump_numeric(n1
, buf1
);
490 dump_numeric(n2
, buf2
);
491 (void) printf("\t%s: %s, %s.\n", name
, buf1
, buf2
);
496 if (n1
!= ABSENT_NUMERIC
) {
499 n2
= next_entry
->Numbers
[idx
];
506 dump_numeric(n1
, buf1
);
507 (void) printf("\t%s= %s.\n", name
, buf1
);
513 if (n1
== ABSENT_NUMERIC
) {
516 n2
= next_entry
->Numbers
[idx
];
523 (void) printf("\t!%s.\n", name
);
531 check_user_definable(idx
, STRWRITE
);
532 s1
= e1
->tterm
.Strings
[idx
];
535 s2
= next_entry
->Strings
[idx
];
536 if (capcmp(idx
, s1
, s2
)) {
537 dump_string(s1
, buf1
);
538 dump_string(s2
, buf2
);
539 if (strcmp(buf1
, buf2
))
540 (void) printf("\t%s: %s, %s.\n", name
, buf1
, buf2
);
545 if (s1
!= ABSENT_STRING
) {
548 s2
= next_entry
->Strings
[idx
];
549 if (capcmp(idx
, s1
, s2
) != 0) {
555 (void) printf("\t%s= '%s'.\n", name
, TIC_EXPAND(s1
));
561 if (s1
== ABSENT_STRING
) {
564 s2
= next_entry
->Strings
[idx
];
571 (void) printf("\t!%s.\n", name
);
579 /* unlike the other modes, this compares *all* use entries */
582 if (!useeq(e1
, e2
)) {
583 (void) fputs("\tuse: ", stdout
);
584 print_uses(e1
, stdout
);
586 print_uses(e2
, stdout
);
587 fputs(".\n", stdout
);
595 e2
= &entries
[extra
++];
596 if (e2
->nuses
!= e1
->nuses
|| !useeq(e1
, e2
)) {
602 (void) fputs("\tuse: ", stdout
);
603 print_uses(e1
, stdout
);
604 fputs(".\n", stdout
);
613 e2
= &entries
[extra
++];
614 if (e2
->nuses
!= e1
->nuses
) {
620 (void) printf("\t!use.\n");
628 /***************************************************************************
630 * Init string analysis
632 ***************************************************************************/
634 #define DATA(from, to) { { from }, { to } }
635 #define DATAX() DATA("", "")
642 static const assoc std_caps
[] =
644 /* these are specified by X.364 and iBCS2 */
645 DATA("\033c", "RIS"), /* full reset */
646 DATA("\0337", "SC"), /* save cursor */
647 DATA("\0338", "RC"), /* restore cursor */
648 DATA("\033[r", "RSR"), /* not an X.364 mnemonic */
649 DATA("\033[m", "SGR0"), /* not an X.364 mnemonic */
650 DATA("\033[2J", "ED2"), /* clear page */
652 /* this group is specified by ISO 2022 */
653 DATA("\033(0", "ISO DEC G0"), /* enable DEC graphics for G0 */
654 DATA("\033(A", "ISO UK G0"), /* enable UK chars for G0 */
655 DATA("\033(B", "ISO US G0"), /* enable US chars for G0 */
656 DATA("\033)0", "ISO DEC G1"), /* enable DEC graphics for G1 */
657 DATA("\033)A", "ISO UK G1"), /* enable UK chars for G1 */
658 DATA("\033)B", "ISO US G1"), /* enable US chars for G1 */
660 /* these are DEC private controls widely supported by emulators */
661 DATA("\033=", "DECPAM"), /* application keypad mode */
662 DATA("\033>", "DECPNM"), /* normal keypad mode */
663 DATA("\033<", "DECANSI"), /* enter ANSI mode */
664 DATA("\033[!p", "DECSTR"), /* soft reset */
665 DATA("\033 F", "S7C1T"), /* 7-bit controls */
670 static const assoc std_modes
[] =
671 /* ECMA \E[ ... [hl] modes recognized by many emulators */
673 DATA("2", "AM"), /* keyboard action mode */
674 DATA("4", "IRM"), /* insert/replace mode */
675 DATA("12", "SRM"), /* send/receive mode */
676 DATA("20", "LNM"), /* linefeed mode */
680 static const assoc private_modes
[] =
681 /* DEC \E[ ... [hl] modes recognized by many emulators */
683 DATA("1", "CKM"), /* application cursor keys */
684 DATA("2", "ANM"), /* set VT52 mode */
685 DATA("3", "COLM"), /* 132-column mode */
686 DATA("4", "SCLM"), /* smooth scroll */
687 DATA("5", "SCNM"), /* reverse video mode */
688 DATA("6", "OM"), /* origin mode */
689 DATA("7", "AWM"), /* wraparound mode */
690 DATA("8", "ARM"), /* auto-repeat mode */
694 static const assoc ecma_highlights
[] =
695 /* recognize ECMA attribute sequences */
697 DATA("0", "NORMAL"), /* normal */
698 DATA("1", "+BOLD"), /* bold on */
699 DATA("2", "+DIM"), /* dim on */
700 DATA("3", "+ITALIC"), /* italic on */
701 DATA("4", "+UNDERLINE"), /* underline on */
702 DATA("5", "+BLINK"), /* blink on */
703 DATA("6", "+FASTBLINK"), /* fastblink on */
704 DATA("7", "+REVERSE"), /* reverse on */
705 DATA("8", "+INVISIBLE"), /* invisible on */
706 DATA("9", "+DELETED"), /* deleted on */
707 DATA("10", "MAIN-FONT"), /* select primary font */
708 DATA("11", "ALT-FONT-1"), /* select alternate font 1 */
709 DATA("12", "ALT-FONT-2"), /* select alternate font 2 */
710 DATA("13", "ALT-FONT-3"), /* select alternate font 3 */
711 DATA("14", "ALT-FONT-4"), /* select alternate font 4 */
712 DATA("15", "ALT-FONT-5"), /* select alternate font 5 */
713 DATA("16", "ALT-FONT-6"), /* select alternate font 6 */
714 DATA("17", "ALT-FONT-7"), /* select alternate font 7 */
715 DATA("18", "ALT-FONT-1"), /* select alternate font 1 */
716 DATA("19", "ALT-FONT-1"), /* select alternate font 1 */
717 DATA("20", "FRAKTUR"), /* Fraktur font */
718 DATA("21", "DOUBLEUNDER"), /* double underline */
719 DATA("22", "-DIM"), /* dim off */
720 DATA("23", "-ITALIC"), /* italic off */
721 DATA("24", "-UNDERLINE"), /* underline off */
722 DATA("25", "-BLINK"), /* blink off */
723 DATA("26", "-FASTBLINK"), /* fastblink off */
724 DATA("27", "-REVERSE"), /* reverse off */
725 DATA("28", "-INVISIBLE"), /* invisible off */
726 DATA("29", "-DELETED"), /* deleted off */
733 skip_csi(const char *cap
)
736 if (cap
[0] == '\033' && cap
[1] == '[')
738 else if (UChar(cap
[0]) == 0233)
744 same_param(const char *table
, const char *param
, size_t length
)
747 if (strncmp(table
, param
, length
) == 0) {
748 result
= !isdigit(UChar(param
[length
]));
754 lookup_params(const assoc
* table
, char *dst
, char *src
)
757 const char *ep
= strtok(src
, ";");
765 for (ap
= table
; ap
->from
[0]; ap
++) {
766 size_t tlen
= strlen(ap
->from
);
768 if (same_param(ap
->from
, ep
, tlen
)) {
769 _nc_STRCAT(dst
, ap
->to
, MAX_TERMINFO_LENGTH
);
776 _nc_STRCAT(dst
, ep
, MAX_TERMINFO_LENGTH
);
777 _nc_STRCAT(dst
, ";", MAX_TERMINFO_LENGTH
);
779 ((ep
= strtok((char *) 0, ";")));
781 dst
[strlen(dst
) - 1] = '\0';
789 analyze_string(const char *name
, const char *cap
, TERMTYPE
*tp
)
791 char buf2
[MAX_TERMINFO_LENGTH
];
794 int tp_lines
= tp
->Numbers
[2];
796 if (!VALID_STRING(cap
))
798 (void) printf("%s: ", name
);
800 for (sp
= cap
; *sp
; sp
++) {
805 const char *expansion
= 0;
806 char buf3
[MAX_TERMINFO_LENGTH
];
808 /* first, check other capabilities in this entry */
809 for (i
= 0; i
< STRCOUNT
; i
++) {
810 char *cp
= tp
->Strings
[i
];
812 /* don't use function-key capabilities */
813 if (strnames
[i
][0] == 'k' && strnames
[i
][1] == 'f')
816 if (VALID_STRING(cp
) &&
820 (void) strncpy(buf2
, sp
, len
);
823 if (_nc_capcmp(cp
, buf2
))
826 #define ISRS(s) (!strncmp((s), "is", (size_t) 2) || !strncmp((s), "rs", (size_t) 2))
828 * Theoretically we just passed the test for translation
829 * (equality once the padding is stripped). However, there
830 * are a few more hoops that need to be jumped so that
831 * identical pairs of initialization and reset strings
832 * don't just refer to each other.
834 if (ISRS(name
) || ISRS(strnames
[i
]))
839 expansion
= strnames
[i
];
844 /* now check the standard capabilities */
847 for (ap
= std_caps
; ap
->from
[0]; ap
++) {
848 size_t adj
= (size_t) (csi
? 2 : 0);
850 len
= strlen(ap
->from
);
851 if (csi
&& skip_csi(ap
->from
) != csi
)
854 && strncmp(ap
->from
+ adj
, sp
+ csi
, len
- adj
) == 0) {
863 /* now check for standard-mode sequences */
865 && (csi
= skip_csi(sp
)) != 0
866 && (len
= (strspn
) (sp
+ csi
, "0123456789;"))
867 && (len
< sizeof(buf3
))
868 && (next
= (size_t) csi
+ len
)
869 && ((sp
[next
] == 'h') || (sp
[next
] == 'l'))) {
876 (void) strncpy(buf3
, sp
+ csi
, len
);
878 len
+= (size_t) csi
+ 1;
880 expansion
= lookup_params(std_modes
, buf2
, buf3
);
883 /* now check for private-mode sequences */
885 && (csi
= skip_csi(sp
)) != 0
887 && (len
= (strspn
) (sp
+ csi
+ 1, "0123456789;"))
888 && (len
< sizeof(buf3
))
889 && (next
= (size_t) csi
+ 1 + len
)
890 && ((sp
[next
] == 'h') || (sp
[next
] == 'l'))) {
897 (void) strncpy(buf3
, sp
+ csi
+ 1, len
);
899 len
+= (size_t) csi
+ 2;
901 expansion
= lookup_params(private_modes
, buf2
, buf3
);
904 /* now check for ECMA highlight sequences */
906 && (csi
= skip_csi(sp
)) != 0
907 && (len
= (strspn
) (sp
+ csi
, "0123456789;")) != 0
908 && (len
< sizeof(buf3
))
909 && (next
= (size_t) csi
+ len
)
910 && sp
[next
] == 'm') {
912 _nc_STRCPY(buf2
, "SGR:", sizeof(buf2
));
913 (void) strncpy(buf3
, sp
+ csi
, len
);
915 len
+= (size_t) csi
+ 1;
917 expansion
= lookup_params(ecma_highlights
, buf2
, buf3
);
921 && (csi
= skip_csi(sp
)) != 0
923 len
= (size_t) csi
+ 1;
924 _nc_STRCPY(buf2
, "SGR:", sizeof(buf2
));
925 _nc_STRCAT(buf2
, ecma_highlights
[0].to
, sizeof(buf2
));
929 /* now check for scroll region reset */
931 && (csi
= skip_csi(sp
)) != 0) {
932 if (sp
[csi
] == 'r') {
936 _nc_SPRINTF(buf2
, _nc_SLIMIT(sizeof(buf2
)) "1;%dr", tp_lines
);
938 if (strncmp(buf2
, sp
+ csi
, len
) == 0)
944 /* now check for home-down */
946 && (csi
= skip_csi(sp
)) != 0) {
947 _nc_SPRINTF(buf2
, _nc_SLIMIT(sizeof(buf2
)) "%d;1H", tp_lines
);
949 if (strncmp(buf2
, sp
+ csi
, len
) == 0) {
952 _nc_SPRINTF(buf2
, _nc_SLIMIT(sizeof(buf2
)) "%dH", tp_lines
);
954 if (strncmp(buf2
, sp
+ csi
, len
) == 0) {
961 /* now look at the expansion we got, if any */
963 printf("{%s}", expansion
);
966 /* couldn't match anything */
969 fputs(TIC_EXPAND(buf2
), stdout
);
975 /***************************************************************************
979 ***************************************************************************/
982 file_comparison(int argc
, char *argv
[])
985 /* someday we may allow comparisons on more files */
987 ENTRY
*heads
[MAXCOMPARE
];
991 memset(heads
, 0, sizeof(heads
));
992 dump_init((char *) 0, F_LITERAL
, S_TERMINFO
, 0, 65535, itrace
, FALSE
,
995 for (n
= 0; n
< argc
&& n
< MAXCOMPARE
; n
++) {
996 if (freopen(argv
[n
], "r", stdin
) == 0)
997 _nc_err_abort("Can't open %s", argv
[n
]);
1000 entered
[n
].head
= _nc_head
;
1001 entered
[n
].tail
= _nc_tail
;
1003 _nc_head
= _nc_tail
= 0;
1005 /* parse entries out of the source file */
1006 _nc_set_source(argv
[n
]);
1007 _nc_read_entry_source(stdin
, NULL
, TRUE
, literal
, NULLHOOK
);
1010 (void) fprintf(stderr
, "Resolving file %d...\n", n
- 0);
1012 /* maybe do use resolution */
1013 if (!_nc_resolve_uses2(!limited
, literal
)) {
1014 (void) fprintf(stderr
,
1015 "There are unresolved use entries in %s:\n",
1017 for_entry_list(qp
) {
1019 (void) fputs(qp
->tterm
.term_names
, stderr
);
1020 (void) fputc('\n', stderr
);
1023 ExitProgram(EXIT_FAILURE
);
1026 heads
[filecount
] = _nc_head
;
1030 /* OK, all entries are in core. Ready to do the comparison */
1032 (void) fprintf(stderr
, "Entries are now in core...\n");
1034 /* The entry-matching loop. Sigh, this is intrinsically quadratic. */
1035 for (qp
= heads
[0]; qp
; qp
= qp
->next
) {
1036 for (rp
= heads
[1]; rp
; rp
= rp
->next
)
1037 if (_nc_entry_match(qp
->tterm
.term_names
, rp
->tterm
.term_names
)) {
1038 if (qp
->ncrosslinks
< MAX_CROSSLINKS
)
1039 qp
->crosslinks
[qp
->ncrosslinks
] = rp
;
1042 if (rp
->ncrosslinks
< MAX_CROSSLINKS
)
1043 rp
->crosslinks
[rp
->ncrosslinks
] = qp
;
1048 /* now we have two circular lists with crosslinks */
1050 (void) fprintf(stderr
, "Name matches are done...\n");
1052 for (qp
= heads
[0]; qp
; qp
= qp
->next
) {
1053 if (qp
->ncrosslinks
> 1) {
1054 (void) fprintf(stderr
,
1055 "%s in file 1 (%s) has %d matches in file 2 (%s):\n",
1056 _nc_first_name(qp
->tterm
.term_names
),
1060 for (i
= 0; i
< qp
->ncrosslinks
; i
++)
1061 (void) fprintf(stderr
,
1063 _nc_first_name((qp
->crosslinks
[i
])->tterm
.term_names
));
1067 for (rp
= heads
[1]; rp
; rp
= rp
->next
) {
1068 if (rp
->ncrosslinks
> 1) {
1069 (void) fprintf(stderr
,
1070 "%s in file 2 (%s) has %d matches in file 1 (%s):\n",
1071 _nc_first_name(rp
->tterm
.term_names
),
1075 for (i
= 0; i
< rp
->ncrosslinks
; i
++)
1076 (void) fprintf(stderr
,
1078 _nc_first_name((rp
->crosslinks
[i
])->tterm
.term_names
));
1082 (void) printf("In file 1 (%s) only:\n", argv
[0]);
1083 for (qp
= heads
[0]; qp
; qp
= qp
->next
)
1084 if (qp
->ncrosslinks
== 0)
1085 (void) printf("\t%s\n",
1086 _nc_first_name(qp
->tterm
.term_names
));
1088 (void) printf("In file 2 (%s) only:\n", argv
[1]);
1089 for (rp
= heads
[1]; rp
; rp
= rp
->next
)
1090 if (rp
->ncrosslinks
== 0)
1091 (void) printf("\t%s\n",
1092 _nc_first_name(rp
->tterm
.term_names
));
1094 (void) printf("The following entries are equivalent:\n");
1095 for (qp
= heads
[0]; qp
; qp
= qp
->next
) {
1096 if (qp
->ncrosslinks
== 1) {
1097 rp
= qp
->crosslinks
[0];
1099 repair_acsc(&qp
->tterm
);
1100 repair_acsc(&rp
->tterm
);
1102 _nc_align_termtype(&qp
->tterm
, &rp
->tterm
);
1104 if (entryeq(&qp
->tterm
, &rp
->tterm
) && useeq(qp
, rp
)) {
1105 char name1
[NAMESIZE
], name2
[NAMESIZE
];
1107 (void) canonical_name(qp
->tterm
.term_names
, name1
);
1108 (void) canonical_name(rp
->tterm
.term_names
, name2
);
1110 (void) printf("%s = %s\n", name1
, name2
);
1115 (void) printf("Differing entries:\n");
1117 for (qp
= heads
[0]; qp
; qp
= qp
->next
) {
1119 if (qp
->ncrosslinks
== 1) {
1120 rp
= qp
->crosslinks
[0];
1122 /* sorry - we have to do this on each pass */
1123 _nc_align_termtype(&qp
->tterm
, &rp
->tterm
);
1125 if (!(entryeq(&qp
->tterm
, &rp
->tterm
) && useeq(qp
, rp
))) {
1126 char name1
[NAMESIZE
], name2
[NAMESIZE
];
1136 (void) canonical_name(qp
->tterm
.term_names
, name1
);
1137 (void) canonical_name(rp
->tterm
.term_names
, name2
);
1141 show_comparing(names
);
1142 compare_entry(compare_predicate
, &entries
->tterm
, quiet
);
1146 show_comparing(names
);
1147 compare_entry(compare_predicate
, &entries
->tterm
, quiet
);
1151 show_comparing(names
);
1152 compare_entry(compare_predicate
, &entries
->tterm
, quiet
);
1164 #define DATA(s) s "\n"
1165 static const char head
[] =
1167 DATA("Usage: infocmp [options] [-A directory] [-B directory] [termname...]")
1173 static const char options
[][45] =
1175 " -0 print single-row"
1176 ," -1 print single-column"
1177 ," -C use termcap-names"
1178 ," -D print database locations"
1179 ," -E format output as C tables"
1180 ," -F compare terminfo-files"
1181 ," -G format %{number} to %'char'"
1182 ," -I use terminfo-names"
1183 ," -K use termcap-names and BSD syntax"
1184 ," -L use long names"
1185 ," -R subset (see manpage)"
1186 ," -T eliminate size limits (test)"
1187 ," -U do not post-process entries"
1188 ," -V print version"
1190 ," -a with -F, list commented-out caps"
1192 ," -c list common capabilities"
1193 ," -d list different capabilities"
1194 ," -e format output for C initializer"
1195 ," -f with -1, format complex strings"
1196 ," -g format %'char' to %{number}"
1197 ," -i analyze initialization/reset"
1198 ," -l output terminfo names"
1199 ," -n list capabilities in neither"
1200 ," -p ignore padding specifiers"
1201 ," -Q number dump compiled description"
1202 ," -q brief listing, removes headers"
1203 ," -r with -C, output in termcap form"
1204 ," -r with -F, resolve use-references"
1205 ," -s [d|i|l|c] sort fields"
1207 ," -t suppress commented-out capabilities"
1209 ," -u produce source with 'use='"
1210 ," -v number (verbose)"
1211 ," -w number (width)"
1213 ," -x unknown capabilities are user-defined"
1217 const size_t last
= SIZEOF(options
);
1218 const size_t left
= (last
+ 1) / 2;
1221 fputs(head
, stderr
);
1222 for (n
= 0; n
< left
; n
++) {
1223 size_t m
= n
+ left
;
1225 fprintf(stderr
, "%-40.40s%s\n", options
[n
], options
[m
]);
1227 fprintf(stderr
, "%s\n", options
[n
]);
1229 ExitProgram(EXIT_FAILURE
);
1233 any_initializer(const char *fmt
, const char *type
)
1235 static char *initializer
;
1239 if (initializer
== 0) {
1240 need
= (strlen(entries
->tterm
.term_names
)
1243 initializer
= (char *) malloc(need
+ 1);
1244 if (initializer
== 0)
1245 failed("any_initializer");
1248 _nc_STRCPY(initializer
, entries
->tterm
.term_names
, need
);
1249 for (s
= initializer
; *s
!= 0 && *s
!= '|'; s
++) {
1250 if (!isalnum(UChar(*s
)))
1254 _nc_SPRINTF(s
, _nc_SLIMIT(need
) fmt
, type
);
1259 name_initializer(const char *type
)
1261 return any_initializer("_%s_data", type
);
1265 string_variable(const char *type
)
1267 return any_initializer("_s_%s", type
);
1270 /* dump C initializers for the terminal type */
1272 dump_initializers(TERMTYPE
*term
)
1275 const char *str
= 0;
1277 printf("\nstatic char %s[] = \"%s\";\n\n",
1278 name_initializer("alias"), entries
->tterm
.term_names
);
1280 for_each_string(n
, term
) {
1281 char buf
[MAX_STRING
], *sp
, *tp
;
1283 if (VALID_STRING(term
->Strings
[n
])) {
1285 #define TP_LIMIT ((MAX_STRING - 5) - (size_t)(tp - buf))
1287 for (sp
= term
->Strings
[n
];
1288 *sp
!= 0 && TP_LIMIT
> 2;
1290 if (isascii(UChar(*sp
))
1291 && isprint(UChar(*sp
))
1296 _nc_SPRINTF(tp
, _nc_SLIMIT(TP_LIMIT
) "\\%03o", UChar(*sp
));
1302 (void) printf("static char %-20s[] = %s;\n",
1303 string_variable(ExtStrname(term
, (int) n
, strnames
)),
1309 (void) printf("static char %s[] = %s\n", name_initializer("bool"), L_CURL
);
1311 for_each_boolean(n
, term
) {
1312 switch ((int) (term
->Booleans
[n
])) {
1321 case ABSENT_BOOLEAN
:
1322 str
= "ABSENT_BOOLEAN";
1325 case CANCELLED_BOOLEAN
:
1326 str
= "CANCELLED_BOOLEAN";
1329 (void) printf("\t/* %3u: %-8s */\t%s,\n",
1330 n
, ExtBoolname(term
, (int) n
, boolnames
), str
);
1332 (void) printf("%s;\n", R_CURL
);
1334 (void) printf("static short %s[] = %s\n", name_initializer("number"), L_CURL
);
1336 for_each_number(n
, term
) {
1338 switch (term
->Numbers
[n
]) {
1339 case ABSENT_NUMERIC
:
1340 str
= "ABSENT_NUMERIC";
1342 case CANCELLED_NUMERIC
:
1343 str
= "CANCELLED_NUMERIC";
1346 _nc_SPRINTF(buf
, _nc_SLIMIT(sizeof(buf
)) "%d", term
->Numbers
[n
]);
1350 (void) printf("\t/* %3u: %-8s */\t%s,\n", n
,
1351 ExtNumname(term
, (int) n
, numnames
), str
);
1353 (void) printf("%s;\n", R_CURL
);
1355 (void) printf("static char * %s[] = %s\n", name_initializer("string"), L_CURL
);
1357 for_each_string(n
, term
) {
1359 if (term
->Strings
[n
] == ABSENT_STRING
)
1360 str
= "ABSENT_STRING";
1361 else if (term
->Strings
[n
] == CANCELLED_STRING
)
1362 str
= "CANCELLED_STRING";
1364 str
= string_variable(ExtStrname(term
, (int) n
, strnames
));
1366 (void) printf("\t/* %3u: %-8s */\t%s,\n", n
,
1367 ExtStrname(term
, (int) n
, strnames
), str
);
1369 (void) printf("%s;\n", R_CURL
);
1372 if ((NUM_BOOLEANS(term
) != BOOLCOUNT
)
1373 || (NUM_NUMBERS(term
) != NUMCOUNT
)
1374 || (NUM_STRINGS(term
) != STRCOUNT
)) {
1375 (void) printf("static char * %s[] = %s\n",
1376 name_initializer("string_ext"), L_CURL
);
1377 for (n
= BOOLCOUNT
; n
< NUM_BOOLEANS(term
); ++n
) {
1378 (void) printf("\t/* %3u: bool */\t\"%s\",\n",
1379 n
, ExtBoolname(term
, (int) n
, boolnames
));
1381 for (n
= NUMCOUNT
; n
< NUM_NUMBERS(term
); ++n
) {
1382 (void) printf("\t/* %3u: num */\t\"%s\",\n",
1383 n
, ExtNumname(term
, (int) n
, numnames
));
1385 for (n
= STRCOUNT
; n
< NUM_STRINGS(term
); ++n
) {
1386 (void) printf("\t/* %3u: str */\t\"%s\",\n",
1387 n
, ExtStrname(term
, (int) n
, strnames
));
1389 (void) printf("%s;\n", R_CURL
);
1394 /* dump C initializers for the terminal type */
1396 dump_termtype(TERMTYPE
*term
)
1398 (void) printf("\t%s\n\t\t%s,\n", L_CURL
, name_initializer("alias"));
1399 (void) printf("\t\t(char *)0,\t/* pointer to string table */\n");
1401 (void) printf("\t\t%s,\n", name_initializer("bool"));
1402 (void) printf("\t\t%s,\n", name_initializer("number"));
1404 (void) printf("\t\t%s,\n", name_initializer("string"));
1407 (void) printf("#if NCURSES_XNAMES\n");
1408 (void) printf("\t\t(char *)0,\t/* pointer to extended string table */\n");
1409 (void) printf("\t\t%s,\t/* ...corresponding names */\n",
1410 ((NUM_BOOLEANS(term
) != BOOLCOUNT
)
1411 || (NUM_NUMBERS(term
) != NUMCOUNT
)
1412 || (NUM_STRINGS(term
) != STRCOUNT
))
1413 ? name_initializer("string_ext")
1416 (void) printf("\t\t%d,\t\t/* count total Booleans */\n", NUM_BOOLEANS(term
));
1417 (void) printf("\t\t%d,\t\t/* count total Numbers */\n", NUM_NUMBERS(term
));
1418 (void) printf("\t\t%d,\t\t/* count total Strings */\n", NUM_STRINGS(term
));
1420 (void) printf("\t\t%d,\t\t/* count extensions to Booleans */\n",
1421 NUM_BOOLEANS(term
) - BOOLCOUNT
);
1422 (void) printf("\t\t%d,\t\t/* count extensions to Numbers */\n",
1423 NUM_NUMBERS(term
) - NUMCOUNT
);
1424 (void) printf("\t\t%d,\t\t/* count extensions to Strings */\n",
1425 NUM_STRINGS(term
) - STRCOUNT
);
1427 (void) printf("#endif /* NCURSES_XNAMES */\n");
1430 #endif /* NCURSES_XNAMES */
1431 (void) printf("\t%s\n", R_CURL
);
1435 optarg_to_number(void)
1438 long value
= strtol(optarg
, &temp
, 0);
1440 if (temp
== 0 || temp
== optarg
|| *temp
!= 0) {
1441 fprintf(stderr
, "Expected a number, not \"%s\"\n", optarg
);
1442 ExitProgram(EXIT_FAILURE
);
1452 if ((terminal
= getenv("TERM")) == 0) {
1453 (void) fprintf(stderr
,
1454 "%s: environment variable TERM not set\n",
1462 * Show the databases that infocmp knows about. The location to which it writes is
1465 show_databases(void)
1471 _nc_first_db(&state
, &offset
);
1472 while ((path2
= _nc_next_db(&state
, &offset
)) != 0) {
1473 printf("%s\n", path2
);
1478 /***************************************************************************
1482 ***************************************************************************/
1485 #define MAIN_LEAKS() \
1490 #define MAIN_LEAKS() /* nothing */
1494 main(int argc
, char *argv
[])
1496 /* Avoid "local data >32k" error with mwcc */
1497 /* Also avoid overflowing smaller stacks on systems like AmigaOS */
1504 char *firstdir
, *restdir
;
1506 bool formatted
= FALSE
;
1507 bool filecompare
= FALSE
;
1509 bool init_analyze
= FALSE
;
1510 bool suppress_untranslatable
= FALSE
;
1513 /* where is the terminfo database location going to default to? */
1514 restdir
= firstdir
= 0;
1517 use_extended_names(FALSE
);
1521 _nc_progname
= _nc_rootname(argv
[0]);
1523 /* make sure we have enough space to add two terminal entries */
1524 myargv
= typeCalloc(char *, (size_t) (argc
+ 3));
1528 memcpy(myargv
, argv
, (sizeof(char *) * (size_t) argc
));
1531 while ((c
= getopt(argc
,
1533 "01A:aB:CcDdEeFfGgIiKLlnpQ:qR:rs:TtUuVv:w:x")) != -1) {
1550 _nc_disable_period
= TRUE
;
1551 use_extended_names(TRUE
);
1562 outform
= F_TERMCAP
;
1564 if (sortmode
== S_DEFAULT
)
1565 sortmode
= S_TERMCAP
;
1570 ExitProgram(EXIT_SUCCESS
);
1578 compare
= C_DIFFERENCE
;
1606 outform
= F_TERMINFO
;
1607 if (sortmode
== S_DEFAULT
)
1608 sortmode
= S_VARIABLE
;
1613 init_analyze
= TRUE
;
1617 outform
= F_VARIABLE
;
1618 if (sortmode
== S_DEFAULT
)
1619 sortmode
= S_VARIABLE
;
1623 outform
= F_TERMINFO
;
1635 quickdump
= optarg_to_number();
1655 sortmode
= S_NOSORT
;
1656 else if (*optarg
== 'i')
1657 sortmode
= S_TERMINFO
;
1658 else if (*optarg
== 'l')
1659 sortmode
= S_VARIABLE
;
1660 else if (*optarg
== 'c')
1661 sortmode
= S_TERMCAP
;
1663 (void) fprintf(stderr
,
1664 "%s: unknown sort mode\n",
1666 ExitProgram(EXIT_FAILURE
);
1676 _nc_disable_period
= FALSE
;
1677 suppress_untranslatable
= TRUE
;
1690 puts(curses_version());
1691 ExitProgram(EXIT_SUCCESS
);
1694 itrace
= (unsigned) optarg_to_number();
1695 set_trace_level(itrace
);
1699 mwidth
= optarg_to_number();
1704 use_extended_names(TRUE
);
1713 maxterms
= (size_t) (argc
+ 2 - optind
);
1714 if ((tfile
= typeMalloc(path
, maxterms
)) == 0)
1716 if ((tname
= typeCalloc(char *, maxterms
)) == 0)
1718 if ((entries
= typeCalloc(ENTRY
, maxterms
)) == 0)
1721 if ((entered
= typeCalloc(ENTERED
, maxterms
)) == 0)
1728 fprintf(stderr
, "%s: not enough memory\n", _nc_progname
);
1729 ExitProgram(EXIT_FAILURE
);
1732 /* by default, sort by terminfo name */
1733 if (sortmode
== S_DEFAULT
)
1734 sortmode
= S_TERMINFO
;
1736 /* make sure we have at least one terminal name to work with */
1738 argv
[argc
++] = terminal_env();
1740 /* if user is after a comparison, make sure we have two entries */
1741 if (compare
!= C_DEFAULT
&& optind
>= argc
- 1)
1742 argv
[argc
++] = terminal_env();
1744 /* exactly one terminal name with no options means display it */
1745 /* exactly two terminal names with no options means do -d */
1746 if (compare
== C_DEFAULT
) {
1747 switch (argc
- optind
) {
1749 fprintf(stderr
, "%s: too many names to compare\n", _nc_progname
);
1750 ExitProgram(EXIT_FAILURE
);
1754 compare
= C_DIFFERENCE
;
1759 /* set up for display */
1760 dump_init(tversion
, outform
, sortmode
, mwidth
, mheight
, itrace
,
1761 formatted
, FALSE
, quickdump
);
1764 /* grab the entries */
1766 for (; optind
< argc
; optind
++) {
1767 const char *directory
= termcount
? restdir
: firstdir
;
1770 tname
[termcount
] = argv
[optind
];
1773 #if NCURSES_USE_DATABASE
1774 #if MIXEDCASE_FILENAMES
1775 #define LEAF_FMT "%c"
1777 #define LEAF_FMT "%02x"
1779 _nc_SPRINTF(tfile
[termcount
],
1780 _nc_SLIMIT(sizeof(path
))
1781 "%s/" LEAF_FMT
"/%s",
1783 UChar(*argv
[optind
]), argv
[optind
]);
1785 (void) fprintf(stderr
,
1786 "%s: reading entry %s from file %s\n",
1788 argv
[optind
], tfile
[termcount
]);
1790 status
= _nc_read_file_entry(tfile
[termcount
],
1791 &entries
[termcount
].tterm
);
1793 (void) fprintf(stderr
, "%s: terminfo files not supported\n",
1796 ExitProgram(EXIT_FAILURE
);
1800 (void) fprintf(stderr
,
1801 "%s: reading entry %s from database\n",
1805 status
= _nc_read_entry(tname
[termcount
],
1807 &entries
[termcount
].tterm
);
1811 (void) fprintf(stderr
,
1812 "%s: couldn't open terminfo file %s.\n",
1816 ExitProgram(EXIT_FAILURE
);
1818 repair_acsc(&entries
[termcount
].tterm
);
1824 _nc_align_termtype(&entries
[0].tterm
, &entries
[1].tterm
);
1827 /* dump as C initializer for the terminal type */
1830 dump_termtype(&entries
[0].tterm
);
1832 dump_initializers(&entries
[0].tterm
);
1835 /* analyze the init strings */
1836 else if (init_analyze
) {
1838 #define CUR entries[0].tterm.
1839 analyze_string("is1", init_1string
, &entries
[0].tterm
);
1840 analyze_string("is2", init_2string
, &entries
[0].tterm
);
1841 analyze_string("is3", init_3string
, &entries
[0].tterm
);
1842 analyze_string("rs1", reset_1string
, &entries
[0].tterm
);
1843 analyze_string("rs2", reset_2string
, &entries
[0].tterm
);
1844 analyze_string("rs3", reset_3string
, &entries
[0].tterm
);
1845 analyze_string("smcup", enter_ca_mode
, &entries
[0].tterm
);
1846 analyze_string("rmcup", exit_ca_mode
, &entries
[0].tterm
);
1847 analyze_string("smkx", keypad_xmit
, &entries
[0].tterm
);
1848 analyze_string("rmkx", keypad_local
, &entries
[0].tterm
);
1853 * Here's where the real work gets done
1858 (void) fprintf(stderr
,
1859 "%s: about to dump %s\n",
1864 printf("#\tReconstructed via infocmp from file: %s\n",
1866 dump_entry(&entries
[0].tterm
,
1867 suppress_untranslatable
,
1873 (void) fprintf(stderr
, "%s: length %d\n", _nc_progname
, len
);
1877 show_comparing(tname
);
1878 compare_entry(compare_predicate
, &entries
->tterm
, quiet
);
1882 show_comparing(tname
);
1883 compare_entry(compare_predicate
, &entries
->tterm
, quiet
);
1887 show_comparing(tname
);
1888 compare_entry(compare_predicate
, &entries
->tterm
, quiet
);
1893 (void) fprintf(stderr
, "%s: dumping use entry\n", _nc_progname
);
1894 dump_entry(&entries
[0].tterm
,
1895 suppress_untranslatable
,
1899 for (i
= 1; i
< termcount
; i
++)
1900 dump_uses(tname
[i
], !(outform
== F_TERMCAP
1901 || outform
== F_TCONVERR
));
1904 (void) fprintf(stderr
, "%s: length %d\n", _nc_progname
, len
);
1908 } else if (compare
== C_USEALL
) {
1909 (void) fprintf(stderr
, "Sorry, -u doesn't work with -F\n");
1910 } else if (compare
== C_DEFAULT
) {
1911 (void) fprintf(stderr
, "Use `tic -[CI] <file>' for this.\n");
1912 } else if (argc
- optind
!= 2) {
1913 (void) fprintf(stderr
,
1914 "File comparison needs exactly two file arguments.\n");
1916 file_comparison(argc
- optind
, argv
+ optind
);
1920 ExitProgram(EXIT_SUCCESS
);
1923 /* infocmp.c ends here */