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