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