d59a925d55110a41c4df66c8fe5de65ba8e3b369
[ncurses.git] / progs / dump_entry.c
1 /****************************************************************************
2  * Copyright (c) 1998-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: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey                        1996 on                 *
33  ****************************************************************************/
34
35 #define __INTERNAL_CAPS_VISIBLE
36 #include <progs.priv.h>
37
38 #include "dump_entry.h"
39 #include "termsort.c"           /* this C file is generated */
40 #include <parametrized.h>       /* so is this */
41
42 MODULE_ID("$Id: dump_entry.c,v 1.118 2015/07/07 08:06:39 Werner.Fink Exp $")
43
44 #define INDENT                  8
45 #define DISCARD(string) string = ABSENT_STRING
46 #define PRINTF (void) printf
47
48 #define OkIndex(index,array) ((int)(index) >= 0 && (int)(index) < (int) SIZEOF(array))
49
50 typedef struct {
51     char *text;
52     size_t used;
53     size_t size;
54 } DYNBUF;
55
56 static int tversion;            /* terminfo version */
57 static int outform;             /* output format to use */
58 static int sortmode;            /* sort mode to use */
59 static int width = 60;          /* max line width for listings */
60 static int height = 65535;      /* max number of lines for listings */
61 static int column;              /* current column, limited by 'width' */
62 static int oldcol;              /* last value of column before wrap */
63 static bool pretty;             /* true if we format if-then-else strings */
64 static bool checking;           /* true if we are checking for tic */
65
66 static char *save_sgr;
67
68 static DYNBUF outbuf;
69 static DYNBUF tmpbuf;
70
71 /* indirection pointers for implementing sort and display modes */
72 static const PredIdx *bool_indirect, *num_indirect, *str_indirect;
73 static NCURSES_CONST char *const *bool_names;
74 static NCURSES_CONST char *const *num_names;
75 static NCURSES_CONST char *const *str_names;
76
77 static const char *separator = "", *trailer = "";
78
79 /* cover various ports and variants of terminfo */
80 #define V_ALLCAPS       0       /* all capabilities (SVr4, XSI, ncurses) */
81 #define V_SVR1          1       /* SVR1, Ultrix */
82 #define V_HPUX          2       /* HP/UX */
83 #define V_AIX           3       /* AIX */
84 #define V_BSD           4       /* BSD */
85
86 #if NCURSES_XNAMES
87 #define OBSOLETE(n) (!_nc_user_definable && (n[0] == 'O' && n[1] == 'T'))
88 #else
89 #define OBSOLETE(n) (n[0] == 'O' && n[1] == 'T')
90 #endif
91
92 #define isObsolete(f,n) ((f == F_TERMINFO || f == F_VARIABLE) && OBSOLETE(n))
93
94 #if NCURSES_XNAMES
95 #define BoolIndirect(j) ((j >= BOOLCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : bool_indirect[j]))
96 #define NumIndirect(j)  ((j >= NUMCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : num_indirect[j]))
97 #define StrIndirect(j)  ((j >= STRCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : str_indirect[j]))
98 #else
99 #define BoolIndirect(j) ((sortmode == S_NOSORT) ? (j) : bool_indirect[j])
100 #define NumIndirect(j)  ((sortmode == S_NOSORT) ? (j) : num_indirect[j])
101 #define StrIndirect(j)  ((sortmode == S_NOSORT) ? (j) : str_indirect[j])
102 #endif
103
104 static void failed(const char *) GCC_NORETURN;
105
106 static void
107 failed(const char *s)
108 {
109     perror(s);
110     ExitProgram(EXIT_FAILURE);
111 }
112
113 static void
114 strncpy_DYN(DYNBUF * dst, const char *src, size_t need)
115 {
116     size_t want = need + dst->used + 1;
117     if (want > dst->size) {
118         dst->size += (want + 1024);     /* be generous */
119         dst->text = typeRealloc(char, dst->size, dst->text);
120         if (dst->text == 0)
121             failed("strncpy_DYN");
122     }
123     (void) strncpy(dst->text + dst->used, src, need);
124     dst->used += need;
125     dst->text[dst->used] = 0;
126 }
127
128 static void
129 strcpy_DYN(DYNBUF * dst, const char *src)
130 {
131     if (src == 0) {
132         dst->used = 0;
133         strcpy_DYN(dst, "");
134     } else {
135         strncpy_DYN(dst, src, strlen(src));
136     }
137 }
138
139 #if NO_LEAKS
140 static void
141 free_DYN(DYNBUF * p)
142 {
143     if (p->text != 0)
144         free(p->text);
145     p->text = 0;
146     p->size = 0;
147     p->used = 0;
148 }
149
150 void
151 _nc_leaks_dump_entry(void)
152 {
153     free_DYN(&outbuf);
154     free_DYN(&tmpbuf);
155 }
156 #endif
157
158 #define NameTrans(check,result) \
159             if ((np->nte_index <= OK_ ## check) \
160                 && check[np->nte_index]) \
161                 return (result[np->nte_index])
162
163 NCURSES_CONST char *
164 nametrans(const char *name)
165 /* translate a capability name to termcap from terminfo */
166 {
167     const struct name_table_entry *np;
168
169     if ((np = _nc_find_entry(name, _nc_get_hash_table(0))) != 0) {
170         switch (np->nte_type) {
171         case BOOLEAN:
172             NameTrans(bool_from_termcap, boolcodes);
173             break;
174
175         case NUMBER:
176             NameTrans(num_from_termcap, numcodes);
177             break;
178
179         case STRING:
180             NameTrans(str_from_termcap, strcodes);
181             break;
182         }
183     }
184
185     return (0);
186 }
187
188 void
189 dump_init(const char *version,
190           int mode,
191           int sort,
192           int twidth,
193           int theight,
194           unsigned traceval,
195           bool formatted,
196           bool check)
197 /* set up for entry display */
198 {
199     width = twidth;
200     height = theight;
201     pretty = formatted;
202     checking = check;
203
204     /* versions */
205     if (version == 0)
206         tversion = V_ALLCAPS;
207     else if (!strcmp(version, "SVr1") || !strcmp(version, "SVR1")
208              || !strcmp(version, "Ultrix"))
209         tversion = V_SVR1;
210     else if (!strcmp(version, "HP"))
211         tversion = V_HPUX;
212     else if (!strcmp(version, "AIX"))
213         tversion = V_AIX;
214     else if (!strcmp(version, "BSD"))
215         tversion = V_BSD;
216     else
217         tversion = V_ALLCAPS;
218
219     /* implement display modes */
220     switch (outform = mode) {
221     case F_LITERAL:
222     case F_TERMINFO:
223         bool_names = boolnames;
224         num_names = numnames;
225         str_names = strnames;
226         separator = (twidth > 0 && theight > 1) ? ", " : ",";
227         trailer = "\n\t";
228         break;
229
230     case F_VARIABLE:
231         bool_names = boolfnames;
232         num_names = numfnames;
233         str_names = strfnames;
234         separator = (twidth > 0 && theight > 1) ? ", " : ",";
235         trailer = "\n\t";
236         break;
237
238     case F_TERMCAP:
239     case F_TCONVERR:
240         bool_names = boolcodes;
241         num_names = numcodes;
242         str_names = strcodes;
243         separator = ":";
244         trailer = "\\\n\t:";
245         break;
246     }
247
248     /* implement sort modes */
249     switch (sortmode = sort) {
250     case S_NOSORT:
251         if (traceval)
252             (void) fprintf(stderr,
253                            "%s: sorting by term structure order\n", _nc_progname);
254         break;
255
256     case S_TERMINFO:
257         if (traceval)
258             (void) fprintf(stderr,
259                            "%s: sorting by terminfo name order\n", _nc_progname);
260         bool_indirect = bool_terminfo_sort;
261         num_indirect = num_terminfo_sort;
262         str_indirect = str_terminfo_sort;
263         break;
264
265     case S_VARIABLE:
266         if (traceval)
267             (void) fprintf(stderr,
268                            "%s: sorting by C variable order\n", _nc_progname);
269         bool_indirect = bool_variable_sort;
270         num_indirect = num_variable_sort;
271         str_indirect = str_variable_sort;
272         break;
273
274     case S_TERMCAP:
275         if (traceval)
276             (void) fprintf(stderr,
277                            "%s: sorting by termcap name order\n", _nc_progname);
278         bool_indirect = bool_termcap_sort;
279         num_indirect = num_termcap_sort;
280         str_indirect = str_termcap_sort;
281         break;
282     }
283
284     if (traceval)
285         (void) fprintf(stderr,
286                        "%s: width = %d, tversion = %d, outform = %d\n",
287                        _nc_progname, width, tversion, outform);
288 }
289
290 static TERMTYPE *cur_type;
291
292 static int
293 dump_predicate(PredType type, PredIdx idx)
294 /* predicate function to use for ordinary decompilation */
295 {
296     switch (type) {
297     case BOOLEAN:
298         return (cur_type->Booleans[idx] == FALSE)
299             ? FAIL : cur_type->Booleans[idx];
300
301     case NUMBER:
302         return (cur_type->Numbers[idx] == ABSENT_NUMERIC)
303             ? FAIL : cur_type->Numbers[idx];
304
305     case STRING:
306         return (cur_type->Strings[idx] != ABSENT_STRING)
307             ? (int) TRUE : FAIL;
308     }
309
310     return (FALSE);             /* pacify compiler */
311 }
312
313 static void set_obsolete_termcaps(TERMTYPE *tp);
314
315 /* is this the index of a function key string? */
316 #define FNKEY(i) \
317     (((i) >= STR_IDX(key_f0) && \
318       (i) <= STR_IDX(key_f9)) || \
319      ((i) >= STR_IDX(key_f11) && \
320       (i) <= STR_IDX(key_f63)))
321
322 /*
323  * If we configure with a different Caps file, the offsets into the arrays
324  * will change.  So we use an address expression.
325  */
326 #define BOOL_IDX(name) (PredType) (&(name) - &(CUR Booleans[0]))
327 #define NUM_IDX(name)  (PredType) (&(name) - &(CUR Numbers[0]))
328 #define STR_IDX(name)  (PredType) (&(name) - &(CUR Strings[0]))
329
330 static bool
331 version_filter(PredType type, PredIdx idx)
332 /* filter out capabilities we may want to suppress */
333 {
334     switch (tversion) {
335     case V_ALLCAPS:             /* SVr4, XSI Curses */
336         return (TRUE);
337
338     case V_SVR1:                /* System V Release 1, Ultrix */
339         switch (type) {
340         case BOOLEAN:
341             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
342         case NUMBER:
343             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
344         case STRING:
345             return ((idx <= STR_IDX(prtr_non)) ? TRUE : FALSE);
346         }
347         break;
348
349     case V_HPUX:                /* Hewlett-Packard */
350         switch (type) {
351         case BOOLEAN:
352             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
353         case NUMBER:
354             return ((idx <= NUM_IDX(label_width)) ? TRUE : FALSE);
355         case STRING:
356             if (idx <= STR_IDX(prtr_non))
357                 return (TRUE);
358             else if (FNKEY(idx))        /* function keys */
359                 return (TRUE);
360             else if (idx == STR_IDX(plab_norm)
361                      || idx == STR_IDX(label_on)
362                      || idx == STR_IDX(label_off))
363                 return (TRUE);
364             else
365                 return (FALSE);
366         }
367         break;
368
369     case V_AIX:         /* AIX */
370         switch (type) {
371         case BOOLEAN:
372             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
373         case NUMBER:
374             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
375         case STRING:
376             if (idx <= STR_IDX(prtr_non))
377                 return (TRUE);
378             else if (FNKEY(idx))        /* function keys */
379                 return (TRUE);
380             else
381                 return (FALSE);
382         }
383         break;
384
385 #define is_termcap(type) (OkIndex(idx, type##_from_termcap) && \
386                           type##_from_termcap[idx])
387
388     case V_BSD:         /* BSD */
389         switch (type) {
390         case BOOLEAN:
391             return is_termcap(bool);
392         case NUMBER:
393             return is_termcap(num);
394         case STRING:
395             return is_termcap(str);
396         }
397         break;
398     }
399
400     return (FALSE);             /* pacify the compiler */
401 }
402
403 static void
404 trim_trailing(void)
405 {
406     while (outbuf.used > 0 && outbuf.text[outbuf.used - 1] == ' ')
407         outbuf.text[--outbuf.used] = '\0';
408 }
409
410 static void
411 force_wrap(void)
412 {
413     oldcol = column;
414     trim_trailing();
415     strcpy_DYN(&outbuf, trailer);
416     column = INDENT;
417 }
418
419 static void
420 wrap_concat(const char *src)
421 {
422     size_t need = strlen(src);
423     size_t want = strlen(separator) + need;
424
425     if (column > INDENT
426         && column + (int) want > width) {
427         force_wrap();
428     }
429     strcpy_DYN(&outbuf, src);
430     strcpy_DYN(&outbuf, separator);
431     column += (int) need;
432 }
433
434 #define IGNORE_SEP_TRAIL(first,last,sep_trail) \
435         if ((size_t)(last - first) > sizeof(sep_trail)-1 \
436          && !strncmp(first, sep_trail, sizeof(sep_trail)-1)) \
437                 first += sizeof(sep_trail)-2
438
439 /* Returns the nominal length of the buffer assuming it is termcap format,
440  * i.e., the continuation sequence is treated as a single character ":".
441  *
442  * There are several implementations of termcap which read the text into a
443  * fixed-size buffer.  Generally they strip the newlines from the text, but may
444  * not do it until after the buffer is read.  Also, "tc=" resolution may be
445  * expanded in the same buffer.  This function is useful for measuring the size
446  * of the best fixed-buffer implementation; the worst case may be much worse.
447  */
448 #ifdef TEST_TERMCAP_LENGTH
449 static int
450 termcap_length(const char *src)
451 {
452     static const char pattern[] = ":\\\n\t:";
453
454     int len = 0;
455     const char *const t = src + strlen(src);
456
457     while (*src != '\0') {
458         IGNORE_SEP_TRAIL(src, t, pattern);
459         src++;
460         len++;
461     }
462     return len;
463 }
464 #else
465 #define termcap_length(src) strlen(src)
466 #endif
467
468 static void
469 indent_DYN(DYNBUF * buffer, int level)
470 {
471     int n;
472
473     for (n = 0; n < level; n++)
474         strncpy_DYN(buffer, "\t", (size_t) 1);
475 }
476
477 bool
478 has_params(const char *src)
479 {
480     bool result = FALSE;
481     int len = (int) strlen(src);
482     int n;
483     bool ifthen = FALSE;
484     bool params = FALSE;
485
486     for (n = 0; n < len - 1; ++n) {
487         if (!strncmp(src + n, "%p", (size_t) 2)) {
488             params = TRUE;
489         } else if (!strncmp(src + n, "%;", (size_t) 2)) {
490             ifthen = TRUE;
491             result = params;
492             break;
493         }
494     }
495     if (!ifthen) {
496         result = ((len > 50) && params);
497     }
498     return result;
499 }
500
501 static char *
502 fmt_complex(TERMTYPE *tterm, const char *capability, char *src, int level)
503 {
504     bool percent = FALSE;
505     bool params = has_params(src);
506
507     while (*src != '\0') {
508         switch (*src) {
509         case '\\':
510             percent = FALSE;
511             strncpy_DYN(&tmpbuf, src++, (size_t) 1);
512             break;
513         case '%':
514             percent = TRUE;
515             break;
516         case '?':               /* "if" */
517         case 't':               /* "then" */
518         case 'e':               /* "else" */
519             if (percent) {
520                 percent = FALSE;
521                 tmpbuf.text[tmpbuf.used - 1] = '\n';
522                 /* treat a "%e" as else-if, on the same level */
523                 if (*src == 'e') {
524                     indent_DYN(&tmpbuf, level);
525                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
526                     strncpy_DYN(&tmpbuf, src, (size_t) 1);
527                     src++;
528                     params = has_params(src);
529                     if (!params && *src != '\0' && *src != '%') {
530                         strncpy_DYN(&tmpbuf, "\n", (size_t) 1);
531                         indent_DYN(&tmpbuf, level + 1);
532                     }
533                 } else {
534                     indent_DYN(&tmpbuf, level + 1);
535                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
536                     strncpy_DYN(&tmpbuf, src, (size_t) 1);
537                     if (*src++ == '?') {
538                         src = fmt_complex(tterm, capability, src, level + 1);
539                         if (*src != '\0' && *src != '%') {
540                             strncpy_DYN(&tmpbuf, "\n", (size_t) 1);
541                             indent_DYN(&tmpbuf, level + 1);
542                         }
543                     } else if (level == 1) {
544                         if (checking)
545                             _nc_warning("%s: %%%c without %%? in %s",
546                                         _nc_first_name(tterm->term_names),
547                                         *src, capability);
548                     }
549                 }
550                 continue;
551             }
552             break;
553         case ';':               /* "endif" */
554             if (percent) {
555                 percent = FALSE;
556                 if (level > 1) {
557                     tmpbuf.text[tmpbuf.used - 1] = '\n';
558                     indent_DYN(&tmpbuf, level);
559                     strncpy_DYN(&tmpbuf, "%", (size_t) 1);
560                     strncpy_DYN(&tmpbuf, src++, (size_t) 1);
561                     if (src[0] == '%'
562                         && src[1] != '\0'
563                         && (strchr("?e;", src[1])) == 0) {
564                         tmpbuf.text[tmpbuf.used++] = '\n';
565                         indent_DYN(&tmpbuf, level);
566                     }
567                     return src;
568                 }
569                 if (checking)
570                     _nc_warning("%s: %%; without %%? in %s",
571                                 _nc_first_name(tterm->term_names),
572                                 capability);
573             }
574             break;
575         case 'p':
576             if (percent && params) {
577                 tmpbuf.text[tmpbuf.used - 1] = '\n';
578                 indent_DYN(&tmpbuf, level + 1);
579                 strncpy_DYN(&tmpbuf, "%", (size_t) 1);
580             }
581             params = FALSE;
582             percent = FALSE;
583             break;
584         case ' ':
585             strncpy_DYN(&tmpbuf, "\\s", (size_t) 2);
586             ++src;
587             continue;
588         default:
589             percent = FALSE;
590             break;
591         }
592         strncpy_DYN(&tmpbuf, src++, (size_t) 1);
593     }
594     return src;
595 }
596
597 #define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap)
598 #define EXTRA_CAP 20
599
600 int
601 fmt_entry(TERMTYPE *tterm,
602           PredFunc pred,
603           int content_only,
604           int suppress_untranslatable,
605           int infodump,
606           int numbers)
607 {
608     PredIdx i, j;
609     char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP];
610     char *capability;
611     NCURSES_CONST char *name;
612     int predval, len;
613     PredIdx num_bools = 0;
614     PredIdx num_values = 0;
615     PredIdx num_strings = 0;
616     bool outcount = 0;
617
618 #define WRAP_CONCAT     \
619         wrap_concat(buffer); \
620         outcount = TRUE
621
622     len = 12;                   /* terminfo file-header */
623
624     if (pred == 0) {
625         cur_type = tterm;
626         pred = dump_predicate;
627     }
628
629     strcpy_DYN(&outbuf, 0);
630     if (content_only) {
631         column = INDENT;        /* FIXME: workaround to prevent empty lines */
632     } else {
633         strcpy_DYN(&outbuf, tterm->term_names);
634
635         /*
636          * Colon is legal in terminfo descriptions, but not in termcap.
637          */
638         if (!infodump) {
639             char *p = outbuf.text;
640             while (*p) {
641                 if (*p == ':') {
642                     *p = '=';
643                 }
644                 ++p;
645             }
646         }
647         strcpy_DYN(&outbuf, separator);
648         column = (int) outbuf.used;
649         if (height > 1)
650             force_wrap();
651     }
652
653     for_each_boolean(j, tterm) {
654         i = BoolIndirect(j);
655         name = ExtBoolname(tterm, (int) i, bool_names);
656         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
657
658         if (!version_filter(BOOLEAN, i))
659             continue;
660         else if (isObsolete(outform, name))
661             continue;
662
663         predval = pred(BOOLEAN, i);
664         if (predval != FAIL) {
665             _nc_STRCPY(buffer, name, sizeof(buffer));
666             if (predval <= 0)
667                 _nc_STRCAT(buffer, "@", sizeof(buffer));
668             else if (i + 1 > num_bools)
669                 num_bools = i + 1;
670             WRAP_CONCAT;
671         }
672     }
673
674     if (column != INDENT && height > 1)
675         force_wrap();
676
677     for_each_number(j, tterm) {
678         i = NumIndirect(j);
679         name = ExtNumname(tterm, (int) i, num_names);
680         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
681
682         if (!version_filter(NUMBER, i))
683             continue;
684         else if (isObsolete(outform, name))
685             continue;
686
687         predval = pred(NUMBER, i);
688         if (predval != FAIL) {
689             if (tterm->Numbers[i] < 0) {
690                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
691                             "%s@", name);
692             } else {
693                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
694                             "%s#%d", name, tterm->Numbers[i]);
695                 if (i + 1 > num_values)
696                     num_values = i + 1;
697             }
698             WRAP_CONCAT;
699         }
700     }
701
702     if (column != INDENT && height > 1)
703         force_wrap();
704
705     len += (int) (num_bools
706                   + num_values * 2
707                   + strlen(tterm->term_names) + 1);
708     if (len & 1)
709         len++;
710
711 #undef CUR
712 #define CUR tterm->
713     if (outform == F_TERMCAP) {
714         if (termcap_reset != ABSENT_STRING) {
715             if (init_3string != ABSENT_STRING
716                 && !strcmp(init_3string, termcap_reset))
717                 DISCARD(init_3string);
718
719             if (reset_2string != ABSENT_STRING
720                 && !strcmp(reset_2string, termcap_reset))
721                 DISCARD(reset_2string);
722         }
723     }
724
725     for_each_string(j, tterm) {
726         i = StrIndirect(j);
727         name = ExtStrname(tterm, (int) i, str_names);
728         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
729
730         capability = tterm->Strings[i];
731
732         if (!version_filter(STRING, i))
733             continue;
734         else if (isObsolete(outform, name))
735             continue;
736
737 #if NCURSES_XNAMES
738         /*
739          * Extended names can be longer than 2 characters, but termcap programs
740          * cannot read those (filter them out).
741          */
742         if (outform == F_TERMCAP && (strlen(name) > 2))
743             continue;
744 #endif
745
746         if (outform == F_TERMCAP) {
747             /*
748              * Some older versions of vi want rmir/smir to be defined
749              * for ich/ich1 to work.  If they're not defined, force
750              * them to be output as defined and empty.
751              */
752             if (PRESENT(insert_character) || PRESENT(parm_ich)) {
753                 if (SAME_CAP(i, enter_insert_mode)
754                     && enter_insert_mode == ABSENT_STRING) {
755                     _nc_STRCPY(buffer, "im=", sizeof(buffer));
756                     WRAP_CONCAT;
757                     continue;
758                 }
759
760                 if (SAME_CAP(i, exit_insert_mode)
761                     && exit_insert_mode == ABSENT_STRING) {
762                     _nc_STRCPY(buffer, "ei=", sizeof(buffer));
763                     WRAP_CONCAT;
764                     continue;
765                 }
766             }
767             /*
768              * termcap applications such as screen will be confused if sgr0
769              * is translated to a string containing rmacs.  Filter that out.
770              */
771             if (PRESENT(exit_attribute_mode)) {
772                 if (SAME_CAP(i, exit_attribute_mode)) {
773                     char *trimmed_sgr0;
774                     char *my_sgr = set_attributes;
775
776                     set_attributes = save_sgr;
777
778                     trimmed_sgr0 = _nc_trim_sgr0(tterm);
779                     if (strcmp(capability, trimmed_sgr0))
780                         capability = trimmed_sgr0;
781                     else {
782                         if (trimmed_sgr0 != exit_attribute_mode)
783                             free(trimmed_sgr0);
784                     }
785
786                     set_attributes = my_sgr;
787                 }
788             }
789         }
790
791         predval = pred(STRING, i);
792         buffer[0] = '\0';
793
794         if (predval != FAIL) {
795             if (capability != ABSENT_STRING
796                 && i + 1 > num_strings)
797                 num_strings = i + 1;
798
799             if (!VALID_STRING(capability)) {
800                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
801                             "%s@", name);
802                 WRAP_CONCAT;
803             } else if (outform == F_TERMCAP || outform == F_TCONVERR) {
804                 char *srccap = _nc_tic_expand(capability, TRUE, numbers);
805                 int params = (((i < (int) SIZEOF(parametrized)) &&
806                                (i < STRCOUNT))
807                               ? parametrized[i]
808                               : ((*srccap == 'k')
809                                  ? 0
810                                  : has_params(srccap)));
811                 char *cv = _nc_infotocap(name, srccap, params);
812
813                 if (cv == 0) {
814                     if (outform == F_TCONVERR) {
815                         _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
816                                     "%s=!!! %s WILL NOT CONVERT !!!",
817                                     name, srccap);
818                     } else if (suppress_untranslatable) {
819                         continue;
820                     } else {
821                         char *s = srccap, *d = buffer;
822                         _nc_SPRINTF(d, _nc_SLIMIT(sizeof(buffer)) "..%s=", name);
823                         d += strlen(d);
824                         while ((*d = *s++) != 0) {
825                             if (*d == ':') {
826                                 *d++ = '\\';
827                                 *d = ':';
828                             } else if (*d == '\\') {
829                                 *++d = *s++;
830                             }
831                             d++;
832                         }
833                     }
834                 } else {
835                     _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
836                                 "%s=%s", name, cv);
837                 }
838                 len += (int) strlen(capability) + 1;
839                 WRAP_CONCAT;
840             } else {
841                 char *src = _nc_tic_expand(capability,
842                                            outform == F_TERMINFO, numbers);
843
844                 strcpy_DYN(&tmpbuf, 0);
845                 strcpy_DYN(&tmpbuf, name);
846                 strcpy_DYN(&tmpbuf, "=");
847                 if (pretty
848                     && (outform == F_TERMINFO
849                         || outform == F_VARIABLE)) {
850                     fmt_complex(tterm, name, src, 1);
851                 } else {
852                     strcpy_DYN(&tmpbuf, src);
853                 }
854                 len += (int) strlen(capability) + 1;
855                 wrap_concat(tmpbuf.text);
856                 outcount = TRUE;
857             }
858         }
859         /* e.g., trimmed_sgr0 */
860         if (capability != ABSENT_STRING &&
861             capability != CANCELLED_STRING &&
862             capability != tterm->Strings[i])
863             free(capability);
864     }
865     len += (int) (num_strings * 2);
866
867     /*
868      * This piece of code should be an effective inverse of the functions
869      * postprocess_terminfo() and postprocess_terminfo() in parse_entry.c.
870      * Much more work should be done on this to support dumping termcaps.
871      */
872     if (tversion == V_HPUX) {
873         if (VALID_STRING(memory_lock)) {
874             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
875                         "meml=%s", memory_lock);
876             WRAP_CONCAT;
877         }
878         if (VALID_STRING(memory_unlock)) {
879             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
880                         "memu=%s", memory_unlock);
881             WRAP_CONCAT;
882         }
883     } else if (tversion == V_AIX) {
884         if (VALID_STRING(acs_chars)) {
885             bool box_ok = TRUE;
886             const char *acstrans = "lqkxjmwuvtn";
887             const char *cp;
888             char *tp, *sp, boxchars[11];
889
890             tp = boxchars;
891             for (cp = acstrans; *cp; cp++) {
892                 sp = (strchr) (acs_chars, *cp);
893                 if (sp)
894                     *tp++ = sp[1];
895                 else {
896                     box_ok = FALSE;
897                     break;
898                 }
899             }
900             tp[0] = '\0';
901
902             if (box_ok) {
903                 char *tmp = _nc_tic_expand(boxchars,
904                                            (outform == F_TERMINFO),
905                                            numbers);
906                 _nc_STRCPY(buffer, "box1=", sizeof(buffer));
907                 while (*tmp != '\0') {
908                     size_t have = strlen(buffer);
909                     size_t next = strlen(tmp);
910                     size_t want = have + next + 1;
911                     size_t last = next;
912                     char save = '\0';
913
914                     /*
915                      * If the expanded string is too long for the buffer,
916                      * chop it off and save the location where we chopped it.
917                      */
918                     if (want >= sizeof(buffer)) {
919                         save = tmp[last];
920                         tmp[last] = '\0';
921                     }
922                     _nc_STRCAT(buffer, tmp, sizeof(buffer));
923
924                     /*
925                      * If we chopped the buffer, replace the missing piece and
926                      * shift everything to append the remainder.
927                      */
928                     if (save != '\0') {
929                         next = 0;
930                         tmp[last] = save;
931                         while ((tmp[next] = tmp[last + next]) != '\0') {
932                             ++next;
933                         }
934                     } else {
935                         break;
936                     }
937                 }
938                 WRAP_CONCAT;
939             }
940         }
941     }
942
943     /*
944      * kludge: trim off trailer to avoid an extra blank line
945      * in infocmp -u output when there are no string differences
946      */
947     if (outcount) {
948         bool trimmed = FALSE;
949         j = (PredIdx) outbuf.used;
950         if (j >= 2
951             && outbuf.text[j - 1] == '\t'
952             && outbuf.text[j - 2] == '\n') {
953             outbuf.used -= 2;
954             trimmed = TRUE;
955         } else if (j >= 4
956                    && outbuf.text[j - 1] == ':'
957                    && outbuf.text[j - 2] == '\t'
958                    && outbuf.text[j - 3] == '\n'
959                    && outbuf.text[j - 4] == '\\') {
960             outbuf.used -= 4;
961             trimmed = TRUE;
962         }
963         if (trimmed) {
964             outbuf.text[outbuf.used] = '\0';
965             column = oldcol;
966             strcpy_DYN(&outbuf, " ");
967         }
968     }
969 #if 0
970     fprintf(stderr, "num_bools = %d\n", num_bools);
971     fprintf(stderr, "num_values = %d\n", num_values);
972     fprintf(stderr, "num_strings = %d\n", num_strings);
973     fprintf(stderr, "term_names=%s, len=%d, strlen(outbuf)=%d, outbuf=%s\n",
974             tterm->term_names, len, outbuf.used, outbuf.text);
975 #endif
976     /*
977      * Here's where we use infodump to trigger a more stringent length check
978      * for termcap-translation purposes.
979      * Return the length of the raw entry, without tc= expansions,
980      * It gives an idea of which entries are deadly to even *scan past*,
981      * as opposed to *use*.
982      */
983     return (infodump ? len : (int) termcap_length(outbuf.text));
984 }
985
986 static bool
987 kill_string(TERMTYPE *tterm, char *cap)
988 {
989     unsigned n;
990     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
991         if (cap == tterm->Strings[n]) {
992             tterm->Strings[n] = ABSENT_STRING;
993             return TRUE;
994         }
995     }
996     return FALSE;
997 }
998
999 static char *
1000 find_string(TERMTYPE *tterm, char *name)
1001 {
1002     PredIdx n;
1003     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
1004         if (version_filter(STRING, n)
1005             && !strcmp(name, strnames[n])) {
1006             char *cap = tterm->Strings[n];
1007             if (VALID_STRING(cap)) {
1008                 return cap;
1009             }
1010             break;
1011         }
1012     }
1013     return ABSENT_STRING;
1014 }
1015
1016 /*
1017  * This is used to remove function-key labels from a termcap entry to
1018  * make it smaller.
1019  */
1020 static int
1021 kill_labels(TERMTYPE *tterm, int target)
1022 {
1023     int n;
1024     int result = 0;
1025     char *cap;
1026     char name[10];
1027
1028     for (n = 0; n <= 10; ++n) {
1029         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "lf%d", n);
1030         if ((cap = find_string(tterm, name)) != ABSENT_STRING
1031             && kill_string(tterm, cap)) {
1032             target -= (int) (strlen(cap) + 5);
1033             ++result;
1034             if (target < 0)
1035                 break;
1036         }
1037     }
1038     return result;
1039 }
1040
1041 /*
1042  * This is used to remove function-key definitions from a termcap entry to
1043  * make it smaller.
1044  */
1045 static int
1046 kill_fkeys(TERMTYPE *tterm, int target)
1047 {
1048     int n;
1049     int result = 0;
1050     char *cap;
1051     char name[10];
1052
1053     for (n = 60; n >= 0; --n) {
1054         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "kf%d", n);
1055         if ((cap = find_string(tterm, name)) != ABSENT_STRING
1056             && kill_string(tterm, cap)) {
1057             target -= (int) (strlen(cap) + 5);
1058             ++result;
1059             if (target < 0)
1060                 break;
1061         }
1062     }
1063     return result;
1064 }
1065
1066 /*
1067  * Check if the given acsc string is a 1-1 mapping, i.e., just-like-vt100.
1068  * Also, since this is for termcap, we only care about the line-drawing map.
1069  */
1070 #define isLine(c) (strchr("lmkjtuvwqxn", c) != 0)
1071
1072 static bool
1073 one_one_mapping(const char *mapping)
1074 {
1075     bool result = TRUE;
1076
1077     if (mapping != ABSENT_STRING) {
1078         int n = 0;
1079         while (mapping[n] != '\0') {
1080             if (isLine(mapping[n]) &&
1081                 mapping[n] != mapping[n + 1]) {
1082                 result = FALSE;
1083                 break;
1084             }
1085             n += 2;
1086         }
1087     }
1088     return result;
1089 }
1090
1091 #define FMT_ENTRY() \
1092                 fmt_entry(tterm, pred, \
1093                         0, \
1094                         suppress_untranslatable, \
1095                         infodump, numbers)
1096
1097 #define SHOW_WHY PRINTF
1098
1099 static bool
1100 purged_acs(TERMTYPE *tterm)
1101 {
1102     bool result = FALSE;
1103
1104     if (VALID_STRING(acs_chars)) {
1105         if (!one_one_mapping(acs_chars)) {
1106             enter_alt_charset_mode = ABSENT_STRING;
1107             exit_alt_charset_mode = ABSENT_STRING;
1108             SHOW_WHY("# (rmacs/smacs removed for consistency)\n");
1109         }
1110         result = TRUE;
1111     }
1112     return result;
1113 }
1114
1115 /*
1116  * Dump a single entry.
1117  */
1118 void
1119 dump_entry(TERMTYPE *tterm,
1120            int suppress_untranslatable,
1121            int limited,
1122            int numbers,
1123            PredFunc pred)
1124 {
1125     TERMTYPE save_tterm;
1126     int len, critlen;
1127     const char *legend;
1128     bool infodump;
1129
1130     if (outform == F_TERMCAP || outform == F_TCONVERR) {
1131         critlen = MAX_TERMCAP_LENGTH;
1132         legend = "older termcap";
1133         infodump = FALSE;
1134         set_obsolete_termcaps(tterm);
1135     } else {
1136         critlen = MAX_TERMINFO_LENGTH;
1137         legend = "terminfo";
1138         infodump = TRUE;
1139     }
1140
1141     save_sgr = set_attributes;
1142
1143     if ((FMT_ENTRY() > critlen)
1144         && limited) {
1145
1146         save_tterm = *tterm;
1147         if (!suppress_untranslatable) {
1148             SHOW_WHY("# (untranslatable capabilities removed to fit entry within %d bytes)\n",
1149                      critlen);
1150             suppress_untranslatable = TRUE;
1151         }
1152         if (FMT_ENTRY() > critlen) {
1153             /*
1154              * We pick on sgr because it's a nice long string capability that
1155              * is really just an optimization hack.  Another good candidate is
1156              * acsc since it is both long and unused by BSD termcap.
1157              */
1158             bool changed = FALSE;
1159
1160 #if NCURSES_XNAMES
1161             /*
1162              * Extended names are most likely function-key definitions.  Drop
1163              * those first.
1164              */
1165             unsigned n;
1166             for (n = STRCOUNT; n < NUM_STRINGS(tterm); n++) {
1167                 const char *name = ExtStrname(tterm, (int) n, strnames);
1168
1169                 if (VALID_STRING(tterm->Strings[n])) {
1170                     set_attributes = ABSENT_STRING;
1171                     /* we remove long names anyway - only report the short */
1172                     if (strlen(name) <= 2) {
1173                         SHOW_WHY("# (%s removed to fit entry within %d bytes)\n",
1174                                  name,
1175                                  critlen);
1176                     }
1177                     changed = TRUE;
1178                     if (FMT_ENTRY() <= critlen)
1179                         break;
1180                 }
1181             }
1182 #endif
1183             if (VALID_STRING(set_attributes)) {
1184                 set_attributes = ABSENT_STRING;
1185                 SHOW_WHY("# (sgr removed to fit entry within %d bytes)\n",
1186                          critlen);
1187                 changed = TRUE;
1188             }
1189             if (!changed || (FMT_ENTRY() > critlen)) {
1190                 if (purged_acs(tterm)) {
1191                     acs_chars = ABSENT_STRING;
1192                     SHOW_WHY("# (acsc removed to fit entry within %d bytes)\n",
1193                              critlen);
1194                     changed = TRUE;
1195                 }
1196             }
1197             if (!changed || (FMT_ENTRY() > critlen)) {
1198                 int oldversion = tversion;
1199
1200                 tversion = V_BSD;
1201                 SHOW_WHY("# (terminfo-only capabilities suppressed to fit entry within %d bytes)\n",
1202                          critlen);
1203
1204                 len = FMT_ENTRY();
1205                 if (len > critlen
1206                     && kill_labels(tterm, len - critlen)) {
1207                     SHOW_WHY("# (some labels capabilities suppressed to fit entry within %d bytes)\n",
1208                              critlen);
1209                     len = FMT_ENTRY();
1210                 }
1211                 if (len > critlen
1212                     && kill_fkeys(tterm, len - critlen)) {
1213                     SHOW_WHY("# (some function-key capabilities suppressed to fit entry within %d bytes)\n",
1214                              critlen);
1215                     len = FMT_ENTRY();
1216                 }
1217                 if (len > critlen) {
1218                     (void) fprintf(stderr,
1219                                    "warning: %s entry is %d bytes long\n",
1220                                    _nc_first_name(tterm->term_names),
1221                                    len);
1222                     SHOW_WHY("# WARNING: this entry, %d bytes long, may core-dump %s libraries!\n",
1223                              len, legend);
1224                 }
1225                 tversion = oldversion;
1226             }
1227             set_attributes = save_sgr;
1228             *tterm = save_tterm;
1229         }
1230     } else if (!version_filter(STRING, STR_IDX(acs_chars))) {
1231         save_tterm = *tterm;
1232         if (purged_acs(tterm)) {
1233             (void) FMT_ENTRY();
1234         }
1235         *tterm = save_tterm;
1236     }
1237 }
1238
1239 void
1240 dump_uses(const char *name, bool infodump)
1241 /* dump "use=" clauses in the appropriate format */
1242 {
1243     char buffer[MAX_TERMINFO_LENGTH];
1244
1245     if (outform == F_TERMCAP || outform == F_TCONVERR)
1246         trim_trailing();
1247     _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1248                 "%s%s", infodump ? "use=" : "tc=", name);
1249     wrap_concat(buffer);
1250 }
1251
1252 int
1253 show_entry(void)
1254 {
1255     /*
1256      * Trim any remaining whitespace.
1257      */
1258     if (outbuf.used != 0) {
1259         bool infodump = (outform != F_TERMCAP && outform != F_TCONVERR);
1260         char delim = (char) (infodump ? ',' : ':');
1261         int j;
1262
1263         for (j = (int) outbuf.used - 1; j > 0; --j) {
1264             char ch = outbuf.text[j];
1265             if (ch == '\n') {
1266                 ;
1267             } else if (isspace(UChar(ch))) {
1268                 outbuf.used = (size_t) j;
1269             } else if (!infodump && ch == '\\') {
1270                 outbuf.used = (size_t) j;
1271             } else if (ch == delim && (j == 0 || outbuf.text[j - 1] != '\\')) {
1272                 outbuf.used = (size_t) (j + 1);
1273             } else {
1274                 break;
1275             }
1276         }
1277         outbuf.text[outbuf.used] = '\0';
1278     }
1279     (void) fputs(outbuf.text, stdout);
1280     putchar('\n');
1281     return (int) outbuf.used;
1282 }
1283
1284 void
1285 compare_entry(PredHook hook,
1286               TERMTYPE *tp GCC_UNUSED,
1287               bool quiet)
1288 /* compare two entries */
1289 {
1290     PredIdx i, j;
1291     NCURSES_CONST char *name;
1292
1293     if (!quiet)
1294         fputs("    comparing booleans.\n", stdout);
1295     for_each_boolean(j, tp) {
1296         i = BoolIndirect(j);
1297         name = ExtBoolname(tp, (int) i, bool_names);
1298
1299         if (isObsolete(outform, name))
1300             continue;
1301
1302         (*hook) (CMP_BOOLEAN, i, name);
1303     }
1304
1305     if (!quiet)
1306         fputs("    comparing numbers.\n", stdout);
1307     for_each_number(j, tp) {
1308         i = NumIndirect(j);
1309         name = ExtNumname(tp, (int) i, num_names);
1310
1311         if (isObsolete(outform, name))
1312             continue;
1313
1314         (*hook) (CMP_NUMBER, i, name);
1315     }
1316
1317     if (!quiet)
1318         fputs("    comparing strings.\n", stdout);
1319     for_each_string(j, tp) {
1320         i = StrIndirect(j);
1321         name = ExtStrname(tp, (int) i, str_names);
1322
1323         if (isObsolete(outform, name))
1324             continue;
1325
1326         (*hook) (CMP_STRING, i, name);
1327     }
1328
1329     /* (void) fputs("    comparing use entries.\n", stdout); */
1330     (*hook) (CMP_USE, 0, "use");
1331
1332 }
1333
1334 #define NOTSET(s)       ((s) == 0)
1335
1336 /*
1337  * This bit of legerdemain turns all the terminfo variable names into
1338  * references to locations in the arrays Booleans, Numbers, and Strings ---
1339  * precisely what's needed.
1340  */
1341 #undef CUR
1342 #define CUR tp->
1343
1344 static void
1345 set_obsolete_termcaps(TERMTYPE *tp)
1346 {
1347 #include "capdefaults.c"
1348 }
1349
1350 /*
1351  * Convert an alternate-character-set string to canonical form: sorted and
1352  * unique.
1353  */
1354 void
1355 repair_acsc(TERMTYPE *tp)
1356 {
1357     if (VALID_STRING(acs_chars)) {
1358         size_t n, m;
1359         char mapped[256];
1360         char extra = 0;
1361         unsigned source;
1362         unsigned target;
1363         bool fix_needed = FALSE;
1364
1365         for (n = 0, source = 0; acs_chars[n] != 0; n++) {
1366             target = UChar(acs_chars[n]);
1367             if (source >= target) {
1368                 fix_needed = TRUE;
1369                 break;
1370             }
1371             source = target;
1372             if (acs_chars[n + 1])
1373                 n++;
1374         }
1375         if (fix_needed) {
1376             memset(mapped, 0, sizeof(mapped));
1377             for (n = 0; acs_chars[n] != 0; n++) {
1378                 source = UChar(acs_chars[n]);
1379                 if ((target = (unsigned char) acs_chars[n + 1]) != 0) {
1380                     mapped[source] = (char) target;
1381                     n++;
1382                 } else {
1383                     extra = (char) source;
1384                 }
1385             }
1386             for (n = m = 0; n < sizeof(mapped); n++) {
1387                 if (mapped[n]) {
1388                     acs_chars[m++] = (char) n;
1389                     acs_chars[m++] = mapped[n];
1390                 }
1391             }
1392             if (extra)
1393                 acs_chars[m++] = extra;         /* garbage in, garbage out */
1394             acs_chars[m] = 0;
1395         }
1396     }
1397 }