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