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