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