]> ncurses.scripts.mit.edu Git - ncurses.git/blob - test/demo_termcap.c
ncurses 6.1 - patch 20191130
[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.58 2019/08/24 21:47:19 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     if (result != 0)
163         printf("** %s\n", result);
164     return result;
165 }
166
167 #if NO_LEAKS
168 static void
169 free_dblist(void)
170 {
171     if (db_list) {
172         int n;
173         for (n = 0; db_list[n]; ++n)
174             free(db_list[n]);
175         free(db_list);
176         db_list = 0;
177     }
178 }
179 #endif /* NO_LEAKS */
180
181 static void
182 show_string(const char *name, const char *value)
183 {
184     printf(FNAME(str), name);
185     if (value == ((char *) -1)) {
186         printf("CANCELLED");
187     } else if (value == ((char *) 0)) {
188         printf("ABSENT");
189     } else {
190         while (*value != 0) {
191             int ch = UChar(*value++);
192             switch (ch) {
193             case '\177':
194                 fputs("^?", stdout);
195                 break;
196             case '\033':
197                 fputs("\\E", stdout);
198                 break;
199             case '\b':
200                 fputs("\\b", stdout);
201                 break;
202             case '\f':
203                 fputs("\\f", stdout);
204                 break;
205             case '\n':
206                 fputs("\\n", stdout);
207                 break;
208             case '\r':
209                 fputs("\\r", stdout);
210                 break;
211             case ' ':
212                 fputs("\\s", stdout);
213                 break;
214             case '\t':
215                 fputs("\\t", stdout);
216                 break;
217             case '^':
218                 fputs("\\^", stdout);
219                 break;
220             case ':':
221                 fputs("\\072", stdout);
222                 break;
223             case '\\':
224                 fputs("\\\\", stdout);
225                 break;
226             default:
227                 if (isgraph(ch))
228                     fputc(ch, stdout);
229                 else if (ch < 32)
230                     printf("^%c", ch + '@');
231                 else
232                     printf("\\%03o", ch);
233                 break;
234             }
235         }
236     }
237     printf("\n");
238 }
239
240 static void
241 show_number(const char *name, int value)
242 {
243     printf(FNAME(num), name);
244     printf(" %d\n", value);
245 }
246
247 static void
248 dumpit(NCURSES_CONST char *cap)
249 {
250     /*
251      * One of the limitations of the termcap interface is that the library
252      * cannot determine the size of the buffer passed via tgetstr(), nor the
253      * amount of space remaining.  This demo simply reuses the whole buffer
254      * for each call; a normal termcap application would try to use the buffer
255      * to hold all of the strings extracted from the terminal entry.
256      */
257     char area[1024], *ap = area;
258     char *str;
259     int num;
260
261     if ((str = tgetstr(cap, &ap)) != 0) {
262         total_values++;
263         total_s_values++;
264         if (!q_opt) {
265             /*
266              * Note that the strings returned are mostly terminfo format, since
267              * ncurses does not convert except for a handful of special cases.
268              */
269             show_string(cap, str);
270         }
271     } else if ((num = tgetnum(cap)) >= 0) {
272         total_values++;
273         total_n_values++;
274         if (!q_opt) {
275             show_number(cap, num);
276         }
277     } else if (tgetflag(cap) > 0) {
278         total_values++;
279         total_b_values++;
280         if (!q_opt) {
281             printf(FNAME(flg), cap);
282             printf("%s\n", "true");
283         }
284     }
285
286     if (!q_opt)
287         fflush(stdout);
288 }
289
290 static void
291 brute_force(const char *name)
292 {
293     char buffer[1024];
294
295     if (db_list) {
296         putenv(next_dbitem());
297     }
298     if (!q_opt)
299         printf("Terminal type \"%s\"\n", name);
300     if (tgetent(buffer, name) >= 0) {
301         char cap[3];
302         int c1, c2;
303
304         cap[2] = 0;
305         for (EachCapName(c1)) {
306             cap[0] = (char) c1;
307             if (isCapName(c1)) {
308                 for (EachCapName(c2)) {
309                     cap[1] = (char) c2;
310                     if (isCapName(c2)) {
311                         dumpit(cap);
312                     }
313                 }
314             }
315         }
316     }
317 }
318
319 #if NCURSES_XNAMES
320 static void
321 dump_xname(NCURSES_CONST char *cap)
322 {
323     if (strlen(cap) == 2)
324         dumpit(cap);
325 }
326 #endif
327
328 static void
329 demo_termcap(NCURSES_CONST char *name)
330 {
331     char buffer[1024];
332
333     if (db_list) {
334         putenv(next_dbitem());
335     }
336     if (!q_opt)
337         printf("Terminal type \"%s\"\n", name);
338     if (tgetent(buffer, name) >= 0) {
339         NCURSES_CONST char *cap;
340         unsigned n;
341
342         if (b_opt) {
343             for (n = 0;; ++n) {
344                 cap = my_boolcodes[n];
345                 if (cap == 0)
346                     break;
347                 dumpit(cap);
348             }
349         }
350
351         if (n_opt) {
352             for (n = 0;; ++n) {
353                 cap = my_numcodes[n];
354                 if (cap == 0)
355                     break;
356                 dumpit(cap);
357             }
358         }
359
360         if (s_opt) {
361             for (n = 0;; ++n) {
362                 cap = my_strcodes[n];
363                 if (cap == 0)
364                     break;
365                 dumpit(cap);
366             }
367         }
368 #ifdef NCURSES_VERSION
369         if (x_opt && (my_blob == 0) && y_opt) {
370 #if NCURSES_XNAMES
371             TERMTYPE *term = (TERMTYPE *) cur_term;
372             if (term != 0
373                 && ((NUM_BOOLEANS(term) != BOOLCOUNT)
374                     || (NUM_NUMBERS(term) != NUMCOUNT)
375                     || (NUM_STRINGS(term) != STRCOUNT))) {
376                 for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
377                     dump_xname(ExtBoolname(term, (int) n, boolnames));
378                 }
379                 for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
380                     dump_xname(ExtNumname(term, (int) n, numnames));
381                 }
382                 for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
383                     dump_xname(ExtStrname(term, (int) n, strnames));
384                 }
385             }
386 #endif
387         }
388 #endif
389     }
390 }
391
392 typedef enum {
393     pDefault = 0
394     ,pComment
395     ,pDescription
396     ,pEscaped
397     ,pNewline
398     ,pName
399     ,pNumber
400     ,pString
401 } STATE;
402
403 static void
404 parse_description(const char *input_name)
405 {
406     static char empty[1];
407
408     FILE *fp;
409     struct stat sb;
410     size_t count_bools = 0;
411     size_t count_nums = 0;
412     size_t count_strs = 0;
413     size_t len;
414     size_t j, k;
415     STATE state;
416
417     if (stat(input_name, &sb) != 0
418         || (sb.st_mode & S_IFMT) != S_IFREG) {
419         failed("input is not a file");
420     }
421
422     if (sb.st_size == 0) {
423         failed("input is empty");
424     }
425
426     /*
427      * None of the arrays could be larger than the input-file, and since it
428      * is small, just allocate the maximum for simplicity.
429      */
430     if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0 ||
431         (my_boolcodes = typeCalloc(char *, sb.st_size)) == 0 ||
432           (my_numcodes = typeCalloc(char *, sb.st_size)) == 0 ||
433           (my_numvalues = typeCalloc(char *, sb.st_size)) == 0 ||
434           (my_strcodes = typeCalloc(char *, sb.st_size)) == 0 ||
435           (my_strvalues = typeCalloc(char *, sb.st_size)) == 0) {
436         failed("cannot allocate memory for input-file");
437     }
438
439     if ((fp = fopen(input_name, "r")) == 0)
440         failed("cannot open input-file");
441     len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
442     my_blob[sb.st_size] = '\0';
443     fclose(fp);
444
445     /*
446      * First, get rid of comments and escaped newlines, as well as repeated
447      * colons to construct a canonical entry.
448      *
449      * FIXME: actually this should make an additional pass just to strip
450      * comment-lines and escaped newlines.  But it is workable for infocmp
451      * output.
452      */
453     state = pNewline;
454     for (j = k = 0; j < len; ++j) {
455         int ch = my_blob[j];
456         if (ch == '\t') {
457             ch = ' ';
458         }
459         switch (state) {
460         case pNewline:
461             if (ch == ' ') {
462                 continue;
463             }
464             if (ch == '#') {
465                 state = pComment;
466                 continue;
467             }
468             state = pDefault;
469             /* FALLTHRU */
470         case pDefault:
471             switch (ch) {
472             case '|':
473                 state = pDescription;
474                 continue;
475             case '\\':
476                 state = pEscaped;
477                 continue;
478             case '\n':
479                 state = pNewline;
480                 continue;
481             case ' ':
482             case ':':
483                 break;
484             default:
485                 state = pName;
486                 break;
487             }
488             my_blob[k++] = (char) ch;
489             break;
490         case pComment:
491             if (ch == '\n')
492                 state = pNewline;
493             break;
494         case pDescription:
495             switch (ch) {
496             case ':':
497                 state = pDefault;
498                 break;
499             case '\n':
500                 state = pNewline;
501                 break;
502             }
503             break;
504         case pEscaped:
505             if (ch != '\n') {
506                 my_blob[k++] = (char) ch;
507                 state = pDefault;
508             } else {
509                 state = pNewline;
510             }
511             break;
512         case pName:
513             switch (ch) {
514             case '\n':
515                 state = pNewline;
516                 continue;
517             case ' ':
518             case ':':
519                 state = pDefault;
520                 break;
521             case '#':
522                 state = pNumber;
523                 break;
524             case '|':
525                 state = pDescription;
526                 continue;
527             }
528             my_blob[k++] = (char) ch;
529             break;
530         case pNumber:
531             switch (ch) {
532             case '\n':
533                 state = pNewline;
534                 continue;
535             case ':':
536                 state = pDefault;
537                 break;
538             case ' ':
539                 state = pDefault;
540                 continue;
541             }
542             my_blob[k++] = (char) ch;
543             break;
544         case pString:
545             switch (ch) {
546             case '\\':
547                 if (my_blob[j + 1] == '\0') {
548                     state = pDefault;
549                     continue;
550                 }
551                 break;
552             case '\n':
553                 state = pNewline;
554                 continue;
555             case ':':
556                 state = pDefault;
557                 break;
558             }
559             my_blob[k++] = (char) ch;
560             break;
561         default:
562             /* not used */
563             break;
564         }
565     }
566     my_blob[k] = '\0';
567
568     /*
569      * Then, parse what's left, making indexes of the names and values.
570      */
571     state = pDefault;
572     for (j = 0; my_blob[j] != '\0'; ++j) {
573         switch (state) {
574         case pDefault:
575             switch (my_blob[j]) {
576             case '\\':
577                 state = pEscaped;
578                 break;
579             case ':':
580                 my_blob[j] = '\0';
581                 if (my_blob[j + 1] != '\0' && my_blob[j + 1] != ':')
582                     state = pName;
583                 break;
584             case ' ':
585                 break;
586             default:
587                 break;
588             }
589         case pEscaped:
590             break;
591         case pName:
592             state = pDefault;
593             /*
594              * Commented-out capabilities might be accessible (they are in
595              * ncurses).
596              */
597             if (my_blob[j] == '.' && my_blob[j + 1] == '.') {
598                 j += 2;
599             }
600             if (my_blob[j + 1] != '\0') {
601                 switch (my_blob[j + 2]) {
602                 case '#':
603                     my_numvalues[count_nums] = &my_blob[j + 3];
604                     my_numcodes[count_nums++] = &my_blob[j];
605                     my_blob[j + 2] = '\0';
606                     state = pNumber;
607                     j += 2;
608                     break;
609                 case '=':
610                     my_strvalues[count_strs] = &my_blob[j + 3];
611                     my_strcodes[count_strs++] = &my_blob[j];
612                     my_blob[j + 2] = '\0';
613                     state = pString;
614                     j += 2;
615                     break;
616                 default:
617                     if (my_blob[j + 2] == '@') {
618                         /*
619                          * We cannot get the type for a cancelled item
620                          * directly, but can infer it assuming the input
621                          * came from infocmp, which puts the data in a
622                          * known order.
623                          */
624                         if (count_strs) {
625                             my_strvalues[count_strs] = empty;
626                             my_strcodes[count_strs++] = &my_blob[j];
627                         } else if (count_nums) {
628                             my_numvalues[count_nums] = empty;
629                             my_numcodes[count_nums++] = &my_blob[j];
630                         } else {
631                             my_boolcodes[count_bools++] = &my_blob[j];
632                         }
633                     } else {
634                         my_boolcodes[count_bools++] = &my_blob[j];
635                     }
636                     j++;
637                     break;
638                 }
639             }
640             break;
641         case pNumber:
642             if (!isdigit(UChar(my_blob[j]))) {
643                 --j;
644                 state = pDefault;
645             }
646             break;
647         case pString:
648             switch (my_blob[j]) {
649             case '\\':
650                 if (my_blob[j + 1] == '\0') {
651                     state = pDefault;
652                     continue;
653                 } else {
654                     ++j;
655                 }
656                 break;
657             case '\n':
658                 state = pNewline;
659                 continue;
660             case ':':
661                 --j;
662                 state = pDefault;
663                 break;
664             }
665             break;
666         case pNewline:
667         case pComment:
668         case pDescription:
669         default:
670             break;
671         }
672     }
673     my_boolcodes[count_bools] = 0;
674     my_numcodes[count_nums] = 0;
675     my_numvalues[count_nums] = 0;
676     my_strcodes[count_strs] = 0;
677     my_strvalues[count_strs] = 0;
678
679 #if 0
680     printf("bools:%d\n", (int) count_bools);
681     for (j = 0; my_boolcodes[j]; ++j)
682         printf("%5d:%s\n", (int) j, my_boolcodes[j]);
683
684     printf("numbers:%d\n", (int) count_nums);
685     for (j = 0; my_numcodes[j]; ++j)
686         printf("%5d:%s(%s)\n", (int) j, my_numcodes[j], my_numvalues[j]);
687
688     printf("strings:%d\n", (int) count_strs);
689     for (j = 0; my_strcodes[j]; ++j)
690         printf("%5d:%s(%s)\n", (int) j, my_strcodes[j], my_strvalues[j]);
691 #endif
692 }
693
694 #if USE_CODE_LISTS
695 static char **
696 copy_code_list(NCURSES_CONST char *const *list)
697 {
698     int pass;
699     size_t count;
700     size_t length = 1;
701     char **result = 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             char *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