ncurses 5.9 - patch 20150307
[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.46 2014/10/10 00:35:43 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     static char empty[1];
373
374     FILE *fp;
375     struct stat sb;
376     size_t count_bools = 0;
377     size_t count_nums = 0;
378     size_t count_strs = 0;
379     size_t len;
380     size_t j, k;
381     STATE state;
382
383     if (stat(input_name, &sb) != 0
384         || (sb.st_mode & S_IFMT) != S_IFREG) {
385         failed("input is not a file");
386     }
387
388     if (sb.st_size == 0) {
389         failed("input is empty");
390     }
391
392     /*
393      * None of the arrays could be larger than the input-file, and since it
394      * is small, just allocate the maximum for simplicity.
395      */
396     if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0 ||
397         (my_boolcodes = typeCalloc(char *, sb.st_size)) == 0 ||
398           (my_numcodes = typeCalloc(char *, sb.st_size)) == 0 ||
399           (my_numvalues = typeCalloc(char *, sb.st_size)) == 0 ||
400           (my_strcodes = typeCalloc(char *, sb.st_size)) == 0 ||
401           (my_strvalues = typeCalloc(char *, sb.st_size)) == 0) {
402         failed("cannot allocate memory for input-file");
403     }
404
405     if ((fp = fopen(input_name, "r")) == 0)
406         failed("cannot open input-file");
407     len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
408     my_blob[sb.st_size] = '\0';
409     fclose(fp);
410
411     /*
412      * First, get rid of comments and escaped newlines, as well as repeated
413      * colons to construct a canonical entry.
414      *
415      * FIXME: actually this should make an additional pass just to strip
416      * comment-lines and escaped newlines.  But it is workable for infocmp
417      * output.
418      */
419     state = pNewline;
420     for (j = k = 0; j < len; ++j) {
421         int ch = my_blob[j];
422         if (ch == '\t') {
423             ch = ' ';
424         }
425         switch (state) {
426         case pNewline:
427             if (ch == ' ') {
428                 continue;
429             }
430             if (ch == '#') {
431                 state = pComment;
432                 continue;
433             }
434             state = pDefault;
435             /* FALLTHRU */
436         case pDefault:
437             switch (ch) {
438             case '|':
439                 state = pDescription;
440                 continue;
441             case '\\':
442                 state = pEscaped;
443                 continue;
444             case '\n':
445                 state = pNewline;
446                 continue;
447             case ' ':
448             case ':':
449                 break;
450             default:
451                 state = pName;
452                 break;
453             }
454             my_blob[k++] = (char) ch;
455             break;
456         case pComment:
457             if (ch == '\n')
458                 state = pNewline;
459             break;
460         case pDescription:
461             switch (ch) {
462             case ':':
463                 state = pDefault;
464                 break;
465             case '\n':
466                 state = pNewline;
467                 break;
468             }
469             break;
470         case pEscaped:
471             if (ch != '\n') {
472                 my_blob[k++] = (char) ch;
473                 state = pDefault;
474             } else {
475                 state = pNewline;
476             }
477             break;
478         case pName:
479             switch (ch) {
480             case '\n':
481                 state = pNewline;
482                 continue;
483             case ' ':
484             case ':':
485                 state = pDefault;
486                 break;
487             case '#':
488                 state = pNumber;
489                 break;
490             case '|':
491                 state = pDescription;
492                 continue;
493             }
494             my_blob[k++] = (char) ch;
495             break;
496         case pNumber:
497             switch (ch) {
498             case '\n':
499                 state = pNewline;
500                 continue;
501             case ':':
502                 state = pDefault;
503                 break;
504             case ' ':
505                 state = pDefault;
506                 continue;
507             }
508             my_blob[k++] = (char) ch;
509             break;
510         case pString:
511             switch (ch) {
512             case '\\':
513                 if (my_blob[j + 1] == '\0') {
514                     state = pDefault;
515                     continue;
516                 }
517                 break;
518             case '\n':
519                 state = pNewline;
520                 continue;
521             case ':':
522                 state = pDefault;
523                 break;
524             }
525             my_blob[k++] = (char) ch;
526             break;
527         default:
528             /* not used */
529             break;
530         }
531     }
532     my_blob[k] = '\0';
533
534     /*
535      * Then, parse what's left, making indexes of the names and values.
536      */
537     state = pDefault;
538     for (j = 0; my_blob[j] != '\0'; ++j) {
539         switch (state) {
540         case pDefault:
541             switch (my_blob[j]) {
542             case '\\':
543                 state = pEscaped;
544                 break;
545             case ':':
546                 my_blob[j] = '\0';
547                 if (my_blob[j + 1] != '\0' && my_blob[j + 1] != ':')
548                     state = pName;
549                 break;
550             case ' ':
551                 break;
552             default:
553                 break;
554             }
555         case pEscaped:
556             break;
557         case pName:
558             state = pDefault;
559             /*
560              * Commented-out capabilities might be accessible (they are in
561              * ncurses).
562              */
563             if (my_blob[j] == '.' && my_blob[j + 1] == '.') {
564                 j += 2;
565             }
566             if (my_blob[j + 1] != '\0') {
567                 switch (my_blob[j + 2]) {
568                 case '#':
569                     my_numvalues[count_nums] = &my_blob[j + 3];
570                     my_numcodes[count_nums++] = &my_blob[j];
571                     my_blob[j + 2] = '\0';
572                     state = pNumber;
573                     j += 2;
574                     break;
575                 case '=':
576                     my_strvalues[count_strs] = &my_blob[j + 3];
577                     my_strcodes[count_strs++] = &my_blob[j];
578                     my_blob[j + 2] = '\0';
579                     state = pString;
580                     j += 2;
581                     break;
582                 default:
583                     if (my_blob[j + 2] == '@') {
584                         /*
585                          * We cannot get the type for a cancelled item
586                          * directly, but can infer it assuming the input
587                          * came from infocmp, which puts the data in a
588                          * known order.
589                          */
590                         if (count_strs) {
591                             my_strvalues[count_strs] = empty;
592                             my_strcodes[count_strs++] = &my_blob[j];
593                         } else if (count_nums) {
594                             my_numvalues[count_nums] = empty;
595                             my_numcodes[count_nums++] = &my_blob[j];
596                         } else {
597                             my_boolcodes[count_bools++] = &my_blob[j];
598                         }
599                     } else {
600                         my_boolcodes[count_bools++] = &my_blob[j];
601                     }
602                     j++;
603                     break;
604                 }
605             }
606             break;
607         case pNumber:
608             if (!isdigit(UChar(my_blob[j]))) {
609                 --j;
610                 state = pDefault;
611             }
612             break;
613         case pString:
614             switch (my_blob[j]) {
615             case '\\':
616                 if (my_blob[j + 1] == '\0') {
617                     state = pDefault;
618                     continue;
619                 } else {
620                     ++j;
621                 }
622                 break;
623             case '\n':
624                 state = pNewline;
625                 continue;
626             case ':':
627                 --j;
628                 state = pDefault;
629                 break;
630             }
631             break;
632         case pNewline:
633         case pComment:
634         case pDescription:
635         default:
636             break;
637         }
638     }
639     my_boolcodes[count_bools] = 0;
640     my_numcodes[count_nums] = 0;
641     my_numvalues[count_nums] = 0;
642     my_strcodes[count_strs] = 0;
643     my_strvalues[count_strs] = 0;
644
645 #if 0
646     printf("bools:%d\n", (int) count_bools);
647     for (j = 0; my_boolcodes[j]; ++j)
648         printf("%5d:%s\n", (int) j, my_boolcodes[j]);
649
650     printf("numbers:%d\n", (int) count_nums);
651     for (j = 0; my_numcodes[j]; ++j)
652         printf("%5d:%s(%s)\n", (int) j, my_numcodes[j], my_numvalues[j]);
653
654     printf("strings:%d\n", (int) count_strs);
655     for (j = 0; my_strcodes[j]; ++j)
656         printf("%5d:%s(%s)\n", (int) j, my_strcodes[j], my_strvalues[j]);
657 #endif
658 }
659
660 #if USE_CODE_LISTS
661 static char **
662 copy_code_list(NCURSES_CONST char *const *list)
663 {
664     int pass;
665     size_t count;
666     size_t length = 1;
667     char **result = 0;
668     char *blob = 0;
669     char *unused = 0;
670
671     for (pass = 0; pass < 2; ++pass) {
672         for (count = 0; list[count] != 0; ++count) {
673             size_t chunk = strlen(list[count]) + 1;
674             if (pass == 0) {
675                 length += chunk;
676             } else {
677                 result[count] = unused;
678                 strcpy(unused, list[count]);
679                 unused += chunk;
680             }
681         }
682         if (pass == 0) {
683             blob = malloc(length);
684             result = typeCalloc(char *, count + 1);
685             unused = blob;
686             if (blob == 0 || result == 0)
687                 failed("copy_code_list failed");
688         }
689     }
690
691     return result;
692 }
693 #endif
694
695 static void
696 usage(void)
697 {
698     static const char *msg[] =
699     {
700         "Usage: demo_termcap [options] [terminal]",
701         "",
702         "If no options are given, print all (boolean, numeric, string)",
703         "capabilities for the given terminal, using short names.",
704         "",
705         "Options:",
706         " -a       try all names, print capabilities found",
707         " -b       print boolean-capabilities",
708         " -d LIST  colon-separated list of databases to use",
709         " -e NAME  environment variable to set with -d option",
710         " -i NAME  terminal description to use as names for \"-a\" option, etc.",
711         " -n       print numeric-capabilities",
712         " -q       quiet (prints only counts)",
713         " -r COUNT repeat for given count",
714         " -s       print string-capabilities",
715 #ifdef NCURSES_VERSION
716         " -x       print extended capabilities",
717 #endif
718     };
719     unsigned n;
720     for (n = 0; n < SIZEOF(msg); ++n) {
721         fprintf(stderr, "%s\n", msg[n]);
722     }
723     ExitProgram(EXIT_FAILURE);
724 }
725
726 int
727 main(int argc, char *argv[])
728 {
729     int n;
730     char *name;
731     bool a_opt = FALSE;
732     char *input_name = 0;
733
734     int repeat;
735     int r_opt = 1;
736
737     while ((n = getopt(argc, argv, "abd:e:i:nqr:sxy")) != -1) {
738         switch (n) {
739         case 'a':
740             a_opt = TRUE;
741             break;
742         case 'b':
743             b_opt = TRUE;
744             break;
745         case 'd':
746             d_opt = optarg;
747             break;
748         case 'e':
749             e_opt = optarg;
750             break;
751         case 'i':
752             input_name = optarg;
753             break;
754         case 'n':
755             n_opt = TRUE;
756             break;
757         case 'q':
758             q_opt = TRUE;
759             break;
760         case 'r':
761             if ((r_opt = atoi(optarg)) <= 0)
762                 usage();
763             break;
764         case 's':
765             s_opt = TRUE;
766             break;
767 #if NCURSES_XNAMES
768         case 'x':
769             x_opt = TRUE;
770             break;
771         case 'y':
772             y_opt = TRUE;
773             x_opt = TRUE;
774             break;
775 #endif
776         default:
777             usage();
778             break;
779         }
780     }
781
782 #if HAVE_USE_EXTENDED_NAMES
783     use_extended_names(x_opt);
784 #endif
785
786     if (!(b_opt || n_opt || s_opt)) {
787         b_opt = TRUE;
788         n_opt = TRUE;
789         s_opt = TRUE;
790     }
791
792     make_dblist();
793
794     if (a_opt) {
795         for (repeat = 0; repeat < r_opt; ++repeat) {
796             if (optind < argc) {
797                 for (n = optind; n < argc; ++n) {
798                     brute_force(argv[n]);
799                 }
800             } else if ((name = getenv("TERM")) != 0) {
801                 brute_force(name);
802             } else {
803                 static char dumb[] = "dumb";
804                 brute_force(dumb);
805             }
806         }
807     } else {
808         if (input_name != 0) {
809             parse_description(input_name);
810         }
811 #if USE_CODE_LISTS
812         else {
813             my_boolcodes = copy_code_list(boolcodes);
814             my_numcodes = copy_code_list(numcodes);
815             my_strcodes = copy_code_list(strcodes);
816         }
817 #else
818         else {
819             failed("no capability-lists available (use -i option)");
820         }
821 #endif /* USE_CODE_LISTS */
822         for (repeat = 0; repeat < r_opt; ++repeat) {
823             if (optind < argc) {
824                 for (n = optind; n < argc; ++n) {
825                     demo_termcap(argv[n]);
826                 }
827             } else if ((name = getenv("TERM")) != 0) {
828                 demo_termcap(name);
829             } else {
830                 static char dumb[] = "dumb";
831                 demo_termcap(dumb);
832             }
833         }
834     }
835
836     printf("%ld values (%ld booleans, %ld numbers, %ld strings)\n",
837            total_values, total_b_values, total_n_values, total_s_values);
838
839     free_dblist();
840
841     ExitProgram(EXIT_SUCCESS);
842 }
843
844 #else
845 int
846 main(int argc GCC_UNUSED,
847      char *argv[]GCC_UNUSED)
848 {
849     failed("This program requires termcap");
850 }
851 #endif