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