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