ncurses 6.0 - patch 20170408
[ncurses.git] / test / demo_termcap.c
1 /****************************************************************************
2  * Copyright (c) 2005-2016,2017 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: Thomas E. Dickey
31  *
32  * $Id: demo_termcap.c,v 1.53 2017/04/08 19:08:42 tom Exp $
33  *
34  * A simple demo of the termcap interface.
35  */
36 #define USE_TINFO
37 #include <test.priv.h>
38 #include <sys/stat.h>
39
40 #if NCURSES_XNAMES
41 #if HAVE_TERM_ENTRY_H
42 #include <term_entry.h>
43 #else
44 #undef NCURSES_XNAMES
45 #define NCURSES_XNAMES 0
46 #endif
47 #endif
48
49 #ifdef NCURSES_VERSION
50 #include <termcap.h>
51 #endif
52
53 static void failed(const char *) GCC_NORETURN;
54
55 static void
56 failed(const char *msg)
57 {
58     fprintf(stderr, "%s\n", msg);
59     ExitProgram(EXIT_FAILURE);
60 }
61
62 #if HAVE_TGETENT
63
64 #if defined(HAVE_CURSES_DATA_BOOLNAMES) || defined(DECL_CURSES_DATA_BOOLNAMES)
65 #define USE_CODE_LISTS 1
66 #else
67 #define USE_CODE_LISTS 0
68 #endif
69
70 #define FCOLS 8
71 #define FNAME(type) "%s %-*s = ", #type, FCOLS
72
73 static bool b_opt = FALSE;
74 static bool n_opt = FALSE;
75 static bool s_opt = FALSE;
76 static bool q_opt = FALSE;
77 static bool x_opt = FALSE;
78 static bool y_opt = FALSE;
79
80 static char *d_opt;
81 static char *e_opt;
82 static char **db_list;
83 static int db_item;
84
85 static char *my_blob;
86 static char **my_boolcodes;
87 static char **my_numcodes;
88 static char **my_numvalues;
89 static char **my_strcodes;
90 static char **my_strvalues;
91
92 static long total_values;
93 static long total_b_values;
94 static long total_n_values;
95 static long total_s_values;
96
97 #define isCapName(c) (isgraph(c) && strchr("^=:\\", c) == 0)
98 #define EachCapName(n) n = 33; n < 127; ++n
99
100 static char *
101 make_dbitem(char *p, char *q)
102 {
103     size_t need = strlen(e_opt) + 2 + (size_t) (p - q);
104     char *result = malloc(need);
105     _nc_SPRINTF(result, _nc_SLIMIT(need) "%s=%.*s", e_opt, (int) (p - q), q);
106     return result;
107 }
108
109 static void
110 make_dblist(void)
111 {
112     if (d_opt && e_opt) {
113         int pass;
114
115         for (pass = 0; pass < 2; ++pass) {
116             char *p, *q;
117             size_t count = 0;
118
119             for (p = q = d_opt; *p != '\0'; ++p) {
120                 if (*p == ':') {
121                     if (p != q + 1) {
122                         if (pass) {
123                             db_list[count] = make_dbitem(p, q);
124                         }
125                         count++;
126                     }
127                     q = p + 1;
128                 }
129             }
130             if (p != q + 1) {
131                 if (pass) {
132                     db_list[count] = make_dbitem(p, q);
133                 }
134                 count++;
135             }
136             if (!pass) {
137                 db_list = typeCalloc(char *, count + 1);
138             }
139         }
140     }
141 }
142
143 static char *
144 next_dbitem(void)
145 {
146     char *result = 0;
147
148     if (db_list) {
149         if ((result = db_list[db_item]) == 0) {
150             db_item = 0;
151             result = db_list[0];
152         } else {
153             db_item++;
154         }
155     }
156     printf("** %s\n", result);
157     return result;
158 }
159
160 #if NO_LEAKS
161 static void
162 free_dblist(void)
163 {
164     if (db_list) {
165         int n;
166         for (n = 0; db_list[n]; ++n)
167             free(db_list[n]);
168         free(db_list);
169         db_list = 0;
170     }
171 }
172 #endif /* NO_LEAKS */
173
174 static void
175 show_string(const char *name, const char *value)
176 {
177     printf(FNAME(str), name);
178     if (value == ((char *) -1)) {
179         printf("CANCELLED");
180     } else if (value == ((char *) 0)) {
181         printf("ABSENT");
182     } else {
183         while (*value != 0) {
184             int ch = UChar(*value++);
185             switch (ch) {
186             case '\177':
187                 fputs("^?", stdout);
188                 break;
189             case '\033':
190                 fputs("\\E", stdout);
191                 break;
192             case '\b':
193                 fputs("\\b", stdout);
194                 break;
195             case '\f':
196                 fputs("\\f", stdout);
197                 break;
198             case '\n':
199                 fputs("\\n", stdout);
200                 break;
201             case '\r':
202                 fputs("\\r", stdout);
203                 break;
204             case ' ':
205                 fputs("\\s", stdout);
206                 break;
207             case '\t':
208                 fputs("\\t", stdout);
209                 break;
210             case '^':
211                 fputs("\\^", stdout);
212                 break;
213             case ':':
214                 fputs("\\072", stdout);
215                 break;
216             case '\\':
217                 fputs("\\\\", stdout);
218                 break;
219             default:
220                 if (isgraph(ch))
221                     fputc(ch, stdout);
222                 else if (ch < 32)
223                     printf("^%c", ch + '@');
224                 else
225                     printf("\\%03o", ch);
226                 break;
227             }
228         }
229     }
230     printf("\n");
231 }
232
233 static void
234 show_number(const char *name, int value)
235 {
236     printf(FNAME(num), name);
237     printf(" %d\n", value);
238 }
239
240 static void
241 dumpit(NCURSES_CONST char *cap)
242 {
243     /*
244      * One of the limitations of the termcap interface is that the library
245      * cannot determine the size of the buffer passed via tgetstr(), nor the
246      * amount of space remaining.  This demo simply reuses the whole buffer
247      * for each call; a normal termcap application would try to use the buffer
248      * to hold all of the strings extracted from the terminal entry.
249      */
250     char area[1024], *ap = area;
251     char *str;
252     int num;
253
254     if ((str = tgetstr(cap, &ap)) != 0) {
255         total_values++;
256         total_s_values++;
257         if (!q_opt) {
258             /*
259              * Note that the strings returned are mostly terminfo format, since
260              * ncurses does not convert except for a handful of special cases.
261              */
262             show_string(cap, str);
263         }
264     } else if ((num = tgetnum(cap)) >= 0) {
265         total_values++;
266         total_n_values++;
267         if (!q_opt) {
268             show_number(cap, num);
269         }
270     } else if (tgetflag(cap) > 0) {
271         total_values++;
272         total_b_values++;
273         if (!q_opt) {
274             printf(FNAME(flg), cap);
275             printf("%s\n", "true");
276         }
277     }
278
279     if (!q_opt)
280         fflush(stdout);
281 }
282
283 static void
284 brute_force(const char *name)
285 {
286     char buffer[1024];
287
288     if (db_list) {
289         putenv(next_dbitem());
290     }
291     if (!q_opt)
292         printf("Terminal type \"%s\"\n", name);
293     if (tgetent(buffer, name) >= 0) {
294         char cap[3];
295         int c1, c2;
296
297         cap[2] = 0;
298         for (EachCapName(c1)) {
299             cap[0] = (char) c1;
300             if (isCapName(c1)) {
301                 for (EachCapName(c2)) {
302                     cap[1] = (char) c2;
303                     if (isCapName(c2)) {
304                         dumpit(cap);
305                     }
306                 }
307             }
308         }
309     }
310 }
311
312 #if NCURSES_XNAMES
313 static void
314 dump_xname(NCURSES_CONST char *cap)
315 {
316     if (strlen(cap) == 2)
317         dumpit(cap);
318 }
319 #endif
320
321 static void
322 demo_termcap(NCURSES_CONST char *name)
323 {
324     unsigned n;
325     NCURSES_CONST char *cap;
326     char buffer[1024];
327
328     if (db_list) {
329         putenv(next_dbitem());
330     }
331     if (!q_opt)
332         printf("Terminal type \"%s\"\n", name);
333     if (tgetent(buffer, name) >= 0) {
334
335         if (b_opt) {
336             for (n = 0;; ++n) {
337                 cap = my_boolcodes[n];
338                 if (cap == 0)
339                     break;
340                 dumpit(cap);
341             }
342         }
343
344         if (n_opt) {
345             for (n = 0;; ++n) {
346                 cap = my_numcodes[n];
347                 if (cap == 0)
348                     break;
349                 dumpit(cap);
350             }
351         }
352
353         if (s_opt) {
354             for (n = 0;; ++n) {
355                 cap = my_strcodes[n];
356                 if (cap == 0)
357                     break;
358                 dumpit(cap);
359             }
360         }
361 #ifdef NCURSES_VERSION
362         if (x_opt && (my_blob == 0) && y_opt) {
363 #if NCURSES_XNAMES
364             TERMTYPE *term = (TERMTYPE *) cur_term;
365             if (term != 0
366                 && ((NUM_BOOLEANS(term) != BOOLCOUNT)
367                     || (NUM_NUMBERS(term) != NUMCOUNT)
368                     || (NUM_STRINGS(term) != STRCOUNT))) {
369                 for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
370                     dump_xname(ExtBoolname(term, (int) n, boolnames));
371                 }
372                 for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
373                     dump_xname(ExtNumname(term, (int) n, numnames));
374                 }
375                 for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
376                     dump_xname(ExtStrname(term, (int) n, strnames));
377                 }
378             }
379 #endif
380         }
381 #endif
382     }
383 }
384
385 typedef enum {
386     pDefault = 0
387     ,pComment
388     ,pDescription
389     ,pEscaped
390     ,pNewline
391     ,pName
392     ,pNumber
393     ,pString
394 } STATE;
395
396 static void
397 parse_description(const char *input_name)
398 {
399     static char empty[1];
400
401     FILE *fp;
402     struct stat sb;
403     size_t count_bools = 0;
404     size_t count_nums = 0;
405     size_t count_strs = 0;
406     size_t len;
407     size_t j, k;
408     STATE state;
409
410     if (stat(input_name, &sb) != 0
411         || (sb.st_mode & S_IFMT) != S_IFREG) {
412         failed("input is not a file");
413     }
414
415     if (sb.st_size == 0) {
416         failed("input is empty");
417     }
418
419     /*
420      * None of the arrays could be larger than the input-file, and since it
421      * is small, just allocate the maximum for simplicity.
422      */
423     if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0 ||
424         (my_boolcodes = typeCalloc(char *, sb.st_size)) == 0 ||
425           (my_numcodes = typeCalloc(char *, sb.st_size)) == 0 ||
426           (my_numvalues = typeCalloc(char *, sb.st_size)) == 0 ||
427           (my_strcodes = typeCalloc(char *, sb.st_size)) == 0 ||
428           (my_strvalues = typeCalloc(char *, sb.st_size)) == 0) {
429         failed("cannot allocate memory for input-file");
430     }
431
432     if ((fp = fopen(input_name, "r")) == 0)
433         failed("cannot open input-file");
434     len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
435     my_blob[sb.st_size] = '\0';
436     fclose(fp);
437
438     /*
439      * First, get rid of comments and escaped newlines, as well as repeated
440      * colons to construct a canonical entry.
441      *
442      * FIXME: actually this should make an additional pass just to strip
443      * comment-lines and escaped newlines.  But it is workable for infocmp
444      * output.
445      */
446     state = pNewline;
447     for (j = k = 0; j < len; ++j) {
448         int ch = my_blob[j];
449         if (ch == '\t') {
450             ch = ' ';
451         }
452         switch (state) {
453         case pNewline:
454             if (ch == ' ') {
455                 continue;
456             }
457             if (ch == '#') {
458                 state = pComment;
459                 continue;
460             }
461             state = pDefault;
462             /* FALLTHRU */
463         case pDefault:
464             switch (ch) {
465             case '|':
466                 state = pDescription;
467                 continue;
468             case '\\':
469                 state = pEscaped;
470                 continue;
471             case '\n':
472                 state = pNewline;
473                 continue;
474             case ' ':
475             case ':':
476                 break;
477             default:
478                 state = pName;
479                 break;
480             }
481             my_blob[k++] = (char) ch;
482             break;
483         case pComment:
484             if (ch == '\n')
485                 state = pNewline;
486             break;
487         case pDescription:
488             switch (ch) {
489             case ':':
490                 state = pDefault;
491                 break;
492             case '\n':
493                 state = pNewline;
494                 break;
495             }
496             break;
497         case pEscaped:
498             if (ch != '\n') {
499                 my_blob[k++] = (char) ch;
500                 state = pDefault;
501             } else {
502                 state = pNewline;
503             }
504             break;
505         case pName:
506             switch (ch) {
507             case '\n':
508                 state = pNewline;
509                 continue;
510             case ' ':
511             case ':':
512                 state = pDefault;
513                 break;
514             case '#':
515                 state = pNumber;
516                 break;
517             case '|':
518                 state = pDescription;
519                 continue;
520             }
521             my_blob[k++] = (char) ch;
522             break;
523         case pNumber:
524             switch (ch) {
525             case '\n':
526                 state = pNewline;
527                 continue;
528             case ':':
529                 state = pDefault;
530                 break;
531             case ' ':
532                 state = pDefault;
533                 continue;
534             }
535             my_blob[k++] = (char) ch;
536             break;
537         case pString:
538             switch (ch) {
539             case '\\':
540                 if (my_blob[j + 1] == '\0') {
541                     state = pDefault;
542                     continue;
543                 }
544                 break;
545             case '\n':
546                 state = pNewline;
547                 continue;
548             case ':':
549                 state = pDefault;
550                 break;
551             }
552             my_blob[k++] = (char) ch;
553             break;
554         default:
555             /* not used */
556             break;
557         }
558     }
559     my_blob[k] = '\0';
560
561     /*
562      * Then, parse what's left, making indexes of the names and values.
563      */
564     state = pDefault;
565     for (j = 0; my_blob[j] != '\0'; ++j) {
566         switch (state) {
567         case pDefault:
568             switch (my_blob[j]) {
569             case '\\':
570                 state = pEscaped;
571                 break;
572             case ':':
573                 my_blob[j] = '\0';
574                 if (my_blob[j + 1] != '\0' && my_blob[j + 1] != ':')
575                     state = pName;
576                 break;
577             case ' ':
578                 break;
579             default:
580                 break;
581             }
582         case pEscaped:
583             break;
584         case pName:
585             state = pDefault;
586             /*
587              * Commented-out capabilities might be accessible (they are in
588              * ncurses).
589              */
590             if (my_blob[j] == '.' && my_blob[j + 1] == '.') {
591                 j += 2;
592             }
593             if (my_blob[j + 1] != '\0') {
594                 switch (my_blob[j + 2]) {
595                 case '#':
596                     my_numvalues[count_nums] = &my_blob[j + 3];
597                     my_numcodes[count_nums++] = &my_blob[j];
598                     my_blob[j + 2] = '\0';
599                     state = pNumber;
600                     j += 2;
601                     break;
602                 case '=':
603                     my_strvalues[count_strs] = &my_blob[j + 3];
604                     my_strcodes[count_strs++] = &my_blob[j];
605                     my_blob[j + 2] = '\0';
606                     state = pString;
607                     j += 2;
608                     break;
609                 default:
610                     if (my_blob[j + 2] == '@') {
611                         /*
612                          * We cannot get the type for a cancelled item
613                          * directly, but can infer it assuming the input
614                          * came from infocmp, which puts the data in a
615                          * known order.
616                          */
617                         if (count_strs) {
618                             my_strvalues[count_strs] = empty;
619                             my_strcodes[count_strs++] = &my_blob[j];
620                         } else if (count_nums) {
621                             my_numvalues[count_nums] = empty;
622                             my_numcodes[count_nums++] = &my_blob[j];
623                         } else {
624                             my_boolcodes[count_bools++] = &my_blob[j];
625                         }
626                     } else {
627                         my_boolcodes[count_bools++] = &my_blob[j];
628                     }
629                     j++;
630                     break;
631                 }
632             }
633             break;
634         case pNumber:
635             if (!isdigit(UChar(my_blob[j]))) {
636                 --j;
637                 state = pDefault;
638             }
639             break;
640         case pString:
641             switch (my_blob[j]) {
642             case '\\':
643                 if (my_blob[j + 1] == '\0') {
644                     state = pDefault;
645                     continue;
646                 } else {
647                     ++j;
648                 }
649                 break;
650             case '\n':
651                 state = pNewline;
652                 continue;
653             case ':':
654                 --j;
655                 state = pDefault;
656                 break;
657             }
658             break;
659         case pNewline:
660         case pComment:
661         case pDescription:
662         default:
663             break;
664         }
665     }
666     my_boolcodes[count_bools] = 0;
667     my_numcodes[count_nums] = 0;
668     my_numvalues[count_nums] = 0;
669     my_strcodes[count_strs] = 0;
670     my_strvalues[count_strs] = 0;
671
672 #if 0
673     printf("bools:%d\n", (int) count_bools);
674     for (j = 0; my_boolcodes[j]; ++j)
675         printf("%5d:%s\n", (int) j, my_boolcodes[j]);
676
677     printf("numbers:%d\n", (int) count_nums);
678     for (j = 0; my_numcodes[j]; ++j)
679         printf("%5d:%s(%s)\n", (int) j, my_numcodes[j], my_numvalues[j]);
680
681     printf("strings:%d\n", (int) count_strs);
682     for (j = 0; my_strcodes[j]; ++j)
683         printf("%5d:%s(%s)\n", (int) j, my_strcodes[j], my_strvalues[j]);
684 #endif
685 }
686
687 #if USE_CODE_LISTS
688 static char **
689 copy_code_list(NCURSES_CONST char *const *list)
690 {
691     int pass;
692     size_t count;
693     size_t length = 1;
694     char **result = 0;
695     char *blob = 0;
696     char *unused = 0;
697
698     for (pass = 0; pass < 2; ++pass) {
699         for (count = 0; list[count] != 0; ++count) {
700             size_t chunk = strlen(list[count]) + 1;
701             if (pass == 0) {
702                 length += chunk;
703             } else {
704                 result[count] = unused;
705                 _nc_STRCPY(unused, list[count], length);
706                 unused += chunk;
707             }
708         }
709         if (pass == 0) {
710             blob = malloc(length);
711             result = typeCalloc(char *, count + 1);
712             unused = blob;
713             if (blob == 0 || result == 0)
714                 failed("copy_code_list failed");
715         }
716     }
717
718     return result;
719 }
720
721 #if NO_LEAKS
722 static void
723 free_code_list(char **list)
724 {
725     if (list) {
726         free(list[0]);
727         free(list);
728     }
729 }
730 #endif /* NO_LEAKS */
731 #endif /* USE_CODE_LISTS */
732
733 static void
734 usage(void)
735 {
736     static const char *msg[] =
737     {
738         "Usage: demo_termcap [options] [terminal]",
739         "",
740         "If no options are given, print all (boolean, numeric, string)",
741         "capabilities for the given terminal, using short names.",
742         "",
743         "Options:",
744         " -a       try all names, print capabilities found",
745         " -b       print boolean-capabilities",
746         " -d LIST  colon-separated list of databases to use",
747         " -e NAME  environment variable to set with -d option",
748         " -i NAME  terminal description to use as names for \"-a\" option, etc.",
749         " -n       print numeric-capabilities",
750         " -q       quiet (prints only counts)",
751         " -r COUNT repeat for given count",
752         " -s       print string-capabilities",
753         " -v       print termcap-variables",
754 #ifdef NCURSES_VERSION
755         " -x       print extended capabilities",
756 #endif
757     };
758     unsigned n;
759     for (n = 0; n < SIZEOF(msg); ++n) {
760         fprintf(stderr, "%s\n", msg[n]);
761     }
762     ExitProgram(EXIT_FAILURE);
763 }
764
765 int
766 main(int argc, char *argv[])
767 {
768     int n;
769     char *name;
770     bool a_opt = FALSE;
771     bool v_opt = FALSE;
772     char *input_name = 0;
773
774     int repeat;
775     int r_opt = 1;
776
777     while ((n = getopt(argc, argv, "abd:e:i:nqr:svxy")) != -1) {
778         switch (n) {
779         case 'a':
780             a_opt = TRUE;
781             break;
782         case 'b':
783             b_opt = TRUE;
784             break;
785         case 'd':
786             d_opt = optarg;
787             break;
788         case 'e':
789             e_opt = optarg;
790             break;
791         case 'i':
792             input_name = optarg;
793             break;
794         case 'n':
795             n_opt = TRUE;
796             break;
797         case 'q':
798             q_opt = TRUE;
799             break;
800         case 'r':
801             if ((r_opt = atoi(optarg)) <= 0)
802                 usage();
803             break;
804         case 's':
805             s_opt = TRUE;
806             break;
807         case 'v':
808             v_opt = TRUE;
809             break;
810 #if NCURSES_XNAMES
811         case 'x':
812             x_opt = TRUE;
813             break;
814         case 'y':
815             y_opt = TRUE;
816             x_opt = TRUE;
817             break;
818 #endif
819         default:
820             usage();
821             break;
822         }
823     }
824
825 #if HAVE_USE_EXTENDED_NAMES
826     use_extended_names(x_opt);
827 #endif
828
829     if (!(b_opt || n_opt || s_opt)) {
830         b_opt = TRUE;
831         n_opt = TRUE;
832         s_opt = TRUE;
833     }
834
835     make_dblist();
836
837     if (a_opt) {
838         for (repeat = 0; repeat < r_opt; ++repeat) {
839             if (optind < argc) {
840                 for (n = optind; n < argc; ++n) {
841                     brute_force(argv[n]);
842                 }
843             } else if ((name = getenv("TERM")) != 0) {
844                 brute_force(name);
845             } else {
846                 static char dumb[] = "dumb";
847                 brute_force(dumb);
848             }
849         }
850     } else {
851         if (input_name != 0) {
852             parse_description(input_name);
853         }
854 #if USE_CODE_LISTS
855         else {
856             my_boolcodes = copy_code_list(boolcodes);
857             my_numcodes = copy_code_list(numcodes);
858             my_strcodes = copy_code_list(strcodes);
859         }
860 #else
861         else {
862             failed("no capability-lists available (use -i option)");
863         }
864 #endif /* USE_CODE_LISTS */
865         for (repeat = 0; repeat < r_opt; ++repeat) {
866             if (optind < argc) {
867                 for (n = optind; n < argc; ++n) {
868                     demo_termcap(argv[n]);
869                 }
870             } else if ((name = getenv("TERM")) != 0) {
871                 demo_termcap(name);
872             } else {
873                 static char dumb[] = "dumb";
874                 demo_termcap(dumb);
875             }
876         }
877     }
878
879     printf("%ld values (%ld booleans, %ld numbers, %ld strings)\n",
880            total_values, total_b_values, total_n_values, total_s_values);
881
882 #if defined(NCURSES_VERSION) || defined(HAVE_CURSES_DATA_OSPEED)
883     if (v_opt) {
884         show_number("PC", PC);
885         show_string("UP", UP);
886         show_string("BC", BC);
887         show_number("ospeed", ospeed);
888     }
889 #endif
890
891 #if NO_LEAKS
892     free_dblist();
893 #if USE_CODE_LISTS
894     free_code_list(my_boolcodes);
895     free_code_list(my_numcodes);
896     free_code_list(my_strcodes);
897 #endif
898 #endif /* NO_LEAKS */
899
900     ExitProgram(EXIT_SUCCESS);
901 }
902
903 #else
904 int
905 main(int argc GCC_UNUSED,
906      char *argv[]GCC_UNUSED)
907 {
908     failed("This program requires termcap");
909 }
910 #endif