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