infocmp.c revision 1.1 1 /* $NetBSD: infocmp.c,v 1.1 2010/02/03 15:16:33 roy Exp $ */
2
3 /*
4 * Copyright (c) 2009 The NetBSD Foundation, Inc.
5 *
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by Roy Marples.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: infocmp.c,v 1.1 2010/02/03 15:16:33 roy Exp $");
32
33 #include <sys/ioctl.h>
34
35 #include <ctype.h>
36 #include <err.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <term_private.h>
41 #include <term.h>
42 #include <unistd.h>
43
44 #define SW 8
45
46 typedef struct tient {
47 char type;
48 const char *id;
49 char flag;
50 short num;
51 const char *str;
52 } TIENT;
53
54 static size_t cols;
55 static int aflag, cflag, nflag, qflag, xflag, raw;
56
57 static size_t
58 outstr(FILE *f, const char *str)
59 {
60 unsigned char ch;
61 size_t r, l;
62
63 r = 0;
64 l = strlen(str);
65 while ((ch = (unsigned char)(*str++)) != '\0') {
66 switch (ch) {
67 case 128:
68 ch = '0';
69 break;
70 case '\033':
71 ch = 'E';
72 break;
73 case '\014':
74 ch = 'f';
75 break;
76 case '^': /* FALLTHROUGH */
77 case ',': /* escape these */
78 break;
79 case ' ':
80 ch = 's';
81 break;
82 default:
83 if (ch == '\177') {
84 if (f != NULL)
85 fputc('^', f);
86 ch = '?';
87 r++;
88 } else if (iscntrl(ch) &&
89 ch < 128 &&
90 ch != '\\' &&
91 (l < 4 || isdigit((unsigned char)*str)))
92 {
93 if (f != NULL)
94 fputc('^', f);
95 ch += '@';
96 r++;
97 } else if (!isprint(ch)) {
98 if (f != NULL)
99 fprintf(f, "\\%03o", ch);
100 r += 4;
101 continue;
102 }
103 goto prnt;
104 }
105
106 if (f != NULL)
107 fputc('\\', f);
108 r++;
109 prnt:
110 if (f != NULL)
111 fputc(ch, f);
112 r++;
113 }
114 return r;
115 }
116
117 static int
118 ent_compare(const void *a, const void *b)
119 {
120 const TIENT *ta, *tb;
121
122 ta = (const TIENT *)a;
123 tb = (const TIENT *)b;
124 return strcmp(ta->id, tb->id);
125 }
126
127 static void
128 setdb(char *db)
129 {
130 size_t len;
131
132 len = strlen(db);
133 if (len > 3 &&
134 db[len - 3] == '.' &&
135 db[len - 2] == 'd' &&
136 db[len - 1] == 'b')
137 db[len - 3] = '\0';
138 setenv("TERMINFO", db, 1);
139 }
140
141 static void
142 print_ent(const TIENT *ents, size_t nents)
143 {
144 size_t col, i, l;
145 char nbuf[64];
146
147 if (nents == 0)
148 return;
149
150 col = SW;
151 printf("\t");
152 for (i = 0; i < nents; i++) {
153 if (*ents[i].id == '.' && aflag == 0)
154 continue;
155 switch (ents[i].type) {
156 case 'f':
157 if (ents[i].flag == ABSENT_BOOLEAN)
158 continue;
159 l = strlen(ents[i].id) + 2;
160 if (ents[i].flag == CANCELLED_BOOLEAN)
161 l++;
162 break;
163 case 'n':
164 if (ents[i].num == ABSENT_NUMERIC)
165 continue;
166 if (VALID_NUMERIC(ents[i].num))
167 l = snprintf(nbuf, sizeof(nbuf), "%s#%d,",
168 ents[i].id, ents[i].num);
169 else
170 l = snprintf(nbuf, sizeof(nbuf), "%s@,",
171 ents[i].id);
172 break;
173 case 's':
174 if (ents[i].str == ABSENT_STRING)
175 continue;
176 if (VALID_STRING(ents[i].str))
177 l = strlen(ents[i].id) +
178 outstr(NULL, ents[i].str) + 7;
179 else
180 l = strlen(ents[i].id) + 3;
181 break;
182 default:
183 errx(1, "invalid type");
184 }
185 if (col != SW) {
186 if (col + l > cols) {
187 printf("\n\t");
188 col = SW;
189 } else
190 col += printf(" ");
191 }
192 switch (ents[i].type) {
193 case 'f':
194 col += printf("%s", ents[i].id);
195 if (ents[i].flag == ABSENT_BOOLEAN ||
196 ents[i].flag == CANCELLED_BOOLEAN)
197 col += printf("@");
198 col += printf(",");
199 break;
200 case 'n':
201 col += printf("%s", nbuf);
202 break;
203 case 's':
204 col += printf("%s", ents[i].id);
205 if (VALID_STRING(ents[i].str)) {
206 col += printf("=");
207 col += outstr(stdout, ents[i].str);
208 } else
209 col += printf("@");
210 col += printf(",");
211 break;
212 }
213 }
214 printf("\n");
215 }
216
217 static size_t
218 load_ents(TIENT *ents, TERMINAL *t, char type)
219 {
220 size_t i, n, max;
221 TERMUSERDEF *ud;
222
223 switch (type) {
224 case 'f':
225 max = TIFLAGMAX;
226 break;
227 case 'n':
228 max = TINUMMAX;
229 break;
230 default:
231 max = TISTRMAX;
232 }
233
234 n = 0;
235 if (xflag < 2) {
236 for (i = 0; i <= max; i++) {
237 switch (type) {
238 case 'f':
239 if (t->flags[i] == 1 ||
240 (raw != 0 &&
241 t->flags[i] == CANCELLED_BOOLEAN))
242 {
243 ents[n].id = _ti_flagid(i);
244 ents[n].type = 'f';
245 ents[n++].flag = t->flags[i];
246 }
247 break;
248 case 'n':
249 if (VALID_NUMERIC(t->nums[i]) ||
250 (raw != 0 &&
251 t->nums[i] == CANCELLED_NUMERIC))
252 {
253 ents[n].id = _ti_numid(i);
254 ents[n].type = 'n';
255 ents[n++].num = t->nums[i];
256 }
257 break;
258 default:
259 if (VALID_STRING(t->strs[i]) ||
260 (raw != 0 &&
261 t->strs[i] == CANCELLED_STRING))
262 {
263 ents[n].id = _ti_strid(i);
264 ents[n].type = 's';
265 ents[n++].str = t->strs[i];
266 }
267 break;
268 }
269 }
270 }
271
272 if (xflag != 0 && t->_nuserdefs != 0) {
273 for (i = 0; i < t->_nuserdefs; i++) {
274 ud = &t->_userdefs[i];
275 if (ud->type == type) {
276 switch (type) {
277 case 'f':
278 if (raw == 0 &&
279 !VALID_BOOLEAN(ud->flag))
280 continue;
281 break;
282 case 'n':
283 if (raw == 0 &&
284 !VALID_NUMERIC(ud->num))
285 continue;
286 break;
287 case 's':
288 if (raw == 0 &&
289 !VALID_STRING(ud->str))
290 continue;
291 break;
292 }
293 ents[n].id = ud->id;
294 ents[n].type = ud->type;
295 ents[n].flag = ud->flag;
296 ents[n].num = ud->num;
297 ents[n++].str = ud->str;
298 }
299 }
300 }
301
302 qsort(ents, n, sizeof(TIENT), ent_compare);
303 return n;
304 }
305
306 static void
307 cprint_ent(TIENT *ent)
308 {
309
310 if (ent == NULL) {
311 if (qflag == 0)
312 printf("NULL");
313 else
314 printf("-");
315 }
316
317 switch (ent->type) {
318 case 'f':
319 if (VALID_BOOLEAN(ent->flag))
320 printf(ent->flag == 1 ? "T" : "F");
321 else if (qflag == 0)
322 printf("F");
323 else if (ent->flag == CANCELLED_BOOLEAN)
324 printf("@");
325 else
326 printf("-");
327 break;
328 case 'n':
329 if (VALID_NUMERIC(ent->num))
330 printf("%d", ent->num);
331 else if (qflag == 0)
332 printf("NULL");
333 else if (ent->num == CANCELLED_NUMERIC)
334 printf("@");
335 else
336 printf("-");
337 break;
338 case 's':
339 if (VALID_STRING(ent->str)) {
340 printf("'");
341 outstr(stdout, ent->str);
342 printf("'");
343 } else if (qflag == 0)
344 printf("NULL");
345 else if (ent->str == CANCELLED_STRING)
346 printf("@");
347 else
348 printf("-");
349 break;
350 }
351 }
352
353 static void
354 compare_ents(TIENT *ents1, size_t n1, TIENT *ents2, size_t n2)
355 {
356 size_t i1, i2;
357 TIENT *e1, *e2, ee;
358 int c;
359
360 i1 = i2 = 0;
361 ee.type = 'f';
362 ee.flag = ABSENT_BOOLEAN;
363 ee.num = ABSENT_NUMERIC;
364 ee.str = ABSENT_STRING;
365 while (i1 != n1 || i2 != n2) {
366 if (i1 == n1)
367 c = 1;
368 else if (i2 == n2)
369 c = -1;
370 else
371 c = strcmp(ents1[i1].id, ents2[i2].id);
372 if (c == 0) {
373 e1 = &ents1[i1++];
374 e2 = &ents2[i2++];
375 } else if (c < 0) {
376 e1 = &ents1[i1++];
377 e2 = ⅇ
378 ee.id = e1->id;
379 ee.type = e1->type;
380 } else {
381 e1 = ⅇ
382 e2 = &ents2[i2++];
383 ee.id = e2->id;
384 ee.type = e2->type;
385 }
386 switch (e1->type) {
387 case 'f':
388 if (cflag != 0) {
389 if (e1->flag == e2->flag)
390 printf("\t%s\n", ents1[i1].id);
391 continue;
392 }
393 if (e1->flag == e2->flag)
394 continue;
395 break;
396 case 'n':
397 if (cflag != 0) {
398 if (e1->num == e2->num)
399 printf("\t%s#%d\n",
400 ents1[i1].id, ents1[i1].num);
401 continue;
402 }
403 if (e1->num == e2->num)
404 continue;
405 break;
406 case 's':
407 if (cflag != 0) {
408 if (VALID_STRING(e1->str) &&
409 VALID_STRING(e2->str) &&
410 strcmp(e1->str, e2->str) == 0) {
411 printf("\t%s=", ents1[i1].id);
412 outstr(stdout, ents1[i1].str);
413 printf("\n");
414 }
415 continue;
416 }
417 if (VALID_STRING(e1->str) &&
418 VALID_STRING(e2->str) &&
419 strcmp(e1->str, e2->str) == 0)
420 continue;
421 break;
422 }
423 printf("\t%s: ", e1->id);
424 cprint_ent(e1);
425 if (e1->type == 'f')
426 printf(":");
427 else
428 printf(", ");
429 cprint_ent(e2);
430 printf(".\n");
431 }
432 }
433
434 static TERMINAL *
435 load_term(const char *name)
436 {
437 TERMINAL *t;
438
439 t = calloc(1, sizeof(*t));
440 if (t == NULL)
441 err(1, "calloc");
442 if (name == NULL)
443 name = getenv("TERM");
444 if (name == NULL)
445 name = "dumb";
446 if (_ti_getterm(t, name, 1) != 1) /* load the raw data */
447 errx(1, "no terminal definition found in %s.db", _ti_database);
448 return t;
449 }
450
451 static void
452 show_missing(TERMINAL *t1, TERMINAL *t2, char type)
453 {
454 ssize_t i, max;
455 const char *id;
456
457 switch (type) {
458 case 'f':
459 max = TIFLAGMAX;
460 break;
461 case 'n':
462 max = TINUMMAX;
463 break;
464 default:
465 max = TISTRMAX;
466 }
467
468 for (i = 0; i <= max; i++) {
469 switch (type) {
470 case 'f':
471 if (t1->flags[i] != ABSENT_BOOLEAN ||
472 t2->flags[i] != ABSENT_BOOLEAN)
473 continue;
474 id = _ti_flagid(i);
475 break;
476 case 'n':
477 if (t1->nums[i] != ABSENT_NUMERIC ||
478 t2->nums[i] != ABSENT_NUMERIC)
479 continue;
480 id = _ti_numid(i);
481 break;
482 default:
483 if (t1->strs[i] != ABSENT_STRING ||
484 t2->strs[i] != ABSENT_STRING)
485 continue;
486 id = _ti_strid(i);
487 break;
488 }
489 printf("\t!%s.\n", id);
490 }
491 }
492
493 static TERMUSERDEF *
494 find_userdef(TERMINAL *term, const char *id)
495 {
496 size_t i;
497
498 for (i = 0; i < term->_nuserdefs; i++)
499 if (strcmp(term->_userdefs[i].id, id) == 0)
500 return &term->_userdefs[i];
501 return NULL;
502 }
503
504 static void
505 use_terms(TERMINAL *term, size_t nuse, char **uterms)
506 {
507 TERMINAL **terms;
508 TERMUSERDEF *ud, *tud;
509 size_t i, j, agree, absent, data;
510
511 terms = malloc(sizeof(**terms) * nuse);
512 if (terms == NULL)
513 err(1, "malloc");
514 for (i = 0; i < nuse; i++) {
515 if (strcmp(term->name, *uterms) == 0)
516 errx(1, "cannot use same terminal");
517 for (j = 0; j < i; j++)
518 if (strcmp(terms[j]->name, *uterms) == 0)
519 errx(1, "cannot use same terminal");
520 terms[i] = load_term(*uterms++);
521 }
522
523 for (i = 0; i < TIFLAGMAX + 1; i++) {
524 agree = absent = data = 0;
525 for (j = 0; j < nuse; j++) {
526 if (terms[j]->flags[i] == ABSENT_BOOLEAN ||
527 terms[j]->flags[i] == CANCELLED_BOOLEAN)
528 absent++;
529 else {
530 data++;
531 if (term->flags[i] == terms[j]->flags[i])
532 agree++;
533 }
534 }
535 if (data == 0)
536 continue;
537 if (agree > 0 && agree + absent == nuse)
538 term->flags[i] = ABSENT_BOOLEAN;
539 else if (term->flags[i] == ABSENT_BOOLEAN)
540 term->flags[i] = CANCELLED_BOOLEAN;
541 }
542
543 for (i = 0; i < TINUMMAX + 1; i++) {
544 agree = absent = data = 0;
545 for (j = 0; j < nuse; j++) {
546 if (terms[j]->nums[i] == ABSENT_NUMERIC ||
547 terms[j]->nums[i] == CANCELLED_NUMERIC)
548 absent++;
549 else {
550 data++;
551 if (term->nums[i] == terms[j]->nums[i])
552 agree++;
553 }
554 }
555 if (data == 0)
556 continue;
557 if (agree > 0 && agree + absent == nuse)
558 term->nums[i] = ABSENT_NUMERIC;
559 else if (term->nums[i] == ABSENT_NUMERIC)
560 term->nums[i] = CANCELLED_NUMERIC;
561 }
562
563 for (i = 0; i < TISTRMAX + 1; i++) {
564 agree = absent = data = 0;
565 for (j = 0; j < nuse; j++) {
566 if (terms[j]->strs[i] == ABSENT_STRING ||
567 terms[j]->strs[i] == CANCELLED_STRING)
568 absent++;
569 else {
570 data++;
571 if (VALID_STRING(term->strs[i]) &&
572 strcmp(term->strs[i],
573 terms[j]->strs[i]) == 0)
574 agree++;
575 }
576 }
577 if (data == 0)
578 continue;
579 if (agree > 0 && agree + absent == nuse)
580 term->strs[i] = ABSENT_STRING;
581 else if (term->strs[i] == ABSENT_STRING)
582 term->strs[i] = CANCELLED_STRING;
583 }
584
585 /* User defined caps are more tricky.
586 First we set any to absent that agree. */
587 for (i = 0; i < term->_nuserdefs; i++) {
588 agree = absent = data = 0;
589 ud = &term->_userdefs[i];
590 for (j = 0; j < nuse; j++) {
591 tud = find_userdef(terms[j], ud->id);
592 if (tud == NULL)
593 absent++;
594 else {
595 data++;
596 switch (ud->type) {
597 case 'f':
598 if (tud->type == 'f' &&
599 tud->flag == ud->flag)
600 agree++;
601 break;
602 case 'n':
603 if (tud->type == 'n' &&
604 tud->num == ud->num)
605 agree++;
606 break;
607 case 's':
608 if (tud->type == 's' &&
609 VALID_STRING(tud->str) &&
610 VALID_STRING(ud->str) &&
611 strcmp(ud->str, tud->str) == 0)
612 agree++;
613 break;
614 }
615 }
616 }
617 if (data == 0)
618 continue;
619 if (agree > 0 && agree + absent == nuse) {
620 ud->flag = ABSENT_BOOLEAN;
621 ud->num = ABSENT_NUMERIC;
622 ud->str = ABSENT_STRING;
623 }
624 }
625
626 /* Now add any that we don't have as cancelled */
627 for (i = 0; i < nuse; i++) {
628 for (j = 0; j < terms[i]->_nuserdefs; j++) {
629 ud = find_userdef(term, terms[i]->_userdefs[j].id);
630 if (ud != NULL)
631 continue; /* We have handled this */
632 term->_userdefs = realloc(term->_userdefs,
633 sizeof(*term->_userdefs) * (term->_nuserdefs + 1));
634 if (term->_userdefs == NULL)
635 err(1, "malloc");
636 tud = &term->_userdefs[term->_nuserdefs++];
637 tud->id = terms[i]->_userdefs[j].id;
638 tud->type = terms[i]->_userdefs[j].flag;
639 tud->flag = CANCELLED_BOOLEAN;
640 tud->num = CANCELLED_NUMERIC;
641 tud->str = CANCELLED_STRING;
642 }
643 }
644 }
645
646 int
647 main(int argc, char **argv)
648 {
649 char *term, *Barg;
650 int ch, uflag;
651 TERMINAL *t, *t2;
652 size_t n, n2;
653 struct winsize ws;
654 TIENT ents[TISTRMAX + 1], ents2[TISTRMAX + 1];
655
656 cols = 80; /* default */
657 term = getenv("COLUMNS");
658 if (term != NULL)
659 cols = strtoul(term, NULL, 10);
660 else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
661 cols = ws.ws_col;
662
663 uflag = xflag = 0;
664 Barg = NULL;
665 while ((ch = getopt(argc, argv, "1A:B:acnquw:x")) != -1)
666 switch (ch) {
667 case '1':
668 cols = 1;
669 break;
670 case 'A':
671 setdb(optarg);
672 break;
673 case 'B':
674 Barg = optarg;
675 break;
676 case 'a':
677 aflag++;
678 xflag++;
679 break;
680 case 'c':
681 cflag++;
682 break;
683 case 'n':
684 nflag++;
685 break;
686 case 'q':
687 qflag++;
688 break;
689 case 'u':
690 uflag++;
691 break;
692 case 'w':
693 cols = strtoul(optarg, NULL, 10);
694 break;
695 case 'x':
696 xflag++;
697 break;
698 case '?':
699 default:
700 fprintf(stderr,
701 "usage: %s [-1acnqux] [-A database] [-B database] "
702 "[-w cols] [term]\n",
703 getprogname());
704 return EXIT_FAILURE;
705 }
706 cols--;
707
708 if (optind + 1 < argc)
709 raw = 1;
710
711 if (optind < argc)
712 term = argv[optind++];
713 else
714 term = NULL;
715 t = load_term(term);
716
717 if (uflag != 0)
718 use_terms(t, argc - optind, argv + optind);
719
720 if ((optind + 1 != argc && nflag == 0) || uflag != 0) {
721 if (uflag == 0)
722 printf("# Reconstructed from %s.db\n", _ti_database);
723 printf("%s", t->name);
724 if (t->desc != NULL && *t->desc != '\0')
725 printf("|%s", t->desc);
726 printf(",\n");
727
728 n = load_ents(ents, t, 'f');
729 print_ent(ents, n);
730 n = load_ents(ents, t, 'n');
731 print_ent(ents, n);
732 n = load_ents(ents, t, 's');
733 print_ent(ents, n);
734
735 if (uflag != 0) {
736 printf("\t");
737 n = SW;
738 for (; optind < argc; optind++) {
739 n2 = 5 + strlen(argv[optind]);
740 if (n != SW) {
741 if (n + n2 > cols) {
742 printf("\n\t");
743 n = SW;
744 } else
745 n += printf(" ");
746 }
747 n += printf("use=%s,", argv[optind]);
748 }
749 printf("\n");
750 }
751 return EXIT_SUCCESS;
752 }
753
754 if (Barg == NULL)
755 unsetenv("TERMINFO");
756 else
757 setdb(Barg);
758 t2 = load_term(argv[optind++]);
759 printf("comparing %s to %s.\n", t->name, t2->name);
760 if (qflag == 0)
761 printf(" comparing booleans.\n");
762 if (nflag == 0) {
763 n = load_ents(ents, t, 'f');
764 n2 = load_ents(ents2, t2, 'f');
765 compare_ents(ents, n, ents2, n2);
766 } else
767 show_missing(t, t2, 'f');
768 if (qflag == 0)
769 printf(" comparing numbers.\n");
770 if (nflag == 0) {
771 n = load_ents(ents, t, 'n');
772 n2 = load_ents(ents2, t2, 'n');
773 compare_ents(ents, n, ents2, n2);
774 } else
775 show_missing(t, t2, 'n');
776 if (qflag == 0)
777 printf(" comparing strings.\n");
778 if (nflag == 0) {
779 n = load_ents(ents, t, 's');
780 n2 = load_ents(ents2, t2, 's');
781 compare_ents(ents, n, ents2, n2);
782 } else
783 show_missing(t, t2, 's');
784 return EXIT_SUCCESS;
785 }
786