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