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