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